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 { useShadowStyles } from '@map/composables/useShadowStyles.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.
|
||||
// THE BUG (Driver-reported 2026-09-23, verbatim console text):
|
||||
// Could not load <doxa-research-map> from ../../doxa-research-map/doxa-research-map.js
|
||||
|
|
@ -134,30 +132,11 @@ function resolveBundleSrc(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) {
|
||||
const url = 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]
|
||||
return loadScriptOnce(resolveBundleSrc(rawUrl))
|
||||
}
|
||||
|
||||
function waitForDefine(tag, timeoutMs = 5000) {
|
||||
|
|
|
|||
|
|
@ -246,22 +246,12 @@ import { tStatic } from '../i18n/index.js'
|
|||
const tr = (key, en) => tStatic(key) || en
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.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
|
||||
// the IIFE has executed (so its customElements.define has run). Mirrors the SPA/studio's once().
|
||||
const _vtlLoaded = {}
|
||||
function injectScriptOnce(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]
|
||||
}
|
||||
// Inject a bundle script exactly once per URL, through the ONE shared loader (library/utils/embedScript.js).
|
||||
// This layout's private copy lacked 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.
|
||||
const injectScriptOnce = (url) => loadScriptOnce(url)
|
||||
|
||||
// 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.
|
||||
|
|
|
|||
|
|
@ -29,11 +29,18 @@
|
|||
* 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
|
||||
* .../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).
|
||||
*/
|
||||
const _loaded = {}
|
||||
export function loadScriptOnce(url, attrs) {
|
||||
if (!url) return Promise.resolve()
|
||||
if (_loaded[url]) return _loaded[url]
|
||||
_loaded[url] = new Promise((ok, fail) => {
|
||||
const s = document.createElement('script')
|
||||
|
|
|
|||
Loading…
Reference in a new issue