@lynx-js/external-bundle-rsbuild-plugin 0.2.0 → 0.4.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,22 @@
1
1
  # @lynx-js/external-bundle-rsbuild-plugin
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 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))
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [[`34318ea`](https://github.com/lynx-family/lynx-stack/commit/34318ea3432b6484a383707458ed9c4ee19e2097)]:
12
+ - @lynx-js/externals-loading-webpack-plugin@0.2.1
13
+
14
+ ## 0.3.0
15
+
16
+ ### Minor Changes
17
+
18
+ - Support Rsbuild v2 in the external bundle plugin by replacing the removed `dev.setupMiddlewares` integration with [`server.setup`](https://rsbuild.rs/guide/upgrade/v1-to-v2#others) and registering local external bundle asset middleware only during dev server startup. ([#2603](https://github.com/lynx-family/lynx-stack/pull/2603))
19
+
3
20
  ## 0.2.0
4
21
 
5
22
  ### 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'],
@@ -125,28 +126,39 @@ export function normalizeBundlePath(bundlePath) {
125
126
  function createBuiltInExternalsPresetDefinitions() {
126
127
  return {
127
128
  reactlynx: {
128
- resolveExternals(value) {
129
- return createReactLynxExternals(normalizeReactLynxPreset(value));
129
+ resolveExternals(value, context) {
130
+ return createReactLynxExternals(normalizeReactLynxPreset(value), isWebEnvironment(context));
130
131
  },
131
132
  resolveManagedAssets(value, context) {
132
133
  const preset = normalizeReactLynxPreset(value);
133
134
  if (!preset || preset.url) {
134
135
  return new Map();
135
136
  }
137
+ const isWeb = isWebEnvironment(context);
136
138
  return new Map([
137
139
  [
138
- getDefaultReactLynxBundlePath(preset),
139
- getReactLynxBundlePath(context.rootPath, preset.reactUmdPackageName ?? DEFAULT_REACT_UMD_PACKAGE_NAME),
140
+ getDefaultReactLynxBundlePath(preset, isWeb),
141
+ getReactLynxBundlePath(context.rootPath, preset.reactUmdPackageName ?? DEFAULT_REACT_UMD_PACKAGE_NAME, isWeb),
140
142
  ],
141
143
  ]);
142
144
  },
143
145
  },
144
146
  };
145
147
  }
146
- function getReactLynxBundlePath(rootPath, reactUmdPackageName) {
147
- const reactUmdExport = process.env['NODE_ENV'] === 'production'
148
- ? `${reactUmdPackageName}/prod`
149
- : `${reactUmdPackageName}/dev`;
148
+ // The bundle encoding follows the target environment: `lynx` / `lynx-*`
149
+ // environments decode `tasm` bundles (mirroring rspeedy's `isLynx`
150
+ // convention), every other environment (e.g. `web`) decodes web-encoded
151
+ // bundles. `async` only controls how the external is mounted.
152
+ function isWebEnvironment(context) {
153
+ const name = context.environmentName;
154
+ if (name === undefined) {
155
+ return false;
156
+ }
157
+ return name !== 'lynx' && !name.startsWith('lynx-');
158
+ }
159
+ function getReactLynxBundlePath(rootPath, reactUmdPackageName, isWeb) {
160
+ const variant = process.env['NODE_ENV'] === 'production' ? 'prod' : 'dev';
161
+ const reactUmdExport = `${reactUmdPackageName}/${variant}${isWeb ? '-web' : ''}`;
150
162
  try {
151
163
  return require.resolve(reactUmdExport, { paths: [rootPath] });
152
164
  }
@@ -238,14 +250,15 @@ function resolvePresetExternals(externalsPresets, presetDefinitions, context) {
238
250
  }
239
251
  return { externals, managedAssets };
240
252
  }
241
- function createReactLynxExternals(preset) {
253
+ function createReactLynxExternals(preset, isWeb) {
242
254
  const bundleReference = preset?.url
243
255
  ? { url: preset.url }
244
- : { bundlePath: getDefaultReactLynxBundlePath(preset) };
256
+ : { bundlePath: getDefaultReactLynxBundlePath(preset, isWeb) };
245
257
  return Object.fromEntries(Object.entries(reactLynxExternalTemplate).map(([request, external]) => [
246
258
  request,
247
259
  {
248
260
  ...external,
261
+ ...(preset?.async === undefined ? {} : { async: preset.async }),
249
262
  ...bundleReference,
250
263
  },
251
264
  ]));
@@ -278,8 +291,13 @@ class EmitManagedBundleAssetsPlugin {
278
291
  });
279
292
  }
280
293
  }
281
- function getDefaultReactLynxBundlePath(preset) {
282
- return normalizeBundlePath(preset?.bundlePath ?? REACT_LYNX_BUNDLE_FILE_NAME);
294
+ function getDefaultReactLynxBundlePath(preset, isWeb) {
295
+ if (preset?.bundlePath) {
296
+ return normalizeBundlePath(preset.bundlePath);
297
+ }
298
+ return isWeb
299
+ ? REACT_LYNX_WEB_BUNDLE_FILE_NAME
300
+ : REACT_LYNX_BUNDLE_FILE_NAME;
283
301
  }
284
302
  function joinUrlPath(base, bundlePath) {
285
303
  const normalizedBase = base ?? '/';
@@ -312,13 +330,6 @@ function getManagedBundleAssets(options, presetManagedAssets, api) {
312
330
  }
313
331
  return assets;
314
332
  }
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
333
  function resolvePluginExternals(externals) {
323
334
  if (!externals) {
324
335
  return {};
@@ -386,47 +397,65 @@ export function pluginExternalBundle(options) {
386
397
  name: 'lynx:external-bundle',
387
398
  setup(api) {
388
399
  const presetDefinitions = resolvePresetDefinitions(options.externalsPresetDefinitions);
389
- const presetResolution = resolvePresetExternals(options.externalsPresets, presetDefinitions, {
390
- rootPath: api.context.rootPath,
391
- });
400
+ // Presets resolve per environment (in `modifyRspackConfig`, where the
401
+ // environment name is known); this map collects every environment's
402
+ // local bundle assets for the dev-server middleware, which reads it at
403
+ // request time.
404
+ const localBundleAssets = new Map();
405
+ let serverBase;
392
406
  api.modifyRsbuildConfig((config, { mergeRsbuildConfig }) => {
393
- const localBundleAssets = getLocalBundleAssets(options, presetResolution.managedAssets, api, config.server?.base);
394
- if (localBundleAssets.size === 0) {
407
+ serverBase = config.server?.base;
408
+ if (options.externals === undefined
409
+ && options.externalsPresets === undefined) {
395
410
  return config;
396
411
  }
397
412
  return mergeRsbuildConfig(config, {
398
- dev: {
399
- setupMiddlewares: [
400
- (middlewares) => {
401
- middlewares.unshift((req, res, next) => {
402
- const bundlePath = req.url
403
- ? localBundleAssets.get(req.url)
404
- : undefined;
405
- if (bundlePath && existsSync(bundlePath)) {
406
- res.setHeader('Content-Type', 'application/octet-stream');
407
- res.setHeader('Access-Control-Allow-Origin', '*');
408
- createReadStream(bundlePath).pipe(res);
409
- return;
410
- }
411
- next();
412
- });
413
- return middlewares;
414
- },
415
- ],
413
+ server: {
414
+ setup: ({ server, action }) => {
415
+ if (action !== 'dev') {
416
+ return;
417
+ }
418
+ server.middlewares.use((req, res, next) => {
419
+ const bundlePath = req.url
420
+ ? localBundleAssets.get(req.url)
421
+ : undefined;
422
+ if (bundlePath && existsSync(bundlePath)) {
423
+ res.setHeader('Content-Type', 'application/octet-stream');
424
+ res.setHeader('Access-Control-Allow-Origin', '*');
425
+ const stream = createReadStream(bundlePath);
426
+ stream.on('error', () => {
427
+ if (!res.headersSent) {
428
+ res.statusCode = 404;
429
+ }
430
+ res.end();
431
+ });
432
+ stream.pipe(res);
433
+ return;
434
+ }
435
+ next();
436
+ });
437
+ },
416
438
  },
417
439
  });
418
440
  });
419
- api.modifyRspackConfig((config) => {
441
+ api.modifyRspackConfig((config, { environment }) => {
420
442
  const LAYERS = api.useExposed(Symbol.for('LAYERS'));
421
443
  if (!LAYERS) {
422
444
  throw new Error('external-bundle-rsbuild-plugin requires exposed `LAYERS`. Please install a DSL plugin, for example `pluginReactLynx` for ReactLynx.');
423
445
  }
446
+ const presetResolution = resolvePresetExternals(options.externalsPresets, presetDefinitions, {
447
+ rootPath: api.context.rootPath,
448
+ environmentName: environment.name,
449
+ });
424
450
  const explicitExternals = resolvePluginExternals(options.externals);
425
451
  const externals = {
426
452
  ...presetResolution.externals,
427
453
  ...explicitExternals,
428
454
  };
429
455
  const managedBundleAssets = getManagedBundleAssets(options, presetResolution.managedAssets, api);
456
+ for (const [bundlePath, sourcePath] of managedBundleAssets) {
457
+ localBundleAssets.set(joinUrlPath(serverBase, bundlePath), sourcePath);
458
+ }
430
459
  config.plugins = config.plugins || [];
431
460
  if (managedBundleAssets.size > 0) {
432
461
  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.2.0",
3
+ "version": "0.4.0",
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
40
  "@microsoft/api-extractor": "7.58.2",
41
- "@rsbuild/core": "1.7.5",
42
- "@lynx-js/react-umd": "0.121.1"
41
+ "@rsbuild/core": "2.1.4",
42
+ "@lynx-js/react-umd": "0.123.0"
43
43
  },
44
44
  "engines": {
45
45
  "node": ">=18"