Skip to content

Commit 8ed9e8e

Browse files
committed
feat: add rolldown optimizer plugins instead of esbuild
1 parent 406ba21 commit 8ed9e8e

File tree

8 files changed

+267
-104
lines changed

8 files changed

+267
-104
lines changed

.changeset/hungry-phones-prove.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+
replace esbuild optimizer with rolldown optimizer if rolldown-vite is used

eslint.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ export default [
1414
'packages/playground/big/src/pages/**', // lots of generated files
1515
'packages/*/types/index.d.ts',
1616
'packages/*/types/index.d.ts.map',
17-
'packages/*/CHANGELOG.md'
17+
'packages/*/CHANGELOG.md',
18+
'packages/e2e-tests/**/logs/**'
1819
]
1920
},
2021
...svelteOrgEslintConfig, // contains setup for svelte and typescript

packages/e2e-tests/scan-deps/__tests__/scan-deps.spec.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@ import { describe, expect, it } from 'vitest';
33
describe('vite import scan', () => {
44
it('should not fail to discover dependencies exported from script module', async () => {
55
// vite logs an error if scan fails but continues, so validate no errors logged
6-
// rolldown-vite logs an error for optimizeDeps.esbuildOptions that is unrelated
7-
const errorLogs = e2eServer.logs.server.err.filter(
8-
(m) => !m.includes('optimizeDeps.esbuildOptions')
9-
);
6+
const errorLogs = e2eServer.logs.server.err;
107
expect(errorLogs.length, `unexpected errors:\n${errorLogs.join('\n')}`).toBe(0);
118
});
129
it('should work with exports from module context', async () => {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ export function svelte(inlineOptions) {
140140
css.meta.vite ??= {};
141141
css.meta.vite.cssScopeTo = [svelteRequest.filename, 'default'];
142142
}
143+
css.moduleType = 'css';
144+
143145
return css;
144146
}
145147
}
@@ -194,6 +196,7 @@ export function svelte(inlineOptions) {
194196
}
195197
return {
196198
...compileData.compiled.js,
199+
moduleType: 'js',
197200
meta: {
198201
vite: {
199202
lang: compileData.lang

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface Code {
1414
map?: any;
1515
dependencies?: any[];
1616
hasGlobal?: boolean;
17+
moduleType?: string; //rolldown-vite
1718
meta?: {
1819
vite?: CustomPluginOptionsVite;
1920
};

packages/vite-plugin-svelte/src/utils/esbuild.js renamed to packages/vite-plugin-svelte/src/utils/optimizer-plugins.js

Lines changed: 142 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,33 @@
11
import { readFileSync } from 'node:fs';
22
import * as svelte from 'svelte/compiler';
33
import { log } from './log.js';
4-
import { toESBuildError } from './error.js';
4+
import { toESBuildError, toRollupError } from './error.js';
55
import { safeBase64Hash } from './hash.js';
66
import { normalize } from './id.js';
77

88
/**
99
* @typedef {NonNullable<import('vite').DepOptimizationOptions['esbuildOptions']>} EsbuildOptions
1010
* @typedef {NonNullable<EsbuildOptions['plugins']>[number]} EsbuildPlugin
1111
*/
12-
13-
export const facadeEsbuildSveltePluginName = 'vite-plugin-svelte:facade';
14-
export const facadeEsbuildSvelteModulePluginName = 'vite-plugin-svelte-module:facade';
12+
/*
13+
* @typedef {NonNullable<import('vite').DepOptimizationOptions['rollupOptions']>} RollupOptions
14+
* @typedef {NonNullable<RollupOptions['plugins']>[number]} RolldownPlugin
15+
*/
16+
// TODO type correctly when the above works
17+
/**
18+
* @typedef {{
19+
* name: string,
20+
* options: ()=>void,
21+
* transform?:{
22+
* filter: any
23+
* handler: (code: string,id: string)=>Promise<any>
24+
* },
25+
* buildStart?:()=>void,
26+
* buildEnd?:()=>void
27+
* }} RolldownPlugin
28+
*/
29+
export const facadeOptimizeSveltePluginName = 'vite-plugin-svelte:facade';
30+
export const facadeOptimizeSvelteModulePluginName = 'vite-plugin-svelte-module:facade';
1531

1632
/**
1733
* @param {import('../types/options.d.ts').ResolvedOptions} options
@@ -36,7 +52,10 @@ export function esbuildSveltePlugin(options) {
3652
build.onLoad({ filter }, async ({ path: filename }) => {
3753
const code = readFileSync(filename, 'utf8');
3854
try {
39-
const contents = await compileSvelte(options, { filename, code }, statsCollection);
55+
const result = await compileSvelte(options, { filename, code }, statsCollection);
56+
const contents = result.map
57+
? result.code + '//# sourceMappingURL=' + result.map.toUrl()
58+
: result.code;
4059
return { contents };
4160
} catch (e) {
4261
return { errors: [toESBuildError(e, options)] };
@@ -49,11 +68,63 @@ export function esbuildSveltePlugin(options) {
4968
};
5069
}
5170

71+
/**
72+
* @param {import('../types/options.d.ts').ResolvedOptions} options
73+
* @returns {RolldownPlugin}
74+
*/
75+
export function rolldownOptimizeSveltePlugin(options) {
76+
/** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */
77+
let statsCollection;
78+
/** @type {RolldownPlugin} */
79+
const plugin = {
80+
name: 'vite-plugin-svelte:rolldown-optimize-svelte',
81+
// @ts-expect-error not typed in rolldown yet
82+
options(opts) {
83+
if (
84+
opts.plugins?.some((/** @type {{ name: string; }} */ p) =>
85+
p.name.startsWith('vite:dep-scan')
86+
)
87+
) {
88+
delete plugin.transform;
89+
delete plugin.buildStart;
90+
delete plugin.buildEnd;
91+
}
92+
},
93+
transform: {
94+
filter: {
95+
// TODO: remove excludes once above options hook works
96+
id: { include: [/^[^?#]+\.svelte(?:[?#]|$)/], exclude: [/^virtual-module:/] },
97+
code: { exclude: [/(?:import|export)[^"\n]+"virtual-module:/] }
98+
},
99+
/**
100+
* @param {string} code
101+
* @param {string} filename
102+
*/
103+
async handler(code, filename) {
104+
try {
105+
return await compileSvelte(options, { filename, code }, statsCollection);
106+
} catch (e) {
107+
throw toRollupError(e, options);
108+
}
109+
}
110+
},
111+
buildStart() {
112+
statsCollection = options.stats?.startCollection('prebundle library components', {
113+
logResult: (c) => c.stats.length > 1
114+
});
115+
},
116+
buildEnd() {
117+
statsCollection?.finish();
118+
}
119+
};
120+
return plugin;
121+
}
122+
52123
/**
53124
* @param {import('../types/options.d.ts').ResolvedOptions} options
54125
* @param {{ filename: string, code: string }} input
55126
* @param {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection} [statsCollection]
56-
* @returns {Promise<string>}
127+
* @returns {Promise<import('../types/compile.d.ts').Code>}
57128
*/
58129
async function compileSvelte(options, { filename, code }, statsCollection) {
59130
let css = options.compilerOptions.css;
@@ -114,9 +185,10 @@ async function compileSvelte(options, { filename, code }, statsCollection) {
114185
if (endStat) {
115186
endStat();
116187
}
117-
return compiled.js.map
118-
? compiled.js.code + '//# sourceMappingURL=' + compiled.js.map.toUrl()
119-
: compiled.js.code;
188+
return {
189+
...compiled.js,
190+
moduleType: 'js'
191+
};
120192
}
121193

122194
/**
@@ -142,7 +214,10 @@ export function esbuildSvelteModulePlugin(options) {
142214
build.onLoad({ filter }, async ({ path: filename }) => {
143215
const code = readFileSync(filename, 'utf8');
144216
try {
145-
const contents = await compileSvelteModule(options, { filename, code }, statsCollection);
217+
const result = await compileSvelteModule(options, { filename, code }, statsCollection);
218+
const contents = result.map
219+
? result.code + '//# sourceMappingURL=' + result.map.toUrl()
220+
: result.code;
146221
return { contents };
147222
} catch (e) {
148223
return { errors: [toESBuildError(e, options)] };
@@ -155,11 +230,63 @@ export function esbuildSvelteModulePlugin(options) {
155230
};
156231
}
157232

233+
/**
234+
* @param {import('../types/options.d.ts').ResolvedOptions} options
235+
* @returns {RolldownPlugin}
236+
*/
237+
export function rolldownOptimizeSvelteModulePlugin(options) {
238+
/** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */
239+
let statsCollection;
240+
/** @type {RolldownPlugin} */
241+
const plugin = {
242+
name: 'vite-plugin-svelte:rolldown-optimize-svelte-module',
243+
// @ts-expect-error not typed in rolldown yet
244+
options(opts) {
245+
if (
246+
opts.plugins?.some((/** @type {{ name: string; }} */ p) =>
247+
p.name.startsWith('vite:dep-scan')
248+
)
249+
) {
250+
delete plugin.transform;
251+
delete plugin.buildStart;
252+
delete plugin.buildEnd;
253+
}
254+
},
255+
transform: {
256+
filter: {
257+
// TODO: remove excludes once above options hook works
258+
id: { include: [/^[^?#]+\.svelte\.js(?:[?#]|$)/], exclude: [/^virtual-module:/] },
259+
code: { exclude: [/(?:import|export)[^"\n]+"virtual-module:/] }
260+
},
261+
/**
262+
* @param {string} code
263+
* @param {string} filename
264+
*/
265+
async handler(code, filename) {
266+
try {
267+
return await compileSvelteModule(options, { filename, code }, statsCollection);
268+
} catch (e) {
269+
throw toRollupError(e, options);
270+
}
271+
}
272+
},
273+
buildStart() {
274+
statsCollection = options.stats?.startCollection('prebundle library components', {
275+
logResult: (c) => c.stats.length > 1
276+
});
277+
},
278+
buildEnd() {
279+
statsCollection?.finish();
280+
}
281+
};
282+
return plugin;
283+
}
284+
158285
/**
159286
* @param {import('../types/options.d.ts').ResolvedOptions} options
160287
* @param {{ filename: string; code: string }} input
161288
* @param {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection} [statsCollection]
162-
* @returns {Promise<string>}
289+
* @returns {Promise<import('../types/compile.d.ts').Code>}
163290
*/
164291
async function compileSvelteModule(options, { filename, code }, statsCollection) {
165292
const endStat = statsCollection?.start(filename);
@@ -171,7 +298,8 @@ async function compileSvelteModule(options, { filename, code }, statsCollection)
171298
if (endStat) {
172299
endStat();
173300
}
174-
return compiled.js.map
175-
? compiled.js.code + '//# sourceMappingURL=' + compiled.js.map.toUrl()
176-
: compiled.js.code;
301+
return {
302+
...compiled.js,
303+
moduleType: 'js'
304+
};
177305
}

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

Lines changed: 66 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import process from 'node:process';
2-
import {
2+
import * as vite from 'vite';
3+
const {
34
defaultClientMainFields,
45
defaultServerMainFields,
56
defaultClientConditions,
67
defaultServerConditions,
7-
normalizePath
8-
} from 'vite';
8+
normalizePath,
9+
//@ts-expect-error rolldownVersion not in type
10+
rolldownVersion
11+
} = vite;
912
import { isDebugNamespaceEnabled, log } from './log.js';
1013
import { loadSvelteConfig } from './load-svelte-config.js';
1114
import {
@@ -20,9 +23,11 @@ import path from 'node:path';
2023
import {
2124
esbuildSvelteModulePlugin,
2225
esbuildSveltePlugin,
23-
facadeEsbuildSvelteModulePluginName,
24-
facadeEsbuildSveltePluginName
25-
} from './esbuild.js';
26+
facadeOptimizeSvelteModulePluginName,
27+
facadeOptimizeSveltePluginName,
28+
rolldownOptimizeSvelteModulePlugin,
29+
rolldownOptimizeSveltePlugin
30+
} from './optimizer-plugins.js';
2631
import { addExtraPreprocessors } from './preprocess.js';
2732
import deepmerge from 'deepmerge';
2833
import {
@@ -386,17 +391,32 @@ export async function buildExtraViteConfig(options, config) {
386391
extraViteConfig.optimizeDeps = {
387392
...extraViteConfig.optimizeDeps,
388393
// 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: {
394+
extensions: options.extensions ?? ['.svelte']
395+
};
396+
// Add esbuild plugin to prebundle Svelte files.
397+
// Currently a placeholder as more information is needed after Vite config is resolved,
398+
// the real Svelte plugin is added in `patchResolvedViteConfig()`
399+
if (rolldownVersion) {
400+
//@ts-expect-error rolldown-vite types not finished
401+
extraViteConfig.optimizeDeps.rollupOptions = {
394402
plugins: [
395-
{ name: facadeEsbuildSveltePluginName, setup: () => {} },
396-
{ name: facadeEsbuildSvelteModulePluginName, setup: () => {} }
403+
{ name: facadeOptimizeSveltePluginName, transform() {}, buildStart() {}, buildEnd() {} },
404+
{
405+
name: facadeOptimizeSvelteModulePluginName,
406+
transform() {},
407+
buildStart() {},
408+
buildEnd() {}
409+
}
397410
]
398-
}
399-
};
411+
};
412+
} else {
413+
extraViteConfig.optimizeDeps.esbuildOptions = {
414+
plugins: [
415+
{ name: facadeOptimizeSveltePluginName, setup: () => {} },
416+
{ name: facadeOptimizeSvelteModulePluginName, setup: () => {} }
417+
]
418+
};
419+
}
400420
}
401421

402422
// enable hmrPartialAccept if not explicitly disabled
@@ -594,19 +614,37 @@ export function patchResolvedViteConfig(viteConfig, options) {
594614
}
595615
}
596616
}
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));
617+
if (rolldownVersion) {
618+
// @ts-expect-error not typed
619+
const plugins = viteConfig.optimizeDeps.rollupOptions.plugins;
620+
if (plugins) {
621+
const facadeSveltePlugin = plugins.find(
622+
(/** @type {{ name: any; }} */ p) => p.name === facadeOptimizeSveltePluginName
623+
);
624+
if (facadeSveltePlugin) {
625+
Object.assign(facadeSveltePlugin, rolldownOptimizeSveltePlugin(options));
626+
}
627+
const facadeSvelteModulePlugin = plugins.find(
628+
(/** @type {{ name: string; }} */ p) => p.name === facadeOptimizeSvelteModulePluginName
629+
);
630+
if (facadeSvelteModulePlugin) {
631+
Object.assign(facadeSvelteModulePlugin, rolldownOptimizeSvelteModulePlugin(options));
632+
}
633+
}
634+
} else {
635+
const plugins = viteConfig.optimizeDeps.esbuildOptions?.plugins;
636+
if (plugins) {
637+
const facadeSveltePlugin = plugins.find((p) => p.name === facadeOptimizeSveltePluginName);
638+
if (facadeSveltePlugin) {
639+
Object.assign(facadeSveltePlugin, esbuildSveltePlugin(options));
640+
}
641+
const facadeSvelteModulePlugin = plugins.find(
642+
(p) => p.name === facadeOptimizeSvelteModulePluginName
643+
);
644+
if (facadeSvelteModulePlugin) {
645+
Object.assign(facadeSvelteModulePlugin, esbuildSvelteModulePlugin(options));
646+
}
647+
}
610648
}
611649
}
612650

0 commit comments

Comments
 (0)