Both library layouts load nested bundles through the shared embedScript, which fixes VerticalTabsLayout on the dev server
VerticalTabsLayout kept a private script loader without the dev-server ES-module rule, so a bundle nested through it under bun run dev loaded as a classic script, reported success, and never defined its tag. It and StackLayout now call library/utils/embedScript.js loadScriptOnce; StackLayout keeps its own resolveBundleSrc step first. loadScriptOnce gains an empty-url no-op (a <script src=""> re-fetched the page) and carries StackLayout's stack-demo measurement beside the gallery's. One copy of the pattern and one cache.
This commit is contained in:
parent
d0939c0492
commit
4ce8604d42
3 changed files with 17 additions and 41 deletions
|
|
@ -80,10 +80,8 @@
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||||
import { bundleTreeRoot } from '@map/utils/embedBase.js'
|
import { bundleTreeRoot } from '@map/utils/embedBase.js'
|
||||||
|
import { loadScriptOnce } from '@map/utils/embedScript.js'
|
||||||
|
|
||||||
// Inject a bundle script exactly once per URL (deduped across all layout instances). Same helper the
|
|
||||||
// tabs layout + the SPA use — resolves after the IIFE's customElements.define has run.
|
|
||||||
const _stlLoaded = {}
|
|
||||||
// ★ ANCHOR A SIBLING-BUNDLE src TO THE TREE ROOT, NOT TO A GUESSED DEPTH.
|
// ★ ANCHOR A SIBLING-BUNDLE src TO THE TREE ROOT, NOT TO A GUESSED DEPTH.
|
||||||
// THE BUG (Driver-reported 2026-09-23, verbatim console text):
|
// THE BUG (Driver-reported 2026-09-23, verbatim console text):
|
||||||
// Could not load <doxa-research-map> from ../../doxa-research-map/doxa-research-map.js
|
// Could not load <doxa-research-map> from ../../doxa-research-map/doxa-research-map.js
|
||||||
|
|
@ -134,30 +132,11 @@ function resolveBundleSrc(url) {
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inject a bundle script exactly once per URL, through the ONE shared loader (library/utils/embedScript.js:
|
||||||
|
// the dedupe cache, the dev-server ES-module rule, the empty-url no-op). Only the src resolution is this
|
||||||
|
// layout's own: a sibling-bundle src is anchored to the tree root first (resolveBundleSrc above).
|
||||||
function injectScriptOnce(rawUrl) {
|
function injectScriptOnce(rawUrl) {
|
||||||
const url = resolveBundleSrc(rawUrl)
|
return loadScriptOnce(resolveBundleSrc(rawUrl))
|
||||||
if (!url) return Promise.resolve()
|
|
||||||
if (_stlLoaded[url]) return _stlLoaded[url]
|
|
||||||
_stlLoaded[url] = new Promise((ok, fail) => {
|
|
||||||
const s = document.createElement('script')
|
|
||||||
s.src = url
|
|
||||||
// ★ A DEV ENTRY IS AN ES MODULE, AND A CLASSIC <script> LIES ABOUT LOADING IT.
|
|
||||||
// Pointing a classic script at /plugin-designs/<bundle>/index.js fires onload SUCCESSFULLY and
|
|
||||||
// only THEN throws "Cannot use import statement outside a module", asynchronously. So this
|
|
||||||
// promise resolved, waitForDefine() below timed out, the tag never registered, and the section
|
|
||||||
// rendered empty — with NO failed request and NO console error at the injection site. onload is
|
|
||||||
// structurally incapable of detecting this.
|
|
||||||
// MEASURED on stack-demo's dev leaf 2026-09-22: 0 failed requests, and yet
|
|
||||||
// customElements.get('research-dashboard-map') === false with 0 rows rendered.
|
|
||||||
// Same rule as plugin-studio.vue:255, which hit this first. It belongs HERE because StackLayout
|
|
||||||
// is the SHARED layout — dashboard-plus-grid and research-map-plus-grid inject through this same
|
|
||||||
// function, so one fix covers all three consumers instead of becoming a fourth private copy.
|
|
||||||
if (/^\/plugin-designs\//.test(url) || /\/index\.js(\?|$)/.test(url)) s.type = 'module'
|
|
||||||
s.onload = () => ok()
|
|
||||||
s.onerror = () => { delete _stlLoaded[url]; fail(new Error('failed to load ' + url)) }
|
|
||||||
document.head.appendChild(s)
|
|
||||||
})
|
|
||||||
return _stlLoaded[url]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function waitForDefine(tag, timeoutMs = 5000) {
|
function waitForDefine(tag, timeoutMs = 5000) {
|
||||||
|
|
|
||||||
|
|
@ -246,22 +246,12 @@ import { tStatic } from '../i18n/index.js'
|
||||||
const tr = (key, en) => tStatic(key) || en
|
const tr = (key, en) => tStatic(key) || en
|
||||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||||
import { icon as localIcon } from '@map/layouts/app-wrapper-material/icons.js'
|
import { icon as localIcon } from '@map/layouts/app-wrapper-material/icons.js'
|
||||||
|
import { loadScriptOnce } from '@map/utils/embedScript.js'
|
||||||
|
|
||||||
// Inject a bundle script exactly once per URL (deduped across all layout instances). Resolves after
|
// Inject a bundle script exactly once per URL, through the ONE shared loader (library/utils/embedScript.js).
|
||||||
// the IIFE has executed (so its customElements.define has run). Mirrors the SPA/studio's once().
|
// This layout's private copy lacked the dev-server ES-module rule, so a bundle nested through it under
|
||||||
const _vtlLoaded = {}
|
// bun run dev loaded as a classic script, reported success, and never defined its tag.
|
||||||
function injectScriptOnce(url) {
|
const injectScriptOnce = (url) => loadScriptOnce(url)
|
||||||
if (!url) return Promise.resolve()
|
|
||||||
if (_vtlLoaded[url]) return _vtlLoaded[url]
|
|
||||||
_vtlLoaded[url] = new Promise((ok, fail) => {
|
|
||||||
const s = document.createElement('script')
|
|
||||||
s.src = url
|
|
||||||
s.onload = () => ok()
|
|
||||||
s.onerror = () => { delete _vtlLoaded[url]; fail(new Error('failed to load ' + url)) }
|
|
||||||
document.head.appendChild(s)
|
|
||||||
})
|
|
||||||
return _vtlLoaded[url]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait until a custom element tag is defined (up to ~5s). If a src bundle was injected, its
|
// Wait until a custom element tag is defined (up to ~5s). If a src bundle was injected, its
|
||||||
// define() has already run by onload; this covers elements registered async after script exec.
|
// define() has already run by onload; this covers elements registered async after script exec.
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,18 @@
|
||||||
* The built tree ships IIFEs, where a classic tag is correct, so this only ever bites under the dev
|
* The built tree ships IIFEs, where a classic tag is correct, so this only ever bites under the dev
|
||||||
* server — exactly the surface a developer works on. Rule: a rooted /plugin-designs/ entry (or any
|
* server — exactly the surface a developer works on. Rule: a rooted /plugin-designs/ entry (or any
|
||||||
* .../index.js) is a dev ES module; everything else is a built IIFE.
|
* .../index.js) is a dev ES module; everything else is a built IIFE.
|
||||||
|
* MEASURED a second time, independently, on stack-demo's dev leaf 2026-09-22 (StackLayout, before it
|
||||||
|
* used this file): 0 failed requests, and yet customElements.get('research-dashboard-map') === false
|
||||||
|
* with 0 rows rendered. VerticalTabsLayout's private copy LACKED this rule until it moved here, so a
|
||||||
|
* bundle nested through it on the dev server never defined its tag.
|
||||||
|
*
|
||||||
|
* An empty url is a no-op: a <script src=""> would re-fetch the page itself.
|
||||||
*
|
*
|
||||||
* SRI is optional (attrs.integrity).
|
* SRI is optional (attrs.integrity).
|
||||||
*/
|
*/
|
||||||
const _loaded = {}
|
const _loaded = {}
|
||||||
export function loadScriptOnce(url, attrs) {
|
export function loadScriptOnce(url, attrs) {
|
||||||
|
if (!url) return Promise.resolve()
|
||||||
if (_loaded[url]) return _loaded[url]
|
if (_loaded[url]) return _loaded[url]
|
||||||
_loaded[url] = new Promise((ok, fail) => {
|
_loaded[url] = new Promise((ok, fail) => {
|
||||||
const s = document.createElement('script')
|
const s = document.createElement('script')
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue