diff --git a/examples/react-externals/README.md b/examples/react-externals/README.md new file mode 100644 index 0000000000..064e5e5c8c --- /dev/null +++ b/examples/react-externals/README.md @@ -0,0 +1,16 @@ +# @lynx-js/example-react-externals + +In this example, we show: + +- Use `@lynx-js/lynx-bundle-rslib-config` to bundle ReactLynx runtime to a separate Lynx bundle. +- Use `@lynx-js/lynx-bundle-rslib-config` to bundle a simple ReactLynx component library to a separate Lynx bundle. +- Use `@lynx-js/externals-loading-webpack-plugin` to load ReactLynx runtime (sync) and component bundle (async). + +## Usage + +```bash +pnpm build:reactlynx +pnpm build:comp-lib +pnpx http-server -p 8080 dist +EXTERNAL_BUNDLE_PREFIX=http://${YOUR_IP_HERE}$:8080 pnpm dev +``` diff --git a/examples/react-externals/external-bundle/CompLib.tsx b/examples/react-externals/external-bundle/CompLib.tsx new file mode 100644 index 0000000000..3c7068933a --- /dev/null +++ b/examples/react-externals/external-bundle/CompLib.tsx @@ -0,0 +1 @@ +export { App } from '../src/App.js'; diff --git a/examples/react-externals/external-bundle/ReactLynx.ts b/examples/react-externals/external-bundle/ReactLynx.ts new file mode 100644 index 0000000000..401d2ca086 --- /dev/null +++ b/examples/react-externals/external-bundle/ReactLynx.ts @@ -0,0 +1,10 @@ +export * as React from '@lynx-js/react'; +export * as ReactInternal from '@lynx-js/react/internal'; +export * as ReactLazyImport from '@lynx-js/react/experimental/lazy/import'; +export * as ReactLegacyRuntime from '@lynx-js/react/legacy-react-runtime'; +export * as ReactComponents from '@lynx-js/react/runtime-components'; +export * as ReactWorkletRuntime from '@lynx-js/react/worklet-runtime/bindings'; +export * as ReactDebug from '@lynx-js/react/debug'; +// @ts-expect-error preact is aliased by rspeedy +// eslint-disable-next-line import/no-unresolved +export * as Preact from 'preact'; diff --git a/examples/react-externals/lynx.config.js b/examples/react-externals/lynx.config.js new file mode 100644 index 0000000000..98efc148f1 --- /dev/null +++ b/examples/react-externals/lynx.config.js @@ -0,0 +1,108 @@ +import { ExternalsLoadingPlugin } from '@lynx-js/externals-loading-webpack-plugin'; +import { pluginQRCode } from '@lynx-js/qrcode-rsbuild-plugin'; +import { LAYERS, pluginReactLynx } from '@lynx-js/react-rsbuild-plugin'; +import { defineConfig } from '@lynx-js/rspeedy'; + +const enableBundleAnalysis = !!process.env['RSPEEDY_BUNDLE_ANALYSIS']; +const EXTERNAL_BUNDLE_PREFIX = process.env['EXTERNAL_BUNDLE_PREFIX'] || ''; + +export default defineConfig({ + tools: { + rspack: { + plugins: [ + new ExternalsLoadingPlugin({ + mainThreadChunks: ['main__main-thread'], + backgroundChunks: ['main'], + mainThreadLayer: LAYERS.MAIN_THREAD, + backgroundLayer: LAYERS.BACKGROUND, + externals: { + '@lynx-js/react': { + libraryName: ['ReactLynx', 'React'], + url: `${EXTERNAL_BUNDLE_PREFIX}/react.lynx.bundle`, + background: { sectionPath: 'ReactLynx' }, + mainThread: { sectionPath: 'ReactLynx__main-thread' }, + async: false, + }, + '@lynx-js/react/internal': { + libraryName: ['ReactLynx', 'ReactInternal'], + url: `${EXTERNAL_BUNDLE_PREFIX}/react.lynx.bundle`, + background: { sectionPath: 'ReactLynx' }, + mainThread: { sectionPath: 'ReactLynx__main-thread' }, + async: false, + }, + '@lynx-js/react/experimental/lazy/import': { + libraryName: ['ReactLynx', 'ReactLazyImport'], + url: `${EXTERNAL_BUNDLE_PREFIX}/react.lynx.bundle`, + background: { sectionPath: 'ReactLynx' }, + mainThread: { sectionPath: 'ReactLynx__main-thread' }, + async: false, + }, + '@lynx-js/react/legacy-react-runtime': { + libraryName: ['ReactLynx', 'ReactLegacyRuntime'], + url: `${EXTERNAL_BUNDLE_PREFIX}/react.lynx.bundle`, + background: { sectionPath: 'ReactLynx' }, + mainThread: { sectionPath: 'ReactLynx__main-thread' }, + async: false, + }, + '@lynx-js/react/runtime-components': { + libraryName: ['ReactLynx', 'ReactComponents'], + url: `${EXTERNAL_BUNDLE_PREFIX}/react.lynx.bundle`, + background: { sectionPath: 'ReactLynx' }, + mainThread: { sectionPath: 'ReactLynx__main-thread' }, + async: false, + }, + '@lynx-js/react/worklet-runtime/bindings': { + libraryName: ['ReactLynx', 'ReactWorkletRuntime'], + url: `${EXTERNAL_BUNDLE_PREFIX}/react.lynx.bundle`, + background: { sectionPath: 'ReactLynx' }, + mainThread: { sectionPath: 'ReactLynx__main-thread' }, + async: false, + }, + '@lynx-js/react/debug': { + libraryName: ['ReactLynx', 'ReactDebug'], + url: `${EXTERNAL_BUNDLE_PREFIX}/react.lynx.bundle`, + background: { sectionPath: 'ReactLynx' }, + mainThread: { sectionPath: 'ReactLynx__main-thread' }, + async: false, + }, + preact: { + libraryName: ['ReactLynx', 'Preact'], + url: `${EXTERNAL_BUNDLE_PREFIX}/react.lynx.bundle`, + background: { sectionPath: 'ReactLynx' }, + mainThread: { sectionPath: 'ReactLynx__main-thread' }, + async: false, + }, + './App.js': { + libraryName: 'CompLib', + url: `${EXTERNAL_BUNDLE_PREFIX}/comp-lib.lynx.bundle`, + background: { sectionPath: 'CompLib' }, + mainThread: { sectionPath: 'CompLib__main-thread' }, + async: false, + }, + }, + }), + ], + }, + }, + plugins: [ + pluginReactLynx(), + pluginQRCode({ + schema(url) { + // We use `?fullscreen=true` to open the page in LynxExplorer in full screen mode + return `${url}?fullscreen=true`; + }, + }), + ], + environments: { + web: {}, + lynx: { + performance: { + profile: enableBundleAnalysis, + }, + }, + }, + output: { + filenameHash: 'contenthash:8', + cleanDistPath: false, + }, +}); diff --git a/examples/react-externals/package.json b/examples/react-externals/package.json new file mode 100644 index 0000000000..ccf2f8e918 --- /dev/null +++ b/examples/react-externals/package.json @@ -0,0 +1,27 @@ +{ + "name": "@lynx-js/example-react-externals", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "rspeedy build", + "build:comp-lib": "rslib build --config rslib-comp-lib.config.ts", + "build:reactlynx": "rslib build --config rslib-reactlynx.config.ts", + "dev": "rspeedy dev", + "test:type": "vitest --typecheck.only" + }, + "dependencies": { + "@lynx-js/react": "workspace:*" + }, + "devDependencies": { + "@lynx-js/externals-loading-webpack-plugin": "workspace:*", + "@lynx-js/lynx-bundle-rslib-config": "workspace:*", + "@lynx-js/preact-devtools": "^5.0.1-cf9aef5", + "@lynx-js/qrcode-rsbuild-plugin": "workspace:*", + "@lynx-js/react-rsbuild-plugin": "workspace:*", + "@lynx-js/react-webpack-plugin": "workspace:*", + "@lynx-js/rspeedy": "workspace:*", + "@lynx-js/types": "3.4.11", + "@types/react": "^18.3.25" + } +} diff --git a/examples/react-externals/rslib-comp-lib.config.ts b/examples/react-externals/rslib-comp-lib.config.ts new file mode 100644 index 0000000000..a9afe92215 --- /dev/null +++ b/examples/react-externals/rslib-comp-lib.config.ts @@ -0,0 +1,81 @@ +import { createRequire } from 'node:module'; +import path from 'node:path'; + +import { + LAYERS, + defineExternalBundleRslibConfig, +} from '@lynx-js/lynx-bundle-rslib-config'; +import { ReactWebpackPlugin } from '@lynx-js/react-webpack-plugin'; + +const require = createRequire(import.meta.url); +const reactLynxDir = path.dirname( + require.resolve('@lynx-js/react/package.json'), +); +const preactDir = path.dirname( + require.resolve('preact/package.json', { paths: [reactLynxDir] }), +); + +export default defineExternalBundleRslibConfig({ + id: 'comp-lib', + tools: { + rspack: { + module: { + rules: [ + { + test: /\.(?:js|jsx|mjs|cjs|ts|tsx|mts|cts)$/, + issuerLayer: LAYERS.BACKGROUND, + loader: ReactWebpackPlugin.loaders.BACKGROUND, + }, + { + test: /\.(?:js|jsx|mjs|cjs|ts|tsx|mts|cts)$/, + issuerLayer: LAYERS.MAIN_THREAD, + loader: ReactWebpackPlugin.loaders.MAIN_THREAD, + }, + ], + }, + }, + }, + source: { + entry: { + 'CompLib': './external-bundle/CompLib.tsx', + }, + define: { + '__DEV__': 'false', + 'process.env.NODE_ENV': '"production"', + '__FIRST_SCREEN_SYNC_TIMING__': '"immediately"', + '__ENABLE_SSR__': 'false', + '__PROFILE__': 'false', + '__EXTRACT_STR__': 'false', + }, + }, + resolve: { + alias: { + 'react': preactDir, + 'preact': preactDir, + }, + }, + output: { + cleanDistPath: false, + dataUriLimit: Number.POSITIVE_INFINITY, + externals: { + '@lynx-js/react': ['ReactLynx', 'React'], + '@lynx-js/react/internal': ['ReactLynx', 'ReactInternal'], + '@lynx-js/react/experimental/lazy/import': [ + 'ReactLynx', + 'ReactLazyImport', + ], + '@lynx-js/react/legacy-react-runtime': [ + 'ReactLynx', + 'ReactLegacyRuntime', + ], + '@lynx-js/react/runtime-components': ['ReactLynx', 'ReactComponents'], + '@lynx-js/react/worklet-runtime/bindings': [ + 'ReactLynx', + 'ReactWorkletRuntime', + ], + '@lynx-js/react/debug': ['ReactLynx', 'ReactDebug'], + 'preact': ['ReactLynx', 'Preact'], + }, + minify: false, + }, +}); diff --git a/examples/react-externals/rslib-reactlynx.config.ts b/examples/react-externals/rslib-reactlynx.config.ts new file mode 100644 index 0000000000..c16f9ce09c --- /dev/null +++ b/examples/react-externals/rslib-reactlynx.config.ts @@ -0,0 +1,59 @@ +import { createRequire } from 'node:module'; +import path from 'node:path'; + +import { + LAYERS, + defineExternalBundleRslibConfig, +} from '@lynx-js/lynx-bundle-rslib-config'; +import { ReactWebpackPlugin } from '@lynx-js/react-webpack-plugin'; + +const require = createRequire(import.meta.url); +const reactLynxDir = path.dirname( + require.resolve('@lynx-js/react/package.json'), +); +const preactDir = path.dirname( + require.resolve('preact/package.json', { paths: [reactLynxDir] }), +); + +export default defineExternalBundleRslibConfig({ + id: 'react', + tools: { + rspack: { + module: { + rules: [ + { + issuerLayer: LAYERS.BACKGROUND, + loader: ReactWebpackPlugin.loaders.BACKGROUND, + }, + { + issuerLayer: LAYERS.MAIN_THREAD, + loader: ReactWebpackPlugin.loaders.MAIN_THREAD, + }, + ], + }, + }, + }, + source: { + entry: { + 'ReactLynx': './external-bundle/ReactLynx.ts', + }, + define: { + '__DEV__': 'false', + 'process.env.NODE_ENV': '"production"', + '__FIRST_SCREEN_SYNC_TIMING__': '"immediately"', + '__ENABLE_SSR__': 'false', + '__PROFILE__': 'false', + '__EXTRACT_STR__': 'false', + }, + }, + resolve: { + alias: { + 'react': preactDir, + 'preact': preactDir, + }, + }, + output: { + cleanDistPath: false, + minify: false, + }, +}); diff --git a/examples/react-externals/src/App.css b/examples/react-externals/src/App.css new file mode 100644 index 0000000000..650e423282 --- /dev/null +++ b/examples/react-externals/src/App.css @@ -0,0 +1,119 @@ +:root { + background-color: #000; + --color-text: #fff; +} + +.Background { + position: fixed; + background: radial-gradient( + 71.43% 62.3% at 46.43% 36.43%, + rgba(18, 229, 229, 0) 15%, + rgba(239, 155, 255, 0.3) 56.35%, + #ff6448 100% + ); + box-shadow: 0px 12.93px 28.74px 0px #ffd28db2 inset; + border-radius: 50%; + width: 200vw; + height: 200vw; + top: -60vw; + left: -14.27vw; + transform: rotate(15.25deg); +} + +.App { + position: relative; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +text { + color: var(--color-text); +} + +.Banner { + flex: 5; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + z-index: 100; +} + +.Logo { + flex-direction: column; + align-items: center; + justify-content: center; + margin-bottom: 8px; +} + +.Logo--react { + width: 100px; + height: 100px; + animation: Logo--spin infinite 20s linear; +} + +.Logo--lynx { + width: 100px; + height: 100px; + animation: Logo--shake infinite 0.5s ease; +} + +@keyframes Logo--spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@keyframes Logo--shake { + 0% { + transform: scale(1); + } + 50% { + transform: scale(0.9); + } + 100% { + transform: scale(1); + } +} + +.Content { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.Arrow { + width: 24px; + height: 24px; +} + +.Title { + font-size: 36px; + font-weight: 700; +} + +.Subtitle { + font-style: italic; + font-size: 22px; + font-weight: 600; + margin-bottom: 8px; +} + +.Description { + font-size: 20px; + color: rgba(255, 255, 255, 0.85); + margin: 15rpx; +} + +.Hint { + font-size: 12px; + margin: 5px; + color: rgba(255, 255, 255, 0.65); +} diff --git a/examples/react-externals/src/App.tsx b/examples/react-externals/src/App.tsx new file mode 100644 index 0000000000..5da30edede --- /dev/null +++ b/examples/react-externals/src/App.tsx @@ -0,0 +1,52 @@ +import { useCallback, useEffect, useState } from '@lynx-js/react'; + +import './App.css'; +import arrow from './assets/arrow.png'; +import lynxLogo from './assets/lynx-logo.png'; +import reactLynxLogo from './assets/react-logo.png'; + +export function App() { + const [alterLogo, setAlterLogo] = useState(false); + + useEffect(() => { + console.info('Hello, ReactLynx'); + }, []); + + const onTap = useCallback(() => { + 'background-only'; + setAlterLogo(prevAlterLogo => !prevAlterLogo); + }, []); + + return ( + + + + + + {alterLogo + ? + : } + + React + on Lynx + + + + Tap the logo and have fun! + + Edit + {' src/App.tsx '} + + to see updates! + + + + + + ); +} diff --git a/examples/react-externals/src/assets/arrow.png b/examples/react-externals/src/assets/arrow.png new file mode 100644 index 0000000000..435c8ad4d1 Binary files /dev/null and b/examples/react-externals/src/assets/arrow.png differ diff --git a/examples/react-externals/src/assets/lynx-logo.png b/examples/react-externals/src/assets/lynx-logo.png new file mode 100644 index 0000000000..fe44bf0e3c Binary files /dev/null and b/examples/react-externals/src/assets/lynx-logo.png differ diff --git a/examples/react-externals/src/assets/react-logo.png b/examples/react-externals/src/assets/react-logo.png new file mode 100644 index 0000000000..4ad12a6b55 Binary files /dev/null and b/examples/react-externals/src/assets/react-logo.png differ diff --git a/examples/react-externals/src/index.tsx b/examples/react-externals/src/index.tsx new file mode 100644 index 0000000000..d0d893b024 --- /dev/null +++ b/examples/react-externals/src/index.tsx @@ -0,0 +1,17 @@ +import '@lynx-js/react/debug'; +import { root } from '@lynx-js/react'; + +import { App } from './App.js'; + +// We have to manually import the css now +// TODO: load css from external bundle +// when it is supported in Lynx engine +import './App.css'; + +root.render( + , +); + +if (import.meta.webpackHot) { + import.meta.webpackHot.accept(); +} diff --git a/examples/react-externals/src/rspeedy-env.d.ts b/examples/react-externals/src/rspeedy-env.d.ts new file mode 100644 index 0000000000..1c813a68b0 --- /dev/null +++ b/examples/react-externals/src/rspeedy-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react-externals/tsconfig.json b/examples/react-externals/tsconfig.json new file mode 100644 index 0000000000..8f6d4de332 --- /dev/null +++ b/examples/react-externals/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "@lynx-js/react", + "noEmit": true, + + "allowJs": true, + "checkJs": true, + "isolatedDeclarations": false, + }, + "include": ["src", "external-bundle", "lynx.config.js", "rslib-comp-lib.config.ts", "rslib-reactlynx.config.ts"], + "references": [ + { "path": "../../packages/react/tsconfig.json" }, + { "path": "../../packages/rspeedy/core/tsconfig.build.json" }, + { "path": "../../packages/rspeedy/plugin-qrcode/tsconfig.build.json" }, + { "path": "../../packages/rspeedy/plugin-react/tsconfig.build.json" }, + ], +} diff --git a/packages/rspeedy/lynx-bundle-rslib-config/src/externalBundleRslibConfig.ts b/packages/rspeedy/lynx-bundle-rslib-config/src/externalBundleRslibConfig.ts index 81dd438f1e..67d1c3a02e 100644 --- a/packages/rspeedy/lynx-bundle-rslib-config/src/externalBundleRslibConfig.ts +++ b/packages/rspeedy/lynx-bundle-rslib-config/src/externalBundleRslibConfig.ts @@ -75,6 +75,45 @@ export const defaultExternalBundleLibConfig: LibConfig = { }, } +type Externals = Record + +type LibOutputConfig = Required['output'] + +interface OutputConfig extends LibOutputConfig { + externals?: Externals +} + +interface ExternalBundleLibConfig extends LibConfig { + output?: OutputConfig +} + +function transformExternals( + externals?: Externals, +): Required['externals'] { + if (!externals) return {} + + return function({ request, contextInfo }, callback) { + if (!request) return callback() + const libraryName = externals[request] + if (!libraryName) return callback() + + if (contextInfo?.issuerLayer === LAYERS.MAIN_THREAD) { + callback(undefined, [ + 'globalThis', + 'lynx_ex', + ...(Array.isArray(libraryName) ? libraryName : [libraryName]), + ], 'var') + } else { + callback(undefined, [ + 'lynxCoreInject', + 'tt', + 'lynx_ex', + ...(Array.isArray(libraryName) ? libraryName : [libraryName]), + ], 'var') + } + } +} + /** * Get the rslib config for building Lynx external bundles. * @@ -146,7 +185,7 @@ export const defaultExternalBundleLibConfig: LibConfig = { * Then you can use `lynx.loadScript('utils', { bundleName: 'utils-lib-bundle-url' })` in background thread and `lynx.loadScript('utils__main-thread', { bundleName: 'utils-lib-bundle-url' })` in main-thread. */ export function defineExternalBundleRslibConfig( - userLibConfig: LibConfig, + userLibConfig: ExternalBundleLibConfig, encodeOptions: EncodeOptions = {}, ): RslibConfig { return { @@ -154,7 +193,13 @@ export function defineExternalBundleRslibConfig( // eslint-disable-next-line import/namespace rsbuild.mergeRsbuildConfig( defaultExternalBundleLibConfig, - userLibConfig, + { + ...userLibConfig, + output: { + ...userLibConfig.output, + externals: transformExternals(userLibConfig.output?.externals), + }, + }, ), ], plugins: [ diff --git a/packages/webpack/externals-loading-webpack-plugin/src/index.ts b/packages/webpack/externals-loading-webpack-plugin/src/index.ts index 3926da1903..9f4d635bf7 100644 --- a/packages/webpack/externals-loading-webpack-plugin/src/index.ts +++ b/packages/webpack/externals-loading-webpack-plugin/src/index.ts @@ -144,11 +144,32 @@ export interface ExternalsLoadingPluginOptions { * } * ``` * + * You can pass an array to specify subpath of the external. Same as https://webpack.js.org/configuration/externals/#string-1. For example: + * + * ```js + * ExternalsLoadingPlugin({ + * externals: { + * preact: { + * libraryName: ['ReactLynx', 'Preact'], + * url: '……', + * }, + * } + * }) + * ``` + * + * Will generate the following webpack externals config: + * + * ```js + * externals: { + * preact: '__webpack_require__.lynx_ex.ReactLynx.Preact', + * } + * ``` + * * @defaultValue `undefined` * * @example `Lodash` */ - libraryName?: string; + libraryName?: string | string[]; /** * Whether the source should be loaded asynchronously or not. @@ -193,6 +214,13 @@ export interface LayerOptions { sectionPath: string; } +function getLynxExternalGlobal(layer: 'background' | 'mainThread') { + // We do not use `globalThis` in BTS to avoid issues when sharing js context is enabled + return `${ + layer === 'background' ? 'lynxCoreInject.tt.lynx_ex' : 'globalThis.lynx_ex' + }`; +} + /** * The webpack plugin to load lynx external bundles. * @@ -228,10 +256,6 @@ export interface LayerOptions { * @public */ export class ExternalsLoadingPlugin { - static RuntimeGlobals = { - lynxExternals: '__webpack_require__.lynx_ex', - }; - constructor(private options: ExternalsLoadingPluginOptions) {} apply(compiler: Compiler): void { @@ -279,7 +303,7 @@ export class ExternalsLoadingPlugin { const syncLoadCode: string[] = []; // filter duplicate externals by libraryName or package name to avoid loading the same external multiple times. We keep the last one. const externalsMap = new Map< - string, + string | string[], ExternalsLoadingPluginOptions['externals'][string] >(); for ( @@ -294,8 +318,7 @@ export class ExternalsLoadingPlugin { if (externals.length === 0) { return ''; } - const runtimeGlobalsInit = - `${ExternalsLoadingPlugin.RuntimeGlobals.lynxExternals} = {};`; + const runtimeGlobalsInit = `${getLynxExternalGlobal(layer)} = {};`; const loadExternalFunc = ` function createLoadExternalAsync(handler, sectionPath) { return new Promise((resolve, reject) => { @@ -328,6 +351,8 @@ function createLoadExternalSync(handler, sectionPath, timeout) { } `; + const hasUrlLibraryNamePairInjected = new Set(); + for (let i = 0; i < externals.length; i++) { const [pkgName, external] = externals[i]!; const { @@ -343,13 +368,26 @@ function createLoadExternalSync(handler, sectionPath, timeout) { if (!layerOptions?.sectionPath) { continue; } + + const libraryNameWithDefault = libraryName ?? pkgName; + const libraryNameStr = Array.isArray(libraryNameWithDefault) + ? libraryNameWithDefault[0] + : libraryNameWithDefault; + + const hash = `${url}-${libraryNameStr}`; + if (hasUrlLibraryNamePairInjected.has(hash)) { + continue; + } + hasUrlLibraryNamePairInjected.add(hash); + fetchCode.push( `const handler${i} = lynx.fetchBundle(${JSON.stringify(url)}, {});`, ); + if (async) { asyncLoadCode.push( - `${ExternalsLoadingPlugin.RuntimeGlobals.lynxExternals}[${ - JSON.stringify(libraryName ?? pkgName) + `${getLynxExternalGlobal(layer)}[${ + JSON.stringify(libraryNameStr) }] = createLoadExternalAsync(handler${i}, ${ JSON.stringify(layerOptions.sectionPath) });`, @@ -358,8 +396,8 @@ function createLoadExternalSync(handler, sectionPath, timeout) { } syncLoadCode.push( - `${ExternalsLoadingPlugin.RuntimeGlobals.lynxExternals}[${ - JSON.stringify(libraryName ?? pkgName) + `${getLynxExternalGlobal(layer)}[${ + JSON.stringify(libraryNameStr) }] = createLoadExternalSync(handler${i}, ${ JSON.stringify(layerOptions.sectionPath) }, ${timeout});`, @@ -428,13 +466,14 @@ function createLoadExternalSync(handler, sectionPath, timeout) { && externals[request]?.[currentLayer] ) { const isAsync = externals[request]?.async ?? true; + const libraryName = externals[request]?.libraryName ?? request; return callback( undefined, - `${ - isAsync ? 'promise ' : '' - }${ExternalsLoadingPlugin.RuntimeGlobals.lynxExternals}[${ - JSON.stringify(externals[request]?.libraryName ?? request) - }]`, + [ + getLynxExternalGlobal(currentLayer), + ...(Array.isArray(libraryName) ? libraryName : [libraryName]), + ], + isAsync ? 'promise' : undefined, ); } // Continue without externalizing the import diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2b29c7b398..4d3343fdc4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -242,6 +242,40 @@ importers: specifier: 0.0.0-experimental-0566679-20250709 version: 0.0.0-experimental-0566679-20250709 + examples/react-externals: + dependencies: + '@lynx-js/react': + specifier: workspace:* + version: link:../../packages/react + devDependencies: + '@lynx-js/externals-loading-webpack-plugin': + specifier: workspace:* + version: link:../../packages/webpack/externals-loading-webpack-plugin + '@lynx-js/lynx-bundle-rslib-config': + specifier: workspace:* + version: link:../../packages/rspeedy/lynx-bundle-rslib-config + '@lynx-js/preact-devtools': + specifier: ^5.0.1-cf9aef5 + version: 5.0.1 + '@lynx-js/qrcode-rsbuild-plugin': + specifier: workspace:* + version: link:../../packages/rspeedy/plugin-qrcode + '@lynx-js/react-rsbuild-plugin': + specifier: workspace:* + version: link:../../packages/rspeedy/plugin-react + '@lynx-js/react-webpack-plugin': + specifier: workspace:* + version: link:../../packages/webpack/react-webpack-plugin + '@lynx-js/rspeedy': + specifier: workspace:* + version: link:../../packages/rspeedy/core + '@lynx-js/types': + specifier: 3.4.11 + version: 3.4.11 + '@types/react': + specifier: ^18.3.25 + version: 18.3.25 + examples/react-lazy-bundle: dependencies: '@lynx-js/react':