@lynx-js/external-bundle-rsbuild-plugin 0.3.0 → 0.4.1

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,26 @@
1
1
  # @lynx-js/external-bundle-rsbuild-plugin
2
2
 
3
+ ## 0.4.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Ship the refresh runtime in the shared external bundle so it is loaded once instead of per card. ([#3009](https://github.com/lynx-family/lynx-stack/pull/3009))
8
+
9
+ `@lynx-js/react/refresh` was missing from both the `react-umd` entry and the `reactlynx` externals preset, so every card bundled its own copy. Each copy overwrites `options.debounceRendering` on the _shared_ ReactLynx runtime with a closure that defers through that card's own `Promise`. The last card loaded wins, and once it is destroyed its microtask queue stops draining — the lost flush leaves Preact's scheduling counter set, so no card in the shared context ever re-renders again.
10
+
11
+ Only the development bundle carries it; the production bundle is unchanged in size.
12
+
13
+ ## 0.4.0
14
+
15
+ ### Minor Changes
16
+
17
+ - The `reactlynx` externals preset accepts `{ async: true }`, mounting ReactLynx as an awaited promise so async runtimes can load it via `fetchBundle().then` (the sync array form reads `React.memo` etc. off a pending promise and gets `undefined`). Externals presets resolve per environment (`environmentName` is available in the preset context): `lynx` / `lynx-*` environments use `react.lynx.bundle`, other environments (e.g. `web`) use the web-encoded `@lynx-js/react-umd/{dev,prod}-web` `react.web.bundle` — `async` only controls the mount form. ([#2934](https://github.com/lynx-family/lynx-stack/pull/2934))
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies [[`34318ea`](https://github.com/lynx-family/lynx-stack/commit/34318ea3432b6484a383707458ed9c4ee19e2097)]:
22
+ - @lynx-js/externals-loading-webpack-plugin@0.2.1
23
+
3
24
  ## 0.3.0
4
25
 
5
26
  ### Minor Changes
package/lib/index.d.ts CHANGED
@@ -28,6 +28,19 @@ export interface ReactLynxExternalsPresetOptions {
28
28
  * @defaultValue `'@lynx-js/react-umd'`
29
29
  */
30
30
  reactUmdPackageName?: string;
31
+ /**
32
+ * Load the ReactLynx runtime bundle asynchronously, for the web target.
33
+ *
34
+ * The web runtime (`@lynx-js/web-core`) can only fetch external bundles
35
+ * asynchronously (`fetchBundle().then`), so ReactLynx must be mounted as a
36
+ * promise that consuming modules await before reading a subpath (otherwise
37
+ * `React.memo` etc. are read off a pending promise and are `undefined`).
38
+ * Enabling this also resolves the web-encoded `@lynx-js/react-umd/{dev,prod}-web`
39
+ * bundle and defaults `bundlePath` to `react.web.bundle`.
40
+ *
41
+ * @defaultValue `false`
42
+ */
43
+ async?: boolean;
31
44
  /**
32
45
  * Override the runtime bundle URL directly.
33
46
  *
@@ -65,6 +78,16 @@ export interface ExternalsPresetContext {
65
78
  * The current Rsbuild project root path.
66
79
  */
67
80
  rootPath: string;
81
+ /**
82
+ * The Rsbuild environment being resolved (e.g. `'lynx'`, `'web'`).
83
+ *
84
+ * @remarks
85
+ *
86
+ * Presets use this to pick environment-specific artifacts — for example the
87
+ * built-in `reactlynx` preset resolves the web-encoded `react.web.bundle`
88
+ * for the `web` environment and `react.lynx.bundle` otherwise.
89
+ */
90
+ environmentName?: string;
68
91
  }
69
92
  /**
70
93
  * Definition for a named externals preset.
package/lib/index.js CHANGED
@@ -13,6 +13,7 @@ import { ExternalsLoadingPlugin } from '@lynx-js/externals-loading-webpack-plugi
13
13
  const require = createRequire(import.meta.url);
14
14
  const DEFAULT_REACT_UMD_PACKAGE_NAME = '@lynx-js/react-umd';
15
15
  const REACT_LYNX_BUNDLE_FILE_NAME = 'react.lynx.bundle';
16
+ const REACT_LYNX_WEB_BUNDLE_FILE_NAME = 'react.web.bundle';
16
17
  const reactLynxExternalTemplate = {
17
18
  'react': {
18
19
  libraryName: ['ReactLynx', 'React'],
@@ -86,6 +87,12 @@ const reactLynxExternalTemplate = {
86
87
  mainThread: { sectionPath: 'ReactLynx__main-thread' },
87
88
  async: false,
88
89
  },
90
+ '@lynx-js/react/refresh': {
91
+ libraryName: ['ReactLynx', 'ReactRefresh'],
92
+ background: { sectionPath: 'ReactLynx' },
93
+ mainThread: { sectionPath: 'ReactLynx__main-thread' },
94
+ async: false,
95
+ },
89
96
  preact: {
90
97
  libraryName: ['ReactLynx', 'Preact'],
91
98
  background: { sectionPath: 'ReactLynx' },
@@ -125,28 +132,39 @@ export function normalizeBundlePath(bundlePath) {
125
132
  function createBuiltInExternalsPresetDefinitions() {
126
133
  return {
127
134
  reactlynx: {
128
- resolveExternals(value) {
129
- return createReactLynxExternals(normalizeReactLynxPreset(value));
135
+ resolveExternals(value, context) {
136
+ return createReactLynxExternals(normalizeReactLynxPreset(value), isWebEnvironment(context));
130
137
  },
131
138
  resolveManagedAssets(value, context) {
132
139
  const preset = normalizeReactLynxPreset(value);
133
140
  if (!preset || preset.url) {
134
141
  return new Map();
135
142
  }
143
+ const isWeb = isWebEnvironment(context);
136
144
  return new Map([
137
145
  [
138
- getDefaultReactLynxBundlePath(preset),
139
- getReactLynxBundlePath(context.rootPath, preset.reactUmdPackageName ?? DEFAULT_REACT_UMD_PACKAGE_NAME),
146
+ getDefaultReactLynxBundlePath(preset, isWeb),
147
+ getReactLynxBundlePath(context.rootPath, preset.reactUmdPackageName ?? DEFAULT_REACT_UMD_PACKAGE_NAME, isWeb),
140
148
  ],
141
149
  ]);
142
150
  },
143
151
  },
144
152
  };
145
153
  }
146
- function getReactLynxBundlePath(rootPath, reactUmdPackageName) {
147
- const reactUmdExport = process.env['NODE_ENV'] === 'production'
148
- ? `${reactUmdPackageName}/prod`
149
- : `${reactUmdPackageName}/dev`;
154
+ // The bundle encoding follows the target environment: `lynx` / `lynx-*`
155
+ // environments decode `tasm` bundles (mirroring rspeedy's `isLynx`
156
+ // convention), every other environment (e.g. `web`) decodes web-encoded
157
+ // bundles. `async` only controls how the external is mounted.
158
+ function isWebEnvironment(context) {
159
+ const name = context.environmentName;
160
+ if (name === undefined) {
161
+ return false;
162
+ }
163
+ return name !== 'lynx' && !name.startsWith('lynx-');
164
+ }
165
+ function getReactLynxBundlePath(rootPath, reactUmdPackageName, isWeb) {
166
+ const variant = process.env['NODE_ENV'] === 'production' ? 'prod' : 'dev';
167
+ const reactUmdExport = `${reactUmdPackageName}/${variant}${isWeb ? '-web' : ''}`;
150
168
  try {
151
169
  return require.resolve(reactUmdExport, { paths: [rootPath] });
152
170
  }
@@ -238,14 +256,15 @@ function resolvePresetExternals(externalsPresets, presetDefinitions, context) {
238
256
  }
239
257
  return { externals, managedAssets };
240
258
  }
241
- function createReactLynxExternals(preset) {
259
+ function createReactLynxExternals(preset, isWeb) {
242
260
  const bundleReference = preset?.url
243
261
  ? { url: preset.url }
244
- : { bundlePath: getDefaultReactLynxBundlePath(preset) };
262
+ : { bundlePath: getDefaultReactLynxBundlePath(preset, isWeb) };
245
263
  return Object.fromEntries(Object.entries(reactLynxExternalTemplate).map(([request, external]) => [
246
264
  request,
247
265
  {
248
266
  ...external,
267
+ ...(preset?.async === undefined ? {} : { async: preset.async }),
249
268
  ...bundleReference,
250
269
  },
251
270
  ]));
@@ -278,8 +297,13 @@ class EmitManagedBundleAssetsPlugin {
278
297
  });
279
298
  }
280
299
  }
281
- function getDefaultReactLynxBundlePath(preset) {
282
- return normalizeBundlePath(preset?.bundlePath ?? REACT_LYNX_BUNDLE_FILE_NAME);
300
+ function getDefaultReactLynxBundlePath(preset, isWeb) {
301
+ if (preset?.bundlePath) {
302
+ return normalizeBundlePath(preset.bundlePath);
303
+ }
304
+ return isWeb
305
+ ? REACT_LYNX_WEB_BUNDLE_FILE_NAME
306
+ : REACT_LYNX_BUNDLE_FILE_NAME;
283
307
  }
284
308
  function joinUrlPath(base, bundlePath) {
285
309
  const normalizedBase = base ?? '/';
@@ -312,13 +336,6 @@ function getManagedBundleAssets(options, presetManagedAssets, api) {
312
336
  }
313
337
  return assets;
314
338
  }
315
- function getLocalBundleAssets(options, presetManagedAssets, api, serverBase) {
316
- const assets = new Map();
317
- for (const [bundlePath, sourcePath] of getManagedBundleAssets(options, presetManagedAssets, api)) {
318
- assets.set(joinUrlPath(serverBase, bundlePath), sourcePath);
319
- }
320
- return assets;
321
- }
322
339
  function resolvePluginExternals(externals) {
323
340
  if (!externals) {
324
341
  return {};
@@ -386,12 +403,16 @@ export function pluginExternalBundle(options) {
386
403
  name: 'lynx:external-bundle',
387
404
  setup(api) {
388
405
  const presetDefinitions = resolvePresetDefinitions(options.externalsPresetDefinitions);
389
- const presetResolution = resolvePresetExternals(options.externalsPresets, presetDefinitions, {
390
- rootPath: api.context.rootPath,
391
- });
406
+ // Presets resolve per environment (in `modifyRspackConfig`, where the
407
+ // environment name is known); this map collects every environment's
408
+ // local bundle assets for the dev-server middleware, which reads it at
409
+ // request time.
410
+ const localBundleAssets = new Map();
411
+ let serverBase;
392
412
  api.modifyRsbuildConfig((config, { mergeRsbuildConfig }) => {
393
- const localBundleAssets = getLocalBundleAssets(options, presetResolution.managedAssets, api, config.server?.base);
394
- if (localBundleAssets.size === 0) {
413
+ serverBase = config.server?.base;
414
+ if (options.externals === undefined
415
+ && options.externalsPresets === undefined) {
395
416
  return config;
396
417
  }
397
418
  return mergeRsbuildConfig(config, {
@@ -423,17 +444,24 @@ export function pluginExternalBundle(options) {
423
444
  },
424
445
  });
425
446
  });
426
- api.modifyRspackConfig((config) => {
447
+ api.modifyRspackConfig((config, { environment }) => {
427
448
  const LAYERS = api.useExposed(Symbol.for('LAYERS'));
428
449
  if (!LAYERS) {
429
450
  throw new Error('external-bundle-rsbuild-plugin requires exposed `LAYERS`. Please install a DSL plugin, for example `pluginReactLynx` for ReactLynx.');
430
451
  }
452
+ const presetResolution = resolvePresetExternals(options.externalsPresets, presetDefinitions, {
453
+ rootPath: api.context.rootPath,
454
+ environmentName: environment.name,
455
+ });
431
456
  const explicitExternals = resolvePluginExternals(options.externals);
432
457
  const externals = {
433
458
  ...presetResolution.externals,
434
459
  ...explicitExternals,
435
460
  };
436
461
  const managedBundleAssets = getManagedBundleAssets(options, presetResolution.managedAssets, api);
462
+ for (const [bundlePath, sourcePath] of managedBundleAssets) {
463
+ localBundleAssets.set(joinUrlPath(serverBase, bundlePath), sourcePath);
464
+ }
437
465
  config.plugins = config.plugins || [];
438
466
  if (managedBundleAssets.size > 0) {
439
467
  config.plugins.push(new EmitManagedBundleAssetsPlugin(managedBundleAssets));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lynx-js/external-bundle-rsbuild-plugin",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "An rsbuild plugin for loading lynx external bundles.",
5
5
  "keywords": [
6
6
  "rsbuild",
@@ -34,12 +34,12 @@
34
34
  "README.md"
35
35
  ],
36
36
  "dependencies": {
37
- "@lynx-js/externals-loading-webpack-plugin": "0.2.0"
37
+ "@lynx-js/externals-loading-webpack-plugin": "0.2.1"
38
38
  },
39
39
  "devDependencies": {
40
- "@microsoft/api-extractor": "7.58.2",
41
- "@rsbuild/core": "2.0.11",
42
- "@lynx-js/react-umd": "0.121.2"
40
+ "@microsoft/api-extractor": "7.58.12",
41
+ "@rsbuild/core": "2.1.7",
42
+ "@lynx-js/react-umd": "0.123.1"
43
43
  },
44
44
  "engines": {
45
45
  "node": ">=18"