@gooddata/sdk-ui-loaders 11.49.0-alpha.0 → 11.49.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.
@@ -106,13 +106,82 @@ function addScriptTag(url) {
106
106
  element,
107
107
  };
108
108
  }
109
+ // React, ReactDOM and the JSX runtimes must resolve to the single host-provided instance —
110
+ // hooks and context break across duplicate React copies. A prebuilt (webpack) plugin bundles
111
+ // its own React and registers it into the share scope on container.init(). When that copy is a
112
+ // higher semver than the host's, the plugin's singleton consume selects the plugin's (lazily
113
+ // loaded) copy and reads a null React at render ("Cannot read properties of null (reading
114
+ // 'useEffect')"), blanking the dashboard.
115
+ const REACT_SINGLETON_SHARES = [
116
+ "react",
117
+ "react-dom",
118
+ "react-dom/client",
119
+ "react/jsx-runtime",
120
+ "react/jsx-dev-runtime",
121
+ ];
122
+ function snapshotSharedVersions(shareScope) {
123
+ const snapshot = {};
124
+ for (const key of REACT_SINGLETON_SHARES) {
125
+ snapshot[key] = new Set(Object.keys(shareScope?.[key] ?? {}));
126
+ }
127
+ return snapshot;
128
+ }
129
+ // The host's React versions are captured once per share scope and cached. Snapshotting them
130
+ // per loadEntry call would race under concurrent plugin loads: a later call could snapshot the
131
+ // scope after an earlier plugin's container.init() already registered its own React and mistake
132
+ // that copy for a host version, leaving a plugin React in the scope for the singleton consume to
133
+ // pick. The read-then-set below is synchronous, so the first call to reach it captures the scope
134
+ // before any plugin container has initialized, and every other call reuses that snapshot.
135
+ const hostReactVersionsByScope = new WeakMap();
136
+ function getHostReactVersions(shareScope) {
137
+ if (!shareScope) {
138
+ return snapshotSharedVersions(shareScope);
139
+ }
140
+ let snapshot = hostReactVersionsByScope.get(shareScope);
141
+ if (!snapshot) {
142
+ snapshot = snapshotSharedVersions(shareScope);
143
+ hostReactVersionsByScope.set(shareScope, snapshot);
144
+ }
145
+ return snapshot;
146
+ }
147
+ // Drop the React copies the plugin just registered so it reuses the host's already-provided
148
+ // singleton, then resolve that copy up front so the plugin's synchronous consume reads a live
149
+ // module rather than an unresolved lazy factory.
150
+ async function pinReactSharesToHost(shareScope, hostVersions) {
151
+ await Promise.all(REACT_SINGLETON_SHARES.map(async (key) => {
152
+ const versions = shareScope?.[key];
153
+ if (!versions) {
154
+ return;
155
+ }
156
+ const hostKeys = Object.keys(versions).filter((version) => hostVersions[key]?.has(version));
157
+ // Only strip plugin-added copies when a host copy remains to fall back on.
158
+ if (hostKeys.length > 0) {
159
+ for (const version of Object.keys(versions)) {
160
+ if (!hostVersions[key]?.has(version)) {
161
+ delete versions[version];
162
+ }
163
+ }
164
+ }
165
+ const entry = versions[hostKeys[0] ?? Object.keys(versions)[0]];
166
+ if (entry && typeof entry.get === "function") {
167
+ const factory = await entry.get();
168
+ if (typeof factory === "function") {
169
+ factory();
170
+ }
171
+ }
172
+ }));
173
+ }
109
174
  function loadEntry(moduleName, { __webpack_init_sharing__, __webpack_share_scopes__ }) {
110
175
  return async () => {
111
176
  // Initializes the share scope. This fills it with known provided modules from this build and all remotes
112
177
  await __webpack_init_sharing__("default");
178
+ const shareScope = __webpack_share_scopes__.default;
179
+ // The host's React versions, captured once before any plugin container contributes its own.
180
+ const hostReactVersions = getHostReactVersions(shareScope);
113
181
  const container = window[moduleName]; // or get the container somewhere else
114
182
  // Initialize the container, it may provide shared modules
115
- await container.init(__webpack_share_scopes__.default);
183
+ await container.init(shareScope);
184
+ await pinReactSharesToHost(shareScope, hostReactVersions);
116
185
  // the `./${moduleName}_ENTRY` corresponds to exposes in the dashboard-plugin-template webpack config
117
186
  const entryFactory = await window[moduleName].get(`./${moduleName}_ENTRY`);
118
187
  const entryModule = entryFactory();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gooddata/sdk-ui-loaders",
3
- "version": "11.49.0-alpha.0",
3
+ "version": "11.49.0",
4
4
  "description": "GoodData SDK Runtime Component Loaders",
5
5
  "license": "LicenseRef-LICENSE",
6
6
  "author": "GoodData Corporation",
@@ -32,10 +32,10 @@
32
32
  "semver": "7.7.4",
33
33
  "ts-invariant": "0.10.3",
34
34
  "tslib": "2.8.1",
35
- "@gooddata/sdk-backend-spi": "11.49.0-alpha.0",
36
- "@gooddata/sdk-model": "11.49.0-alpha.0",
37
- "@gooddata/sdk-ui-dashboard": "11.49.0-alpha.0",
38
- "@gooddata/sdk-ui": "11.49.0-alpha.0"
35
+ "@gooddata/sdk-backend-spi": "11.49.0",
36
+ "@gooddata/sdk-model": "11.49.0",
37
+ "@gooddata/sdk-ui": "11.49.0",
38
+ "@gooddata/sdk-ui-dashboard": "11.49.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@microsoft/api-documenter": "^7.17.0",
@@ -69,8 +69,8 @@
69
69
  "react": "19.1.1",
70
70
  "react-dom": "19.1.1",
71
71
  "typescript": "5.9.3",
72
- "@gooddata/eslint-config": "11.49.0-alpha.0",
73
- "@gooddata/oxlint-config": "11.49.0-alpha.0"
72
+ "@gooddata/eslint-config": "11.49.0",
73
+ "@gooddata/oxlint-config": "11.49.0"
74
74
  },
75
75
  "peerDependencies": {
76
76
  "react": "^18.0.0 || ^19.0.0",