@lynx-js/react-webpack-plugin-canary 0.12.1 → 0.13.0-canary-20261014-bdb7471c

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,23 @@
1
1
  # @lynx-js/react-webpack-plugin
2
2
 
3
+ ## 0.13.0-canary-20261014081315-bdb7471ce6e98461b062d1572df766ecccf6bca4
4
+
5
+ ### Minor Changes
6
+
7
+ - Deliver the main-thread runtime through named imports from its init entry, including background-only definitions that carry their registration dependencies into the main-thread graph. Runtime code participates in business defines and release processing. Every supported transform supplies semantic runtime requirements, including for definitions injected from background-only modules; watch rebuilds refresh their dependencies and metadata. ([#4066](https://github.com/lynx-family/lynx-stack/pull/4066))
8
+
9
+ Upgrade the build plugins and `@lynx-js/react` together. This release requires `@lynx-js/react@^0.128.0`; its runtime and bundled transform use the same module-delivery protocol. The refresh plugin adds support for `@lynx-js/react-webpack-plugin@^0.13.0` while retaining its existing supported versions. Older compiler output is unsupported. The prebuilt worklet-runtime chunk, its package entries, and `loadWorkletRuntime` are removed. Combined lazy chunks share their paired main runtime, and standalone lazy bundles bootstrap themselves.
10
+
11
+ The worklet init entry follows `worklet.runtimePkg`: remove a trailing `/internal`, if present, and append `/worklet-runtime/init`. Its `registerWorklet` export uses the existing host registration function when the runtime is already initialized. Default worklet options use `@lynx-js/react/internal` for helpers.
12
+
13
+ Testing-library initializes module-delivered worklets on its emulated main thread in Vitest and Rstest.
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [[`bdb7471`](https://github.com/lynx-family/lynx-stack/commit/bdb7471ce6e98461b062d1572df766ecccf6bca4), [`89b828a`](https://github.com/lynx-family/lynx-stack/commit/89b828aba5e4eff14d8d2b5c44baecc121856c7d), [`16b45de`](https://github.com/lynx-family/lynx-stack/commit/16b45defdc76e822086b5207c0c290d51224da0e)]:
18
+ - @lynx-js/react@0.128.0-canary-20261014081315-bdb7471ce6e98461b062d1572df766ecccf6bca4
19
+ - @lynx-js/template-webpack-plugin@0.16.4-canary-20261014081315-bdb7471ce6e98461b062d1572df766ecccf6bca4
20
+
3
21
  ## 0.12.1
4
22
 
5
23
  ### Patch Changes
package/lib/Defines.d.ts CHANGED
@@ -4,6 +4,14 @@ export interface Define {
4
4
  id: string;
5
5
  code: string;
6
6
  unmergeable?: boolean;
7
+ imports?: Array<{
8
+ source: string;
9
+ imported: string;
10
+ local: string;
11
+ }>;
12
+ runtimeRequirements?: {
13
+ mainThreadProgrammability: boolean;
14
+ };
7
15
  }
8
16
  export declare function collectDefines<TModule>(modules: Iterable<TModule>, buildInfoKey: string, getModuleIdentifier: (module: TModule) => string): Define[];
9
17
  export declare function selectMissingDefines(candidates: readonly Define[], present: readonly Define[]): Define[];
package/lib/Defines.js CHANGED
@@ -32,9 +32,22 @@ export function collectDefines(modules, buildInfoKey, getModuleIdentifier) {
32
32
  defines.set(define.id, define);
33
33
  continue;
34
34
  }
35
- if (seen.code !== define.code) {
35
+ if (seen.code !== define.code
36
+ || JSON.stringify(seen.imports?.map(({ source, imported, local }) => [source, imported, local]))
37
+ !== JSON.stringify(define.imports?.map(({ source, imported, local }) => [source, imported, local]))) {
36
38
  throw new Error(`Two different definitions share the id ${define.id}.`);
37
39
  }
40
+ // Equal code and dependencies identify one executable definition. Keep
41
+ // semantic evidence from any copy instead of making it depend on order.
42
+ if (define.runtimeRequirements !== undefined) {
43
+ defines.set(define.id, {
44
+ ...seen,
45
+ runtimeRequirements: {
46
+ mainThreadProgrammability: seen.runtimeRequirements?.mainThreadProgrammability === true
47
+ || define.runtimeRequirements.mainThreadProgrammability,
48
+ },
49
+ });
50
+ }
38
51
  }
39
52
  return [...defines.values()];
40
53
  }
@@ -49,15 +62,30 @@ export function renderDefines(defines) {
49
62
  }
50
63
  export function renderDefinesModule(definesForSnapshot, definesForWorklet) {
51
64
  const prelude = [`import * as ReactLynx from '@lynx-js/react/internal';`];
52
- if (definesForWorklet.length > 0) {
53
- prelude.push(`var loadWorkletRuntime = ReactLynx.loadWorkletRuntime;`);
54
- }
55
65
  const defines = [...definesForWorklet, ...definesForSnapshot];
66
+ const locals = new Set(defines.flatMap(define => define.imports?.map(({ local }) => local) ?? []));
67
+ const imports = new Map();
68
+ let nextImport = 0;
69
+ const boundDefines = defines.map(define => {
70
+ const bindings = (define.imports ?? []).map(({ source, imported, local }) => {
71
+ const key = JSON.stringify([source, imported]);
72
+ let alias = imports.get(key);
73
+ if (alias === undefined) {
74
+ do {
75
+ alias = `__lynxDefineImport${nextImport++}`;
76
+ } while (locals.has(alias));
77
+ imports.set(key, alias);
78
+ prelude.push(`import { ${imported} as ${alias} } from ${JSON.stringify(source)};`);
79
+ }
80
+ return `const ${local} = ${alias};`;
81
+ });
82
+ return { ...define, code: [...bindings, define.code].join('\n') };
83
+ });
56
84
  if (defines.some(({ code }) => /\brequire\(/.test(code))) {
57
85
  prelude.push(`var require = function () { return ReactLynx; };`);
58
86
  }
59
87
  return `${prelude.join('\n')}
60
- ${renderDefines(defines)}
88
+ ${renderDefines(boundDefines)}
61
89
  `;
62
90
  }
63
91
  //# sourceMappingURL=Defines.js.map
@@ -5,6 +5,7 @@ import * as path from 'node:path';
5
5
  import { DEFINES_FOR_SNAPSHOT_BUILD_INFO, DEFINES_FOR_WORKLET_BUILD_INFO, collectDefines, renderDefinesModule, selectMissingDefines, } from './Defines.js';
6
6
  import { LAYERS } from './layer.js';
7
7
  import { boundaryKey, definesImportByBoundary, } from './loaders/defines-import-by-boundary.js';
8
+ import { REACT_RUNTIME_REQUIREMENTS_BUILD_INFO, } from './loaders/main-thread.js';
8
9
  export function applyDefinesInjection(compiler, entryPairs, pluginName) {
9
10
  const { EntryPlugin, experiments } = compiler.webpack;
10
11
  const virtualModules = new experiments.VirtualModulesPlugin({});
@@ -12,7 +13,21 @@ export function applyDefinesInjection(compiler, entryPairs, pluginName) {
12
13
  compiler.hooks.finishMake.tapPromise(pluginName, async (compilation) => {
13
14
  const { moduleGraph } = compilation;
14
15
  const definesImports = definesImportByBoundary(compiler);
16
+ const previousDefinesImports = new Map(definesImports);
15
17
  definesImports.clear();
18
+ const requirementsByRequest = new Map();
19
+ compilation.hooks.finishModules.tap(pluginName, modules => {
20
+ // Rebuilding an async boundary does not finish its new dependencies.
21
+ // Transfer compiler metadata only after all injected modules are built.
22
+ for (const module of modules) {
23
+ const resource = module.resource;
24
+ const requirements = resource && requirementsByRequest.get(resource);
25
+ if (requirements) {
26
+ module.buildInfo[REACT_RUNTIME_REQUIREMENTS_BUILD_INFO] =
27
+ requirements;
28
+ }
29
+ }
30
+ });
16
31
  const traverse = (roots) => {
17
32
  const visited = new Set();
18
33
  const asyncBoundaries = new Map();
@@ -82,8 +97,17 @@ export function applyDefinesInjection(compiler, entryPairs, pluginName) {
82
97
  throw new Error(`The main thread lacks the worklet definition(s) ${unmergeable.map(({ id }) => id).join(', ')} and they cannot be merged: they close over shared-runtime imports. Make the owning module reachable from the main thread, or avoid closing over a shared import inside the worklet.`);
83
98
  }
84
99
  if (missingSnapshot.length > 0 || missingWorklet.length > 0) {
100
+ const previousModule = [...compilation.modules].find(module => module.resource === request);
85
101
  virtualModules.writeModule(request, renderDefinesModule(missingSnapshot, missingWorklet));
102
+ // Updating the virtual file does not invalidate a module already
103
+ // reused by this compilation. Rebuild it before reusing its imports.
104
+ if (previousModule) {
105
+ await rebuildModule(previousModule);
106
+ }
86
107
  await inject(request);
108
+ requirementsByRequest.set(request, {
109
+ mainThreadProgrammability: missingWorklet.some(define => define.runtimeRequirements.mainThreadProgrammability),
110
+ });
87
111
  }
88
112
  for (const [resource, backgroundBoundary] of background.asyncBoundaries) {
89
113
  const mainThreadBoundary = mainThread.asyncBoundaries.get(resource);
@@ -111,6 +135,19 @@ export function applyDefinesInjection(compiler, entryPairs, pluginName) {
111
135
  }
112
136
  });
113
137
  }));
138
+ // A background boundary can lose its last definition or disappear
139
+ // entirely while its main-thread module remains cached and reachable.
140
+ // Rebuild every former owner that no longer needs a synthetic import.
141
+ await Promise.all([...compilation.modules].flatMap(module => {
142
+ const { resource, layer } = module;
143
+ if (!resource) {
144
+ return [];
145
+ }
146
+ const key = boundaryKey(layer, resource);
147
+ return previousDefinesImports.has(key) && !definesImports.has(key)
148
+ ? [rebuildModule(module)]
149
+ : [];
150
+ }));
114
151
  });
115
152
  }
116
153
  //# sourceMappingURL=DefinesInjection.js.map
@@ -65,10 +65,6 @@ interface ReactWebpackPluginOptions {
65
65
  * @defaultValue `false` when production, `true` when development
66
66
  */
67
67
  profile?: boolean | undefined;
68
- /**
69
- * The file path of `@lynx-js/react/worklet-runtime`.
70
- */
71
- workletRuntimePath: string;
72
68
  /**
73
69
  * Whether to enable Element Template compilation.
74
70
  *
@@ -1,7 +1,6 @@
1
1
  // Copyright 2024 The Lynx Authors. All rights reserved.
2
2
  // Licensed under the Apache License Version 2.0 that can be found in the
3
3
  // LICENSE file in the root directory of this source tree.
4
- import * as fs from 'node:fs';
5
4
  import { createRequire } from 'node:module';
6
5
  import invariant from 'tiny-invariant';
7
6
  import { LynxTemplatePlugin } from '@lynx-js/template-webpack-plugin';
@@ -187,7 +186,6 @@ class ReactWebpackPlugin {
187
186
  extractStr: false,
188
187
  experimental_isLazyBundle: false,
189
188
  profile: undefined,
190
- workletRuntimePath: '',
191
189
  experimental_useElementTemplate: false,
192
190
  experimental_transformBuiltinAttributeNames: false,
193
191
  lazyBundleFetcher: 'QueryComponent',
@@ -287,20 +285,7 @@ class ReactWebpackPlugin {
287
285
  // two layers keep their own chunk. Strip it again here: the resulting
288
286
  // chunk groups still make up a single lazy bundle.
289
287
  hooks.asyncChunkName.tap(this.constructor.name, (chunkName) => chunkName.replace(/-react__(?:background|main-thread)$/, ''));
290
- const { RawSource, ConcatSource } = compiler.webpack.sources;
291
- hooks.beforeEncode.tap(this.constructor.name, (args) => {
292
- const lepusCode = args.encodeData.lepusCode;
293
- if (lepusCode.root?.source.source().toString()?.includes('registerWorkletInternal')) {
294
- lepusCode.chunks.push({
295
- name: 'worklet-runtime',
296
- source: new RawSource(fs.readFileSync(options.workletRuntimePath, 'utf8')),
297
- info: {
298
- ['lynx:main-thread']: true,
299
- },
300
- });
301
- }
302
- return args;
303
- });
288
+ const { ConcatSource } = compiler.webpack.sources;
304
289
  if (compiler.options.plugins.some((p) => p instanceof LynxTemplatePlugin)) {
305
290
  compilation.hooks.processAssets.tap({
306
291
  name: this.constructor.name,
@@ -1,5 +1,9 @@
1
1
  import type { LoaderDefinitionFunction } from '@rspack/core';
2
2
  import type { ReactLoaderOptions } from './options.js';
3
3
  export declare const ELEMENT_TEMPLATE_BUILD_INFO = "lynx:element-templates";
4
+ export declare const REACT_RUNTIME_REQUIREMENTS_BUILD_INFO = "lynx:react-runtime-requirements";
5
+ export interface ReactRuntimeRequirementsBuildInfo {
6
+ mainThreadProgrammability: boolean;
7
+ }
4
8
  declare const mainThreadLoader: LoaderDefinitionFunction<ReactLoaderOptions>;
5
9
  export default mainThreadLoader;
@@ -7,6 +7,7 @@ import { DEFINES_FOR_SNAPSHOT_BUILD_INFO, DEFINES_FOR_WORKLET_BUILD_INFO, } from
7
7
  import { boundaryKey, definesImportByBoundary, } from './defines-import-by-boundary.js';
8
8
  import { getMainThreadTransformOptions } from './options.js';
9
9
  export const ELEMENT_TEMPLATE_BUILD_INFO = 'lynx:element-templates';
10
+ export const REACT_RUNTIME_REQUIREMENTS_BUILD_INFO = 'lynx:react-runtime-requirements';
10
11
  const mainThreadLoader = function (content, sourceMap) {
11
12
  const require = createRequire(import.meta.url);
12
13
  const { transformPath = '@lynx-js/react/transform' } = this.getOptions();
@@ -83,6 +84,14 @@ const mainThreadLoader = function (content, sourceMap) {
83
84
  const buildInfo = currentModule?.buildInfo;
84
85
  if (buildInfo) {
85
86
  buildInfo[UI_SOURCE_MAP_RECORDS_BUILD_INFO] = result.uiSourceMapRecords;
87
+ if (typeof result.runtimeRequirements !== 'object'
88
+ || result.runtimeRequirements === null
89
+ || typeof result.runtimeRequirements.mainThreadProgrammability
90
+ !== 'boolean') {
91
+ throw new TypeError('react-transform must provide runtimeRequirements.mainThreadProgrammability as a boolean; upgrade the build plugins and @lynx-js/react (runtime and transform) together to the module-delivery release (^0.128.0).');
92
+ }
93
+ buildInfo[REACT_RUNTIME_REQUIREMENTS_BUILD_INFO] =
94
+ result.runtimeRequirements;
86
95
  if (result.elementTemplates && result.elementTemplates.length > 0) {
87
96
  buildInfo[ELEMENT_TEMPLATE_BUILD_INFO] = result.elementTemplates;
88
97
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lynx-js/react-webpack-plugin-canary",
3
- "version": "0.12.1",
3
+ "version": "0.13.0-canary-20261014-bdb7471c",
4
4
  "description": "A webpack plugin for ReactLynx",
5
5
  "keywords": [
6
6
  "webpack",
@@ -38,11 +38,12 @@
38
38
  "tiny-invariant": "^1.3.3"
39
39
  },
40
40
  "devDependencies": {
41
+ "@lynx-js/chunk-loading-webpack-plugin": "npm:@lynx-js/chunk-loading-webpack-plugin-canary@0.4.2",
41
42
  "@lynx-js/css-extract-webpack-plugin": "npm:@lynx-js/css-extract-webpack-plugin-canary@0.11.1",
42
- "@lynx-js/react": "npm:@lynx-js/react-canary@0.127.0",
43
- "@lynx-js/template-webpack-plugin": "npm:@lynx-js/template-webpack-plugin-canary@0.16.3",
43
+ "@lynx-js/react": "npm:@lynx-js/react-canary@0.128.0-canary-20261014-bdb7471c",
44
+ "@lynx-js/template-webpack-plugin": "npm:@lynx-js/template-webpack-plugin-canary@0.16.4-canary-20261014-bdb7471c",
44
45
  "@lynx-js/test-tools": "0.0.0",
45
- "@microsoft/api-extractor": "7.59.2",
46
+ "@microsoft/api-extractor": "7.59.4",
46
47
  "@rspack/core": "2.2.7",
47
48
  "@rspack/lite-tapable": "1.1.5",
48
49
  "@rstest/core": "0.12.1",
@@ -50,6 +51,7 @@
50
51
  "swc-loader": "^0.2.7"
51
52
  },
52
53
  "peerDependencies": {
54
+ "@lynx-js/react": "*",
53
55
  "@lynx-js/template-webpack-plugin": "*"
54
56
  },
55
57
  "peerDependenciesMeta": {