@sveltejs/vite-plugin-svelte 6.0.0-next.0 → 6.0.0-next.2
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 +7 -7
- package/src/index.js +27 -254
- package/src/plugins/compile-module.js +51 -0
- package/src/plugins/compile.js +73 -0
- package/src/plugins/configure.js +99 -0
- package/src/plugins/hot-update.js +181 -0
- package/src/plugins/load-compiled-css.js +46 -0
- package/src/plugins/load-custom.js +42 -0
- package/src/plugins/preprocess.js +133 -0
- package/src/{utils/optimizer-plugins.js → plugins/setup-optimizer.js} +153 -8
- package/src/types/compile.d.ts +20 -2
- package/src/types/id.d.ts +1 -8
- package/src/types/plugin-api.d.ts +14 -7
- package/src/utils/compile.js +18 -41
- package/src/utils/constants.js +1 -4
- package/src/utils/dependencies.js +0 -29
- package/src/utils/id.js +7 -4
- package/src/utils/load-svelte-config.js +18 -60
- package/src/utils/options.js +4 -89
- package/src/utils/preprocess.js +2 -44
- package/src/utils/vite-plugin-svelte-cache.js +1 -60
- package/src/utils/vite-plugin-svelte-stats.js +59 -11
- package/src/utils/watch.js +3 -13
- package/types/index.d.ts +5 -0
- package/types/index.d.ts.map +1 -1
- package/src/handle-hot-update.js +0 -145
- package/src/utils/load-raw.js +0 -125
- package/src/utils/optimizer.js +0 -53
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { log } from '../utils/log.js';
|
|
2
|
+
import { setupWatchers } from '../utils/watch.js';
|
|
3
|
+
import { SVELTE_VIRTUAL_STYLE_ID_REGEX } from '../utils/constants.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {import('../types/plugin-api.d.ts').PluginAPI} api
|
|
7
|
+
* @returns {import('vite').Plugin}
|
|
8
|
+
*/
|
|
9
|
+
export function hotUpdate(api) {
|
|
10
|
+
/**
|
|
11
|
+
* @type {import("../types/options.js").ResolvedOptions}
|
|
12
|
+
*/
|
|
13
|
+
let options;
|
|
14
|
+
/**
|
|
15
|
+
* @type {import('../types/id.d.ts').IdParser}
|
|
16
|
+
*/
|
|
17
|
+
let idParser;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
*
|
|
21
|
+
* @type {Map<string|null,string>}
|
|
22
|
+
*/
|
|
23
|
+
const transformResultCache = new Map();
|
|
24
|
+
|
|
25
|
+
/** @type {import('vite').Plugin} */
|
|
26
|
+
const plugin = {
|
|
27
|
+
name: 'vite-plugin-svelte:hot-update',
|
|
28
|
+
enforce: 'post',
|
|
29
|
+
configResolved() {
|
|
30
|
+
options = api.options;
|
|
31
|
+
idParser = api.idParser;
|
|
32
|
+
|
|
33
|
+
// @ts-expect-error
|
|
34
|
+
plugin.transform.filter = {
|
|
35
|
+
id: {
|
|
36
|
+
// reinclude virtual styles to get their output
|
|
37
|
+
include: [...api.idFilter.id.include, SVELTE_VIRTUAL_STYLE_ID_REGEX],
|
|
38
|
+
exclude: [
|
|
39
|
+
// ignore files in node_modules, we don't hot update them
|
|
40
|
+
/\/node_modules\//,
|
|
41
|
+
// remove style exclusion
|
|
42
|
+
...api.idFilter.id.exclude.filter((filter) => filter !== SVELTE_VIRTUAL_STYLE_ID_REGEX)
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
applyToEnvironment(env) {
|
|
49
|
+
// we only handle updates for client components
|
|
50
|
+
// ssr frameworks have to handle updating/reloading themselves as v-p-s can't know what they prefer
|
|
51
|
+
const hmrEnabled = options.compilerOptions.hmr && options.emitCss;
|
|
52
|
+
return hmrEnabled && env.config.consumer === 'client';
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
configureServer(server) {
|
|
56
|
+
const clientEnvironment = Object.values(server.environments).find(
|
|
57
|
+
(e) => e.config.consumer === 'client'
|
|
58
|
+
);
|
|
59
|
+
if (clientEnvironment) {
|
|
60
|
+
setupWatchers(options, api.getEnvironmentCache({ environment: clientEnvironment }));
|
|
61
|
+
} else {
|
|
62
|
+
log.warn(
|
|
63
|
+
'No client environment found, not adding watchers for svelte config and preprocessor dependencies'
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
buildStart() {
|
|
69
|
+
transformResultCache.clear();
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
transform: {
|
|
73
|
+
order: 'post',
|
|
74
|
+
handler(code, id) {
|
|
75
|
+
transformResultCache.set(id, code);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
hotUpdate: {
|
|
79
|
+
order: 'post',
|
|
80
|
+
async handler(ctx) {
|
|
81
|
+
const svelteRequest = idParser(ctx.file, false, ctx.timestamp);
|
|
82
|
+
if (svelteRequest) {
|
|
83
|
+
const { modules } = ctx;
|
|
84
|
+
const svelteModules = [];
|
|
85
|
+
const nonSvelteModules = [];
|
|
86
|
+
for (const mod of modules) {
|
|
87
|
+
if (transformResultCache.has(mod.id)) {
|
|
88
|
+
svelteModules.push(mod);
|
|
89
|
+
} else {
|
|
90
|
+
nonSvelteModules.push(mod);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (svelteModules.length === 0) {
|
|
95
|
+
return; // nothing to do for us
|
|
96
|
+
}
|
|
97
|
+
const affectedModules = [];
|
|
98
|
+
const prevResults = svelteModules.map((m) => transformResultCache.get(m.id));
|
|
99
|
+
for (let i = 0; i < svelteModules.length; i++) {
|
|
100
|
+
const mod = svelteModules[i];
|
|
101
|
+
const prev = prevResults[i];
|
|
102
|
+
await this.environment.transformRequest(mod.url);
|
|
103
|
+
const next = transformResultCache.get(mod.id);
|
|
104
|
+
if (hasCodeChanged(prev, next, mod.id)) {
|
|
105
|
+
affectedModules.push(mod);
|
|
106
|
+
} else {
|
|
107
|
+
log.debug(
|
|
108
|
+
`skipping hot update for ${mod.id} because result is unchanged`,
|
|
109
|
+
undefined,
|
|
110
|
+
'hmr'
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
log.debug(
|
|
115
|
+
`hotUpdate for ${svelteRequest.id} result: [${affectedModules.map((m) => m.id).join(', ')}]`,
|
|
116
|
+
undefined,
|
|
117
|
+
'hmr'
|
|
118
|
+
);
|
|
119
|
+
return [...affectedModules, ...nonSvelteModules];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
return plugin;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @param {string | undefined | null} prev
|
|
130
|
+
* @param {string | undefined | null} next
|
|
131
|
+
* @param {string | null} id
|
|
132
|
+
* @returns {boolean}
|
|
133
|
+
*/
|
|
134
|
+
function hasCodeChanged(prev, next, id) {
|
|
135
|
+
const isStrictEqual = nullSafeEqual(prev, next);
|
|
136
|
+
if (isStrictEqual) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
const isLooseEqual = nullSafeEqual(normalize(prev), normalize(next));
|
|
140
|
+
if (!isStrictEqual && isLooseEqual) {
|
|
141
|
+
log.debug(
|
|
142
|
+
`ignoring compiler output change for ${id} as it is equal to previous output after normalization`,
|
|
143
|
+
undefined,
|
|
144
|
+
'hmr'
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
return !isLooseEqual;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @param {string | null | undefined} prev
|
|
152
|
+
* @param {string | null | undefined} next
|
|
153
|
+
* @returns {boolean}
|
|
154
|
+
*/
|
|
155
|
+
function nullSafeEqual(prev, next) {
|
|
156
|
+
return (prev == null && next == null) || (prev != null && next != null && prev === next);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* remove code that only changes metadata and does not require a js update for the component to keep working
|
|
161
|
+
*
|
|
162
|
+
* 1) location numbers argument from $.add_locations calls in svelte output eg [[1,2],[3,4]]
|
|
163
|
+
* 2) timestamp queries added to imports by vite eg ?t=0123456789123
|
|
164
|
+
*
|
|
165
|
+
* @param {string | null | undefined } code
|
|
166
|
+
* @returns {string | null | undefined}
|
|
167
|
+
*/
|
|
168
|
+
function normalize(code) {
|
|
169
|
+
if (code == null) {
|
|
170
|
+
return code;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
code
|
|
175
|
+
// svelte5 $.add_locations line numbers argument [[1,2],[3,4]]
|
|
176
|
+
// uses matching group replace to keep the template argument intact
|
|
177
|
+
.replace(/(\$\.add_locations\(.*), (\[\[[\d, [\]]+]])\)/g, '$1, []')
|
|
178
|
+
// vite import analysis timestamp queries, ?t=0123456789123&
|
|
179
|
+
.replace(/[?&]t=\d{13}\b/g, '')
|
|
180
|
+
);
|
|
181
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { log } from '../utils/log.js';
|
|
2
|
+
import { SVELTE_VIRTUAL_STYLE_ID_REGEX } from '../utils/constants.js';
|
|
3
|
+
|
|
4
|
+
const filter = { id: SVELTE_VIRTUAL_STYLE_ID_REGEX };
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {import('../types/plugin-api.d.ts').PluginAPI} api
|
|
8
|
+
* @returns {import('vite').Plugin}
|
|
9
|
+
*/
|
|
10
|
+
export function loadCompiledCss(api) {
|
|
11
|
+
return {
|
|
12
|
+
name: 'vite-plugin-svelte:load-compiled-css',
|
|
13
|
+
|
|
14
|
+
resolveId: {
|
|
15
|
+
filter, // same filter in load to ensure minimal work
|
|
16
|
+
handler(id) {
|
|
17
|
+
log.debug(`resolveId resolved virtual css module ${id}`, undefined, 'resolve');
|
|
18
|
+
return id;
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
load: {
|
|
22
|
+
filter,
|
|
23
|
+
async handler(id) {
|
|
24
|
+
const ssr = this.environment.config.consumer === 'server';
|
|
25
|
+
const svelteRequest = api.idParser(id, ssr);
|
|
26
|
+
if (!svelteRequest) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const cache = api.getEnvironmentCache(this);
|
|
30
|
+
const cachedCss = cache.getCSS(svelteRequest);
|
|
31
|
+
if (cachedCss) {
|
|
32
|
+
const { hasGlobal, ...css } = cachedCss;
|
|
33
|
+
if (hasGlobal === false) {
|
|
34
|
+
// hasGlobal was added in svelte 5.26.0, so make sure it is boolean false
|
|
35
|
+
css.meta ??= {};
|
|
36
|
+
css.meta.vite ??= {};
|
|
37
|
+
// TODO is that slice the best way to get the filename without parsing the id?
|
|
38
|
+
css.meta.vite.cssScopeTo = [id.slice(0, id.lastIndexOf('?')), 'default'];
|
|
39
|
+
}
|
|
40
|
+
css.moduleType = 'css';
|
|
41
|
+
return css;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { log } from '../utils/log.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* if svelte config includes files that vite treats as assets (e.g. .svg)
|
|
6
|
+
* we have to manually load them to avoid getting urls
|
|
7
|
+
*
|
|
8
|
+
* @param {import('../types/plugin-api.d.ts').PluginAPI} api
|
|
9
|
+
* @returns {import('vite').Plugin}
|
|
10
|
+
*/
|
|
11
|
+
export function loadCustom(api) {
|
|
12
|
+
/** @type {import('vite').Plugin} */
|
|
13
|
+
const plugin = {
|
|
14
|
+
name: 'vite-plugin-svelte:load-custom',
|
|
15
|
+
enforce: 'pre', // must come before vites own asset handling or custom extensions like .svg won't work
|
|
16
|
+
configResolved() {
|
|
17
|
+
//@ts-expect-error load defined below but filter not in type
|
|
18
|
+
plugin.load.filter = api.idFilter;
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
load: {
|
|
22
|
+
//filter: is set in configResolved
|
|
23
|
+
async handler(id) {
|
|
24
|
+
const config = this.environment.config;
|
|
25
|
+
const ssr = config.consumer === 'server';
|
|
26
|
+
const svelteRequest = api.idParser(id, ssr);
|
|
27
|
+
if (svelteRequest) {
|
|
28
|
+
const { filename, query } = svelteRequest;
|
|
29
|
+
if (!query.url && config.assetsInclude(filename)) {
|
|
30
|
+
log.debug(
|
|
31
|
+
`loading ${filename} to prevent vite asset handling to turn it into a url by default`,
|
|
32
|
+
undefined,
|
|
33
|
+
'load'
|
|
34
|
+
);
|
|
35
|
+
return fs.readFileSync(filename, 'utf-8');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
return plugin;
|
|
42
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { toRollupError } from '../utils/error.js';
|
|
2
|
+
import { mapToRelative } from '../utils/sourcemaps.js';
|
|
3
|
+
import * as svelte from 'svelte/compiler';
|
|
4
|
+
import { log } from '../utils/log.js';
|
|
5
|
+
import { arraify } from '../utils/options.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {import('../types/plugin-api.d.ts').PluginAPI} api
|
|
9
|
+
* @returns {import('vite').Plugin}
|
|
10
|
+
*/
|
|
11
|
+
export function preprocess(api) {
|
|
12
|
+
/**
|
|
13
|
+
* @type {import("../types/options.js").ResolvedOptions}
|
|
14
|
+
*/
|
|
15
|
+
let options;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @type {import("../types/compile.d.ts").PreprocessSvelte}
|
|
19
|
+
*/
|
|
20
|
+
let preprocessSvelte;
|
|
21
|
+
/** @type {import('vite').Plugin} */
|
|
22
|
+
const plugin = {
|
|
23
|
+
name: 'vite-plugin-svelte:preprocess',
|
|
24
|
+
enforce: 'pre',
|
|
25
|
+
configResolved(c) {
|
|
26
|
+
//@ts-expect-error defined below but filter not in type
|
|
27
|
+
plugin.transform.filter = api.idFilter;
|
|
28
|
+
options = api.options;
|
|
29
|
+
if (arraify(options.preprocess).length > 0) {
|
|
30
|
+
preprocessSvelte = createPreprocessSvelte(options, c);
|
|
31
|
+
} else {
|
|
32
|
+
log.debug(
|
|
33
|
+
`disabling ${plugin.name} because no preprocessor is configured`,
|
|
34
|
+
undefined,
|
|
35
|
+
'preprocess'
|
|
36
|
+
);
|
|
37
|
+
delete plugin.transform;
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
transform: {
|
|
42
|
+
async handler(code, id) {
|
|
43
|
+
const cache = api.getEnvironmentCache(this);
|
|
44
|
+
const ssr = this.environment.config.consumer === 'server';
|
|
45
|
+
const svelteRequest = api.idParser(id, ssr);
|
|
46
|
+
if (!svelteRequest) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return await preprocessSvelte(svelteRequest, code, options);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
cache.setError(svelteRequest, e);
|
|
53
|
+
throw toRollupError(e, options);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
return plugin;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* @param {import("../types/options.js").ResolvedOptions} options
|
|
62
|
+
* @param {import("vite").ResolvedConfig} resolvedConfig
|
|
63
|
+
* @returns {import('../types/compile.d.ts').PreprocessSvelte}
|
|
64
|
+
*/
|
|
65
|
+
function createPreprocessSvelte(options, resolvedConfig) {
|
|
66
|
+
/** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */
|
|
67
|
+
let stats;
|
|
68
|
+
/** @type {Array<import('svelte/compiler').PreprocessorGroup>} */
|
|
69
|
+
const preprocessors = arraify(options.preprocess);
|
|
70
|
+
|
|
71
|
+
for (const preprocessor of preprocessors) {
|
|
72
|
+
if (preprocessor.style && '__resolvedConfig' in preprocessor.style) {
|
|
73
|
+
preprocessor.style.__resolvedConfig = resolvedConfig;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** @type {import('../types/compile.d.ts').PreprocessSvelte} */
|
|
78
|
+
return async function preprocessSvelte(svelteRequest, code, options) {
|
|
79
|
+
const { filename, ssr } = svelteRequest;
|
|
80
|
+
|
|
81
|
+
if (options.stats) {
|
|
82
|
+
if (options.isBuild) {
|
|
83
|
+
if (!stats) {
|
|
84
|
+
// build is either completely ssr or csr, create stats collector on first compile
|
|
85
|
+
// it is then finished in the buildEnd hook.
|
|
86
|
+
stats = options.stats.startCollection(`${ssr ? 'ssr' : 'dom'} preprocess`, {
|
|
87
|
+
logInProgress: () => false
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
// dev time ssr, it's a ssr request and there are no stats, assume new page load and start collecting
|
|
92
|
+
if (ssr && !stats) {
|
|
93
|
+
stats = options.stats.startCollection('ssr preprocess');
|
|
94
|
+
}
|
|
95
|
+
// stats are being collected but this isn't an ssr request, assume page loaded and stop collecting
|
|
96
|
+
if (!ssr && stats) {
|
|
97
|
+
stats.finish();
|
|
98
|
+
stats = undefined;
|
|
99
|
+
}
|
|
100
|
+
// TODO find a way to trace dom compile during dev
|
|
101
|
+
// problem: we need to call finish at some point but have no way to tell if page load finished
|
|
102
|
+
// also they for hmr updates too
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let preprocessed;
|
|
107
|
+
|
|
108
|
+
if (preprocessors && preprocessors.length > 0) {
|
|
109
|
+
try {
|
|
110
|
+
const endStat = stats?.start(filename);
|
|
111
|
+
preprocessed = await svelte.preprocess(code, preprocessors, { filename }); // full filename here so postcss works
|
|
112
|
+
endStat?.();
|
|
113
|
+
} catch (e) {
|
|
114
|
+
e.message = `Error while preprocessing ${filename}${e.message ? ` - ${e.message}` : ''}`;
|
|
115
|
+
throw e;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (typeof preprocessed?.map === 'object') {
|
|
119
|
+
mapToRelative(preprocessed?.map, filename);
|
|
120
|
+
}
|
|
121
|
+
return /** @type {import('../types/compile.d.ts').PreprocessTransformOutput} */ {
|
|
122
|
+
code: preprocessed.code,
|
|
123
|
+
// @ts-expect-error
|
|
124
|
+
map: preprocessed.map,
|
|
125
|
+
meta: {
|
|
126
|
+
svelte: {
|
|
127
|
+
preprocessed
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
1
3
|
import { readFileSync } from 'node:fs';
|
|
2
4
|
import * as svelte from 'svelte/compiler';
|
|
3
|
-
import { log } from '
|
|
4
|
-
import { toESBuildError, toRollupError } from '
|
|
5
|
-
import { safeBase64Hash } from '
|
|
6
|
-
import { normalize } from '
|
|
5
|
+
import { log } from '../utils/log.js';
|
|
6
|
+
import { toESBuildError, toRollupError } from '../utils/error.js';
|
|
7
|
+
import { safeBase64Hash } from '../utils/hash.js';
|
|
8
|
+
import { normalize } from '../utils/id.js';
|
|
9
|
+
import * as vite from 'vite';
|
|
10
|
+
// @ts-expect-error not typed on vite
|
|
11
|
+
const { rolldownVersion } = vite;
|
|
7
12
|
|
|
8
13
|
/**
|
|
9
14
|
* @typedef {NonNullable<import('vite').DepOptimizationOptions['esbuildOptions']>} EsbuildOptions
|
|
@@ -13,14 +18,86 @@ import { normalize } from './id.js';
|
|
|
13
18
|
* @typedef {NonNullable<import('vite').Rollup.Plugin>} RollupPlugin
|
|
14
19
|
*/
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
const optimizeSveltePluginName = 'vite-plugin-svelte:optimize';
|
|
22
|
+
const optimizeSvelteModulePluginName = 'vite-plugin-svelte:optimize-module';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {import('../types/plugin-api.d.ts').PluginAPI} api
|
|
26
|
+
* @returns {import('vite').Plugin}
|
|
27
|
+
*/
|
|
28
|
+
export function setupOptimizer(api) {
|
|
29
|
+
/** @type {import('vite').ResolvedConfig} */
|
|
30
|
+
let viteConfig;
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
name: 'vite-plugin-svelte:setup-optimizer',
|
|
34
|
+
apply: 'serve',
|
|
35
|
+
config() {
|
|
36
|
+
/** @type {import('vite').UserConfig['optimizeDeps']} */
|
|
37
|
+
const optimizeDeps = {
|
|
38
|
+
// Experimental Vite API to allow these extensions to be scanned and prebundled
|
|
39
|
+
extensions: ['.svelte']
|
|
40
|
+
};
|
|
41
|
+
// Add optimizer plugins to prebundle Svelte files.
|
|
42
|
+
// Currently, a placeholder as more information is needed after Vite config is resolved,
|
|
43
|
+
// the added plugins are patched in configResolved below
|
|
44
|
+
if (rolldownVersion) {
|
|
45
|
+
//@ts-expect-error rolldown types not finished
|
|
46
|
+
optimizeDeps.rollupOptions = {
|
|
47
|
+
plugins: [
|
|
48
|
+
placeholderRolldownOptimizerPlugin(optimizeSveltePluginName),
|
|
49
|
+
placeholderRolldownOptimizerPlugin(optimizeSvelteModulePluginName)
|
|
50
|
+
]
|
|
51
|
+
};
|
|
52
|
+
} else {
|
|
53
|
+
optimizeDeps.esbuildOptions = {
|
|
54
|
+
plugins: [
|
|
55
|
+
{ name: optimizeSveltePluginName, setup: () => {} },
|
|
56
|
+
{ name: optimizeSvelteModulePluginName, setup: () => {} }
|
|
57
|
+
]
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return { optimizeDeps };
|
|
61
|
+
},
|
|
62
|
+
configResolved(c) {
|
|
63
|
+
viteConfig = c;
|
|
64
|
+
const optimizeDeps = c.optimizeDeps;
|
|
65
|
+
if (rolldownVersion) {
|
|
66
|
+
const plugins =
|
|
67
|
+
// @ts-expect-error not typed
|
|
68
|
+
optimizeDeps.rollupOptions?.plugins?.filter((p) =>
|
|
69
|
+
[optimizeSveltePluginName, optimizeSvelteModulePluginName].includes(p.name)
|
|
70
|
+
) ?? [];
|
|
71
|
+
for (const plugin of plugins) {
|
|
72
|
+
patchRolldownOptimizerPlugin(plugin, api.options);
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
const plugins =
|
|
76
|
+
optimizeDeps.esbuildOptions?.plugins?.filter((p) =>
|
|
77
|
+
[optimizeSveltePluginName, optimizeSvelteModulePluginName].includes(p.name)
|
|
78
|
+
) ?? [];
|
|
79
|
+
for (const plugin of plugins) {
|
|
80
|
+
patchESBuildOptimizerPlugin(plugin, api.options);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
async buildStart() {
|
|
85
|
+
if (!api.options.prebundleSvelteLibraries) return;
|
|
86
|
+
const changed = await svelteMetadataChanged(viteConfig.cacheDir, api.options);
|
|
87
|
+
if (changed) {
|
|
88
|
+
// Force Vite to optimize again. Although we mutate the config here, it works because
|
|
89
|
+
// Vite's optimizer runs after `buildStart()`.
|
|
90
|
+
viteConfig.optimizeDeps.force = true;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
18
95
|
|
|
19
96
|
/**
|
|
20
97
|
* @param {EsbuildPlugin} plugin
|
|
21
98
|
* @param {import('../types/options.d.ts').ResolvedOptions} options
|
|
22
99
|
*/
|
|
23
|
-
|
|
100
|
+
function patchESBuildOptimizerPlugin(plugin, options) {
|
|
24
101
|
const components = plugin.name === optimizeSveltePluginName;
|
|
25
102
|
const compileFn = components ? compileSvelte : compileSvelteModule;
|
|
26
103
|
const statsName = components ? 'prebundle library components' : 'prebundle library modules';
|
|
@@ -57,7 +134,7 @@ export function patchESBuildOptimizerPlugin(plugin, options) {
|
|
|
57
134
|
* @param {RollupPlugin} plugin
|
|
58
135
|
* @param {import('../types/options.d.ts').ResolvedOptions} options
|
|
59
136
|
*/
|
|
60
|
-
|
|
137
|
+
function patchRolldownOptimizerPlugin(plugin, options) {
|
|
61
138
|
const components = plugin.name === optimizeSveltePluginName;
|
|
62
139
|
const compileFn = components ? compileSvelte : compileSvelteModule;
|
|
63
140
|
const statsName = components ? 'prebundle library components' : 'prebundle library modules';
|
|
@@ -193,3 +270,71 @@ async function compileSvelteModule(options, { filename, code }, statsCollection)
|
|
|
193
270
|
moduleType: 'js'
|
|
194
271
|
};
|
|
195
272
|
}
|
|
273
|
+
|
|
274
|
+
// List of options that changes the prebundling result
|
|
275
|
+
/** @type {(keyof import('../types/options.d.ts').ResolvedOptions)[]} */
|
|
276
|
+
const PREBUNDLE_SENSITIVE_OPTIONS = [
|
|
277
|
+
'compilerOptions',
|
|
278
|
+
'configFile',
|
|
279
|
+
'experimental',
|
|
280
|
+
'extensions',
|
|
281
|
+
'ignorePluginPreprocessors',
|
|
282
|
+
'preprocess'
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* stores svelte metadata in cache dir and compares if it has changed
|
|
287
|
+
*
|
|
288
|
+
* @param {string} cacheDir
|
|
289
|
+
* @param {import('../types/options.d.ts').ResolvedOptions} options
|
|
290
|
+
* @returns {Promise<boolean>} Whether the Svelte metadata has changed
|
|
291
|
+
*/
|
|
292
|
+
async function svelteMetadataChanged(cacheDir, options) {
|
|
293
|
+
const svelteMetadata = generateSvelteMetadata(options);
|
|
294
|
+
const svelteMetadataPath = path.resolve(cacheDir, '_svelte_metadata.json');
|
|
295
|
+
|
|
296
|
+
const currentSvelteMetadata = JSON.stringify(svelteMetadata, (_, value) => {
|
|
297
|
+
// Handle preprocessors
|
|
298
|
+
return typeof value === 'function' ? value.toString() : value;
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
/** @type {string | undefined} */
|
|
302
|
+
let existingSvelteMetadata;
|
|
303
|
+
try {
|
|
304
|
+
existingSvelteMetadata = await fs.readFile(svelteMetadataPath, 'utf8');
|
|
305
|
+
} catch {
|
|
306
|
+
// ignore
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
await fs.mkdir(cacheDir, { recursive: true });
|
|
310
|
+
await fs.writeFile(svelteMetadataPath, currentSvelteMetadata);
|
|
311
|
+
return currentSvelteMetadata !== existingSvelteMetadata;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
*
|
|
316
|
+
* @param {string} name
|
|
317
|
+
* @returns {import('vite').Rollup.Plugin}
|
|
318
|
+
*/
|
|
319
|
+
function placeholderRolldownOptimizerPlugin(name) {
|
|
320
|
+
return {
|
|
321
|
+
name,
|
|
322
|
+
options() {},
|
|
323
|
+
buildStart() {},
|
|
324
|
+
buildEnd() {},
|
|
325
|
+
transform: { filter: { id: /^$/ }, handler() {} }
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* @param {import('../types/options.d.ts').ResolvedOptions} options
|
|
331
|
+
* @returns {Partial<import('../types/options.d.ts').ResolvedOptions>}
|
|
332
|
+
*/
|
|
333
|
+
function generateSvelteMetadata(options) {
|
|
334
|
+
/** @type {Record<string, any>} */
|
|
335
|
+
const metadata = {};
|
|
336
|
+
for (const key of PREBUNDLE_SENSITIVE_OPTIONS) {
|
|
337
|
+
metadata[key] = options[key];
|
|
338
|
+
}
|
|
339
|
+
return metadata;
|
|
340
|
+
}
|
package/src/types/compile.d.ts
CHANGED
|
@@ -1,14 +1,31 @@
|
|
|
1
1
|
import type { Processed, CompileResult } from 'svelte/compiler';
|
|
2
2
|
import type { SvelteRequest } from './id.d.ts';
|
|
3
3
|
import type { ResolvedOptions } from './options.d.ts';
|
|
4
|
-
import type { CustomPluginOptionsVite } from 'vite';
|
|
4
|
+
import type { CustomPluginOptionsVite, Rollup } from 'vite';
|
|
5
5
|
|
|
6
6
|
export type CompileSvelte = (
|
|
7
7
|
svelteRequest: SvelteRequest,
|
|
8
8
|
code: string,
|
|
9
|
-
options: Partial<ResolvedOptions
|
|
9
|
+
options: Partial<ResolvedOptions>,
|
|
10
|
+
preprocessed?: Processed
|
|
10
11
|
) => Promise<CompileData>;
|
|
11
12
|
|
|
13
|
+
export type PreprocessSvelte = (
|
|
14
|
+
svelteRequest: SvelteRequest,
|
|
15
|
+
code: string,
|
|
16
|
+
options: Partial<ResolvedOptions>
|
|
17
|
+
) => Promise<PreprocessTransformOutput | undefined>;
|
|
18
|
+
|
|
19
|
+
export interface PreprocessTransformOutput {
|
|
20
|
+
code: string;
|
|
21
|
+
map: Rollup.SourceMapInput;
|
|
22
|
+
meta: {
|
|
23
|
+
svelte: {
|
|
24
|
+
preprocessed: Processed;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
12
29
|
export interface Code {
|
|
13
30
|
code: string;
|
|
14
31
|
map?: any;
|
|
@@ -23,6 +40,7 @@ export interface Code {
|
|
|
23
40
|
export interface CompileData {
|
|
24
41
|
filename: string;
|
|
25
42
|
normalizedFilename: string;
|
|
43
|
+
cssId: string;
|
|
26
44
|
lang: string;
|
|
27
45
|
compiled: CompileResult;
|
|
28
46
|
ssr: boolean | undefined;
|
package/src/types/id.d.ts
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export type SvelteQueryTypes = 'style' | 'script' | 'preprocessed' | 'all';
|
|
1
|
+
export type SvelteQueryTypes = 'style';
|
|
4
2
|
|
|
5
3
|
export interface RequestQuery {
|
|
6
4
|
// our own
|
|
7
5
|
svelte?: boolean;
|
|
8
6
|
type?: SvelteQueryTypes;
|
|
9
|
-
sourcemap?: boolean;
|
|
10
|
-
compilerOptions?: Pick<
|
|
11
|
-
CompileOptions,
|
|
12
|
-
'generate' | 'dev' | 'css' | 'customElement' | 'immutable'
|
|
13
|
-
>;
|
|
14
7
|
// vite specific
|
|
15
8
|
url?: boolean;
|
|
16
9
|
raw?: boolean;
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import type { ResolvedOptions } from './options.d.ts';
|
|
2
|
+
import type { IdFilter, IdParser } from './id.d.ts';
|
|
3
|
+
import type { CompileSvelte } from './compile.d.ts';
|
|
4
|
+
import type { Environment } from 'vite';
|
|
5
|
+
// eslint-disable-next-line n/no-missing-import
|
|
6
|
+
import { VitePluginSvelteCache } from '../utils/vite-plugin-svelte-cache.js';
|
|
7
|
+
|
|
8
|
+
interface EnvContext {
|
|
9
|
+
environment: Environment;
|
|
10
|
+
}
|
|
2
11
|
|
|
3
12
|
export interface PluginAPI {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
options?: ResolvedOptions;
|
|
10
|
-
// TODO expose compile cache here so other utility plugins can use it
|
|
13
|
+
options: ResolvedOptions;
|
|
14
|
+
getEnvironmentCache: (arg: EnvContext) => VitePluginSvelteCache;
|
|
15
|
+
idFilter: IdFilter;
|
|
16
|
+
idParser: IdParser;
|
|
17
|
+
compileSvelte: CompileSvelte;
|
|
11
18
|
}
|