Skip to content

Commit 31ce52c

Browse files
authored
Chore/v2 cleanup (#782)
* feat: add experimental support for Svelte 5 * add experimental note
1 parent 907e810 commit 31ce52c

File tree

11 files changed

+158
-14
lines changed

11 files changed

+158
-14
lines changed

.changeset/poor-avocados-learn.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+
feat: add experimental support for Svelte 5

packages/vite-plugin-svelte/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
"vitefu": "^0.2.4"
4848
},
4949
"peerDependencies": {
50-
"svelte": "^3.54.0 || ^4.0.0",
50+
"svelte": "^3.54.0 || ^4.0.0 || ^5.0.0-next.0",
5151
"vite": "^4.0.0"
5252
},
5353
"devDependencies": {

packages/vite-plugin-svelte/src/index.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,16 @@ interface ExperimentalOptions {
170170
* @default false
171171
*/
172172
disableSvelteResolveWarnings?: boolean;
173+
/**
174+
* Options for compiling Svelte JS/TS modules
175+
*/
176+
compileModule?: CompileModuleOptions;
177+
}
178+
179+
interface CompileModuleOptions {
180+
extensions?: string[];
181+
include?: Arrayable<string>;
182+
exclude?: Arrayable<string>;
173183
}
174184

175185
type ModuleFormat = NonNullable<'esm'>;

packages/vite-plugin-svelte/src/index.js

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import fs from 'node:fs';
22
import { version as viteVersion } from 'vite';
3+
import * as svelteCompiler from 'svelte/compiler';
34

45
import { svelteInspector } from '@sveltejs/vite-plugin-svelte-inspector';
56

67
import { isDepExcluded } from 'vitefu';
78
import { handleHotUpdate } from './handle-hot-update.js';
89
import { log, logCompilerWarnings } from './utils/log.js';
910
import { createCompileSvelte } from './utils/compile.js';
10-
import { buildIdParser } from './utils/id.js';
11+
import { buildIdParser, buildModuleIdParser } from './utils/id.js';
1112
import {
1213
buildExtraViteConfig,
1314
validateInlineOptions,
@@ -24,7 +25,7 @@ import { saveSvelteMetadata } from './utils/optimizer.js';
2425
import { VitePluginSvelteCache } from './utils/vite-plugin-svelte-cache.js';
2526
import { loadRaw } from './utils/load-raw.js';
2627
import { FAQ_LINK_CONFLICTS_IN_SVELTE_RESOLVE } from './utils/constants.js';
27-
import { isSvelte3 } from './utils/svelte-version.js';
28+
import { isSvelte3, isSvelte5 } from './utils/svelte-version.js';
2829

2930
const isVite4_0 = viteVersion.startsWith('4.0');
3031

@@ -38,6 +39,8 @@ export function svelte(inlineOptions) {
3839
// updated in configResolved hook
3940
/** @type {import('./types/id.d.ts').IdParser} */
4041
let requestParser;
42+
/** @type {import('./types/id.d.ts').ModuleIdParser} */
43+
let moduleRequestParser;
4144
/** @type {import('./types/options.d.ts').ResolvedOptions} */
4245
let options;
4346
/** @type {import('vite').ResolvedConfig} */
@@ -268,9 +271,44 @@ export function svelte(inlineOptions) {
268271
);
269272
}
270273
}
271-
},
272-
svelteInspector()
274+
}
273275
];
276+
277+
if (!isSvelte5) {
278+
plugins.push(svelteInspector()); // TODO reenable once svelte5 has support
279+
}
280+
if (isSvelte5) {
281+
log.warn(
282+
'svelte 5 support in v-p-s is experimental, breaking changes can occur in any release until this notice is removed'
283+
);
284+
log.warn('svelte 5 does not support svelte-inspector yet, disabling it');
285+
// TODO move to separate file
286+
plugins.push({
287+
name: 'vite-plugin-svelte-module',
288+
enforce: 'post',
289+
async configResolved() {
290+
moduleRequestParser = buildModuleIdParser(options);
291+
},
292+
async transform(code, id, opts) {
293+
const ssr = !!opts?.ssr;
294+
const moduleRequest = moduleRequestParser(id, ssr);
295+
if (!moduleRequest) {
296+
return;
297+
}
298+
try {
299+
const compileResult = await svelteCompiler.compileModule(code, {
300+
generate: ssr ? 'ssr' : 'dom',
301+
filename: moduleRequest.filename
302+
});
303+
logCompilerWarnings(moduleRequest, compileResult.warnings, options);
304+
return compileResult.js;
305+
} catch (e) {
306+
throw toRollupError(e, options);
307+
}
308+
}
309+
});
310+
}
311+
274312
return plugins;
275313
}
276314

