Skip to content

Commit 3d87d1e

Browse files
authored
feat: default to hydratable: false and add dynamicCompileOptions (#122)
* feat: default to hydratable: false and add experimental.dynamicCompileOptions * chore: cleanup e2e-tests vite configs
1 parent 90cfc19 commit 3d87d1e

File tree

8 files changed

+122
-20
lines changed

8 files changed

+122
-20
lines changed

.changeset/calm-dancers-accept.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@sveltejs/vite-plugin-svelte': major
3+
---
4+
5+
change default value of compilerOptions.hydratable to false
6+
7+
This is done to align with svelte compiler defaults and improve output in non-ssr scenarios.
8+
9+
Add `{compilerOptions: {hydratable: true}}` to vite-plugin-svelte config if you need hydration (eg. for ssr)

.changeset/curly-pianos-brush.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': minor
3+
---
4+
5+
add config option `experimental.dynamicCompileOptions` for finegrained control over compileOptions

packages/e2e-tests/kit-node/__tests__/kit.spec.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import {
2-
readFileContent,
32
editFile,
43
editFileAndWaitForHmrComplete,
54
getColor,
65
getEl,
76
getText,
87
isBuild,
8+
readFileContent,
9+
sleep,
910
untilUpdated
1011
} from '../../testUtils';
1112

@@ -14,12 +15,37 @@ import path from 'path';
1415

1516
describe('kit-node', () => {
1617
describe('index route', () => {
17-
it('should contain greeting', async () => {
18-
// TODO is hydration testing needed here?
19-
expect(await page.textContent('h1')).toMatch('Hello world!'); // after hydration
18+
it('should hydrate', async () => {
19+
// mark initial nodes
20+
await page.$eval('#load', (e) => {
21+
e['__initialNode'] = true;
22+
});
23+
await page.$eval('#mount', (e) => {
24+
e['__initialNode'] = true;
25+
});
26+
27+
// check content before hydration
28+
expect(await getText('h1')).toBe('Hello world!');
29+
expect(await getText('#load')).toBe('SERVER_LOADED');
30+
expect(await getText('#mount')).toBe('BEFORE_MOUNT');
2031

32+
// also get page as text to confirm
2133
const html = await (await fetch(page.url())).text();
22-
expect(html).toMatch('Hello world!'); // before hydration
34+
expect(html).toMatch('Hello world!');
35+
expect(html).toMatch('SERVER_LOADED');
36+
expect(html).toMatch('BEFORE_MOUNT');
37+
38+
// wait a bit for hydration to kick in
39+
await sleep(250);
40+
41+
// check hydrated content
42+
expect(await getText('#load')).toBe('CLIENT_LOADED');
43+
expect(await getText('#mount')).toBe('AFTER_MOUNT');
44+
45+
// check that it did not replace the dom elements with new ones
46+
expect(await page.$eval('#load', (e) => e['__initialNode'])).toBe(true);
47+
expect(await page.$eval('#mount', (e) => e['__initialNode'])).toBe(true);
48+
2349
if (isBuild) {
2450
// TODO additional testing needed here once vite-plugin-svelte implements indexHtmlTransform hook
2551
}
@@ -51,7 +77,7 @@ describe('kit-node', () => {
5177

5278
it('should load dynamic import in onMount', async () => {
5379
// expect log to contain message with dynamic import value from onMount
54-
expect(browserLogs.some((x) => x === `onMount dynamic imported isSSR: false`)).toBe(true);
80+
expect(browserLogs.some((x) => x === 'onMount dynamic imported isSSR: false')).toBe(true);
5581
});
5682

5783
if (isBuild) {

packages/e2e-tests/kit-node/src/routes/index.svelte

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,47 @@
1+
<script context="module">
2+
/**
3+
* @type {import('@sveltejs/kit').Load}
4+
*/
5+
export async function load() {
6+
if (globalThis.window) {
7+
// delay load on client so we can test hydration with playwright
8+
return new Promise((resolve) =>
9+
setTimeout(() => {
10+
resolve({ props: { load_status: 'CLIENT_LOADED' } });
11+
}, 200)
12+
);
13+
} else {
14+
return { props: { load_status: 'SERVER_LOADED' } };
15+
}
16+
}
17+
</script>
18+
119
<script>
220
import { onMount } from 'svelte';
321
// eslint-disable-next-line node/no-missing-import
422
import Counter from '$lib/Counter.svelte';
523
// eslint-disable-next-line node/no-missing-import
624
import Child from '$lib/Child.svelte';
7-
25+
export let load_status = 'NOT_LOADED';
26+
let mount_status = 'BEFORE_MOUNT';
827
onMount(async () => {
928
const isSSR = (await import('../client-only-module.js')).default;
1029
console.log(`onMount dynamic imported isSSR: ${isSSR}`);
30+
mount_status = 'AFTER_MOUNT';
1131
});
1232
</script>
1333

1434
<main>
1535
<h1>Hello world!</h1>
16-
1736
<Counter />
1837

1938
<p>Visit <a href="https://svelte.dev">svelte.dev</a> to learn how to build Svelte apps.</p>
2039

2140
<div id="before-child">before-child</div>
2241
<Child testId="test-child" />
2342
<div id="after-child">after-child</div>
43+
<div id="load">{load_status}</div>
44+
<div id="mount">{mount_status}</div>
2445
</main>
2546

2647
<!-- HMR-TEMPLATE-INJECT -->

packages/e2e-tests/vite-ssr/vite.config.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ const { svelte } = require('@sveltejs/vite-plugin-svelte');
44
module.exports = defineConfig(({ command, mode }) => {
55
const isProduction = mode === 'production';
66
return {
7-
plugins: [svelte()],
7+
plugins: [
8+
svelte({
9+
compilerOptions: {
10+
hydratable: true /* required for clientside hydration */
11+
}
12+
})
13+
],
814
build: {
915
minify: isProduction,
1016
assetsInlineLimit: 0

packages/vite-plugin-svelte/src/utils/compile.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,43 @@ const _createCompileSvelte = (makeHot: Function) =>
1515
const { filename, normalizedFilename, cssId, ssr } = svelteRequest;
1616
const { emitCss = true } = options;
1717
const dependencies = [];
18-
const finalCompilerOptions: CompileOptions = {
18+
19+
const compileOptions: CompileOptions = {
1920
...options.compilerOptions,
2021
filename,
2122
generate: ssr ? 'ssr' : 'dom'
2223
};
2324
if (options.hot && options.emitCss) {
2425
const hash = `s-${safeBase64Hash(normalizedFilename)}`;
2526
log.debug(`setting cssHash ${hash} for ${normalizedFilename}`);
26-
finalCompilerOptions.cssHash = () => hash;
27+
compileOptions.cssHash = () => hash;
2728
}
2829

2930
let preprocessed;
3031

3132
if (options.preprocess) {
3233
preprocessed = await preprocess(code, options.preprocess, { filename });
3334
if (preprocessed.dependencies) dependencies.push(...preprocessed.dependencies);
34-
if (preprocessed.map) finalCompilerOptions.sourcemap = preprocessed.map;
35+
if (preprocessed.map) compileOptions.sourcemap = preprocessed.map;
3536
}
36-
37-
const compiled = compile(preprocessed ? preprocessed.code : code, finalCompilerOptions);
37+
const finalCode = preprocessed ? preprocessed.code : code;
38+
const dynamicCompileOptions = await options.experimental?.dynamicCompileOptions?.({
39+
filename,
40+
code: finalCode,
41+
compileOptions
42+
});
43+
if (dynamicCompileOptions && log.debug.enabled) {
44+
log.debug(
45+
`dynamic compile options for ${filename}: ${JSON.stringify(dynamicCompileOptions)}`
46+
);
47+
}
48+
const finalCompileOptions = dynamicCompileOptions
49+
? {
50+
...compileOptions,
51+
...dynamicCompileOptions
52+
}
53+
: compileOptions;
54+
const compiled = compile(finalCode, finalCompileOptions);
3855

3956
if (emitCss && compiled.css.code) {
4057
// TODO properly update sourcemap?
@@ -49,7 +66,7 @@ const _createCompileSvelte = (makeHot: Function) =>
4966
hotOptions: options.hot,
5067
compiled,
5168
originalCode: code,
52-
compileOptions: finalCompilerOptions
69+
compileOptions: finalCompileOptions
5370
});
5471
}
5572

@@ -58,6 +75,7 @@ const _createCompileSvelte = (makeHot: Function) =>
5875
return {
5976
filename,
6077
normalizedFilename,
78+
// @ts-ignore
6179
compiled,
6280
ssr,
6381
dependencies

packages/vite-plugin-svelte/src/utils/options.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ function buildDefaultOptions(isProduction: boolean, options: Partial<Options>):
4444
compilerOptions: {
4545
format: 'esm',
4646
css: !emitCss,
47-
dev: !isProduction,
48-
hydratable: true
47+
dev: !isProduction
4948
}
5049
};
5150
log.debug(`default options for ${isProduction ? 'production' : 'development'}`, defaultOptions);
@@ -311,6 +310,26 @@ export interface ExperimentalOptions {
311310
* to use this option you have to install "diff-match-patch"
312311
*/
313312
generateMissingPreprocessorSourcemaps?: boolean;
313+
314+
/**
315+
* function to update compilerOptions before compilation
316+
*
317+
* data.filename is the file to be compiled,
318+
* data.code is the already preprocessed code
319+
* data.compileOptions are the compilerOptions that are going to be used
320+
*
321+
* to change one, you should return an object with the changes you need, eg:
322+
*
323+
* ```
324+
* ({filename,compileOptions}) => { if( compileWithHydratable(filename) && !compileOptions.hydratable ){ return {hydratable: true}}}
325+
* ```
326+
* @default undefined
327+
*/
328+
dynamicCompileOptions?: (data: {
329+
filename: string;
330+
code: string;
331+
compileOptions: Partial<CompileOptions>;
332+
}) => Promise<Partial<CompileOptions> | void> | Partial<CompileOptions> | void;
314333
}
315334

316335
export interface ResolvedOptions extends Options {

scripts/jestPerTestSetup.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,7 @@ beforeAll(async () => {
117117
const port = await getUniqueTestPort(e2eTestsRoot, testName, isBuild);
118118
server = await serve(tempDir, isBuild, port);
119119
const url = ((global as any).viteTestUrl = `http://localhost:${port}`);
120-
await (isBuild
121-
? page.goto(url, { waitUntil: 'networkidle' })
122-
: goToUrlAndWaitForViteWSConnect(page, url));
120+
await (isBuild ? page.goto(url) : goToUrlAndWaitForViteWSConnect(page, url));
123121
}
124122
} catch (e) {
125123
// jest doesn't exit if our setup has error here

0 commit comments

Comments
 (0)