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/curly-tires-tickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lynx-js/css-extract-webpack-plugin": patch
---

feat(css-extra-webpack-plugin): Support css hmr for lazy bundle
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,38 @@
}

function updateStyle(cssId = 0) {
const filename = __webpack_require__.lynxCssFileName;
if (!filename) {
throw new Error('Css Filename not found');
const cssHotUpdateList = __webpack_require__.cssHotUpdateList;
if (!cssHotUpdateList) {
throw new Error('cssHotUpdateList is not found');
}

lynx.requireModuleAsync(
// lynx.requireModuleAsync has two level hash and we cannot delete
// the LynxGroup level cache here.
// Temporarily using `Date.now` to avoid being cached.
__webpack_require__.p + filename,
(err, ret) => {
if (err) {
throw new Error('Load update css file `' + filename + '` failed');
}

if (ret.content) {
lynx.getCoreContext().dispatchEvent({
type: 'lynx.hmr.css',
data: { cssId, content: ret.content, deps: ret.deps },
});
}
},
);
for (const [chunkName, cssHotUpdatePath] of cssHotUpdateList) {
lynx.requireModuleAsync(
// lynx.requireModuleAsync has two level hash and we cannot delete
// the LynxGroup level cache here.
// Temporarily using `Date.now` to avoid being cached.
__webpack_require__.p + cssHotUpdatePath,
(err, ret) => {
if (err) {
throw new Error(
`Failed to load CSS update file: ${cssHotUpdatePath}`,
);
}

Check warning on line 36 in packages/webpack/css-extract-webpack-plugin/runtime/hotModuleReplacement.cjs

View check run for this annotation

Codecov / codecov/patch

packages/webpack/css-extract-webpack-plugin/runtime/hotModuleReplacement.cjs#L33-L36

Added lines #L33 - L36 were not covered by tests

if (ret.content) {
lynx.getCoreContext().dispatchEvent({
type: 'lynx.hmr.css',
data: {
cssId,
content: ret.content,
deps: ret.deps,
entry: lynx.__chunk_entries__[chunkName],
},
});
}
},
);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@
try {
lynx.getJSContext().addEventListener('lynx.hmr.css', (event) => {
try {
const { data: { cssId, content, deps } } = event;
const { data: { cssId, content, deps, entry } } = event;
// Update the css deps first because the css deps are updated actually.
if (Array.isArray(deps[cssId])) {
deps[cssId].forEach(depCSSId => {
lynx.getDevtool().replaceStyleSheetByIdWithBase64(
Number(depCSSId),
content,
entry,

Check warning on line 12 in packages/webpack/css-extract-webpack-plugin/runtime/hotModuleReplacement.lepus.cjs

View check run for this annotation

Codecov / codecov/patch

packages/webpack/css-extract-webpack-plugin/runtime/hotModuleReplacement.lepus.cjs#L12

Added line #L12 was not covered by tests
);
});
}

lynx.getDevtool().replaceStyleSheetByIdWithBase64(
Number(cssId),
content,
entry,
);

__FlushElementTree();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,20 +170,38 @@
this.name,
(runtimeModule, chunk) => {
if (runtimeModule.name === 'require_chunk_loading') {
const asyncChunks = Array.from(chunk.getAllAsyncChunks())
.map(c => {
const { path } = compilation.getAssetPathWithInfo(
options.chunkFilename ?? '.rspeedy/async/[name]/[name].css',
{ chunk: c },
);
return [c.name!, path];
});

Check warning on line 180 in packages/webpack/css-extract-webpack-plugin/src/CssExtractRspackPlugin.ts

View check run for this annotation

Codecov / codecov/patch

packages/webpack/css-extract-webpack-plugin/src/CssExtractRspackPlugin.ts#L173-L180

Added lines #L173 - L180 were not covered by tests

const { path } = compilation.getPathWithInfo(
options.filename ?? '[name].css',
// Rspack does not pass JsChunk to Rust.
// See: https://github.com/web-infra-dev/rspack/blob/73c31abcb78472eb5a3d93e4ece19d9f106727a6/crates/rspack_binding_values/src/path_data.rs#L62
{ filename: chunk.name! },
);

this.#overrideChunkLoadingRuntimeModule(
compiler,
runtimeModule,
path.replace(
const initialChunk = [chunk.name!, path];

Check warning on line 189 in packages/webpack/css-extract-webpack-plugin/src/CssExtractRspackPlugin.ts

View check run for this annotation

Codecov / codecov/patch

packages/webpack/css-extract-webpack-plugin/src/CssExtractRspackPlugin.ts#L189

Added line #L189 was not covered by tests

const cssHotUpdateList = [...asyncChunks, initialChunk].map((
[chunkName, cssHotUpdatePath],
) => [
chunkName!,
cssHotUpdatePath!.replace(

Check warning on line 195 in packages/webpack/css-extract-webpack-plugin/src/CssExtractRspackPlugin.ts

View check run for this annotation

Codecov / codecov/patch

packages/webpack/css-extract-webpack-plugin/src/CssExtractRspackPlugin.ts#L191-L195

Added lines #L191 - L195 were not covered by tests
'.css',
`${this.hash ? `.${this.hash}` : ''}.css.hot-update.json`,
),
]);

Check warning on line 199 in packages/webpack/css-extract-webpack-plugin/src/CssExtractRspackPlugin.ts

View check run for this annotation

Codecov / codecov/patch

packages/webpack/css-extract-webpack-plugin/src/CssExtractRspackPlugin.ts#L199

Added line #L199 was not covered by tests

this.#overrideChunkLoadingRuntimeModule(
compiler,
runtimeModule,
cssHotUpdateList,

Check warning on line 204 in packages/webpack/css-extract-webpack-plugin/src/CssExtractRspackPlugin.ts

View check run for this annotation

Codecov / codecov/patch

packages/webpack/css-extract-webpack-plugin/src/CssExtractRspackPlugin.ts#L201-L204

Added lines #L201 - L204 were not covered by tests
);
}
},
Expand Down Expand Up @@ -253,7 +271,6 @@
true,
),
);
this.hash = compilation.hash;
} catch (error) {
if (
error && typeof error === 'object' && 'error_msg' in error
Expand All @@ -272,6 +289,7 @@
}
}
}
this.hash = compilation.hash;
},
);
}
Expand All @@ -281,15 +299,15 @@
#overrideChunkLoadingRuntimeModule(
compiler: Compiler,
runtimeModule: RuntimeModule,
filename: string,
cssHotUpdateList: string[][],

