@asteby/metacore-runtime-react 13.0.0 → 13.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 13.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8d9c602: AddonLoader carga remotes de federación ESM vía `import()` dinámico (fix "Cannot use import statement outside a module").
8
+
9
+ Los remotes built con Vite/@originjs `format:"esm"` (el estándar de `metacoreFederationShared`) son módulos ES que hacen `import` top-level y exportan `{ init, get }` — DEBEN cargarse como módulo. El `AddonLoader` los inyectaba como `<script>` clásico → el browser tiraba `Cannot use import statement outside a module` y la UI federada nunca cargaba. Ahora hace `import()` dinámico (vía `new Function` para que ningún bundler reescriba el import del URL externo) y usa el namespace del módulo como container; los remotes legacy "var"/window siguen soportados con fallback a `<script>` + `window[scope]`.
10
+
3
11
  ## 13.0.0
4
12
 
5
13
  ### Minor Changes
@@ -1 +1 @@
1
- {"version":3,"file":"addon-loader.d.ts","sourceRoot":"","sources":["../src/addon-loader.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAGjE,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,MAAM;QACZ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;QAClB,wBAAwB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;QAC3D,wBAAwB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KACrD;CACJ;AAED,MAAM,WAAW,gBAAgB;IAC7B,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAA;IACb,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAA;IACX,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,+DAA+D;IAC/D,GAAG,EAAE,QAAQ,CAAA;IACb,wCAAwC;IACxC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAA;IAC9B;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC7B;AAuCD,wBAAgB,WAAW,CAAC,EACxB,KAAK,EACL,GAAG,EACH,MAAqB,EACrB,GAAG,EACH,QAAe,EACf,OAAO,EACP,OAAO,EACP,MAAM,EACN,QAAQ,GACX,EAAE,gBAAgB,2CAsClB"}
1
+ {"version":3,"file":"addon-loader.d.ts","sourceRoot":"","sources":["../src/addon-loader.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAGjE,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,MAAM;QACZ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;QAClB,wBAAwB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;QAC3D,wBAAwB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KACrD;CACJ;AAED,MAAM,WAAW,gBAAgB;IAC7B,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAA;IACb,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAA;IACX,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,+DAA+D;IAC/D,GAAG,EAAE,QAAQ,CAAA;IACb,wCAAwC;IACxC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAA;IAC9B;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC7B;AAqFD,wBAAgB,WAAW,CAAC,EACxB,KAAK,EACL,GAAG,EACH,MAAqB,EACrB,GAAG,EACH,QAAe,EACf,OAAO,EACP,OAAO,EACP,MAAM,EACN,QAAQ,GACX,EAAE,gBAAgB,2CAoClB"}
@@ -4,6 +4,10 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
4
4
  // addon's `register(api)` export with the AddonAPI injected by the host.
5
5
  import { useEffect, useRef, useState } from 'react';
6
6
  import { useDeclareAddonLayout } from './addon-layout-context';
7
+ // Runtime dynamic import of an external URL. Wrapped in `new Function` so no
8
+ // build tool (tsc here, Vite in the consuming host) tries to statically
9
+ // analyse or rewrite the import — it stays a genuine runtime ESM fetch.
10
+ const importModule = new Function('u', 'return import(u)');
7
11
  const loadedScripts = new Map();
8
12
  function loadScript(url, scope) {
9
13
  const key = `${scope}::${url}`;
@@ -22,15 +26,53 @@ function loadScript(url, scope) {
22
26
  loadedScripts.set(key, promise);
23
27
  return promise;
24
28
  }
25
- async function loadRemote(scope, module) {
29
+ const esmContainers = new Map();
30
+ // Resolve a federation container for the remote. Vite/@originjs remotes built
31
+ // with `format:"esm"` (our standard) are ES modules that top-level `import`
32
+ // their preload helper and export `{ init, get }` — they MUST be loaded as a
33
+ // module (a classic <script> throws "Cannot use import statement outside a
34
+ // module"), so we dynamic-import them and use the module namespace as the
35
+ // container. Legacy "var"/window remotes (which assign `window[scope]`) are
36
+ // still supported via the classic <script> fallback.
37
+ async function resolveContainer(scope, url) {
38
+ const key = `${scope}::${url}`;
39
+ const cached = esmContainers.get(key);
40
+ if (cached)
41
+ return cached;
42
+ const p = (async () => {
43
+ try {
44
+ const mod = await importModule(url);
45
+ if (mod && typeof mod.init === 'function' && typeof mod.get === 'function') {
46
+ return mod;
47
+ }
48
+ }
49
+ catch {
50
+ // Not an importable module (legacy var-format remote) — fall back.
51
+ }
52
+ await loadScript(url, scope);
53
+ return window[scope];
54
+ })();
55
+ esmContainers.set(key, p);
56
+ p.catch(() => esmContainers.delete(key));
57
+ return p;
58
+ }
59
+ async function loadRemote(scope, url, module) {
26
60
  if (typeof window.__webpack_init_sharing__ === 'function') {
27
61
  await window.__webpack_init_sharing__('default');
28
62
  }
29
- const container = window[scope];
30
- if (!container)
31
- throw new Error(`Addon container "${scope}" not found on window`);
32
- if (typeof container.init === 'function' && window.__webpack_share_scopes__) {
33
- await container.init(window.__webpack_share_scopes__.default);
63
+ const container = await resolveContainer(scope, url);
64
+ if (!container) {
65
+ throw new Error(`Addon container "${scope}" not found (neither ESM export nor window[scope])`);
66
+ }
67
+ if (typeof container.init === 'function') {
68
+ const shareScope = window.__webpack_share_scopes__?.default ??
69
+ (window.__METACORE_SHARE_SCOPE__ ??= {});
70
+ try {
71
+ await container.init(shareScope);
72
+ }
73
+ catch {
74
+ // Container already initialized (re-entrant load) — safe to ignore.
75
+ }
34
76
  }
35
77
  const factory = await container.get(module);
36
78
  return factory();
@@ -48,10 +90,7 @@ export function AddonLoader({ scope, url, module = './register', api, fallback =
48
90
  let cancelled = false;
49
91
  (async () => {
50
92
  try {
51
- await loadScript(url, scope);
52
- if (cancelled)
53
- return;
54
- const mod = await loadRemote(scope, module);
93
+ const mod = await loadRemote(scope, url, module);
55
94
  if (cancelled)
56
95
  return;
57
96
  if (!didRegister.current && typeof mod?.register === 'function') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "13.0.0",
3
+ "version": "13.1.0",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -43,6 +43,18 @@ export interface AddonLoaderProps {
43
43
  children?: React.ReactNode
44
44
  }
45
45
 
46
+ interface FederationContainer {
47
+ init: (shareScope: unknown) => Promise<void>
48
+ get: (module: string) => Promise<() => any>
49
+ }
50
+
51
+ // Runtime dynamic import of an external URL. Wrapped in `new Function` so no
52
+ // build tool (tsc here, Vite in the consuming host) tries to statically
53
+ // analyse or rewrite the import — it stays a genuine runtime ESM fetch.
54
+ const importModule = new Function('u', 'return import(u)') as (
55
+ u: string,
56
+ ) => Promise<Record<string, unknown>>
57
+
46
58
  const loadedScripts = new Map<string, Promise<void>>()
47
59
 
48
60
  function loadScript(url: string, scope: string): Promise<void> {
@@ -62,19 +74,53 @@ function loadScript(url: string, scope: string): Promise<void> {
62
74
  return promise
63
75
  }
64
76
 
65
- interface FederationContainer {
66
- init: (shareScope: unknown) => Promise<void>
67
- get: (module: string) => Promise<() => any>
77
+ const esmContainers = new Map<string, Promise<FederationContainer | undefined>>()
78
+
79
+ // Resolve a federation container for the remote. Vite/@originjs remotes built
80
+ // with `format:"esm"` (our standard) are ES modules that top-level `import`
81
+ // their preload helper and export `{ init, get }` — they MUST be loaded as a
82
+ // module (a classic <script> throws "Cannot use import statement outside a
83
+ // module"), so we dynamic-import them and use the module namespace as the
84
+ // container. Legacy "var"/window remotes (which assign `window[scope]`) are
85
+ // still supported via the classic <script> fallback.
86
+ async function resolveContainer(scope: string, url: string): Promise<FederationContainer | undefined> {
87
+ const key = `${scope}::${url}`
88
+ const cached = esmContainers.get(key)
89
+ if (cached) return cached
90
+ const p = (async () => {
91
+ try {
92
+ const mod = await importModule(url)
93
+ if (mod && typeof mod.init === 'function' && typeof mod.get === 'function') {
94
+ return mod as unknown as FederationContainer
95
+ }
96
+ } catch {
97
+ // Not an importable module (legacy var-format remote) — fall back.
98
+ }
99
+ await loadScript(url, scope)
100
+ return (window as any)[scope] as FederationContainer | undefined
101
+ })()
102
+ esmContainers.set(key, p)
103
+ p.catch(() => esmContainers.delete(key))
104
+ return p
68
105
  }
69
106
 
70
- async function loadRemote(scope: string, module: string) {
107
+ async function loadRemote(scope: string, url: string, module: string) {
71
108
  if (typeof window.__webpack_init_sharing__ === 'function') {
72
109
  await window.__webpack_init_sharing__('default')
73
110
  }
74
- const container = window[scope] as FederationContainer | undefined
75
- if (!container) throw new Error(`Addon container "${scope}" not found on window`)
76
- if (typeof container.init === 'function' && window.__webpack_share_scopes__) {
77
- await container.init(window.__webpack_share_scopes__.default)
111
+ const container = await resolveContainer(scope, url)
112
+ if (!container) {
113
+ throw new Error(`Addon container "${scope}" not found (neither ESM export nor window[scope])`)
114
+ }
115
+ if (typeof container.init === 'function') {
116
+ const shareScope =
117
+ window.__webpack_share_scopes__?.default ??
118
+ ((window as any).__METACORE_SHARE_SCOPE__ ??= {})
119
+ try {
120
+ await container.init(shareScope)
121
+ } catch {
122
+ // Container already initialized (re-entrant load) — safe to ignore.
123
+ }
78
124
  }
79
125
  const factory = await container.get(module)
80
126
  return factory()
@@ -105,9 +151,7 @@ export function AddonLoader({
105
151
  let cancelled = false
106
152
  ;(async () => {
107
153
  try {
108
- await loadScript(url, scope)
109
- if (cancelled) return
110
- const mod = await loadRemote(scope, module)
154
+ const mod = await loadRemote(scope, url, module)
111
155
  if (cancelled) return
112
156
  if (!didRegister.current && typeof mod?.register === 'function') {
113
157
  didRegister.current = true