@sveltejs/vite-plugin-svelte 5.1.0 → 6.0.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/vite-plugin-svelte",
3
- "version": "5.1.0",
3
+ "version": "6.0.0-next.0",
4
4
  "license": "MIT",
5
5
  "author": "dominikg",
6
6
  "files": [
@@ -11,14 +11,12 @@
11
11
  "types": "types/index.d.ts",
12
12
  "exports": {
13
13
  ".": {
14
- "import": {
15
- "types": "./types/index.d.ts",
16
- "default": "./src/index.js"
17
- }
14
+ "types": "./types/index.d.ts",
15
+ "default": "./src/index.js"
18
16
  }
19
17
  },
20
18
  "engines": {
21
- "node": "^18.0.0 || ^20.0.0 || >=22"
19
+ "node": "^20.19 || ^22.12 || >=24"
22
20
  },
23
21
  "repository": {
24
22
  "type": "git",
@@ -36,22 +34,22 @@
36
34
  },
37
35
  "homepage": "https://github.com/sveltejs/vite-plugin-svelte#readme",
38
36
  "dependencies": {
39
- "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1",
40
37
  "debug": "^4.4.1",
41
38
  "deepmerge": "^4.3.1",
42
39
  "kleur": "^4.1.5",
43
40
  "magic-string": "^0.30.17",
44
- "vitefu": "^1.0.6"
41
+ "vitefu": "^1.0.6",
42
+ "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0-next.0"
45
43
  },
46
44
  "peerDependencies": {
47
45
  "svelte": "^5.0.0",
48
- "vite": "^6.0.0"
46
+ "vite": "^6.3.0 || ^7.0.0-beta.0"
49
47
  },
50
48
  "devDependencies": {
51
49
  "@types/debug": "^4.1.12",
52
- "sass": "^1.89.0",
53
- "svelte": "^5.33.3",
54
- "vite": "^6.3.5"
50
+ "sass": "^1.89.1",
51
+ "svelte": "^5.33.18",
52
+ "vite": "^7.0.0-beta.0"
55
53
  },
56
54
  "scripts": {
57
55
  "check:publint": "publint --strict",
package/src/index.js CHANGED
@@ -4,7 +4,12 @@ import { svelteInspector } from '@sveltejs/vite-plugin-svelte-inspector';
4
4
  import { handleHotUpdate } from './handle-hot-update.js';
5
5
  import { log, logCompilerWarnings } from './utils/log.js';
6
6
  import { createCompileSvelte } from './utils/compile.js';
7
- import { buildIdParser, buildModuleIdParser } from './utils/id.js';
7
+ import {
8
+ buildIdFilter,
9
+ buildIdParser,
10
+ buildModuleIdFilter,
11
+ buildModuleIdParser
12
+ } from './utils/id.js';
8
13
  import {
9
14
  buildExtraViteConfig,
10
15
  validateInlineOptions,
@@ -20,6 +25,10 @@ import { saveSvelteMetadata } from './utils/optimizer.js';
20
25
  import { VitePluginSvelteCache } from './utils/vite-plugin-svelte-cache.js';
21
26
  import { loadRaw } from './utils/load-raw.js';
22
27
  import * as svelteCompiler from 'svelte/compiler';
28
+ import { SVELTE_VIRTUAL_STYLE_ID_REGEX } from './utils/constants.js';
29
+ import * as vite from 'vite';
30
+ // @ts-expect-error rolldownVersion
31
+ const { version: viteVersion, rolldownVersion } = vite;
23
32
 
24
33
  /**
25
34
  * @param {Partial<import('./public.d.ts').Options>} [inlineOptions]
@@ -29,6 +38,12 @@ export function svelte(inlineOptions) {
29
38
  if (process.env.DEBUG != null) {
30
39
  log.setLevel('debug');
31
40
  }
41
+ if (rolldownVersion) {
42
+ log.warn.once(
43
+ `!!! Support for rolldown-vite in vite-plugin-svelte is experimental (rolldown: ${rolldownVersion}, vite: ${viteVersion}) !!!`
44
+ );
45
+ }
46
+
32
47
  validateInlineOptions(inlineOptions);
33
48
  const cache = new VitePluginSvelteCache();
34
49
  // updated in configResolved hook
@@ -42,67 +57,74 @@ export function svelte(inlineOptions) {
42
57
  let viteConfig;
43
58
  /** @type {import('./types/compile.d.ts').CompileSvelte} */
44
59
  let compileSvelte;
45
- /** @type {import('./types/plugin-api.d.ts').PluginAPI} */
46
- const api = {};
47
- /** @type {import('vite').Plugin[]} */
48
- const plugins = [
49
- {
50
- name: 'vite-plugin-svelte',
51
- // make sure our resolver runs before vite internal resolver to resolve svelte field correctly
52
- enforce: 'pre',
53
- api,
54
- async config(config, configEnv) {
55
- // setup logger
56
- if (process.env.DEBUG) {
57
- log.setLevel('debug');
58
- } else if (config.logLevel) {
59
- log.setLevel(config.logLevel);
60
- }
61
- // @ts-expect-error temporarily lend the options variable until fixed in configResolved
62
- options = await preResolveOptions(inlineOptions, config, configEnv);
63
- // extra vite config
64
- const extraViteConfig = await buildExtraViteConfig(options, config);
65
- log.debug('additional vite config', extraViteConfig, 'config');
66
- return extraViteConfig;
67
- },
68
-
69
- configEnvironment(name, config, opts) {
70
- ensureConfigEnvironmentMainFields(name, config, opts);
71
- // @ts-expect-error the function above should make `resolve.mainFields` non-nullable
72
- config.resolve.mainFields.unshift('svelte');
73
-
74
- ensureConfigEnvironmentConditions(name, config, opts);
75
- // @ts-expect-error the function above should make `resolve.conditions` non-nullable
76
- config.resolve.conditions.push('svelte');
77
- },
78
-
79
- async configResolved(config) {
80
- options = resolveOptions(options, config, cache);
81
- patchResolvedViteConfig(config, options);
82
- requestParser = buildIdParser(options);
83
- compileSvelte = createCompileSvelte();
84
- viteConfig = config;
85
- // TODO deep clone to avoid mutability from outside?
86
- api.options = options;
87
- log.debug('resolved options', options, 'config');
88
- },
89
-
90
- async buildStart() {
91
- if (!options.prebundleSvelteLibraries) return;
92
- const isSvelteMetadataChanged = await saveSvelteMetadata(viteConfig.cacheDir, options);
93
- if (isSvelteMetadataChanged) {
94
- // Force Vite to optimize again. Although we mutate the config here, it works because
95
- // Vite's optimizer runs after `buildStart()`.
96
- viteConfig.optimizeDeps.force = true;
97
- }
98
- },
99
60
 
100
- configureServer(server) {
101
- options.server = server;
102
- setupWatchers(options, cache, requestParser);
103
- },
61
+ /** @type {import('vite').Plugin} */
62
+ const compilePlugin = {
63
+ name: 'vite-plugin-svelte',
64
+ // make sure our resolver runs before vite internal resolver to resolve svelte field correctly
65
+ enforce: 'pre',
66
+ /** @type {import('./types/plugin-api.d.ts').PluginAPI} */
67
+ api: {},
68
+ async config(config, configEnv) {
69
+ // setup logger
70
+ if (process.env.DEBUG) {
71
+ log.setLevel('debug');
72
+ } else if (config.logLevel) {
73
+ log.setLevel(config.logLevel);
74
+ }
75
+ // @ts-expect-error temporarily lend the options variable until fixed in configResolved
76
+ options = await preResolveOptions(inlineOptions, config, configEnv);
77
+ // extra vite config
78
+ const extraViteConfig = await buildExtraViteConfig(options, config);
79
+ log.debug('additional vite config', extraViteConfig, 'config');
80
+ return extraViteConfig;
81
+ },
82
+
83
+ configEnvironment(name, config, opts) {
84
+ ensureConfigEnvironmentMainFields(name, config, opts);
85
+ // @ts-expect-error the function above should make `resolve.mainFields` non-nullable
86
+ config.resolve.mainFields.unshift('svelte');
87
+
88
+ ensureConfigEnvironmentConditions(name, config, opts);
89
+ // @ts-expect-error the function above should make `resolve.conditions` non-nullable
90
+ config.resolve.conditions.push('svelte');
91
+ },
92
+
93
+ async configResolved(config) {
94
+ options = resolveOptions(options, config, cache);
95
+ patchResolvedViteConfig(config, options);
96
+ const filter = buildIdFilter(options);
97
+ //@ts-expect-error transform defined below but filter not in type
98
+ compilePlugin.transform.filter = filter;
99
+ //@ts-expect-error load defined below but filter not in type
100
+ compilePlugin.load.filter = filter;
101
+
102
+ requestParser = buildIdParser(options);
103
+ compileSvelte = createCompileSvelte();
104
+ viteConfig = config;
105
+ // TODO deep clone to avoid mutability from outside?
106
+ compilePlugin.api.options = options;
107
+ log.debug('resolved options', options, 'config');
108
+ log.debug('filters', filter, 'config');
109
+ },
110
+
111
+ async buildStart() {
112
+ if (!options.prebundleSvelteLibraries) return;
113
+ const isSvelteMetadataChanged = await saveSvelteMetadata(viteConfig.cacheDir, options);
114
+ if (isSvelteMetadataChanged) {
115
+ // Force Vite to optimize again. Although we mutate the config here, it works because
116
+ // Vite's optimizer runs after `buildStart()`.
117
+ viteConfig.optimizeDeps.force = true;
118
+ }
119
+ },
104
120
 
105
- async load(id, opts) {
121
+ configureServer(server) {
122
+ options.server = server;
123
+ setupWatchers(options, cache, requestParser);
124
+ },
125
+
126
+ load: {
127
+ async handler(id, opts) {
106
128
  const ssr = !!opts?.ssr;
107
129
  const svelteRequest = requestParser(id, !!ssr);
108
130
  if (svelteRequest) {
@@ -127,6 +149,8 @@ export function svelte(inlineOptions) {
127
149
  css.meta.vite ??= {};
128
150
  css.meta.vite.cssScopeTo = [svelteRequest.filename, 'default'];
129
151
  }
152
+ css.moduleType = 'css';
153
+
130
154
  return css;
131
155
  }
132
156
  }
@@ -137,30 +161,23 @@ export function svelte(inlineOptions) {
137
161
  }
138
162
  }
139
163
  }
140
- },
164
+ }
165
+ },
141
166
 
142
- async resolveId(importee, importer, opts) {
143
- const ssr = !!opts?.ssr;
144
- const svelteRequest = requestParser(importee, ssr);
145
- if (svelteRequest?.query.svelte) {
146
- if (
147
- svelteRequest.query.type === 'style' &&
148
- !svelteRequest.raw &&
149
- !svelteRequest.query.inline
150
- ) {
151
- // return cssId with root prefix so postcss pipeline of vite finds the directory correctly
152
- // see https://github.com/sveltejs/vite-plugin-svelte/issues/14
153
- log.debug(
154
- `resolveId resolved virtual css module ${svelteRequest.cssId}`,
155
- undefined,
156
- 'resolve'
157
- );
158
- return svelteRequest.cssId;
159
- }
160
- }
161
- },
167
+ resolveId: {
168
+ // we don't use our generic filter here but a reduced one that only matches our virtual css
169
+ filter: { id: SVELTE_VIRTUAL_STYLE_ID_REGEX },
170
+ handler(id) {
171
+ // return cssId with root prefix so postcss pipeline of vite finds the directory correctly
172
+ // see https://github.com/sveltejs/vite-plugin-svelte/issues/14
173
+ log.debug(`resolveId resolved virtual css module ${id}`, undefined, 'resolve');
174
+ // TODO: do we have to repeat the dance for constructing the virtual id here? our transform added it that way
175
+ return id;
176
+ }
177
+ },
162
178
 
163
- async transform(code, id, opts) {
179
+ transform: {
180
+ async handler(code, id, opts) {
164
181
  const ssr = !!opts?.ssr;
165
182
  const svelteRequest = requestParser(id, ssr);
166
183
  if (!svelteRequest || svelteRequest.query.type === 'style' || svelteRequest.raw) {
@@ -188,34 +205,41 @@ export function svelte(inlineOptions) {
188
205
  }
189
206
  return {
190
207
  ...compileData.compiled.js,
208
+ moduleType: 'js',
191
209
  meta: {
192
210
  vite: {
193
211
  lang: compileData.lang
194
212
  }
195
213
  }
196
214
  };
197
- },
215
+ }
216
+ },
198
217
 
199
- handleHotUpdate(ctx) {
200
- if (!options.compilerOptions.hmr || !options.emitCss) {
201
- return;
202
- }
203
- const svelteRequest = requestParser(ctx.file, false, ctx.timestamp);
204
- if (svelteRequest) {
205
- return handleHotUpdate(compileSvelte, ctx, svelteRequest, cache, options);
206
- }
207
- },
208
- async buildEnd() {
209
- await options.stats?.finishAll();
218
+ handleHotUpdate(ctx) {
219
+ if (!options.compilerOptions.hmr || !options.emitCss) {
220
+ return;
221
+ }
222
+ const svelteRequest = requestParser(ctx.file, false, ctx.timestamp);
223
+ if (svelteRequest) {
224
+ return handleHotUpdate(compileSvelte, ctx, svelteRequest, cache, options);
210
225
  }
211
226
  },
212
- {
213
- name: 'vite-plugin-svelte-module',
214
- enforce: 'post',
215
- async configResolved() {
216
- moduleRequestParser = buildModuleIdParser(options);
217
- },
218
- async transform(code, id, opts) {
227
+ async buildEnd() {
228
+ await options.stats?.finishAll();
229
+ }
230
+ };
231
+
232
+ /** @type {import('vite').Plugin} */
233
+ const moduleCompilePlugin = {
234
+ name: 'vite-plugin-svelte-module',
235
+ enforce: 'post',
236
+ async configResolved() {
237
+ //@ts-expect-error transform defined below but filter not in type
238
+ moduleCompilePlugin.transform.filter = buildModuleIdFilter(options);
239
+ moduleRequestParser = buildModuleIdParser(options);
240
+ },
241
+ transform: {
242
+ async handler(code, id, opts) {
219
243
  const ssr = !!opts?.ssr;
220
244
  const moduleRequest = moduleRequestParser(id, ssr);
221
245
  if (!moduleRequest) {
@@ -233,9 +257,11 @@ export function svelte(inlineOptions) {
233
257
  throw toRollupError(e, options);
234
258
  }
235
259
  }
236
- },
237
- svelteInspector()
238
- ];
260
+ }
261
+ };
262
+
263
+ /** @type {import('vite').Plugin[]} */
264
+ const plugins = [compilePlugin, moduleCompilePlugin, svelteInspector()];
239
265
  return plugins;
240
266
  }
241
267
 
package/src/preprocess.js CHANGED
@@ -1,7 +1,16 @@
1
1
  import process from 'node:process';
2
- import { isCSSRequest, preprocessCSS, resolveConfig, transformWithEsbuild } from 'vite';
2
+ import * as vite from 'vite';
3
3
  import { mapToRelative, removeLangSuffix } from './utils/sourcemaps.js';
4
-
4
+ const {
5
+ isCSSRequest,
6
+ preprocessCSS,
7
+ resolveConfig,
8
+ transformWithEsbuild,
9
+ //@ts-expect-error rolldown types don't exist
10
+ rolldownVersion,
11
+ //@ts-expect-error rolldown types don't exist
12
+ transformWithOxc
13
+ } = vite;
5
14
  /**
6
15
  * @typedef {(code: string, filename: string) => Promise<{ code: string; map?: any; deps?: Set<string> }>} CssTransform
7
16
  */
@@ -18,7 +27,7 @@ export function vitePreprocess(opts) {
18
27
  /** @type {import('svelte/compiler').PreprocessorGroup} */
19
28
  const preprocessor = { name: 'vite-preprocess' };
20
29
  if (opts?.script === true) {
21
- preprocessor.script = viteScript().script;
30
+ preprocessor.script = rolldownVersion ? viteScriptOxc().script : viteScript().script;
22
31
  }
23
32
  if (opts?.style !== false) {
24
33
  const styleOpts = typeof opts?.style == 'object' ? opts?.style : undefined;
@@ -57,6 +66,37 @@ function viteScript() {
57
66
  };
58
67
  }
59
68
 
69
+ /**
70
+ * @returns {{ script: import('svelte/compiler').Preprocessor }}
71
+ */
72
+ function viteScriptOxc() {
73
+ return {
74
+ async script({ attributes, content, filename = '' }) {
75
+ const lang = /** @type {string} */ (attributes.lang);
76
+ if (!supportedScriptLangs.includes(lang)) return;
77
+ const { code, map } = await transformWithOxc(content, filename, {
78
+ lang,
79
+ target: 'esnext'
80
+ // TODO, how to pass tsconfig compilerOptions (or not needed as config is loaded for file
81
+ /*tsconfigRaw: {
82
+ compilerOptions: {
83
+ // svelte typescript needs this flag to work with type imports
84
+ importsNotUsedAsValues: 'preserve',
85
+ preserveValueImports: true
86
+ }
87
+ }*/
88
+ });
89
+
90
+ mapToRelative(map, filename);
91
+
92
+ return {
93
+ code,
94
+ map
95
+ };
96
+ }
97
+ };
98
+ }
99
+
60
100
  /**
61
101
  * @param {import('vite').ResolvedConfig | import('vite').InlineConfig} config
62
102
  * @returns {{ style: import('svelte/compiler').Preprocessor }}
package/src/public.d.ts CHANGED
@@ -22,14 +22,14 @@ export interface PluginOptions {
22
22
  *
23
23
  * @see https://github.com/micromatch/picomatch
24
24
  */
25
- include?: Arrayable<string>;
25
+ include?: Arrayable<string | RegExp>;
26
26
  /**
27
27
  * A `picomatch` pattern, or array of patterns, which specifies the files to be ignored by the
28
28
  * plugin. By default, no files are ignored.
29
29
  *
30
30
  * @see https://github.com/micromatch/picomatch
31
31
  */
32
- exclude?: Arrayable<string>;
32
+ exclude?: Arrayable<string | RegExp>;
33
33
  /**
34
34
  * Emit Svelte styles as virtual CSS files for Vite and other plugins to process
35
35
  *
@@ -187,8 +187,8 @@ interface CompileModuleOptions {
187
187
  * @default ['.ts','.js']
188
188
  */
189
189
  extensions?: string[];
190
- include?: Arrayable<string>;
191
- exclude?: Arrayable<string>;
190
+ include?: Arrayable<string | RegExp>;
191
+ exclude?: Arrayable<string | RegExp>;
192
192
  }
193
193
 
194
194
  type Arrayable<T> = T | T[];
@@ -14,6 +14,7 @@ export interface Code {
14
14
  map?: any;
15
15
  dependencies?: any[];
16
16
  hasGlobal?: boolean;
17
+ moduleType?: string; //rolldown-vite
17
18
  meta?: {
18
19
  vite?: CustomPluginOptionsVite;
19
20
  };
package/src/types/id.d.ts CHANGED
@@ -39,6 +39,13 @@ export interface SvelteModuleRequest {
39
39
  }
40
40
 
41
41
  export type IdParser = (id: string, ssr: boolean, timestamp?: number) => SvelteRequest | undefined;
42
+
43
+ export type IdFilter = {
44
+ id: {
45
+ include: Array<string | RegExp>;
46
+ exclude: Array<string | RegExp>;
47
+ };
48
+ };
42
49
  export type ModuleIdParser = (
43
50
  id: string,
44
51
  ssr: boolean,
@@ -29,3 +29,8 @@ export const FAQ_LINK_MISSING_EXPORTS_CONDITION =
29
29
  export const DEFAULT_SVELTE_EXT = ['.svelte'];
30
30
  export const DEFAULT_SVELTE_MODULE_INFIX = ['.svelte.'];
31
31
  export const DEFAULT_SVELTE_MODULE_EXT = ['.js', '.ts'];
32
+
33
+ export const SVELTE_VIRTUAL_STYLE_SUFFIX = '?svelte&type=style&lang.css';
34
+ export const SVELTE_VIRTUAL_STYLE_ID_REGEX = new RegExp(
35
+ `${SVELTE_VIRTUAL_STYLE_SUFFIX.replace(/[?.]/g, '\\$&')}$`
36
+ );
package/src/utils/id.js CHANGED
@@ -1,9 +1,13 @@
1
- import { createFilter, normalizePath } from 'vite';
1
+ import { normalizePath } from 'vite';
2
2
  import fs from 'node:fs';
3
- import path from 'node:path';
4
3
  import process from 'node:process';
5
4
  import { log } from './log.js';
6
- import { DEFAULT_SVELTE_MODULE_EXT, DEFAULT_SVELTE_MODULE_INFIX } from './constants.js';
5
+ import {
6
+ DEFAULT_SVELTE_EXT,
7
+ DEFAULT_SVELTE_MODULE_EXT,
8
+ DEFAULT_SVELTE_MODULE_INFIX
9
+ } from './constants.js';
10
+ import { arraify } from './options.js';
7
11
 
8
12
  const VITE_FS_PREFIX = '/@fs/';
9
13
  const IS_WINDOWS = process.platform === 'win32';
@@ -154,34 +158,34 @@ function stripRoot(normalizedFilename, normalizedRoot) {
154
158
  }
155
159
 
156
160
  /**
157
- * @param {import('../public.d.ts').Options['include'] | undefined} include
158
- * @param {import('../public.d.ts').Options['exclude'] | undefined} exclude
159
- * @param {string[]} extensions
160
- * @returns {(filename: string) => boolean}
161
+ *
162
+ * @param {string} s
163
+ * @returns {string}
161
164
  */
162
- function buildFilter(include, exclude, extensions) {
163
- const rollupFilter = createFilter(include, exclude);
164
- return (filename) => rollupFilter(filename) && extensions.some((ext) => filename.endsWith(ext));
165
+ function escapeRE(s) {
166
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
165
167
  }
166
168
 
167
169
  /**
168
- * @param {import('../public.d.ts').Options['include'] | undefined} include
169
- * @param {import('../public.d.ts').Options['exclude'] | undefined} exclude
170
- * @param {string[]} infixes
171
- * @param {string[]} extensions
172
- * @returns {(filename: string) => boolean}
170
+ * @param {import('../types/options.d.ts').ResolvedOptions} options
171
+ * @returns {import('../types/id.d.ts').IdFilter}
173
172
  */
174
- function buildModuleFilter(include, exclude, infixes, extensions) {
175
- const rollupFilter = createFilter(include, exclude);
176
- return (filename) => {
177
- const basename = path.basename(filename);
178
-
179
- return (
180
- rollupFilter(filename) &&
181
- infixes.some((infix) => basename.includes(infix)) &&
182
- extensions.some((ext) => basename.endsWith(ext))
183
- );
173
+ export function buildIdFilter(options) {
174
+ const { include = [], exclude = [], extensions = DEFAULT_SVELTE_EXT } = options;
175
+ // this regex combines configured extensions and looks for them at the end of the string or directly before first ? or #
176
+ const extensionsRE = new RegExp(
177
+ `^[^?#]+\\.(?:${extensions
178
+ .map((e) => (e.startsWith('.') ? e.slice(1) : e))
179
+ .map(escapeRE)
180
+ .join('|')})(?:[?#]|$)`
181
+ );
182
+ const filter = {
183
+ id: {
184
+ include: [extensionsRE, .../**@type {Array<string|RegExp>}*/ arraify(include)],
185
+ exclude: /**@type {Array<string|RegExp>}*/ arraify(exclude)
186
+ }
184
187
  };
188
+ return filter;
185
189
  }
186
190
 
187
191
  /**
@@ -189,36 +193,50 @@ function buildModuleFilter(include, exclude, infixes, extensions) {
189
193
  * @returns {import('../types/id.d.ts').IdParser}
190
194
  */
191
195
  export function buildIdParser(options) {
192
- const { include, exclude, extensions, root } = options;
193
- const normalizedRoot = normalizePath(root);
194
- const filter = buildFilter(include, exclude, extensions ?? []);
196
+ const normalizedRoot = normalizePath(options.root);
195
197
  return (id, ssr, timestamp = Date.now()) => {
196
198
  const { filename, rawQuery } = splitId(id);
197
- if (filter(filename)) {
198
- return parseToSvelteRequest(id, filename, rawQuery, normalizedRoot, timestamp, ssr);
199
- }
199
+ return parseToSvelteRequest(id, filename, rawQuery, normalizedRoot, timestamp, ssr);
200
200
  };
201
201
  }
202
202
 
203
203
  /**
204
204
  * @param {import('../types/options.d.ts').ResolvedOptions} options
205
- * @returns {import('../types/id.d.ts').ModuleIdParser}
205
+ * @returns {import('../types/id.d.ts').IdFilter}
206
206
  */
207
- export function buildModuleIdParser(options) {
207
+ export function buildModuleIdFilter(options) {
208
208
  const {
209
- include,
210
- exclude,
211
209
  infixes = DEFAULT_SVELTE_MODULE_INFIX,
210
+ include = [],
211
+ exclude = [],
212
212
  extensions = DEFAULT_SVELTE_MODULE_EXT
213
- } = options?.experimental?.compileModule ?? {};
213
+ } = options.experimental?.compileModule ?? {};
214
+ // this regex combines configured infixes and extensions
215
+ // and looks for them at the end of the string or directly before first ? or #
216
+ const infixWithExtRE = new RegExp(
217
+ `^[^?#]+(?:${infixes.map(escapeRE).join('|')})(?:[^.\\\\/]+\\.)*(?:${extensions
218
+ .map((e) => (e.startsWith('.') ? e.slice(1) : e))
219
+ .map(escapeRE)
220
+ .join('|')})(?:[?#]|$)`
221
+ );
222
+ return {
223
+ id: {
224
+ include: [infixWithExtRE, .../**@type {Array<string|RegExp>}*/ arraify(include)],
225
+ exclude: /**@type {Array<string|RegExp>}*/ arraify(exclude)
226
+ }
227
+ };
228
+ }
229
+
230
+ /**
231
+ * @param {import('../types/options.d.ts').ResolvedOptions} options
232
+ * @returns {import('../types/id.d.ts').ModuleIdParser}
233
+ */
234
+ export function buildModuleIdParser(options) {
214
235
  const root = options.root;
215
236
  const normalizedRoot = normalizePath(root);
216
- const filter = buildModuleFilter(include, exclude, infixes, extensions);
217
237
  return (id, ssr, timestamp = Date.now()) => {
218
238
  const { filename, rawQuery } = splitId(id);
219
- if (filter(filename)) {
220
- return parseToSvelteModuleRequest(id, filename, rawQuery, normalizedRoot, timestamp, ssr);
221
- }
239
+ return parseToSvelteModuleRequest(id, filename, rawQuery, normalizedRoot, timestamp, ssr);
222
240
  };
223
241
  }
224
242
 
@@ -1,7 +1,7 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import * as svelte from 'svelte/compiler';
3
3
  import { log } from './log.js';
4
- import { toESBuildError } from './error.js';
4
+ import { toESBuildError, toRollupError } from './error.js';
5
5
  import { safeBase64Hash } from './hash.js';
6
6
  import { normalize } from './id.js';
7
7
 
@@ -9,42 +9,94 @@ import { normalize } from './id.js';
9
9
  * @typedef {NonNullable<import('vite').DepOptimizationOptions['esbuildOptions']>} EsbuildOptions
10
10
  * @typedef {NonNullable<EsbuildOptions['plugins']>[number]} EsbuildPlugin
11
11
  */
12
+ /**
13
+ * @typedef {NonNullable<import('vite').Rollup.Plugin>} RollupPlugin
14
+ */
12
15
 
13
- export const facadeEsbuildSveltePluginName = 'vite-plugin-svelte:facade';
14
- export const facadeEsbuildSvelteModulePluginName = 'vite-plugin-svelte-module:facade';
16
+ export const optimizeSveltePluginName = 'vite-plugin-svelte:optimize';
17
+ export const optimizeSvelteModulePluginName = 'vite-plugin-svelte-module:optimize';
15
18
 
16
19
  /**
20
+ * @param {EsbuildPlugin} plugin
17
21
  * @param {import('../types/options.d.ts').ResolvedOptions} options
18
- * @returns {EsbuildPlugin}
19
22
  */
20
- export function esbuildSveltePlugin(options) {
21
- return {
22
- name: 'vite-plugin-svelte:optimize-svelte',
23
- setup(build) {
24
- // Skip in scanning phase as Vite already handles scanning Svelte files.
25
- // Otherwise this would heavily slow down the scanning phase.
26
- if (build.initialOptions.plugins?.some((v) => v.name === 'vite:dep-scan')) return;
23
+ export function patchESBuildOptimizerPlugin(plugin, options) {
24
+ const components = plugin.name === optimizeSveltePluginName;
25
+ const compileFn = components ? compileSvelte : compileSvelteModule;
26
+ const statsName = components ? 'prebundle library components' : 'prebundle library modules';
27
+ const filter = components ? /\.svelte(?:\?.*)?$/ : /\.svelte\.[jt]s(?:\?.*)?$/;
28
+ plugin.setup = (build) => {
29
+ if (build.initialOptions.plugins?.some((v) => v.name === 'vite:dep-scan')) return;
27
30
 
28
- const filter = /\.svelte(?:\?.*)?$/;
29
- /** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */
30
- let statsCollection;
31
- build.onStart(() => {
32
- statsCollection = options.stats?.startCollection('prebundle library components', {
33
- logResult: (c) => c.stats.length > 1
34
- });
31
+ /** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */
32
+ let statsCollection;
33
+ build.onStart(() => {
34
+ statsCollection = options.stats?.startCollection(statsName, {
35
+ logResult: (c) => c.stats.length > 1
35
36
  });
36
- build.onLoad({ filter }, async ({ path: filename }) => {
37
- const code = readFileSync(filename, 'utf8');
38
- try {
39
- const contents = await compileSvelte(options, { filename, code }, statsCollection);
40
- return { contents };
41
- } catch (e) {
42
- return { errors: [toESBuildError(e, options)] };
37
+ });
38
+ build.onLoad({ filter }, async ({ path: filename }) => {
39
+ const code = readFileSync(filename, 'utf8');
40
+ try {
41
+ const result = await compileFn(options, { filename, code }, statsCollection);
42
+ const contents = result.map
43
+ ? result.code + '//# sourceMappingURL=' + result.map.toUrl()
44
+ : result.code;
45
+ return { contents };
46
+ } catch (e) {
47
+ return { errors: [toESBuildError(e, options)] };
48
+ }
49
+ });
50
+ build.onEnd(() => {
51
+ statsCollection?.finish();
52
+ });
53
+ };
54
+ }
55
+
56
+ /**
57
+ * @param {RollupPlugin} plugin
58
+ * @param {import('../types/options.d.ts').ResolvedOptions} options
59
+ */
60
+ export function patchRolldownOptimizerPlugin(plugin, options) {
61
+ const components = plugin.name === optimizeSveltePluginName;
62
+ const compileFn = components ? compileSvelte : compileSvelteModule;
63
+ const statsName = components ? 'prebundle library components' : 'prebundle library modules';
64
+ const includeRe = components ? /^[^?#]+\.svelte(?:[?#]|$)/ : /^[^?#]+\.svelte\.[jt]s(?:[?#]|$)/;
65
+ /** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */
66
+ let statsCollection;
67
+
68
+ plugin.options = (opts) => {
69
+ // @ts-expect-error plugins is an array here
70
+ const isScanner = opts.plugins.some(
71
+ (/** @type {{ name: string; }} */ p) => p.name === 'vite:dep-scan:resolve'
72
+ );
73
+ if (isScanner) {
74
+ delete plugin.buildStart;
75
+ delete plugin.transform;
76
+ delete plugin.buildEnd;
77
+ } else {
78
+ plugin.transform = {
79
+ filter: { id: includeRe },
80
+ /**
81
+ * @param {string} code
82
+ * @param {string} filename
83
+ */
84
+ async handler(code, filename) {
85
+ try {
86
+ return await compileFn(options, { filename, code }, statsCollection);
87
+ } catch (e) {
88
+ throw toRollupError(e, options);
89
+ }
43
90
  }
44
- });
45
- build.onEnd(() => {
91
+ };
92
+ plugin.buildStart = () => {
93
+ statsCollection = options.stats?.startCollection(statsName, {
94
+ logResult: (c) => c.stats.length > 1
95
+ });
96
+ };
97
+ plugin.buildEnd = () => {
46
98
  statsCollection?.finish();
47
- });
99
+ };
48
100
  }
49
101
  };
50
102
  }
@@ -53,7 +105,7 @@ export function esbuildSveltePlugin(options) {
53
105
  * @param {import('../types/options.d.ts').ResolvedOptions} options
54
106
  * @param {{ filename: string, code: string }} input
55
107
  * @param {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection} [statsCollection]
56
- * @returns {Promise<string>}
108
+ * @returns {Promise<import('../types/compile.d.ts').Code>}
57
109
  */
58
110
  async function compileSvelte(options, { filename, code }, statsCollection) {
59
111
  let css = options.compilerOptions.css;
@@ -114,44 +166,9 @@ async function compileSvelte(options, { filename, code }, statsCollection) {
114
166
  if (endStat) {
115
167
  endStat();
116
168
  }
117
- return compiled.js.map
118
- ? compiled.js.code + '//# sourceMappingURL=' + compiled.js.map.toUrl()
119
- : compiled.js.code;
120
- }
121
-
122
- /**
123
- * @param {import('../types/options.d.ts').ResolvedOptions} options
124
- * @returns {EsbuildPlugin}
125
- */
126
- export function esbuildSvelteModulePlugin(options) {
127
169
  return {
128
- name: 'vite-plugin-svelte-module:optimize-svelte',
129
- setup(build) {
130
- // Skip in scanning phase as Vite already handles scanning Svelte files.
131
- // Otherwise this would heavily slow down the scanning phase.
132
- if (build.initialOptions.plugins?.some((v) => v.name === 'vite:dep-scan')) return;
133
-
134
- const filter = /\.svelte\.[jt]s(?:\?.*)?$/;
135
- /** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */
136
- let statsCollection;
137
- build.onStart(() => {
138
- statsCollection = options.stats?.startCollection('prebundle library modules', {
139
- logResult: (c) => c.stats.length > 1
140
- });
141
- });
142
- build.onLoad({ filter }, async ({ path: filename }) => {
143
- const code = readFileSync(filename, 'utf8');
144
- try {
145
- const contents = await compileSvelteModule(options, { filename, code }, statsCollection);
146
- return { contents };
147
- } catch (e) {
148
- return { errors: [toESBuildError(e, options)] };
149
- }
150
- });
151
- build.onEnd(() => {
152
- statsCollection?.finish();
153
- });
154
- }
170
+ ...compiled.js,
171
+ moduleType: 'js'
155
172
  };
156
173
  }
157
174
 
@@ -159,7 +176,7 @@ export function esbuildSvelteModulePlugin(options) {
159
176
  * @param {import('../types/options.d.ts').ResolvedOptions} options
160
177
  * @param {{ filename: string; code: string }} input
161
178
  * @param {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection} [statsCollection]
162
- * @returns {Promise<string>}
179
+ * @returns {Promise<import('../types/compile.d.ts').Code>}
163
180
  */
164
181
  async function compileSvelteModule(options, { filename, code }, statsCollection) {
165
182
  const endStat = statsCollection?.start(filename);
@@ -171,7 +188,8 @@ async function compileSvelteModule(options, { filename, code }, statsCollection)
171
188
  if (endStat) {
172
189
  endStat();
173
190
  }
174
- return compiled.js.map
175
- ? compiled.js.code + '//# sourceMappingURL=' + compiled.js.map.toUrl()
176
- : compiled.js.code;
191
+ return {
192
+ ...compiled.js,
193
+ moduleType: 'js'
194
+ };
177
195
  }
@@ -1,11 +1,14 @@
1
1
  import process from 'node:process';
2
- import {
2
+ import * as vite from 'vite';
3
+ const {
3
4
  defaultClientMainFields,
4
5
  defaultServerMainFields,
5
6
  defaultClientConditions,
6
7
  defaultServerConditions,
7
- normalizePath
8
- } from 'vite';
8
+ normalizePath,
9
+ //@ts-expect-error rolldownVersion not in type
10
+ rolldownVersion
11
+ } = vite;
9
12
  import { isDebugNamespaceEnabled, log } from './log.js';
10
13
  import { loadSvelteConfig } from './load-svelte-config.js';
11
14
  import {
@@ -18,11 +21,11 @@ import {
18
21
 
19
22
  import path from 'node:path';
20
23
  import {
21
- esbuildSvelteModulePlugin,
22
- esbuildSveltePlugin,
23
- facadeEsbuildSvelteModulePluginName,
24
- facadeEsbuildSveltePluginName
25
- } from './esbuild.js';
24
+ optimizeSvelteModulePluginName,
25
+ optimizeSveltePluginName,
26
+ patchESBuildOptimizerPlugin,
27
+ patchRolldownOptimizerPlugin
28
+ } from './optimizer-plugins.js';
26
29
  import { addExtraPreprocessors } from './preprocess.js';
27
30
  import deepmerge from 'deepmerge';
28
31
  import {
@@ -386,17 +389,39 @@ export async function buildExtraViteConfig(options, config) {
386
389
  extraViteConfig.optimizeDeps = {
387
390
  ...extraViteConfig.optimizeDeps,
388
391
  // Experimental Vite API to allow these extensions to be scanned and prebundled
389
- extensions: options.extensions ?? ['.svelte'],
390
- // Add esbuild plugin to prebundle Svelte files.
391
- // Currently a placeholder as more information is needed after Vite config is resolved,
392
- // the real Svelte plugin is added in `patchResolvedViteConfig()`
393
- esbuildOptions: {
392
+ extensions: options.extensions ?? ['.svelte']
393
+ };
394
+ // Add optimizer plugins to prebundle Svelte files.
395
+ // Currently a placeholder as more information is needed after Vite config is resolved,
396
+ // the added plugins are patched in `patchResolvedViteConfig()`
397
+ if (rolldownVersion) {
398
+ /**
399
+ *
400
+ * @param {string} name
401
+ * @returns {import('vite').Rollup.Plugin}
402
+ */
403
+ const placeholderRolldownOptimizerPlugin = (name) => ({
404
+ name,
405
+ options() {},
406
+ buildStart() {},
407
+ buildEnd() {},
408
+ transform: { filter: { id: /^$/ }, handler() {} }
409
+ });
410
+ //@ts-expect-error rolldown types not finished
411
+ extraViteConfig.optimizeDeps.rollupOptions = {
394
412
  plugins: [
395
- { name: facadeEsbuildSveltePluginName, setup: () => {} },
396
- { name: facadeEsbuildSvelteModulePluginName, setup: () => {} }
413
+ placeholderRolldownOptimizerPlugin(optimizeSveltePluginName),
414
+ placeholderRolldownOptimizerPlugin(optimizeSvelteModulePluginName)
397
415
  ]
398
- }
399
- };
416
+ };
417
+ } else {
418
+ extraViteConfig.optimizeDeps.esbuildOptions = {
419
+ plugins: [
420
+ { name: optimizeSveltePluginName, setup: () => {} },
421
+ { name: optimizeSvelteModulePluginName, setup: () => {} }
422
+ ]
423
+ };
424
+ }
400
425
  }
401
426
 
402
427
  // enable hmrPartialAccept if not explicitly disabled
@@ -594,19 +619,23 @@ export function patchResolvedViteConfig(viteConfig, options) {
594
619
  }
595
620
  }
596
621
  }
597
-
598
- // replace facade esbuild plugin with a real one
599
- const facadeEsbuildSveltePlugin = viteConfig.optimizeDeps.esbuildOptions?.plugins?.find(
600
- (plugin) => plugin.name === facadeEsbuildSveltePluginName
601
- );
602
- if (facadeEsbuildSveltePlugin) {
603
- Object.assign(facadeEsbuildSveltePlugin, esbuildSveltePlugin(options));
604
- }
605
- const facadeEsbuildSvelteModulePlugin = viteConfig.optimizeDeps.esbuildOptions?.plugins?.find(
606
- (plugin) => plugin.name === facadeEsbuildSvelteModulePluginName
607
- );
608
- if (facadeEsbuildSvelteModulePlugin) {
609
- Object.assign(facadeEsbuildSvelteModulePlugin, esbuildSvelteModulePlugin(options));
622
+ if (rolldownVersion) {
623
+ const plugins =
624
+ // @ts-expect-error not typed
625
+ viteConfig.optimizeDeps.rollupOptions?.plugins?.filter((p) =>
626
+ [optimizeSveltePluginName, optimizeSvelteModulePluginName].includes(p.name)
627
+ ) ?? [];
628
+ for (const plugin of plugins) {
629
+ patchRolldownOptimizerPlugin(plugin, options);
630
+ }
631
+ } else {
632
+ const plugins =
633
+ viteConfig.optimizeDeps.esbuildOptions?.plugins?.filter((p) =>
634
+ [optimizeSveltePluginName, optimizeSvelteModulePluginName].includes(p.name)
635
+ ) ?? [];
636
+ for (const plugin of plugins) {
637
+ patchESBuildOptimizerPlugin(plugin, options);
638
+ }
610
639
  }
611
640
  }
612
641
 
@@ -647,9 +676,9 @@ export function ensureConfigEnvironmentConditions(name, config, opts) {
647
676
 
648
677
  /**
649
678
  * @template T
650
- * @param {T | T[]} value
679
+ * @param {T | T[] | null | undefined } value
651
680
  * @returns {T[]}
652
681
  */
653
- function arraify(value) {
654
- return Array.isArray(value) ? value : [value];
682
+ export function arraify(value) {
683
+ return value == null ? [] : Array.isArray(value) ? value : [value];
655
684
  }
package/types/index.d.ts CHANGED
@@ -22,14 +22,14 @@ declare module '@sveltejs/vite-plugin-svelte' {
22
22
  *
23
23
  * @see https://github.com/micromatch/picomatch
24
24
  */
25
- include?: Arrayable<string>;
25
+ include?: Arrayable<string | RegExp>;
26
26
  /**
27
27
  * A `picomatch` pattern, or array of patterns, which specifies the files to be ignored by the
28
28
  * plugin. By default, no files are ignored.
29
29
  *
30
30
  * @see https://github.com/micromatch/picomatch
31
31
  */
32
- exclude?: Arrayable<string>;
32
+ exclude?: Arrayable<string | RegExp>;
33
33
  /**
34
34
  * Emit Svelte styles as virtual CSS files for Vite and other plugins to process
35
35
  *
@@ -187,8 +187,8 @@ declare module '@sveltejs/vite-plugin-svelte' {
187
187
  * @default ['.ts','.js']
188
188
  */
189
189
  extensions?: string[];
190
- include?: Arrayable<string>;
191
- exclude?: Arrayable<string>;
190
+ include?: Arrayable<string | RegExp>;
191
+ exclude?: Arrayable<string | RegExp>;
192
192
  }
193
193
 
194
194
  type Arrayable<T> = T | T[];
@@ -26,6 +26,6 @@
26
26
  null,
27
27
  null
28
28
  ],
29
- "mappings": ";;;;aAIYA,OAAOA;;WAETC,mBAAmBA;;;;;;;;;;;kBAWZC,aAAaA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAgGbC,YAAYA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAiDnBC,mBAAmBA;;;;;;;;;;;;;;;;WAgBnBC,oBAAoBA;;;;;;;;;;;;;;;MAezBC,SAASA;;kBAEGC,qBAAqBA;;;;;;;;;;;;;iBCxKtBC,MAAMA;iBCXNC,cAAcA;iBCgBRC,gBAAgBA",
29
+ "mappings": ";;;;aAIYA,OAAOA;;WAETC,mBAAmBA;;;;;;;;;;;kBAWZC,aAAaA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAgGbC,YAAYA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAiDnBC,mBAAmBA;;;;;;;;;;;;;;;;WAgBnBC,oBAAoBA;;;;;;;;;;;;;;;MAezBC,SAASA;;kBAEGC,qBAAqBA;;;;;;;;;;;;;iBC/JtBC,MAAMA;iBCXNC,cAAcA;iBCORC,gBAAgBA",
30
30
  "ignoreList": []
31
31
  }