Check warning on line 302 in packages/webpack/css-extract-webpack-plugin/src/CssExtractRspackPlugin.ts

View check run for this annotation

Codecov / codecov/patch

packages/webpack/css-extract-webpack-plugin/src/CssExtractRspackPlugin.ts#L302

Added line #L302 was not covered by tests
) {
const { RuntimeGlobals } = compiler.webpack;
runtimeModule.source!.source = Buffer.concat([
Buffer.from(runtimeModule.source!.source),
// lynxCssFileName
// cssHotUpdateList
Buffer.from(`
${RuntimeGlobals.require}.lynxCssFileName = ${
filename ? JSON.stringify(filename) : 'null'
${RuntimeGlobals.require}.cssHotUpdateList = ${
cssHotUpdateList ? JSON.stringify(cssHotUpdateList) : 'null'

Check warning on line 310 in packages/webpack/css-extract-webpack-plugin/src/CssExtractRspackPlugin.ts

View check run for this annotation

Codecov / codecov/patch

packages/webpack/css-extract-webpack-plugin/src/CssExtractRspackPlugin.ts#L309-L310

Added lines #L309 - L310 were not covered by tests
};
`),
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ export function createStubLynx(vi, require, replaceStyleSheetByIdWithBase64) {
getDevtool: vi.fn().mockReturnValue({
replaceStyleSheetByIdWithBase64,
}),
__chunk_entries__: {},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ describe('HMR Runtime', () => {

vi.stubGlobal('lynx', lynx);

lynx.__chunk_entries__ = {
'chunkName': 'entry',
'asyncChunkName': 'asyncEntry',
};

beforeEach(() => {
vi.clearAllMocks();
});
Expand All @@ -33,14 +38,14 @@ describe('HMR Runtime', () => {
expect(vi.getTimerCount()).toBe(1);

expect(() => vi.runAllTimers()).toThrowErrorMatchingInlineSnapshot(
`[Error: Css Filename not found]`,
`[Error: cssHotUpdateList is not found]`,
);
});

test('cssFileName', () => {
vi.stubGlobal('__webpack_require__', {
p: '/',
lynxCssFileName: 'foo.css',
cssHotUpdateList: [['chunkName', 'foo.css']],
});
vi.useFakeTimers();

Expand All @@ -63,7 +68,7 @@ describe('HMR Runtime', () => {
await import('../runtime/hotModuleReplacement.lepus.cjs');
vi.stubGlobal('__webpack_require__', {
p: '/',
lynxCssFileName: 'foo.css',
cssHotUpdateList: [['chunkName', 'foo.css']],
});
const __FlushElementTree = vi.fn();
vi.stubGlobal('__FlushElementTree', __FlushElementTree);
Expand All @@ -83,6 +88,7 @@ describe('HMR Runtime', () => {
expect(replaceStyleSheetByIdWithBase64).toBeCalledWith(
10,
expect.stringContaining('/foo.css'),
'entry',
);

expect(__FlushElementTree).toBeCalled();
Expand All @@ -92,7 +98,7 @@ describe('HMR Runtime', () => {
await import('../runtime/hotModuleReplacement.lepus.cjs');
vi.stubGlobal('__webpack_require__', {
p: '/',
lynxCssFileName: 'bar.css',
cssHotUpdateList: [['chunkName', 'bar.css']],
});
const __FlushElementTree = vi.fn();
vi.stubGlobal('__FlushElementTree', __FlushElementTree);
Expand All @@ -112,6 +118,7 @@ describe('HMR Runtime', () => {
expect(replaceStyleSheetByIdWithBase64).toBeCalledWith(
0,
expect.stringContaining('/bar.css'),
'entry',
);

expect(__FlushElementTree).toBeCalled();
Expand All @@ -121,7 +128,7 @@ describe('HMR Runtime', () => {
await import('../runtime/hotModuleReplacement.lepus.cjs');
vi.stubGlobal('__webpack_require__', {
p: 'https://example.com/',
lynxCssFileName: 'bar.css',
cssHotUpdateList: [['chunkName', 'bar.css']],
});
const __FlushElementTree = vi.fn();
vi.stubGlobal('__FlushElementTree', __FlushElementTree);
Expand All @@ -141,6 +148,49 @@ describe('HMR Runtime', () => {
expect(replaceStyleSheetByIdWithBase64).toBeCalledWith(
0,
expect.stringContaining('https://example.com/bar.css'),
'entry',
);

expect(__FlushElementTree).toBeCalled();
});

test('update lazy bundle', async () => {
await import('../runtime/hotModuleReplacement.lepus.cjs');
vi.stubGlobal('__webpack_require__', {
p: '/',
cssHotUpdateList: [['asyncChunkName', 'async.bar.css'], [
'chunkName',
'foo.css',
]],
});
const __FlushElementTree = vi.fn();
vi.stubGlobal('__FlushElementTree', __FlushElementTree);
vi.useFakeTimers();

const cssReload = update('', null);

cssReload();

// debounce
vi.runAllTimers();

// requireModuleAsync
await vi.runAllTimersAsync();

expect(replaceStyleSheetByIdWithBase64).toBeCalledTimes(2);

expect(replaceStyleSheetByIdWithBase64).toHaveBeenNthCalledWith(
1,
0,
expect.stringContaining('async.bar.css'),
'asyncEntry',
);

expect(replaceStyleSheetByIdWithBase64).toHaveBeenNthCalledWith(
2,
0,
expect.stringContaining('foo.css'),
'entry',
);

expect(__FlushElementTree).toBeCalled();
Expand Down
Loading