Skip to content

Commit 3a6976a

Browse files
authored
fix build --watch (#1189)
* wip: add test mode for build --watch and e2e setup geared towards it. not complete and then needs fix * fix: add missing return * fix: ensure compiled css is loaded correctly when rebuilding in watch mode * fix: ignore our own warning message in error log test
1 parent 3a6344f commit 3a6976a

34 files changed

+693
-76
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: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
"private": true,
44
"type": "module",
55
"scripts": {
6-
"test": "run-s -c test:unit \"test:build {@}\" \"test:serve {@}\" --",
6+
"test": "run-s -c test:unit \"test:build {@}\" \"test:serve {@}\" \"test:build:watch {@}\" --",
77
"test:unit": "vitest run",
88
"test:serve": "vitest run -c vitest.config.e2e.ts",
99
"test:build": "cross-env TEST_BUILD=1 vitest run -c vitest.config.e2e.ts",
10+
"test:build:watch": "cross-env TEST_BUILD_WATCH=1 vitest run -c vitest.config.e2e.ts",
1011
"check": "run-p -c check:*",
1112
"check:audit": "pnpm audit --prod",
1213
"check:publint": "pnpm --filter \"./packages/*\" --parallel check:publint",
@@ -78,7 +79,8 @@
7879
"vite": "$vite",
7980
"@types/node@<=20.12.0": "20.19.9",
8081
"send@<0.19.0": "^0.19.1",
81-
"@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"
8284
},
8385
"onlyBuiltDependencies": [
8486
"esbuild"

packages/e2e-tests/_test_dependencies/vite-plugins/index.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
import path from 'node:path';
22
import fs from 'node:fs';
3+
import MagicString from 'magic-string';
4+
5+
function replaceWithSourceMap(code, value, replacement) {
6+
const s = new MagicString(code);
7+
s.replaceAll(value, replacement);
8+
return {
9+
code: s.toString(),
10+
map: s.generateMap({ hires: 'boundary' })
11+
};
12+
}
313
/**
414
* Ensure transform flow is not interrupted
515
* @returns {import('vite').Plugin[]}
@@ -11,19 +21,19 @@ export function transformValidation() {
1121
enforce: 'pre',
1222
transform(code, id) {
1323
if (id.endsWith('.svelte')) {
14-
return code.replaceAll('__JS_TRANSFORM_1__', '__JS_TRANSFORM_2__');
24+
return replaceWithSourceMap(code, '__JS_TRANSFORM_1__', '__JS_TRANSFORM_2__');
1525
} else if (id.endsWith('.css')) {
16-
return code.replaceAll('__CSS_TRANSFORM_1__', '__CSS_TRANSFORM_2__');
26+
return replaceWithSourceMap(code, '__CSS_TRANSFORM_1__', '__CSS_TRANSFORM_2__');
1727
}
1828
}
1929
},
2030
{
2131
name: 'transform-validation:2',
2232
transform(code, id) {
2333
if (id.endsWith('.svelte')) {
24-
return code.replaceAll('__JS_TRANSFORM_2__', '__JS_TRANSFORM_3__');
34+
return replaceWithSourceMap(code, '__JS_TRANSFORM_2__', '__JS_TRANSFORM_3__');
2535
} else if (id.endsWith('.css')) {
26-
return code.replaceAll('__CSS_TRANSFORM_2__', 'red');
36+
return replaceWithSourceMap(code, '__CSS_TRANSFORM_2__', 'red');
2737
}
2838
}
2939
},
@@ -32,7 +42,7 @@ export function transformValidation() {
3242
enforce: 'post',
3343
transform(code, id) {
3444
if (id.endsWith('.svelte')) {
35-
return code.replaceAll('__JS_TRANSFORM_3__', 'Hello world');
45+
return replaceWithSourceMap(code, '__JS_TRANSFORM_3__', 'Hello world');
3646
}
3747
// can't handle css here as in build, it would be `export default {}`
3848
}

packages/e2e-tests/_test_dependencies/vite-plugins/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,8 @@
66
"main": "./index.js",
77
"files": [
88
"index.js"
9-
]
9+
],
10+
"dependencies": {
11+
"magic-string": "^0.30.17"
12+
}
1013
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import {
2+
isBuildWatch,
3+
getEl,
4+
getText,
5+
editFileAndWaitForBuildWatchComplete,
6+
hmrCount,
7+
untilMatches,
8+
sleep,
9+
getColor,
10+
browserLogs,
11+
e2eServer
12+
} from '~utils';
13+
14+
import * as vite from 'vite';
15+
// @ts-ignore
16+
const isRolldownVite = !!vite.rolldownVersion;
17+
18+
describe.runIf(isBuildWatch)('build-watch', () => {
19+
test('should render App', async () => {
20+
expect(await getText('#app-header')).toBe('Test-App');
21+
});
22+
23+
test('should render static import', async () => {
24+
expect(await getText('#static-import .label')).toBe('static-import');
25+
});
26+
27+
test('should render dependency import', async () => {
28+
expect(await getText('#dependency-import .label')).toBe('dependency-import');
29+
});
30+
31+
test('should render dynamic import', async () => {
32+
expect(await getEl('#dynamic-import')).toBe(null);
33+
const dynamicImportButton = await getEl('#button-import-dynamic');
34+
expect(dynamicImportButton).toBeDefined();
35+
await dynamicImportButton.click();
36+
await untilMatches(
37+
() => getText('#dynamic-import .label'),
38+
'dynamic-import',
39+
'dynamic import loaded after click'
40+
);
41+
});
42+
43+
test('should not have failed requests', async () => {
44+
browserLogs.forEach((msg) => {
45+
expect(msg).not.toMatch('404');
46+
});
47+
});
48+
49+
test('should respect transforms', async () => {
50+
expect(await getText('#js-transform')).toBe('Hello world');
51+
expect(await getColor('#css-transform')).toBe('red');
52+
});
53+
54+
describe('edit files', () => {
55+
const updateHmrTest = editFileAndWaitForBuildWatchComplete.bind(
56+
null,
57+
'src/components/HmrTest.svelte'
58+
);
59+
const updateModuleContext = editFileAndWaitForBuildWatchComplete.bind(
60+
null,
61+
'src/components/partial-hmr/ModuleContext.svelte'
62+
);
63+
const updateApp = editFileAndWaitForBuildWatchComplete.bind(null, 'src/App.svelte');
64+
const updateStore = editFileAndWaitForBuildWatchComplete.bind(null, 'src/stores/hmr-stores.js');
65+
66+
const getWatchErrors = () =>
67+
isRolldownVite
68+
? e2eServer.logs.watch.err.filter(
69+
(m) =>
70+
![
71+
'Support for rolldown-vite in vite-plugin-svelte is experimental',
72+
'See https://github.com/sveltejs/vite-plugin-svelte/issues/1143'
73+
].some((s) => m.includes(s))
74+
)
75+
: e2eServer.logs.watch.err;
76+
77+
test('should have expected initial state', async () => {
78+
// initial state, both counters 0, both labels red
79+
expect(await getText('#hmr-test-1 .counter')).toBe('0');
80+
expect(await getText('#hmr-test-2 .counter')).toBe('0');
81+
expect(await getText('#hmr-test-1 .label')).toBe('hmr-test');
82+
expect(await getText('#hmr-test-2 .label')).toBe('hmr-test');
83+
expect(await getColor('#hmr-test-1 .label')).toBe('red');
84+
expect(await getColor('#hmr-test-2 .label')).toBe('red');
85+
});
86+
87+
test('should have working increment button', async () => {
88+
// increment counter of one instance to have local state to verify after build updates
89+
await (await getEl('#hmr-test-1 .increment')).click();
90+
await sleep(50);
91+
92+
// counter1 = 1, counter2 = 0
93+
expect(await getText('#hmr-test-1 .counter')).toBe('1');
94+
expect(await getText('#hmr-test-2 .counter')).toBe('0');
95+
});
96+
97+
test('should apply css changes in HmrTest.svelte', async () => {
98+
// update style, change label color from red to green
99+
await updateHmrTest((content) => content.replace('color: red', 'color: green'));
100+
101+
// color should have changed
102+
expect(await getColor('#hmr-test-1 .label')).toBe('green');
103+
expect(await getColor('#hmr-test-2 .label')).toBe('green');
104+
expect(getWatchErrors(), 'error log of `build --watch` is not empty').toEqual([]);
105+
});
106+
107+
test('should apply js change in HmrTest.svelte ', async () => {
108+
// update script, change label value
109+
await updateHmrTest((content) =>
110+
content.replace("const label = 'hmr-test'", "const label = 'hmr-test-updated'")
111+
);
112+
expect(await getText('#hmr-test-1 .label')).toBe('hmr-test-updated');
113+
expect(await getText('#hmr-test-2 .label')).toBe('hmr-test-updated');
114+
expect(getWatchErrors(), 'error log of `build --watch` is not empty').toEqual([]);
115+
});
116+
117+
test('should reset state of external store used by HmrTest.svelte when editing App.svelte', async () => {
118+
// update App, add a new instance of HmrTest
119+
await updateApp((content) =>
120+
content.replace(
121+
'<!-- HMR-TEMPLATE-INJECT -->',
122+
'<HmrTest id="hmr-test-3"/>\n<!-- HMR-TEMPLATE-INJECT -->'
123+
)
124+
);
125+
// counter state is reset
126+
expect(await getText('#hmr-test-1 .counter')).toBe('0');
127+
expect(await getText('#hmr-test-2 .counter')).toBe('0');
128+
// a third instance has been added
129+
expect(await getText('#hmr-test-3 .counter')).toBe('0');
130+
expect(getWatchErrors(), 'error log of `build --watch` is not empty').toEqual([]);
131+
});
132+
133+
test('should reset state of store when editing hmr-stores.js', async () => {
134+
// change state
135+
await (await getEl('#hmr-test-2 .increment')).click();
136+
await sleep(50);
137+
expect(await getText('#hmr-test-2 .counter')).toBe('1');
138+
await updateStore((content) => `${content}\n/*trigger change*/\n`);
139+
// counter state is reset
140+
expect(await getText('#hmr-test-2 .counter')).toBe('0');
141+
expect(getWatchErrors(), 'error log of `build --watch` is not empty').toEqual([]);
142+
});
143+
144+
test('should work when editing script context="module"', async () => {
145+
expect(await getText('#hmr-with-context')).toContain('x=0 y=1 slot=1');
146+
expect(await getText('#hmr-without-context')).toContain('x=0 y=1 slot=');
147+
expect(hmrCount('UsingNamed.svelte'), 'updates for UsingNamed.svelte').toBe(0);
148+
expect(hmrCount('UsingDefault.svelte'), 'updates for UsingDefault.svelte').toBe(0);
149+
await updateModuleContext((content) => content.replace('y = 1', 'y = 2'));
150+
expect(await getText('#hmr-with-context')).toContain('x=0 y=2 slot=2');
151+
expect(await getText('#hmr-without-context')).toContain('x=0 y=2 slot=');
152+
expect(hmrCount('UsingNamed.svelte'), 'updates for UsingNamed.svelte').toBe(0);
153+
expect(hmrCount('UsingDefault.svelte'), 'updates for UsingDefault.svelte').toBe(0);
154+
expect(getWatchErrors(), 'error log of `build --watch` is not empty').toEqual([]);
155+
});
156+
});
157+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" href="/favicon.png" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Svelte App</title>
8+
</head>
9+
<body>
10+
<script type="module" src="/src/index.js"></script>
11+
</body>
12+
</html>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "e2e-tests-build-watch",
3+
"private": true,
4+
"version": "0.0.0",
5+
"scripts": {
6+
"dev": "vite dev",
7+
"build": "vite build",
8+
"build:watch": "vite build --watch",
9+
"preview": "vite preview"
10+
},
11+
"dependencies": {
12+
"e2e-test-dep-svelte-simple": "file:../_test_dependencies/svelte-simple"
13+
},
14+
"devDependencies": {
15+
"@sveltejs/vite-plugin-svelte": "workspace:^",
16+
"e2e-test-dep-vite-plugins": "file:../_test_dependencies/vite-plugins",
17+
"node-fetch": "^3.3.2",
18+
"svelte": "^5.36.13",
19+
"vite": "^7.0.5",
20+
"vite-plugin-inspect": "^11.3.2"
21+
},
22+
"type": "module"
23+
}

0 commit comments

Comments
 (0)