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: