Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/blue-emus-brake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@lynx-js/external-bundle-rsbuild-plugin": minor
"@lynx-js/externals-loading-webpack-plugin": minor
"@lynx-js/lynx-bundle-rslib-config": minor
---

**BREAKING CHANGE**:

Simplify the API for external bundle builds by `externalsPresets` and `externalsPresetDefinitions`.
5 changes: 5 additions & 0 deletions .changeset/four-forks-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lynx-js/react-umd": patch
---

Add a new `entry` export to `@lynx-js/react-umd` for reuse by wrapper libraries of `@lynx-js/react`.
5 changes: 5 additions & 0 deletions .github/externals-loading-webpack-plugin.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
applyTo: "packages/webpack/externals-loading-webpack-plugin/**"
---

Keep `ExternalsLoadingPlugin` focused on consuming finalized `externals` maps and generating runtime loading code. Do not bake project-specific preset expansion or filesystem-backed dev serving into this low-level plugin; those concerns belong in higher-level Rsbuild integrations such as `pluginExternalBundle`. It is acceptable for this low-level plugin to resolve a relative `bundlePath` against the runtime `publicPath`, because that stays within generic bundler/runtime behavior instead of Rspeedy-specific URL inference.
5 changes: 5 additions & 0 deletions .github/lynx-bundle-rslib-config.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
applyTo: "packages/rspeedy/lynx-bundle-rslib-config/**"
---

Keep `@lynx-js/lynx-bundle-rslib-config` preset resolution extensible. `output.externalsPresetDefinitions` should live alongside `output.externalsPresets`, so business configs can register new preset names such as `lynxUi` directly in their rslib config while wrappers still layer in defaults. New preset behavior should be expressed through exported preset-definition types and `extends` relationships rather than by hard-coding one-off merge logic outside the shared resolver.
8 changes: 8 additions & 0 deletions .github/plugin-external-bundle.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
applyTo: "packages/rspeedy/plugin-external-bundle/**"
---

Keep `pluginExternalBundle` responsible for expanding built-in externals presets and for Rspeedy-specific dev-server behavior such as serving local external bundles during development. Use `externalBundleRoot` as the source directory for project-owned external bundles referenced by `bundlePath`, but treat `dist-external-bundle` as the normal default and only show `externalBundleRoot` in examples when the bundle source directory is non-default. Resolve the built-in React preset bundle from the `@lynx-js/react-umd/dev` or `@lynx-js/react-umd/prod` peer dependency instead of reaching into the monorepo with a relative filesystem path, and when the preset uses `bundlePath` rather than an explicit `url`, emit that runtime bundle into the user's build output automatically.
Document `bundlePath` as the preferred option over `url` in both public README examples and API docs. README examples for external-bundle workflows should show `externalsPresets.reactlynx` and `externalsPresetDefinitions` instead of hand-written React externals maps or custom middleware, and should prefer the shorthand `externals: { './App.js': 'comp.lynx.bundle' }` form when request keys and section names naturally align.
Treat `externalsPresetDefinitions` as the main extension point for internal wrappers or downstream variants. When a wrapper needs to add aliases like `@byted-lynx/*`, extend the OSS plugin through preset definitions instead of forking the whole plugin implementation.
Because the built-in `reactlynx` preset resolves `@lynx-js/react-umd/dev` or `@lynx-js/react-umd/prod` to generated `dist/*` artifacts, add precise package-level Turbo dependencies for the affected `test` or `build` tasks instead of widening the global workspace `test` pipeline; otherwise clean CI runs can fail even when local machines pass due to stale built files already existing.
1 change: 1 addition & 0 deletions examples/react-externals/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist-external-bundle
6 changes: 2 additions & 4 deletions examples/react-externals/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@

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/external-bundle-rsbuild-plugin` to load ReactLynx runtime (sync) and component bundle (async).
- Use `@lynx-js/external-bundle-rsbuild-plugin` to load the built-in ReactLynx runtime bundle (sync) and component bundle (async).

## Usage

```bash
pnpm build:reactlynx
pnpm build:comp-lib
pnpm dev
```

The dev server will automatically serve the ReactLynx runtime and the component library bundles.
The dev server will automatically serve the built-in ReactLynx runtime bundle and the component library bundle.
178 changes: 4 additions & 174 deletions examples/react-externals/lynx.config.ts
Original file line number Diff line number Diff line change
@@ -1,91 +1,12 @@
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

import { pluginExternalBundle } from '@lynx-js/external-bundle-rsbuild-plugin';
import { pluginQRCode } from '@lynx-js/qrcode-rsbuild-plugin';
import { pluginReactLynx } from '@lynx-js/react-rsbuild-plugin';
import { defineConfig } from '@lynx-js/rspeedy';
import type { RsbuildPlugin } from '@lynx-js/rspeedy';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

function getIPAddress() {
const interfaces = os.networkInterfaces();
for (const devName in interfaces) {
const iface = interfaces[devName];
if (iface) {
for (const alias of iface) {
if (
alias.family === 'IPv4' && alias.address !== '127.0.0.1'
&& !alias.internal
) {
return alias.address;
}
}
}
}
return 'localhost';
}

const enableBundleAnalysis = !!process.env['RSPEEDY_BUNDLE_ANALYSIS'];
const PORT = 8291;
const EXTERNAL_BUNDLE_PREFIX = process.env['EXTERNAL_BUNDLE_PREFIX']
?? `http://${getIPAddress()}:${PORT}`;

