Skip to content

Commit ea2e4dd

Browse files
committed
fix: ensure compiled css is loaded correctly when rebuilding in watch mode
1 parent e00ba38 commit ea2e4dd

File tree

12 files changed

+134
-117
lines changed

12 files changed

+134
-117
lines changed

.changeset/spotty-mirrors-pick.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/vite-plugin-svelte': patch
3+
---
4+
5+
fix: ensure compiled svelte css is loaded correctly when rebuilding in `build --watch`

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,5 @@ coverage
5252
packages/playground/**/*
5353
!packages/playground/README.md
5454

55+
# vite plugin inspect
56+
.vite-inspect

.prettierrc.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ export default {
2525
'**/vite.config.js.timestamp-*.mjs',
2626
'packages/e2e-tests/dynamic-compile-options/src/components/A.svelte',
2727
'packages/playground/big/src/pages/**', // lots of generated files
28-
'packages/e2e-tests/scan-deps/src/Svelte*.svelte' // various syntax tests that require no format
28+
'packages/e2e-tests/scan-deps/src/Svelte*.svelte', // various syntax tests that require no format
29+
'**/.vite-inspect/**'
2930
],
3031
options: {
3132
rangeEnd: 0

eslint.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ export default [
1515
'packages/*/types/index.d.ts',
1616
'packages/*/types/index.d.ts.map',
1717
'packages/*/CHANGELOG.md',
18-
'packages/e2e-tests/**/logs/**'
18+
'packages/e2e-tests/**/logs/**',
19+
'**/.vite-inspect/**'
1920
]
2021
},
2122
...svelteOrgEslintConfig, // contains setup for svelte and typescript

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@
7979
"vite": "$vite",
8080
"@types/node@<=20.12.0": "20.19.9",
8181
"send@<0.19.0": "^0.19.1",
82-
"@sveltejs/kit>cookie@<0.7.0": "^0.7.2"
82+
"@sveltejs/kit>cookie@<0.7.0": "^0.7.2",
83+
"vite-plugin-inspect": "/home/dominikg/develop/vite-plugin-inspect"
8384
},
8485
"onlyBuiltDependencies": [
8586
"esbuild"

packages/e2e-tests/build-watch/__tests__/build-watch.spec.ts

Lines changed: 39 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -7,51 +7,48 @@ import {
77
untilMatches,
88
sleep,
99
getColor,
10-
editFile,
11-
addFile,
12-
removeFile,
13-
editViteConfig,
1410
browserLogs,
15-
waitForBuildWatchAndPageReload
11+
e2eServer
1612
} from '~utils';
13+
import { describe, test, expect } from 'vitest';
1714

18-
test('should render App', async () => {
19-
expect(await getText('#app-header')).toBe('Test-App');
20-
});
15+
describe.runIf(isBuildWatch)('build-watch', () => {
16+
test('should render App', async () => {
17+
expect(await getText('#app-header')).toBe('Test-App');
18+
});
2119

22-
test('should render static import', async () => {
23-
expect(await getText('#static-import .label')).toBe('static-import');
24-
});
20+
test('should render static import', async () => {
21+
expect(await getText('#static-import .label')).toBe('static-import');
22+
});
2523

26-
test('should render dependency import', async () => {
27-
expect(await getText('#dependency-import .label')).toBe('dependency-import');
28-
});
24+
test('should render dependency import', async () => {
25+
expect(await getText('#dependency-import .label')).toBe('dependency-import');
26+
});
2927

30-
test('should render dynamic import', async () => {
31-
expect(await getEl('#dynamic-import')).toBe(null);
32-
const dynamicImportButton = await getEl('#button-import-dynamic');
33-
expect(dynamicImportButton).toBeDefined();
34-
await dynamicImportButton.click();
35-
await untilMatches(
36-
() => getText('#dynamic-import .label'),
37-
'dynamic-import',
38-
'dynamic import loaded after click'
39-
);
40-
});
28+
test('should render dynamic import', async () => {
29+
expect(await getEl('#dynamic-import')).toBe(null);
30+
const dynamicImportButton = await getEl('#button-import-dynamic');
31+
expect(dynamicImportButton).toBeDefined();
32+
await dynamicImportButton.click();
33+
await untilMatches(
34+
() => getText('#dynamic-import .label'),
35+
'dynamic-import',
36+
'dynamic import loaded after click'
37+
);
38+
});
4139

42-
test('should not have failed requests', async () => {
43-
browserLogs.forEach((msg) => {
44-
expect(msg).not.toMatch('404');
40+
test('should not have failed requests', async () => {
41+
browserLogs.forEach((msg) => {
42+
expect(msg).not.toMatch('404');
43+
});
4544
});
46-
});
4745

48-
test('should respect transforms', async () => {
49-
expect(await getText('#js-transform')).toBe('Hello world');
50-
expect(await getColor('#css-transform')).toBe('red');
51-
});
46+
test('should respect transforms', async () => {
47+
expect(await getText('#js-transform')).toBe('Hello world');
48+
expect(await getColor('#css-transform')).toBe('red');
49+
});
5250

53-
if (isBuildWatch) {
54-
describe('build --watch', () => {
51+
describe('edit files', () => {
5552
const updateHmrTest = editFileAndWaitForBuildWatchComplete.bind(
5653
null,
5754
'src/components/HmrTest.svelte'
@@ -90,6 +87,7 @@ if (isBuildWatch) {
9087
// color should have changed
9188
expect(await getColor('#hmr-test-1 .label')).toBe('green');
9289
expect(await getColor('#hmr-test-2 .label')).toBe('green');
90+
expect(e2eServer.logs.watch.err, 'error log of `build --watch` is not empty').toEqual([]);
9391
});
9492

9593
test('should apply js change in HmrTest.svelte ', async () => {
@@ -99,6 +97,7 @@ if (isBuildWatch) {
9997
);
10098
expect(await getText('#hmr-test-1 .label')).toBe('hmr-test-updated');
10199
expect(await getText('#hmr-test-2 .label')).toBe('hmr-test-updated');
100+
expect(e2eServer.logs.watch.err, 'error log of `build --watch` is not empty').toEqual([]);
102101
});
103102

104103
test('should reset state of external store used by HmrTest.svelte when editing App.svelte', async () => {
@@ -114,6 +113,7 @@ if (isBuildWatch) {
114113
expect(await getText('#hmr-test-2 .counter')).toBe('0');
115114
// a third instance has been added
116115
expect(await getText('#hmr-test-3 .counter')).toBe('0');
116+
expect(e2eServer.logs.watch.err, 'error log of `build --watch` is not empty').toEqual([]);
117117
});
118118

119119
test('should reset state of store when editing hmr-stores.js', async () => {
@@ -124,6 +124,7 @@ if (isBuildWatch) {
124124
await updateStore((content) => `${content}\n/*trigger change*/\n`);
125125
// counter state is reset
126126
expect(await getText('#hmr-test-2 .counter')).toBe('0');
127+
expect(e2eServer.logs.watch.err, 'error log of `build --watch` is not empty').toEqual([]);
127128
});
128129

129130
test('should work when editing script context="module"', async () => {
@@ -134,79 +135,9 @@ if (isBuildWatch) {
134135
await updateModuleContext((content) => content.replace('y = 1', 'y = 2'));
135136
expect(await getText('#hmr-with-context')).toContain('x=0 y=2 slot=2');
136137
expect(await getText('#hmr-without-context')).toContain('x=0 y=2 slot=');
137-
expect(hmrCount('UsingNamed.svelte'), 'updates for UsingNamed.svelte').toBe(1);
138+
expect(hmrCount('UsingNamed.svelte'), 'updates for UsingNamed.svelte').toBe(0);
138139
expect(hmrCount('UsingDefault.svelte'), 'updates for UsingDefault.svelte').toBe(0);
139-
});
140-
141-
test('should work with emitCss: false in vite config', async () => {
142-
await editViteConfig((c) => c.replace('svelte()', 'svelte({emitCss:false})'));
143-
expect(await getText('#hmr-test-1 .counter')).toBe('0');
144-
expect(await getColor('#hmr-test-1 .label')).toBe('green');
145-
await (await getEl('#hmr-test-1 .increment')).click();
146-
await sleep(50);
147-
expect(await getText('#hmr-test-1 .counter')).toBe('1');
148-
await updateHmrTest((content) => content.replace('color: green', 'color: red'));
149-
expect(await getColor('#hmr-test-1 .label')).toBe('red');
150-
// counter value is reset
151-
expect(await getText('#hmr-test-1 .counter')).toBe('0');
152-
});
153-
154-
test('should work with emitCss: false in svelte config', async () => {
155-
addFile('svelte.config.js', 'export default {vitePlugin:{emitCss:false}}');
156-
await waitForBuildWatchAndPageReload();
157-
expect(await getColor('#hmr-test-1 .label')).toBe('red');
158-
removeFile('svelte.config.js');
159-
await waitForBuildWatchAndPageReload();
160-
});
161-
162-
test('should detect changes in svelte config and rebuild', async () => {
163-
const injectPreprocessor = ({ content, filename }) => {
164-
if (filename && filename.includes('App.svelte')) {
165-
return {
166-
code: content.replace(
167-
'<!-- HMR-TEMPLATE-INJECT -->',
168-
'<div id="preprocess-inject">Injected</div>\n<!-- HMR-TEMPLATE-INJECT -->'
169-
)
170-
};
171-
}
172-
};
173-
await addFile(
174-
'svelte.config.js',
175-
`export default {
176-
preprocess:[{markup:${injectPreprocessor.toString()}}]};`
177-
);
178-
await waitForBuildWatchAndPageReload();
179-
expect(await getText('#preprocess-inject')).toBe('Injected');
180-
expect(await getText('#hmr-test-1 .counter')).toBe('0');
181-
expect(await getColor('#hmr-test-1 .label')).toBe('red');
182-
await (await getEl('#hmr-test-1 .increment')).click();
183-
await sleep(50);
184-
expect(await getText('#hmr-test-1 .counter')).toBe('1');
185-
await updateHmrTest((content) => content.replace('color: red', 'color: green'));
186-
expect(await getColor('#hmr-test-1 .label')).toBe('green');
187-
expect(await getText('#hmr-test-1 .counter')).toBe('0');
188-
await editFile('svelte.config.js', (content) =>
189-
content
190-
.replace('preprocess-inject', 'preprocess-inject-2')
191-
.replace('Injected', 'Injected 2')
192-
);
193-
await waitForBuildWatchAndPageReload();
194-
expect(await getText('#preprocess-inject-2')).toBe('Injected 2');
195-
expect(await getEl('#preprocess-inject')).toBe(null);
196-
expect(await getColor('#hmr-test-1 .label')).toBe('green');
197-
expect(await getText('#hmr-test-1 .counter')).toBe('0');
198-
await (await getEl('#hmr-test-1 .increment')).click();
199-
await sleep(50);
200-
expect(await getText('#hmr-test-1 .counter')).toBe('1');
201-
await updateHmrTest((content) => content.replace('color: green', 'color: red'));
202-
expect(await getColor('#hmr-test-1 .label')).toBe('red');
203-
expect(await getText('#hmr-test-1 .counter')).toBe('0');
204-
await removeFile('svelte.config.js');
205-
await waitForBuildWatchAndPageReload();
206-
expect(await getEl('#preprocess-inject-2')).toBe(null);
207-
expect(await getEl('#preprocess-inject')).toBe(null);
208-
expect(await getColor('#hmr-test-1 .label')).toBe('red');
209-
expect(await getText('#hmr-test-1 .counter')).toBe('0');
140+
expect(e2eServer.logs.watch.err, 'error log of `build --watch` is not empty').toEqual([]);
210141
});
211142
});
212-
}
143+
});

packages/e2e-tests/build-watch/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"scripts": {
66
"dev": "vite dev",
77
"build": "vite build",
8+
"build:watch": "vite build --watch",
89
"preview": "vite preview"
910
},
1011
"dependencies": {
@@ -15,7 +16,8 @@
1516
"e2e-test-dep-vite-plugins": "file:../_test_dependencies/vite-plugins",
1617
"node-fetch": "^3.3.2",
1718
"svelte": "^5.36.13",
18-
"vite": "^7.0.5"
19+
"vite": "^7.0.5",
20+
"vite-plugin-inspect": "^11.3.2"
1921
},
2022
"type": "module"
2123
}

packages/e2e-tests/build-watch/src/App.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
}
1111
</script>
1212

13-
<h1 id="app-header">Test-App 2</h1>
13+
<h1 id="app-header">Test-App</h1>
1414
<!-- to be transformed into "Hello world!" text -->
1515
<p id="js-transform">{jsTransform}</p>
1616
<!-- to be transformed into "hello-world" class -->

packages/e2e-tests/build-watch/vite.config.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,27 @@
11
import { svelte } from '@sveltejs/vite-plugin-svelte';
22
import { defineConfig } from 'vite';
33
import { transformValidation } from 'e2e-test-dep-vite-plugins';
4+
// import inspect from 'vite-plugin-inspect';
45

56
export default defineConfig(({ command, mode }) => {
67
return {
7-
plugins: [transformValidation(), svelte()],
8+
plugins: [
9+
transformValidation(),
10+
svelte()
11+
/*
12+
inspect({ build: true, outputDir: '.vite-inspect' }),
13+
{
14+
name: 'vite-plugin-no-ssr-fallback-env',
15+
enforce: 'post',
16+
config: {
17+
order: 'post',
18+
handler(c) {
19+
delete c.ssr; // workaround to avoid vite-plugin-inspect never finishing due to unused ssr build
20+
}
21+
}
22+
}
23+
*/
24+
],
825
build: {
926
minify: false,
1027
target: 'esnext',

packages/vite-plugin-svelte/src/plugins/load-compiled-css.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,16 @@ const filter = { id: SVELTE_VIRTUAL_STYLE_ID_REGEX };
88
* @returns {import('vite').Plugin}
99
*/
1010
export function loadCompiledCss(api) {
11+
let isBuildWatch = false;
12+
/** @type{Map<string,any>} */
13+
const buildWatchCssCache = new Map();
1114
return {
1215
name: 'vite-plugin-svelte:load-compiled-css',
1316

17+
configResolved(c) {
18+
isBuildWatch = !!c.build?.watch;
19+
},
20+
1421
resolveId: {
1522
filter, // same filter in load to ensure minimal work
1623
handler(id) {
@@ -26,7 +33,17 @@ export function loadCompiledCss(api) {
2633
if (!svelteRequest) {
2734
return;
2835
}
29-
const cachedCss = this.getModuleInfo(svelteRequest.filename)?.meta.svelte?.css;
36+
let cachedCss = this.getModuleInfo(svelteRequest.filename)?.meta.svelte?.css;
37+
// in build --watch getModuleInfo only returns changed module data.
38+
// To ensure virtual css is loaded unchanged, we cache it here separately
39+
if (isBuildWatch) {
40+
if (cachedCss) {
41+
buildWatchCssCache.set(svelteRequest.filename, cachedCss);
42+
} else {
43+
cachedCss = buildWatchCssCache.get(svelteRequest.filename);
44+
}
45+
}
46+
3047
if (cachedCss) {
3148
const { hasGlobal, ...css } = cachedCss;
3249
if (hasGlobal === false) {
@@ -37,6 +54,8 @@ export function loadCompiledCss(api) {
3754
}
3855
css.moduleType = 'css';
3956
return css;
57+
} else {
58+
log.warn(`failed to load virtual css module ${id}`, undefined, 'load');
4059
}
4160
}
4261
}

0 commit comments

Comments
 (0)