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

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.1",
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",
37
+ "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0-next.0",
40
38
  "debug": "^4.4.1",
41
39
  "deepmerge": "^4.3.1",
42
40
  "kleur": "^4.1.5",
43
41
  "magic-string": "^0.30.17",
44
- "vitefu": "^1.0.6"
42
+ "vitefu": "^1.0.7"
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.2",
51
+ "svelte": "^5.34.7",
52
+ "vite": "^7.0.0-beta.2"
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,16 @@ 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
+ See https://github.com/sveltejs/vite-plugin-svelte/issues/1143 for a list of known issues and to report feedback.`.replace(
45
+ /\t+/g,
46
+ '\t'
47
+ )
48
+ );
49
+ }
50
+
32
51
  validateInlineOptions(inlineOptions);
33
52
  const cache = new VitePluginSvelteCache();
34
53
  // updated in configResolved hook
@@ -42,67 +61,74 @@ export function svelte(inlineOptions) {
42
61
  let viteConfig;
43
62
  /** @type {import('./types/compile.d.ts').CompileSvelte} */
44
63
  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
64
 
100
- configureServer(server) {
101
- options.server = server;
102
- setupWatchers(options, cache, requestParser);
103
- },
65
+ /** @type {import('vite').Plugin} */
66
+ const compilePlugin = {
67
+ name: 'vite-plugin-svelte',
68
+ // make sure our resolver runs before vite internal resolver to resolve svelte field correctly
69
+ enforce: 'pre',
70
+ /** @type {import('./types/plugin-api.d.ts').PluginAPI} */
71
+ api: {},
72
+ async config(config, configEnv) {
73
+ // setup logger
74
+ if (process.env.DEBUG) {
75
+ log.setLevel('debug');
76
+ } else if (config.logLevel) {
77
+ log.setLevel(config.logLevel);
78
+ }
79
+ // @ts-expect-error temporarily lend the options variable until fixed in configResolved
80
+ options = await preResolveOptions(inlineOptions, config, configEnv);
81
+ // extra vite config
82
+ const extraViteConfig = await buildExtraViteConfig(options, config);
83
+ log.debug('additional vite config', extraViteConfig, 'config');
84
+ return extraViteConfig;
85
+ },
86
+
87
+ configEnvironment(name, config, opts) {
88
+ ensureConfigEnvironmentMainFields(name, config, opts);
89
+ // @ts-expect-error the function above should make `resolve.mainFields` non-nullable
90
+ config.resolve.mainFields.unshift('svelte');
91
+
92
+ ensureConfigEnvironmentConditions(name, config, opts);
93
+ // @ts-expect-error the function above should make `resolve.conditions` non-nullable
94
+ config.resolve.conditions.push('svelte');
95
+ },
96
+
97
+ async configResolved(config) {
98
+ options = resolveOptions(options, config, cache);
99
+ patchResolvedViteConfig(config, options);
100
+ const filter = buildIdFilter(options);
101
+ //@ts-expect-error transform defined below but filter not in type
102
+ compilePlugin.transform.filter = filter;
103
+ //@ts-expect-error load defined below but filter not in type
104
+ compilePlugin.load.filter = filter;
105
+
106
+ requestParser = buildIdParser(options);
107
+ compileSvelte = createCompileSvelte();
108
+ viteConfig = config;
109
+ // TODO deep clone to avoid mutability from outside?
110
+ compilePlugin.api.options = options;
111
+ log.debug('resolved options', options, 'config');
112
+ log.debug('filters', filter, 'config');
113
+ },
114
+
115
+ async buildStart() {
116
+ if (!options.prebundleSvelteLibraries) return;
117
+ const isSvelteMetadataChanged = await saveSvelteMetadata(viteConfig.cacheDir, options);
118
+ if (isSvelteMetadataChanged) {
119
+ // Force Vite to optimize again. Although we mutate the config here, it works because
120
+ // Vite's optimizer runs after `buildStart()`.
121
+ viteConfig.optimizeDeps.force = true;
122
+ }
123
+ },
104
124
 
105
- async load(id, opts) {
125
+ configureServer(server) {
126
+ options.server = server;
127
+ setupWatchers(options, cache, requestParser);
128
+ },
129
+
130
+ load: {
131
+ async handler(id, opts) {
106
132
  const ssr = !!opts?.ssr;
107
133
  const svelteRequest = requestParser(id, !!ssr);
108
134
  if (svelteRequest) {
@@ -127,6 +153,8 @@ export function svelte(inlineOptions) {
127
153
  css.meta.vite ??= {};
128
154
  css.meta.vite.cssScopeTo = [svelteRequest.filename, 'default'];
129
155
  }
156
+ css.moduleType = 'css';
157
+
130
158
  return css;
131
159
  }
132
160
  }
@@ -137,30 +165,23 @@ export function svelte(inlineOptions) {
137
165
  }
138
166
  }
139
167
  }
140
- },
168
+ }
169
+ },
141
170
 
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
- },
171
+ resolveId: {
172
+ // we don't use our generic filter here but a reduced one that only matches our virtual css
173
+ filter: { id: SVELTE_VIRTUAL_STYLE_ID_REGEX },
174
+ handler(id) {
175
+ // return cssId with root prefix so postcss pipeline of vite finds the directory correctly
176
+ // see https://github.com/sveltejs/vite-plugin-svelte/issues/14
177
+ log.debug(`resolveId resolved virtual css module ${id}`, undefined, 'resolve');
178
+ // TODO: do we have to repeat the dance for constructing the virtual id here? our transform added it that way
179
+ return id;
180
+ }
181
+ },
162
182
 