const pluginServeExternals = (): RsbuildPlugin => ({
name: 'serve-externals',
setup(api) {
api.modifyRsbuildConfig((config, { mergeRsbuildConfig }) => {
return mergeRsbuildConfig(config, {
dev: {
setupMiddlewares: [
(middlewares) => {
middlewares.unshift((
req: import('node:http').IncomingMessage,
res: import('node:http').ServerResponse,
next: () => void,
) => {
if (req.url === '/react.lynx.bundle') {
const bundlePath = path.resolve(
__dirname,
'../../packages/react-umd/dist/react-dev.lynx.bundle',
);
if (fs.existsSync(bundlePath)) {
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Access-Control-Allow-Origin', '*');
fs.createReadStream(bundlePath).pipe(res);
return;
}
}
if (req.url === '/comp-lib.lynx.bundle') {
const bundlePath = path.resolve(
__dirname,
'./dist/comp-lib.lynx.bundle',
);
if (fs.existsSync(bundlePath)) {
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Access-Control-Allow-Origin', '*');
fs.createReadStream(bundlePath).pipe(res);
return;
}
}
next();
});
return middlewares;
},
],
},
});
});
},
});

export default defineConfig({
plugins: [
pluginServeExternals(),
pluginReactLynx(),
pluginQRCode({
schema(url) {
Expand All @@ -94,98 +15,11 @@ export default defineConfig({
},
}),
pluginExternalBundle({
externalsPresets: {
reactlynx: true,
},
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/jsx-dev-runtime': {
libraryName: ['ReactLynx', 'ReactJSXDevRuntime'],
url: `${EXTERNAL_BUNDLE_PREFIX}/react.lynx.bundle`,
background: { sectionPath: 'ReactLynx' },
mainThread: { sectionPath: 'ReactLynx__main-thread' },
async: false,
},
'@lynx-js/react/jsx-runtime': {
libraryName: ['ReactLynx', 'ReactJSXRuntime'],
url: `${EXTERNAL_BUNDLE_PREFIX}/react.lynx.bundle`,
background: { sectionPath: 'ReactLynx' },
mainThread: { sectionPath: 'ReactLynx__main-thread' },
async: false,
},
'@lynx-js/react/lepus/jsx-dev-runtime': {
libraryName: ['ReactLynx', 'ReactJSXLepusDevRuntime'],
url: `${EXTERNAL_BUNDLE_PREFIX}/react.lynx.bundle`,
background: { sectionPath: 'ReactLynx' },
mainThread: { sectionPath: 'ReactLynx__main-thread' },
async: false,
},
'@lynx-js/react/lepus/jsx-runtime': {
libraryName: ['ReactLynx', 'ReactJSXLepusRuntime'],
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: true,
},
'./App.js': 'comp-lib.lynx.bundle',
},
globalObject: 'globalThis',
}),
Expand All @@ -198,11 +32,7 @@ export default defineConfig({
},
},
},
server: {
port: PORT,
},
output: {
filenameHash: 'contenthash:8',
cleanDistPath: false,
},
});
2 changes: 1 addition & 1 deletion examples/react-externals/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"type": "module",
"scripts": {
"build": "npm run build:comp-lib && npm run build:reactlynx && rspeedy build",
"build": "pnpm run build:comp-lib && pnpm run build:reactlynx && rspeedy build",
"build:comp-lib": "rslib build --config rslib-comp-lib.config.ts",
"build:comp-lib:dev": "cross-env NODE_ENV=development rslib build --config rslib-comp-lib.config.ts",
"build:reactlynx": "pnpm --filter @lynx-js/react-umd build",
Expand Down
31 changes: 3 additions & 28 deletions examples/react-externals/rslib-comp-lib.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,15 @@ export default defineExternalBundleRslibConfig({
id: 'comp-lib',
source: {
entry: {
'CompLib': './external-bundle/CompLib.tsx',
'./App.js': './external-bundle/CompLib.tsx',
},
},
plugins: [
pluginReactLynx(),
],
output: {
cleanDistPath: false,
dataUriLimit: Number.POSITIVE_INFINITY,
externals: {
'@lynx-js/react': ['ReactLynx', 'React'],
'@lynx-js/react/internal': ['ReactLynx', 'ReactInternal'],
'@lynx-js/react/jsx-dev-runtime': ['ReactLynx', 'ReactJSXDevRuntime'],
'@lynx-js/react/jsx-runtime': ['ReactLynx', 'ReactJSXRuntime'],
'@lynx-js/react/lepus/jsx-dev-runtime': [
'ReactLynx',
'ReactJSXLepusDevRuntime',
],
'@lynx-js/react/lepus/jsx-runtime': ['ReactLynx', 'ReactJSXLepusRuntime'],
'@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'],
externalsPresets: {
reactlynx: true,
Comment thread
upupming marked this conversation as resolved.
},
globalObject: 'globalThis',
},
Expand Down
13 changes: 13 additions & 0 deletions examples/react-externals/turbo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "https://turbo.build/schema.json",
"extends": [
"//"
],
"tasks": {
"build": {
"dependsOn": [
"@lynx-js/react-umd#build"
]
}
}
}
Loading
Loading