packages/vite-plugin-svelte/src/types/id.d.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,19 @@ export interface SvelteRequest {
2828
raw: boolean;
2929
}
3030

31+
export interface SvelteModuleRequest {
32+
id: string;
33+
filename: string;
34+
normalizedFilename: string;
35+
query: RequestQuery;
36+
timestamp: number;
37+
ssr: boolean;
38+
}
39+
3140
export type IdParser = (id: string, ssr: boolean, timestamp?: number) => SvelteRequest | undefined;
41+
42+
export type ModuleIdParser = (
43+
id: string,
44+
ssr: boolean,
45+
timestamp?: number
46+
) => SvelteModuleRequest | undefined;

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { compile, preprocess, walk } from 'svelte/compiler';
1+
import * as svelte from 'svelte/compiler';
22
// @ts-ignore
33
import { createMakeHot } from 'svelte-hmr';
44
import { safeBase64Hash } from './hash.js';
@@ -86,7 +86,7 @@ export const _createCompileSvelte = (makeHot) => {
8686
}
8787
if (preprocessors) {
8888
try {
89-
preprocessed = await preprocess(code, preprocessors, { filename }); // full filename here so postcss works
89+
preprocessed = await svelte.preprocess(code, preprocessors, { filename }); // full filename here so postcss works
9090
} catch (e) {
9191
e.message = `Error while preprocessing ${filename}${e.message ? ` - ${e.message}` : ''}`;
9292
throw e;
@@ -123,7 +123,7 @@ export const _createCompileSvelte = (makeHot) => {
123123
: compileOptions;
124124

125125
const endStat = stats?.start(filename);
126-
const compiled = compile(finalCode, finalCompileOptions);
126+
const compiled = svelte.compile(finalCode, finalCompileOptions);
127127

128128
if (isSvelte3) {
129129
// prevent dangling pure comments
@@ -187,7 +187,8 @@ function buildMakeHot(options) {
187187
// @ts-ignore
188188
const adapter = options?.hot?.adapter;
189189
return createMakeHot({
190-
walk,
190+
// TODO Svelte 5 doesn't expose walk anymore. If we decide to make v-p-s 2 work with Svelte 5 HMR, we need to import walk from estree-walker
191+
walk: svelte.walk,
191192
hotApi,
192193
adapter,
193194
hotOptions: { noOverlay: true, .../** @type {object} */ (options.hot) }

packages/vite-plugin-svelte/src/utils/esbuild.js

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { readFileSync } from 'node:fs';
2-
import { compile, preprocess } from 'svelte/compiler';
2+
import * as svelte from 'svelte/compiler';
33
import { log } from './log.js';
44
import { toESBuildError } from './error.js';
5-
import { isSvelte3 } from './svelte-version.js';
5+
import { isSvelte3, isSvelte5 } from './svelte-version.js';
66

77
/**
88
* @typedef {NonNullable<import('vite').DepOptimizationOptions['esbuildOptions']>} EsbuildOptions
@@ -11,6 +11,8 @@ import { isSvelte3 } from './svelte-version.js';
1111

1212
export const facadeEsbuildSveltePluginName = 'vite-plugin-svelte:facade';
1313

14+
const svelteModuleExtension = '.svelte.js';
15+
1416
/**
1517
* @param {import('../types/options.d.ts').ResolvedOptions} options
1618
* @returns {EsbuildPlugin}
@@ -24,6 +26,9 @@ export function esbuildSveltePlugin(options) {
2426
if (build.initialOptions.plugins?.some((v) => v.name === 'vite:dep-scan')) return;
2527

2628
const svelteExtensions = (options.extensions ?? ['.svelte']).map((ext) => ext.slice(1));
29+
if (isSvelte5) {
30+
svelteExtensions.push(svelteModuleExtension.slice(1));
31+
}
2732
const svelteFilter = new RegExp('\\.(' + svelteExtensions.join('|') + ')(\\?.*)?$');
2833
/** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */
2934
let statsCollection;
@@ -55,6 +60,21 @@ export function esbuildSveltePlugin(options) {
5560
* @returns {Promise<string>}
5661
*/
5762
async function compileSvelte(options, { filename, code }, statsCollection) {
63+
if (isSvelte5 && filename.endsWith(svelteModuleExtension)) {
64+
const endStat = statsCollection?.start(filename);
65+
const compiled = svelte.compileModule(code, {
66+
filename,
67+
generate: 'dom',
68+
runes: true
69+
});
70+
if (endStat) {
71+
endStat();
72+
}
73+
return compiled.js.map
74+
? compiled.js.code + '//# sourceMappingURL=' + compiled.js.map.toUrl()
75+
: compiled.js.code;
76+
}
77+
5878
let css = options.compilerOptions.css;
5979
if (css !== 'none') {
6080
// TODO ideally we'd be able to externalize prebundled styles too, but for now always put them in the js
@@ -75,7 +95,7 @@ async function compileSvelte(options, { filename, code }, statsCollection) {
7595

7696
if (options.preprocess) {
7797
try {
78-
preprocessed = await preprocess(code, options.preprocess, { filename });
98+
preprocessed = await svelte.preprocess(code, options.preprocess, { filename });
7999
} catch (e) {
80100
e.message = `Error while preprocessing ${filename}${e.message ? ` - ${e.message}` : ''}`;
81101
throw e;
@@ -102,7 +122,7 @@ async function compileSvelte(options, { filename, code }, statsCollection) {
102122
}
103123
: compileOptions;
104124
const endStat = statsCollection?.start(filename);
105-
const compiled = compile(finalCode, finalCompileOptions);
125+
const compiled = svelte.compile(finalCode, finalCompileOptions);
106126
if (endStat) {
107127
endStat();
108128
}

packages/vite-plugin-svelte/src/utils/id.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,48 @@ export function buildIdParser(options) {
184184
}
185185
};
186186
}
187+
/**
188+
* @param {import('../types/options.d.ts').ResolvedOptions} options
189+
* @returns {import('../types/id.d.ts').ModuleIdParser}
190+
*/
191+
export function buildModuleIdParser(options) {
192+
const { include, exclude, extensions } = options?.experimental?.compileModule ?? {};
193+
const root = options.root;
194+
const normalizedRoot = normalizePath(root);
195+
const filter = buildFilter(include, exclude, extensions ?? ['.svelte.js', '.svelte.ts']);
196+
return (id, ssr, timestamp = Date.now()) => {
197+
const { filename, rawQuery } = splitId(id);
198+
if (filter(filename)) {
199+
return parseToSvelteModuleRequest(id, filename, rawQuery, normalizedRoot, timestamp, ssr);
200+
}
201+
};
202+
}
203+
204+
/**
205+
* @param {string} id
206+
* @param {string} filename
207+
* @param {string} rawQuery
208+
* @param {string} root
209+
* @param {number} timestamp
210+
* @param {boolean} ssr
211+
* @returns {import('../types/id.d.ts').SvelteModuleRequest | undefined}
212+
*/
213+
function parseToSvelteModuleRequest(id, filename, rawQuery, root, timestamp, ssr) {
214+
const query = parseRequestQuery(rawQuery);
215+
216+
if (query.url || query.raw || query.direct) {
217+
// skip requests with special vite tags
218+
return;
219+
}
220+
221+
const normalizedFilename = normalize(filename, root);
222+
223+
return {
224+
id,
225+
filename,
226+
normalizedFilename,
227+
query,
228+
timestamp,
229+
ssr
230+
};
231+
}

packages/vite-plugin-svelte/src/utils/log.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ export const log = {
120120
};
121121

122122
/**
123-
* @param {import('../types/id.d.ts').SvelteRequest} svelteRequest
123+
* @param {import('../types/id.d.ts').SvelteRequest | import('../types/id.d.ts').SvelteModuleRequest} svelteRequest
124124
* @param {import('svelte/types/compiler/interfaces').Warning[]} warnings
125125
* @param {import('../types/options.d.ts').ResolvedOptions} options
126126
*/

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
import { isCommonDepWithoutSvelteField } from './dependencies.js';
2626
import { VitePluginSvelteStats } from './vite-plugin-svelte-stats.js';
2727
import { VitePluginSvelteCache } from './vite-plugin-svelte-cache.js';
28+
import { isSvelte5 } from './svelte-version.js';
2829

2930
const allowedPluginOptions = new Set([
3031
'include',
@@ -227,6 +228,10 @@ export function resolveOptions(preResolveOptions, viteConfig, cache) {
227228
* @param {import('../types/options.d.ts').ResolvedOptions} options
228229
*/
229230
function enforceOptionsForHmr(options) {
231+
if (isSvelte5) {
232+
log.warn('svelte 5 does not support hmr api yet, disabling it for now');
233+
options.hot = false;
234+
}
230235
if (options.hot) {
231236
if (!options.compilerOptions.dev) {
232237
log.warn('hmr is enabled but compilerOptions.dev is false, forcing it to true');

packages/vite-plugin-svelte/src/utils/svelte-version.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,8 @@ import { VERSION } from 'svelte/compiler';
44
* @type {boolean}
55
*/
66
export const isSvelte3 = VERSION.startsWith('3.');
7+
8+
/**
9+
* @type {boolean}
10+
*/
11+
export const isSvelte5 = VERSION.startsWith('5.');

0 commit comments

Comments
 (0)