163
- async transform(code, id, opts) {
183
+ transform: {
184
+ async handler(code, id, opts) {
164
185
  const ssr = !!opts?.ssr;
165
186
  const svelteRequest = requestParser(id, ssr);
166
187
  if (!svelteRequest || svelteRequest.query.type === 'style' || svelteRequest.raw) {
@@ -188,34 +209,41 @@ export function svelte(inlineOptions) {
188
209
  }
189
210
  return {
190
211
  ...compileData.compiled.js,
212
+ moduleType: 'js',
191
213
  meta: {
192
214
  vite: {
193
215
  lang: compileData.lang
194
216
  }
195
217
  }
196
218
  };
197
- },
219
+ }
220
+ },
198
221
 
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();
222
+ handleHotUpdate(ctx) {
223
+ if (!options.compilerOptions.hmr || !options.emitCss) {
224
+ return;
225
+ }
226
+ const svelteRequest = requestParser(ctx.file, false, ctx.timestamp);
227
+ if (svelteRequest) {
228
+ return handleHotUpdate(compileSvelte, ctx, svelteRequest, cache, options);
210
229
  }
211
230
  },
212
- {
213
- name: 'vite-plugin-svelte-module',
214
- enforce: 'post',
215
- async configResolved() {
216
- moduleRequestParser = buildModuleIdParser(options);
217
- },
218
- async transform(code, id, opts) {
231
+ async buildEnd() {
232
+ await options.stats?.finishAll();
233
+ }
234
+ };
235
+
236
+ /** @type {import('vite').Plugin} */
237
+ const moduleCompilePlugin = {
238
+ name: 'vite-plugin-svelte-module',
239
+ enforce: 'post',
240
+ async configResolved() {
241
+ //@ts-expect-error transform defined below but filter not in type
242
+ moduleCompilePlugin.transform.filter = buildModuleIdFilter(options);
243
+ moduleRequestParser = buildModuleIdParser(options);
244
+ },
245
+ transform: {
246
+ async handler(code, id, opts) {
219
247
  const ssr = !!opts?.ssr;
220
248
  const moduleRequest = moduleRequestParser(id, ssr);
221
249
  if (!moduleRequest) {
@@ -233,9 +261,11 @@ export function svelte(inlineOptions) {
233
261
  throw toRollupError(e, options);
234
262
  }
235
263
  }
236
- },
237
- svelteInspector()
238
- ];
264
+ }
265
+ };
266
+
267
+ /** @type {import('vite').Plugin[]} */
268
+ const plugins = [compilePlugin, moduleCompilePlugin, svelteInspector()];
239
269
  return plugins;
240
270
  }
