@lynx-js/externals-loading-webpack-plugin-canary 0.0.0 → 0.0.1-canary-20251222-e9baa022

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 ADDED
@@ -0,0 +1,28 @@
1
+ # @lynx-js/externals-loading-webpack-plugin
2
+
3
+ ## 0.0.1-canary-20251222035050-e9baa022376ae79290d6811fa30597aece884b3f
4
+
5
+ ### Patch Changes
6
+
7
+ - Introduce `@lynx-js/externals-loading-webpack-plugin`. It will help you to load externals built by `@lynx-js/lynx-bundle-rslib-config`. ([#1924](https://github.com/lynx-family/lynx-stack/pull/1924))
8
+
9
+ ```js
10
+ // webpack.config.js
11
+ import { ExternalsLoadingPlugin } from "@lynx-js/externals-loading-webpack-plugin";
12
+
13
+ export default {
14
+ plugins: [
15
+ new ExternalsLoadingPlugin({
16
+ mainThreadLayer: "main-thread",
17
+ backgroundLayer: "background",
18
+ externals: {
19
+ lodash: {
20
+ url: "http://lodash.lynx.bundle",
21
+ background: { sectionPath: "background" },
22
+ mainThread: { sectionPath: "main-thread" },
23
+ },
24
+ },
25
+ }),
26
+ ],
27
+ };
28
+ ```
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ <h2 align="center">@lynx-js/externals-loading-webpack-plugin</h2>
2
+
3
+ A webpack plugin to support loading externals in Lynx.
package/lib/index.d.ts ADDED
@@ -0,0 +1,223 @@
1
+ /**
2
+ * @packageDocumentation
3
+ *
4
+ * A webpack plugin to load externals in Lynx. Use `lynx.fetchBundle()` and `lynx.loadScript()` API to load and parse the externals.
5
+ *
6
+ * @remarks
7
+ * Requires Lynx version 3.5 or later.
8
+ */
9
+ import type { Compiler } from '@rspack/core';
10
+ /**
11
+ * The options of the `ExternalsLoadingPlugin`.
12
+ *
13
+ * @public
14
+ */
15
+ export interface ExternalsLoadingPluginOptions {
16
+ /**
17
+ * The name of the main thread layer.
18
+ */
19
+ mainThreadLayer: string;
20
+ /**
21
+ * The name of the background layer.
22
+ */
23
+ backgroundLayer: string;
24
+ /**
25
+ * Specify the externals to be loaded. The externals should be Lynx Bundles.
26
+ *
27
+ * @example
28
+ *
29
+ * Load `lodash` library in background layer and `main-thread` layer.
30
+ *
31
+ * ```js
32
+ * module.exports = {
33
+ * plugins: [
34
+ * new ExternalsLoadingPlugin({
35
+ * externals: {
36
+ * lodash: {
37
+ * url: 'http://lodash.lynx.bundle',
38
+ * background: { sectionPath: 'background' },
39
+ * mainThread: { sectionPath: 'mainThread' },
40
+ * },
41
+ * },
42
+ * }),
43
+ * ],
44
+ * };
45
+ * ```
46
+ *
47
+ * @example
48
+ *
49
+ * Load `lodash` library only in background layer.
50
+ *
51
+ * ```js
52
+ * module.exports = {
53
+ * plugins: [
54
+ * new ExternalsLoadingPlugin({
55
+ * externals: {
56
+ * lodash: {
57
+ * url: 'http://lodash.lynx.bundle',
58
+ * background: { sectionPath: 'background' }
59
+ * },
60
+ * },
61
+ * }),
62
+ * ],
63
+ * };
64
+ * ```
65
+ */
66
+ externals: Record<string, {
67
+ /**
68
+ * The bundle(lynx.bundle) url of the library. The library source should be placed in `customSections`.
69
+ */
70
+ url: string;
71
+ /**
72
+ * The name of the library. Same as https://webpack.js.org/configuration/externals/#string.
73
+ *
74
+ * By default, the library name is the same as the externals key. For example:
75
+ *
76
+ * The config
77
+ *
78
+ * ```js
79
+ * ExternalsLoadingPlugin({
80
+ * externals: {
81
+ * lodash: {
82
+ * url: '……',
83
+ * }
84
+ * }
85
+ * })
86
+ * ```
87
+ *
88
+ * Will generate the following webpack externals config:
89
+ *
90
+ * ```js
91
+ * externals: {
92
+ * lodash: 'lynx[Symbol.for("__LYNX_EXTERNAL_GLOBAL__")].lodash',
93
+ * }
94
+ * ```
95
+ *
96
+ * If one external bundle contains multiple modules, should set the same library name to ensure it's loaded only once. For example:
97
+ *
98
+ * ```js
99
+ * ExternalsLoadingPlugin({
100
+ * externals: {
101
+ * lodash: {
102
+ * libraryName: 'Lodash',
103
+ * url: '……',
104
+ * },
105
+ * 'lodash-es': {
106
+ * libraryName: 'Lodash',
107
+ * url: '……',
108
+ * }
109
+ * }
110
+ * })
111
+ * ```
112
+ * Will generate the following webpack externals config:
113
+ *
114
+ * ```js
115
+ * externals: {
116
+ * lodash: 'lynx[Symbol.for("__LYNX_EXTERNAL_GLOBAL__")].Lodash',
117
+ * 'lodash-es': 'lynx[Symbol.for("__LYNX_EXTERNAL_GLOBAL__")].Lodash',
118
+ * }
119
+ * ```
120
+ *
121
+ * You can pass an array to specify subpath of the external. Same as https://webpack.js.org/configuration/externals/#string-1. For example:
122
+ *
123
+ * ```js
124
+ * ExternalsLoadingPlugin({
125
+ * externals: {
126
+ * preact: {
127
+ * libraryName: ['ReactLynx', 'Preact'],
128
+ * url: '……',
129
+ * },
130
+ * }
131
+ * })
132
+ * ```
133
+ *
134
+ * Will generate the following webpack externals config:
135
+ *
136
+ * ```js
137
+ * externals: {
138
+ * preact: 'lynx[Symbol.for("__LYNX_EXTERNAL_GLOBAL__")].ReactLynx.Preact',
139
+ * }
140
+ * ```
141
+ *
142
+ * @defaultValue `undefined`
143
+ *
144
+ * @example `Lodash`
145
+ */
146
+ libraryName?: string | string[];
147
+ /**
148
+ * Whether the source should be loaded asynchronously or not.
149
+ *
150
+ * @defaultValue `true`
151
+ */
152
+ async?: boolean;
153
+ /**
154
+ * The options of the background layer.
155
+ *
156
+ * @defaultValue `undefined`
157
+ */
158
+ background?: LayerOptions;
159
+ /**
160
+ * The options of the main-thread layer.
161
+ *
162
+ * @defaultValue `undefined`
163
+ */
164
+ mainThread?: LayerOptions;
165
+ /**
166
+ * The wait time in milliseconds.
167
+ *
168
+ * @defaultValue `2000`
169
+ */
170
+ timeout?: number;
171
+ }>;
172
+ }
173
+ /**
174
+ * The options of the background or main-thread layer.
175
+ *
176
+ * @public
177
+ */
178
+ export interface LayerOptions {
179
+ /**
180
+ * The path in `customSections`.
181
+ */
182
+ sectionPath: string;
183
+ }
184
+ /**
185
+ * The webpack plugin to load lynx external bundles.
186
+ *
187
+ * @example
188
+ * ```js
189
+ * // webpack.config.js
190
+ * import { ExternalsLoadingPlugin } from '@lynx-js/externals-loading-webpack-plugin';
191
+ *
192
+ * export default {
193
+ * plugins: [
194
+ * new ExternalsLoadingPlugin({
195
+ * mainThreadLayer: 'main-thread',
196
+ * backgroundLayer: 'background',
197
+ * mainThreadChunks: ['index__main-thread'],
198
+ * backgroundChunks: ['index'],
199
+ * externals: {
200
+ * 'lodash': {
201
+ * url: 'http://lodash.lynx.bundle',
202
+ * async: true,
203
+ * background: {
204
+ * sectionPath: 'background',
205
+ * },
206
+ * mainThread: {
207
+ * sectionPath: 'mainThread',
208
+ * },
209
+ * },
210
+ * }
211
+ * })
212
+ * ]
213
+ * }
214
+ * ```
215
+ *
216
+ * @public
217
+ */
218
+ export declare class ExternalsLoadingPlugin {
219
+ #private;
220
+ private options;
221
+ constructor(options: ExternalsLoadingPluginOptions);
222
+ apply(compiler: Compiler): void;
223
+ }
package/lib/index.js ADDED
@@ -0,0 +1,206 @@
1
+ // Copyright 2025 The Lynx Authors. All rights reserved.
2
+ // Licensed under the Apache License Version 2.0 that can be found in the
3
+ // LICENSE file in the root directory of this source tree.
4
+ function getLynxExternalGlobal() {
5
+ return `lynx[Symbol.for('__LYNX_EXTERNAL_GLOBAL__')]`;
6
+ }
7
+ /**
8
+ * The webpack plugin to load lynx external bundles.
9
+ *
10
+ * @example
11
+ * ```js
12
+ * // webpack.config.js
13
+ * import { ExternalsLoadingPlugin } from '@lynx-js/externals-loading-webpack-plugin';
14
+ *
15
+ * export default {
16
+ * plugins: [
17
+ * new ExternalsLoadingPlugin({
18
+ * mainThreadLayer: 'main-thread',
19
+ * backgroundLayer: 'background',
20
+ * mainThreadChunks: ['index__main-thread'],
21
+ * backgroundChunks: ['index'],
22
+ * externals: {
23
+ * 'lodash': {
24
+ * url: 'http://lodash.lynx.bundle',
25
+ * async: true,
26
+ * background: {
27
+ * sectionPath: 'background',
28
+ * },
29
+ * mainThread: {
30
+ * sectionPath: 'mainThread',
31
+ * },
32
+ * },
33
+ * }
34
+ * })
35
+ * ]
36
+ * }
37
+ * ```
38
+ *
39
+ * @public
40
+ */
41
+ export class ExternalsLoadingPlugin {
42
+ options;
43
+ constructor(options) {
44
+ this.options = options;
45
+ }
46
+ apply(compiler) {
47
+ const { RuntimeModule } = compiler.webpack;
48
+ const externalsLoadingPluginOptions = this.options;
49
+ class ExternalsLoadingRuntimeModule extends RuntimeModule {
50
+ options;
51
+ constructor(options) {
52
+ super('externals-loading-runtime');
53
+ this.options = options;
54
+ }
55
+ generate() {
56
+ if (!this.chunk?.name || !externalsLoadingPluginOptions.externals) {
57
+ return '';
58
+ }
59
+ return this.#genExternalsLoadingCode(this.options.layer);
60
+ }
61
+ #genExternalsLoadingCode(chunkLayer) {
62
+ const fetchCode = [];
63
+ const loadCode = [];
64
+ // filter duplicate externals by libraryName or package name to avoid loading the same external multiple times. We keep the last one.
65
+ const externalsMap = new Map();
66
+ let layer;
67
+ if (chunkLayer === externalsLoadingPluginOptions.backgroundLayer) {
68
+ layer = 'background';
69
+ }
70
+ else if (chunkLayer === externalsLoadingPluginOptions.mainThreadLayer) {
71
+ layer = 'mainThread';
72
+ }
73
+ else {
74
+ return '';
75
+ }
76
+ for (const [pkgName, external] of Object.entries(externalsLoadingPluginOptions.externals)) {
77
+ externalsMap.set(external.libraryName ?? pkgName, external);
78
+ }
79
+ const externals = Array.from(externalsMap.entries());
80
+ if (externals.length === 0) {
81
+ return '';
82
+ }
83
+ const runtimeGlobalsInit = `${getLynxExternalGlobal()} = {};`;
84
+ const loadExternalFunc = `
85
+ function createLoadExternalAsync(handler, sectionPath) {
86
+ return new Promise((resolve, reject) => {
87
+ handler.then((response) => {
88
+ if (response.code === 0) {
89
+ try {
90
+ const result = lynx.loadScript(sectionPath, { bundleName: response.url });
91
+ resolve(result)
92
+ } catch (error) {
93
+ reject(new Error('Failed to load script ' + sectionPath + ' in ' + response.url, { cause: error }))
94
+ }
95
+ } else {
96
+ reject(new Error('Failed to fetch external source ' + response.url + ' . The response is ' + JSON.stringify(response), { cause: response }));
97
+ }
98
+ })
99
+ })
100
+ }
101
+ function createLoadExternalSync(handler, sectionPath, timeout) {
102
+ const response = handler.wait(timeout)
103
+ if (response.code === 0) {
104
+ try {
105
+ const result = lynx.loadScript(sectionPath, { bundleName: response.url });
106
+ return result
107
+ } catch (error) {
108
+ throw new Error('Failed to load script ' + sectionPath + ' in ' + response.url, { cause: error })
109
+ }
110
+ } else {
111
+ throw new Error('Failed to fetch external source ' + response.url + ' . The response is ' + JSON.stringify(response), { cause: response })
112
+ }
113
+ }
114
+ `;
115
+ const hasUrlLibraryNamePairInjected = new Set();
116
+ for (let i = 0; i < externals.length; i++) {
117
+ const [pkgName, external] = externals[i];
118
+ const { libraryName, url, async = true, timeout: timeoutInMs = 2000, } = external;
119
+ const layerOptions = external[layer];
120
+ // Lynx fetchBundle timeout is in seconds
121
+ const timeout = timeoutInMs / 1000;
122
+ if (!layerOptions?.sectionPath) {
123
+ continue;
124
+ }
125
+ const libraryNameWithDefault = libraryName ?? pkgName;
126
+ const libraryNameStr = Array.isArray(libraryNameWithDefault)
127
+ ? libraryNameWithDefault[0]
128
+ : libraryNameWithDefault;
129
+ const hash = `${url}-${libraryNameStr}`;
130
+ if (hasUrlLibraryNamePairInjected.has(hash)) {
131
+ continue;
132
+ }
133
+ hasUrlLibraryNamePairInjected.add(hash);
134
+ fetchCode.push(`const handler${i} = lynx.fetchBundle(${JSON.stringify(url)}, {});`);
135
+ if (async) {
136
+ loadCode.push(`${getLynxExternalGlobal()}[${JSON.stringify(libraryNameStr)}] = createLoadExternalAsync(handler${i}, ${JSON.stringify(layerOptions.sectionPath)});`);
137
+ continue;
138
+ }
139
+ loadCode.push(`${getLynxExternalGlobal()}[${JSON.stringify(libraryNameStr)}] = createLoadExternalSync(handler${i}, ${JSON.stringify(layerOptions.sectionPath)}, ${timeout});`);
140
+ }
141
+ return [
142
+ runtimeGlobalsInit,
143
+ loadExternalFunc,
144
+ fetchCode,
145
+ loadCode,
146
+ ].flat().join('\n');
147
+ }
148
+ }
149
+ compiler.hooks.environment.tap(ExternalsLoadingPlugin.name, () => {
150
+ compiler.options.externals = [
151
+ ...(Array.isArray(compiler.options.externals)
152
+ ? compiler.options.externals
153
+ : (typeof compiler.options.externals === 'undefined'
154
+ ? []
155
+ : [compiler.options.externals])),
156
+ this.#genExternalsConfig(),
157
+ ];
158
+ });
159
+ compiler.hooks.compilation.tap(ExternalsLoadingRuntimeModule.name, compilation => {
160
+ compilation.hooks.additionalTreeRuntimeRequirements
161
+ .tap(ExternalsLoadingRuntimeModule.name, (chunk) => {
162
+ const modules = compilation.chunkGraph.getChunkModulesIterable(chunk);
163
+ let layer;
164
+ for (const module of modules) {
165
+ if (module.layer) {
166
+ layer = module.layer;
167
+ break;
168
+ }
169
+ }
170
+ if (!layer) {
171
+ // Skip chunks without a layer
172
+ return;
173
+ }
174
+ compilation.addRuntimeModule(chunk, new ExternalsLoadingRuntimeModule({ layer }));
175
+ });
176
+ });
177
+ }
178
+ /**
179
+ * If the external is async, use `promise` external type; otherwise, use `var` external type.
180
+ */
181
+ #genExternalsConfig() {
182
+ const { externals, backgroundLayer, mainThreadLayer } = this.options;
183
+ const externalDeps = new Set(Object.keys(externals));
184
+ return ({ request, contextInfo }, callback) => {
185
+ const currentLayer = contextInfo?.issuerLayer === mainThreadLayer
186
+ ? 'mainThread'
187
+ : (contextInfo?.issuerLayer === backgroundLayer
188
+ ? 'background'
189
+ : undefined);
190
+ if (request
191
+ && externalDeps.has(request)
192
+ && currentLayer
193
+ && externals[request]?.[currentLayer]) {
194
+ const isAsync = externals[request]?.async ?? true;
195
+ const libraryName = externals[request]?.libraryName ?? request;
196
+ return callback(undefined, [
197
+ getLynxExternalGlobal(),
198
+ ...(Array.isArray(libraryName) ? libraryName : [libraryName]),
199
+ ], isAsync ? 'promise' : undefined);
200
+ }
201
+ // Continue without externalizing the import
202
+ callback();
203
+ };
204
+ }
205
+ }
206
+ //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,9 +1,46 @@
1
1
  {
2
2
  "name": "@lynx-js/externals-loading-webpack-plugin-canary",
3
- "type": "module",
4
- "version": "0.0.0",
3
+ "version": "0.0.1-canary-20251222-e9baa022",
5
4
  "description": "A webpack plugin to load lynx external bundles.",
6
- "main": "index.js",
7
- "keywords": [],
8
- "license": "Apache-2.0"
9
- }
5
+ "keywords": [
6
+ "webpack",
7
+ "Lynx"
8
+ ],
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/lynx-family/lynx-stack.git",
12
+ "directory": "packages/webpack/externals-loading-webpack-plugin"
13
+ },
14
+ "license": "Apache-2.0",
15
+ "author": {
16
+ "name": "Hengchang Lu",
17
+ "email": "luhengchang228@gmail.com"
18
+ },
19
+ "type": "module",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./lib/index.d.ts",
23
+ "import": "./lib/index.js"
24
+ },
25
+ "./package.json": "./package.json"
26
+ },
27
+ "types": "./lib/index.d.ts",
28
+ "files": [
29
+ "lib",
30
+ "!lib/**/*.js.map",
31
+ "CHANGELOG.md",
32
+ "README.md"
33
+ ],
34
+ "devDependencies": {
35
+ "@rspack/core": "1.6.6",
36
+ "foo": "./test/foo",
37
+ "@lynx-js/test-tools": "0.0.0"
38
+ },
39
+ "engines": {
40
+ "node": ">=18"
41
+ },
42
+ "scripts": {
43
+ "api-extractor": "api-extractor run --verbose",
44
+ "test": "vitest"
45
+ }
46
+ }
package/index.js DELETED
@@ -1 +0,0 @@
1
- export {}