diff --git a/.changeset/thick-needles-rest.md b/.changeset/thick-needles-rest.md
new file mode 100644
index 0000000000..7cf9c6e75a
--- /dev/null
+++ b/.changeset/thick-needles-rest.md
@@ -0,0 +1,7 @@
+---
+"@lynx-js/externals-loading-webpack-plugin": patch
+"@lynx-js/lynx-bundle-rslib-config": patch
+"@lynx-js/react-rsbuild-plugin": patch
+---
+
+Support bundle and load css in external bundle
diff --git a/examples/react-externals/src/index.css b/examples/react-externals/src/index.css
new file mode 100644
index 0000000000..42b54243cf
--- /dev/null
+++ b/examples/react-externals/src/index.css
@@ -0,0 +1,8 @@
+/* At least a css rule is needed in consumer
+ * to make sure the css engine is loaded
+ * so that the css in external bundles can be applied
+ *
+ * This is a limitation of Lynx engine
+ * we can remove this limitation in the future
+ */
+.dummy {}
diff --git a/examples/react-externals/src/index.tsx b/examples/react-externals/src/index.tsx
index d0d893b024..5bbf167939 100644
--- a/examples/react-externals/src/index.tsx
+++ b/examples/react-externals/src/index.tsx
@@ -3,10 +3,7 @@ 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';
+import './index.css';
root.render(
,
diff --git a/packages/rspeedy/lynx-bundle-rslib-config/package.json b/packages/rspeedy/lynx-bundle-rslib-config/package.json
index 19a31afab6..7af0d5b3fe 100644
--- a/packages/rspeedy/lynx-bundle-rslib-config/package.json
+++ b/packages/rspeedy/lynx-bundle-rslib-config/package.json
@@ -36,8 +36,9 @@
"test": "vitest"
},
"dependencies": {
+ "@lynx-js/css-serializer": "workspace:*",
"@lynx-js/runtime-wrapper-webpack-plugin": "workspace:*",
- "@lynx-js/tasm": "0.0.20"
+ "@lynx-js/tasm": "0.0.26"
},
"devDependencies": {
"@lynx-js/react": "workspace:*",
diff --git a/packages/rspeedy/lynx-bundle-rslib-config/src/externalBundleRslibConfig.ts b/packages/rspeedy/lynx-bundle-rslib-config/src/externalBundleRslibConfig.ts
index c53c963192..f4eb2b9413 100644
--- a/packages/rspeedy/lynx-bundle-rslib-config/src/externalBundleRslibConfig.ts
+++ b/packages/rspeedy/lynx-bundle-rslib-config/src/externalBundleRslibConfig.ts
@@ -59,6 +59,7 @@ export const defaultExternalBundleLibConfig: LibConfig = {
},
},
},
+ target: 'web',
},
source: {
include: [/node_modules/],
diff --git a/packages/rspeedy/lynx-bundle-rslib-config/src/webpack/ExternalBundleWebpackPlugin.ts b/packages/rspeedy/lynx-bundle-rslib-config/src/webpack/ExternalBundleWebpackPlugin.ts
index 4f1cb11505..088274bb23 100644
--- a/packages/rspeedy/lynx-bundle-rslib-config/src/webpack/ExternalBundleWebpackPlugin.ts
+++ b/packages/rspeedy/lynx-bundle-rslib-config/src/webpack/ExternalBundleWebpackPlugin.ts
@@ -3,6 +3,9 @@
// LICENSE file in the root directory of this source tree.
import type { Asset, Compilation, Compiler } from 'webpack'
+import { cssChunksToMap } from '@lynx-js/css-serializer'
+import type { LynxStyleNode } from '@lynx-js/css-serializer'
+
/**
* The options for {@link ExternalBundleWebpackPlugin}.
*
@@ -112,18 +115,49 @@ export class ExternalBundleWebpackPlugin {
async #encode(assets: Readonly[]) {
const customSections = assets
- .filter(({ name }) => name.endsWith('.js'))
- .reduce>((prev, cur) => ({
- ...prev,
- [cur.name.replace(/\.js$/, '')]: {
- content: cur.source.source().toString(),
+ .reduce<
+ Record
+ >(
+ (prev, cur) => {
+ switch (cur.info['assetType']) {
+ case 'javascript':
+ return ({
+ ...prev,
+ [cur.name.replace(/\.js$/, '')]: {
+ content: cur.source.source().toString(),
+ },
+ })
+ case 'extract-css':
+ return ({
+ ...prev,
+ [`${cur.name.replace(/\.css$/, '')}:CSS`]: {
+ 'encoding': 'CSS',
+ content: {
+ ruleList: cssChunksToMap(
+ [cur.source.source().toString()],
+ [],
+ true,
+ ).cssMap[0] ?? [],
+ },
+ },
+ })
+ default:
+ return prev
+ }
},
- }), {})
+ {},
+ )
const compilerOptions: Record = {
enableFiberArch: true,
// `lynx.fetchBundle` and `lynx.loadScript` require engineVersion >= 3.5
targetSdkVersion: this.options.engineVersion ?? '3.5',
+ enableCSSInvalidation: true,
+ enableCSSSelector: true,
}
const encodeOptions = {
diff --git a/packages/rspeedy/lynx-bundle-rslib-config/test/external-bundle.test.ts b/packages/rspeedy/lynx-bundle-rslib-config/test/external-bundle.test.ts
index c6ae70ca89..4ee438ba5f 100644
--- a/packages/rspeedy/lynx-bundle-rslib-config/test/external-bundle.test.ts
+++ b/packages/rspeedy/lynx-bundle-rslib-config/test/external-bundle.test.ts
@@ -167,6 +167,37 @@ describe('should build external bundle', () => {
)
expect(decodedResult['engine-version']).toBe('3.5')
})
+
+ it('should build css into external bundle', async () => {
+ const fixtureDir = path.join(__dirname, './fixtures/css-lib')
+ const rslibConfig = defineExternalBundleRslibConfig({
+ source: {
+ entry: {
+ index: path.join(fixtureDir, 'index.ts'),
+ },
+ },
+ id: 'css-bundle',
+ output: {
+ distPath: {
+ root: path.join(fixtureDir, 'dist'),
+ },
+ },
+ plugins: [pluginReactLynx()],
+ })
+
+ await build(rslibConfig)
+
+ const decodedResult = await decodeTemplate(
+ path.join(fixtureDir, 'dist', 'css-bundle.lynx.bundle'),
+ )
+
+ // Check custom-sections for CSS keys
+ expect(Object.keys(decodedResult['custom-sections']).sort()).toEqual([
+ 'index',
+ 'index:CSS',
+ 'index__main-thread',
+ ])
+ })
})
describe('debug mode artifacts', () => {
diff --git a/packages/rspeedy/lynx-bundle-rslib-config/test/fixtures/css-lib/index.css b/packages/rspeedy/lynx-bundle-rslib-config/test/fixtures/css-lib/index.css
new file mode 100644
index 0000000000..8963f2c513
--- /dev/null
+++ b/packages/rspeedy/lynx-bundle-rslib-config/test/fixtures/css-lib/index.css
@@ -0,0 +1,3 @@
+.container {
+ color: red;
+}
diff --git a/packages/rspeedy/lynx-bundle-rslib-config/test/fixtures/css-lib/index.ts b/packages/rspeedy/lynx-bundle-rslib-config/test/fixtures/css-lib/index.ts
new file mode 100644
index 0000000000..f27ec5542c
--- /dev/null
+++ b/packages/rspeedy/lynx-bundle-rslib-config/test/fixtures/css-lib/index.ts
@@ -0,0 +1,3 @@
+import './index.css'
+
+export const hello = 'world'
diff --git a/packages/rspeedy/plugin-react/src/pluginReactLynx.ts b/packages/rspeedy/plugin-react/src/pluginReactLynx.ts
index cee200a161..c73b921824 100644
--- a/packages/rspeedy/plugin-react/src/pluginReactLynx.ts
+++ b/packages/rspeedy/plugin-react/src/pluginReactLynx.ts
@@ -343,9 +343,7 @@ export function pluginReactLynx(
})
}
- if (!isRslib) {
- applyCSS(api, resolvedOptions)
- }
+ applyCSS(api, resolvedOptions)
applyEntry(api, resolvedOptions)
applyBackgroundOnly(api)
applyGenerator(api, resolvedOptions)
diff --git a/packages/webpack/externals-loading-webpack-plugin/src/index.ts b/packages/webpack/externals-loading-webpack-plugin/src/index.ts
index 782ec78ec7..87d5810fe6 100644
--- a/packages/webpack/externals-loading-webpack-plugin/src/index.ts
+++ b/packages/webpack/externals-loading-webpack-plugin/src/index.ts
@@ -299,6 +299,7 @@ export class ExternalsLoadingPlugin {
} else {
return '';
}
+ const isMainThreadLayer = layer === 'mainThread';
for (
const [pkgName, external] of Object.entries(
externalsLoadingPluginOptions.externals,
@@ -323,8 +324,21 @@ function createLoadExternalAsync(handler, sectionPath) {
if (response.code === 0) {
try {
const result = lynx.loadScript(sectionPath, { bundleName: response.url });
+ ${
+ isMainThreadLayer
+ ? `
+ // TODO: Use configured layer suffix instead of hard-coded __main-thread for CSS section lookup.
+ const styleSheet = __LoadStyleSheet(sectionPath.replace('__main-thread', '') + ':CSS', response.url);
+ if (styleSheet !== null) {
+ __AdoptStyleSheet(styleSheet);
+ __FlushElementTree();
+ }
+ `
+ : ''
+ }
resolve(result)
} catch (error) {
+ console.error(error)
reject(new Error('Failed to load script ' + sectionPath + ' in ' + response.url, { cause: error }))
}
} else {
@@ -338,8 +352,21 @@ function createLoadExternalSync(handler, sectionPath, timeout) {
if (response.code === 0) {
try {
const result = lynx.loadScript(sectionPath, { bundleName: response.url });
+ ${
+ isMainThreadLayer
+ ? `
+ // TODO: Use configured layer suffix instead of hard-coded __main-thread for CSS section lookup.
+ const styleSheet = __LoadStyleSheet(sectionPath.replace('__main-thread', '') + ':CSS', response.url);
+ if (styleSheet !== null) {
+ __AdoptStyleSheet(styleSheet);
+ __FlushElementTree();
+ }
+ `
+ : ''
+ }
return result
} catch (error) {
+ console.error(error)
throw new Error('Failed to load script ' + sectionPath + ' in ' + response.url, { cause: error })
}
} else {
diff --git a/packages/webpack/externals-loading-webpack-plugin/test/helpers/setup-env.js b/packages/webpack/externals-loading-webpack-plugin/test/helpers/setup-env.js
index db86d756ef..5eca24f24d 100644
--- a/packages/webpack/externals-loading-webpack-plugin/test/helpers/setup-env.js
+++ b/packages/webpack/externals-loading-webpack-plugin/test/helpers/setup-env.js
@@ -35,4 +35,14 @@ function __injectGlobals(target) {
target.lynxCoreInject = {};
target.lynxCoreInject.tt = {};
+
+ target.__LoadStyleSheet = () => {
+ return {};
+ };
+ target.__AdoptStyleSheet = () => {
+ //
+ };
+ target.__FlushElementTree = () => {
+ //
+ };
}
diff --git a/packages/webpack/template-webpack-plugin/package.json b/packages/webpack/template-webpack-plugin/package.json
index 890e31db88..7084553224 100644
--- a/packages/webpack/template-webpack-plugin/package.json
+++ b/packages/webpack/template-webpack-plugin/package.json
@@ -37,7 +37,7 @@
},
"dependencies": {
"@lynx-js/css-serializer": "workspace:*",
- "@lynx-js/tasm": "0.0.20",
+ "@lynx-js/tasm": "0.0.26",
"@lynx-js/web-core-wasm": "workspace:*",
"@lynx-js/webpack-runtime-globals": "workspace:^",
"@rspack/lite-tapable": "1.1.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2e20085a6a..4012928e0e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -714,12 +714,15 @@ importers:
packages/rspeedy/lynx-bundle-rslib-config:
dependencies:
+ '@lynx-js/css-serializer':
+ specifier: workspace:*
+ version: link:../../tools/css-serializer
'@lynx-js/runtime-wrapper-webpack-plugin':
specifier: workspace:*
version: link:../../webpack/runtime-wrapper-webpack-plugin
'@lynx-js/tasm':
- specifier: 0.0.20
- version: 0.0.20
+ specifier: 0.0.26
+ version: 0.0.26
devDependencies:
'@lynx-js/react':
specifier: workspace:*
@@ -1636,8 +1639,8 @@ importers:
specifier: workspace:*
version: link:../../tools/css-serializer
'@lynx-js/tasm':
- specifier: 0.0.20
- version: 0.0.20
+ specifier: 0.0.26
+ version: 0.0.26
'@lynx-js/web-core-wasm':
specifier: workspace:*
version: link:../../web-platform/web-core-wasm
@@ -3199,8 +3202,8 @@ packages:
'@lynx-js/preact-devtools@5.0.1':
resolution: {integrity: sha512-ayUU8PJ0TVWABbd5/d/04KI18W9RY4kkaRekBXXbRE/eBkGMZ3lVJ0zOB+B+85B9RUpvEVK2FleqJkS+qBKG6Q==}
- '@lynx-js/tasm@0.0.20':
- resolution: {integrity: sha512-ezMq43s59jqFuQ1YygpsUuZmGXw4XH+00RsB5RVmkYZuHQxEaLt/ECTOixF+9RixvAyhmxzF2eSURvmNckO9xg==}
+ '@lynx-js/tasm@0.0.26':
+ resolution: {integrity: sha512-WmcuTYh/KfdRqriT7+Qg8iwxsCDN4PKbCuXoN35npn23p33grpSNW306DJi9tX7LzP2vDZkOjxulSOknfiB/0Q==}
'@lynx-js/trace-processor@0.0.1':
resolution: {integrity: sha512-Zyl74cKi+BDggeXroLQG4frbBuiQ0DB0yH0C3pMkUHWv17abWTdJ2mw/H9Cd1QiIr+aQHn1U2SRtsrbkmWMtJw==}
@@ -11726,7 +11729,7 @@ snapshots:
errorstacks: 2.4.1
htm: 3.1.1
- '@lynx-js/tasm@0.0.20': {}
+ '@lynx-js/tasm@0.0.26': {}
'@lynx-js/trace-processor@0.0.1':
dependencies: