Skip to content
Draft
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
6 changes: 5 additions & 1 deletion packages/rspack/src/builtin-plugin/css-extract/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export function hotLoader(
},
): string {
const localsJsonString = JSON.stringify(JSON.stringify(context.locals));
// The extracted-CSS runtime owns stylesheet replacement when present; keep
// the loader reload only as the fallback for `CssExtractRspackPlugin({ runtime: false })`.
return `${content}
if(module.hot) {
(function() {
Expand All @@ -60,7 +62,9 @@ export function hotLoader(
}
module.hot.dispose(function(data) {
data.value = localsJsonString;
cssReload();
if (!__webpack_require__.hmrC || !__webpack_require__.hmrC.miniCss) {
cssReload();
}
});
})();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { test, expect } from '@/fixtures';

test('keeps the loader CSS reload fallback when the extracted runtime is disabled', async ({
page,
fileAction,
}) => {
const links = page.locator('link[rel="stylesheet"]');
const responses: string[] = [];
const colors = Array.from(
{ length: 21 },
(_, index) => `rgb(${10 + index}, ${20 + index}, ${30 + index})`,
);

page.on('response', (response) => {
const url = response.url();
if (url.includes('/static/style.css')) responses.push(url);
});

await expect(page.locator('body')).toHaveCSS('background-color', colors[0]);
await expect(links).toHaveCount(1);

for (let index = 1; index < colors.length; index++) {
const previous = colors[index - 1];
const next = colors[index];

fileAction.updateFile('src/index.css', (content) =>
content.replace(previous, next),
);
await expect(page.locator('body')).toHaveCSS('background-color', next);
await expect(links).toHaveCount(1);
}

expect(responses).toHaveLength(colors.length - 1);
expect(new Set(responses).size).toBe(colors.length - 1);
expect(responses.every((url) => url.includes('?'))).toBe(true);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const { rspack } = require('@rspack/core');

/** @type { import('@rspack/core').RspackOptions } */
module.exports = {
context: __dirname,
mode: 'development',
entry: {
main: ['./src/index.css', './src/index.js'],
},
devServer: {
hot: true,
},
plugins: [
new rspack.HtmlRspackPlugin({
template: './src/index.html',
inject: 'body',
}),
new rspack.CssExtractRspackPlugin({
filename: 'static/style.css',
runtime: false,
}),
],
module: {
rules: [
{
test: /\.css$/,
type: 'javascript/auto',
use: [rspack.CssExtractRspackPlugin.loader, 'css-loader'],
},
],
},
optimization: {
splitChunks: {
cacheGroups: {
style: {
name: 'style',
test: /\.css$/,
chunks: 'all',
enforce: true,
},
},
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {
background-color: rgb(10, 20, 30);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body></body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import './index.css';

module.hot.accept();
42 changes: 27 additions & 15 deletions tests/e2e/cases/css/shared-stylesheet-dedup/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { test, expect } from '@/fixtures';

const COLOR_BLUE = 'rgb(10, 20, 30)';
const COLOR_RED = 'rgb(120, 0, 0)';
const COLOR_GREEN = 'rgb(0, 90, 0)';

// The hot-update lists both the `style` and `main` chunks while the fixed
// `filename` maps them to one stylesheet. Without de-duplication the handler
// re-fetched it once per chunk and leaked one <link> per update.
Expand All @@ -12,18 +8,34 @@ test('should keep a single stylesheet link when several updated chunks share it'
fileAction,
}) => {
const links = page.locator('link[rel="stylesheet"]');
await expect(page.locator('body')).toHaveCSS('background-color', COLOR_BLUE);
await expect(links).toHaveCount(1);

fileAction.updateFile('src/index.css', (content) =>
content.replace(COLOR_BLUE, COLOR_RED),
const responses: string[] = [];
const colors = Array.from(
{ length: 21 },
(_, index) => `rgb(${10 + index}, ${20 + index}, ${30 + index})`,
);
await expect(page.locator('body')).toHaveCSS('background-color', COLOR_RED);
await expect(links).toHaveCount(1);

fileAction.updateFile('src/index.css', (content) =>
content.replace(COLOR_RED, COLOR_GREEN),
);
await expect(page.locator('body')).toHaveCSS('background-color', COLOR_GREEN);
page.on('response', (response) => {
const url = response.url();
if (url.includes('/static/style.css')) responses.push(url);
});

await expect(page.locator('body')).toHaveCSS('background-color', colors[0]);
await expect(links).toHaveCount(1);

for (let index = 1; index < colors.length; index++) {
const previous = colors[index - 1];
const next = colors[index];

fileAction.updateFile('src/index.css', (content) =>
content.replace(previous, next),
);
await expect(page.locator('body')).toHaveCSS('background-color', next);
await expect(links).toHaveCount(1);
}

// The loader-level reload is debounced, so repeated edits expose a second
// owner even when a transient two-link state has already settled.
expect(responses).toHaveLength(colors.length - 1);
expect(new Set(responses).size).toBe(colors.length - 1);
expect(responses.every((url) => url.includes('?'))).toBe(true);
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
## Asset Files
- Bundle: bundle.js
- Manifest: main.LAST_HASH.hot-update.json, size: 28
- Update: main.hot-update.js, size: 993
- Update: main.hot-update.js, size: 1087

## Manifest

Expand Down Expand Up @@ -52,7 +52,9 @@ __webpack_require__.r(__webpack_exports__);
}
module.hot.dispose(function(data) {
data.value = localsJsonString;
cssReload();
if (!__webpack_require__.hmrC || !__webpack_require__.hmrC.miniCss) {
cssReload();
}
});
})();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
## Asset Files
- Bundle: bundle.js
- Manifest: main.LAST_HASH.hot-update.json, size: 28
- Update: main.hot-update.js, size: 6038
- Update: main.hot-update.js, size: 6132

## Manifest

Expand Down Expand Up @@ -54,7 +54,9 @@ __webpack_require__.r(__webpack_exports__);
}
module.hot.dispose(function(data) {
data.value = localsJsonString;
cssReload();
if (!__webpack_require__.hmrC || !__webpack_require__.hmrC.miniCss) {
cssReload();
}
});
})();
}
Expand Down
Loading