241
271
 
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,21 +1,12 @@
1
- import { createRequire } from 'node:module';
2
1
  import path from 'node:path';
3
2
  import process from 'node:process';
4
3
  import fs from 'node:fs';
5
4
  import { pathToFileURL } from 'node:url';
6
5
  import { log } from './log.js';
7
6
 
8
- // used to require cjs config in esm.
9
- // NOTE dynamic import() cjs technically works, but timestamp query cache bust
10
- // have no effect, likely because it has another internal cache?
11
- /** @type {NodeRequire}*/
12
- let esmRequire;
13
-
14
- export const knownSvelteConfigNames = [
15
- 'svelte.config.js',
16
- 'svelte.config.cjs',
17
- 'svelte.config.mjs'
18
- ];
7
+ export const knownSvelteConfigNames = ['js', 'ts', 'mjs', 'mts'].map(
8
+ (ext) => `svelte.config.${ext}`
9
+ );
19
10
 
20
11
  /**
21
12
  * @param {string} filePath
@@ -36,56 +27,23 @@ export async function loadSvelteConfig(viteConfig, inlineOptions) {
36
27
  }
37
28
  const configFile = findConfigToLoad(viteConfig, inlineOptions);
38
29
  if (configFile) {
39
- let err;
40
- // try to use dynamic import for svelte.config.js first
41
- if (configFile.endsWith('.js') || configFile.endsWith('.mjs')) {
42
- try {
43
- const result = await dynamicImportDefault(
44
- pathToFileURL(configFile).href,
45
- fs.statSync(configFile).mtimeMs
46
- );
47
- if (result != null) {
48
- return {
49
- ...result,
50
- configFile
51
- };
52
- } else {
53
- throw new Error(`invalid export in ${configFile}`);
54
- }
55
- } catch (e) {
56
- log.error(`failed to import config ${configFile}`, e);
57
- err = e;
58
- }
59
- }
60
- // cjs or error with dynamic import
61
- if (!configFile.endsWith('.mjs')) {
62
- try {
63
- // identify which require function to use (esm and cjs mode)
64
- const _require = import.meta.url
65
- ? (esmRequire ?? (esmRequire = createRequire(import.meta.url)))
66
- : // eslint-disable-next-line no-undef
67
- require;
68
-
69
- // avoid loading cached version on reload
70
- delete _require.cache[_require.resolve(configFile)];
71
- const result = _require(configFile);
72
- if (result != null) {
73
- return {
74
- ...result,
75
- configFile
76
- };
77
- } else {
78
- throw new Error(`invalid export in ${configFile}`);
79
- }
80
- } catch (e) {
81
- log.error(`failed to require config ${configFile}`, e);
82
- if (!err) {
83
- err = e;
84
- }
30
+ try {
31
+ const result = await dynamicImportDefault(
32
+ pathToFileURL(configFile).href,
33
+ fs.statSync(configFile).mtimeMs
34
+ );
35
+ if (result != null) {
36
+ return {
37
+ ...result,
38
+ configFile
39
+ };
40
+ } else {
41
+ throw new Error(`invalid export in ${configFile}`);
85
42
  }
43
+ } catch (e) {
44
+ log.error(`failed to import config ${configFile}`, e);
45
+ throw e;
86
46
  }
87
- // failed to load existing config file
88
- throw err;
89
47
  }
90
48
  }
91
49
 
@@ -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;iBCFRC,gBAAgBA",
30
30
  "ignoreList": []
31
31
  }