Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/brave-news-sin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lynx-js/template-webpack-plugin": patch
---

Fix "Failed to load CSS update file" for lazy bundle
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.LazyComponent {
font-weight: 700;
color: yellow;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import './LazyComponent.css'

export default function LazyComponent() {
return (
<view>
<text className='LazyComponent'>LazyComponent</text>
</view>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.Suspense {
background-color: red;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Suspense, lazy } from '@lynx-js/react'
import './index.css'

const LazyComponent = lazy(() => import('./LazyComponent.js'))

export function App() {
return (
<view className='Suspense'>
<Suspense fallback={<text>Loading...</text>}>
<LazyComponent />
</Suspense>
</view>
)
}
255 changes: 254 additions & 1 deletion packages/rspeedy/plugin-react/test/lazy.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
// Copyright 2024 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
import fs from 'node:fs/promises'
import path from 'node:path'

import type { Rspack } from '@rsbuild/core'
import type { RsbuildPlugin, Rspack } from '@rsbuild/core'
import { describe, expect, test, vi } from 'vitest'

import { LynxTemplatePlugin } from '@lynx-js/template-webpack-plugin'

import { createStubRspeedy as createRspeedy } from './createRspeedy.js'
import { pluginStubRspeedyAPI } from './stub-rspeedy-api.plugin.js'

Expand Down Expand Up @@ -170,4 +173,254 @@ describe('Lazy', () => {
vi.unstubAllEnvs()
})
})

test('lazy bundle beforeEncode entryNames', async () => {
vi.stubEnv('NODE_ENV', 'development')
const { pluginReactLynx } = await import('../src/pluginReactLynx.js')

const entryNamesOfBeforeEncode: string[][] = []
let backgroundJSContent = ''

const rsbuild = await createRspeedy({
rspeedyConfig: {
source: {
entry: {
main: new URL(
'./fixtures/lazy-bundle/index.tsx',
import.meta.url,
).pathname,
},
},
output: {
distPath: {
root: './dist/lazy-bundle',
},
},
plugins: [
pluginReactLynx(),
{
name: 'test',
pre: ['lynx:react'],
setup(api) {
api.modifyBundlerChain((chain, { CHAIN_ID }) => {
const rule = chain.module
.rules.get('css:react:main-thread')
.uses.get(CHAIN_ID.USE.IGNORE_CSS)
rule.loader(
// add .ts suffix to ignore-css-loader
// this workaround is needed because vitest
// runs on our ts files.
rule.get('loader') as string + '.ts',
)
})
},
} as RsbuildPlugin,
],
tools: {
rspack: {
plugins: [
{
name: 'extractBackgroundJSContent',
apply(compiler) {
compiler.hooks.compilation.tap(
'extractBackgroundJSContent',
(compilation) => {
compilation.hooks.processAssets.tap(
'extractBackgroundJSContent',
(assets) => {
for (const key in assets) {
if (/[\\/]background.js$/.test(key)) {
backgroundJSContent = assets[key]!.source()
.toString()!
}
}
},
)
},
)
},
} as Rspack.RspackPluginInstance,
{
name: 'beforeEncode-test',
apply(compiler) {
compiler.hooks.compilation.tap(
'beforeEncode-test',
(compilation) => {
const hooks = LynxTemplatePlugin
.getLynxTemplatePluginHooks(
compilation as unknown as Parameters<
typeof LynxTemplatePlugin.getLynxTemplatePluginHooks
>[0],
)
hooks.beforeEncode.tap(
'beforeEncode-test',
(args) => {
entryNamesOfBeforeEncode.push(args.entryNames)

return args
},
)
},
)
},
} as Rspack.RspackPluginInstance,
],
},
},
},
})

try {
await rsbuild.build()

expect(entryNamesOfBeforeEncode).toMatchInlineSnapshot(`
[
[
"main__main-thread",
"main",
],
[
"./LazyComponent.js-react__main-thread",
"./LazyComponent.js-react__background",
],
]
`)
const cssHotUpdateList =
/\.cssHotUpdateList\s*=\s*(\[\[[\s\S]*?\]\])/.exec(
backgroundJSContent,
)![1]
expect(cssHotUpdateList).toMatchInlineSnapshot(
`"[["./LazyComponent.js-react__background",".rspeedy/async/./LazyComponent.js-react__background/./LazyComponent.js-react__background.css.hot-update.json"],["main",".rspeedy/main/main.css.hot-update.json"]]"`,
)
} finally {
vi.unstubAllEnvs()
}
})
Comment thread
upupming marked this conversation as resolved.

test('lazy bundle app-service.js should not load hot-update.js', async () => {
vi.stubEnv('NODE_ENV', 'development')
const { pluginReactLynx } = await import('../src/pluginReactLynx.js')

let appServiceJSContent = ''
let done = false
const waitCompilationDone = () =>
new Promise(resolve => {
const interval = setInterval(() => {
if (done) {
clearInterval(interval)
done = false
resolve(null)
}
}, 100)
})

const rsbuild = await createRspeedy({
rspeedyConfig: {
source: {
entry: {
main: new URL(
'./fixtures/lazy-bundle/index.tsx',
import.meta.url,
).pathname,
},
},
output: {
distPath: {
root: './dist/lazy-bundle',
},
},
plugins: [
pluginReactLynx(),
{
name: 'test',
pre: ['lynx:react'],
setup(api) {
api.modifyBundlerChain((chain, { CHAIN_ID }) => {
const rule = chain.module
.rules.get('css:react:main-thread')
.uses.get(CHAIN_ID.USE.IGNORE_CSS)
rule.loader(
// add .ts suffix to ignore-css-loader
// this workaround is needed because vitest
// runs on our ts files.
rule.get('loader') as string + '.ts',
)
})
},
} as RsbuildPlugin,
],
tools: {
rspack: {
plugins: [
{
name: 'beforeEncode-test',
apply(compiler) {
compiler.hooks.compilation.tap(
'beforeEncode-test',
(compilation) => {
const hooks = LynxTemplatePlugin
.getLynxTemplatePluginHooks(
compilation as unknown as Parameters<
typeof LynxTemplatePlugin.getLynxTemplatePluginHooks
>[0],
)
hooks.beforeEmit.tap(
'beforeEmit-test',
(args) => {
if (
args.entryNames.some((name) =>
name.includes('LazyComponent')
)
) {
appServiceJSContent = args.finalEncodeOptions
.manifest['/app-service.js']!
}
return args
},
)
},
)
compiler.hooks.done.tap('beforeEncode-test', () => {
done = true
})
},
} as Rspack.RspackPluginInstance,
],
},
},
},
})

const lazyComponentUrl = new URL(
'./fixtures/lazy-bundle/LazyComponent.tsx',
import.meta.url,
)
let tmpContent: string | undefined

try {
await rsbuild.createDevServer()
await waitCompilationDone()
expect(appServiceJSContent).toMatchInlineSnapshot(
`"(function(){'use strict';function n({tt}){tt.define('/app-service.js',function(e,module,_,i,l,u,a,c,s,f,p,d,h,v,g,y,lynx){module.exports=lynx.requireModule("/static/js/async/./LazyComponent.js-react__background.js",globDynamicComponentEntry?globDynamicComponentEntry:'__Card__');});return tt.require('/app-service.js');}return{init:n}})()"`,
)

// Modify the fixtures/lazy-bundle/LazyComponent.tsx file
// to trigger HMR
tmpContent = await fs.readFile(lazyComponentUrl, 'utf-8')
await fs.writeFile(
lazyComponentUrl,
'export default function LazyComponent() { return null }',
)
await waitCompilationDone()

expect(appServiceJSContent).toMatchInlineSnapshot(
`"(function(){'use strict';function n({tt}){tt.define('/app-service.js',function(e,module,_,i,l,u,a,c,s,f,p,d,h,v,g,y,lynx){module.exports=lynx.requireModule("/static/js/async/./LazyComponent.js-react__background.js",globDynamicComponentEntry?globDynamicComponentEntry:'__Card__');});return tt.require('/app-service.js');}return{init:n}})()"`,
)
} finally {
if (tmpContent !== undefined) {
await fs.writeFile(lazyComponentUrl, tmpContent)
}
vi.unstubAllEnvs()
}
})
Comment thread
upupming marked this conversation as resolved.
})
Original file line number Diff line number Diff line change
Expand Up @@ -671,12 +671,16 @@ class LynxTemplatePluginImpl {

await Promise.all(
Object.entries(asyncChunkGroups).map(
([entryName, chunkGroups]): Promise<void> => {
const chunkNames =
// We use the chunk name(provided by `webpackChunkName`) as filename
([_entryName, chunkGroups]): Promise<void> => {
const entryNames = // We use the chunk name(provided by `webpackChunkName`) as filename
chunkGroups
.filter(cg => cg.name !== null && cg.name !== undefined)
.map(cg => hooks.asyncChunkName.call(cg.name!));
.filter(cg => cg.name !== null && cg.name !== undefined).map(cg =>
cg.name!
);

const chunkNames = entryNames.map(name =>
hooks.asyncChunkName.call(name)
);

const filename = Array.from(new Set(chunkNames)).join('_');

Expand Down Expand Up @@ -704,7 +708,7 @@ class LynxTemplatePluginImpl {
return this.#encodeByAssetsInformation(
compilation,
asyncAssetsInfoByGroups,
[entryName],
entryNames,
filenameTemplate,
path.join(intermediateRoot, 'async', filename),
/** isAsync */ true,
Expand Down
Loading