@rsvelte/vite-plugin-svelte 0.1.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/LICENSE +21 -0
- package/README.md +28 -0
- package/package.json +59 -0
- package/src/index.js +42 -0
- package/src/plugins/compile-module.js +130 -0
- package/src/plugins/compile.js +65 -0
- package/src/plugins/configure.js +97 -0
- package/src/plugins/hot-update.js +196 -0
- package/src/plugins/load-compiled-css.js +66 -0
- package/src/plugins/load-custom.js +42 -0
- package/src/plugins/preprocess.js +208 -0
- package/src/plugins/setup-optimizer.js +340 -0
- package/src/preprocess.js +166 -0
- package/src/public.d.ts +217 -0
- package/src/types/compile.d.ts +37 -0
- package/src/types/id.d.ts +46 -0
- package/src/types/log.d.ts +24 -0
- package/src/types/options.d.ts +21 -0
- package/src/types/plugin-api.d.ts +15 -0
- package/src/types/vite-plugin-svelte-stats.d.ts +30 -0
- package/src/utils/compile.js +152 -0
- package/src/utils/constants.js +36 -0
- package/src/utils/dependencies.js +60 -0
- package/src/utils/error.js +167 -0
- package/src/utils/hash.js +43 -0
- package/src/utils/id.js +274 -0
- package/src/utils/load-svelte-config.js +80 -0
- package/src/utils/log.js +256 -0
- package/src/utils/options.js +618 -0
- package/src/utils/preprocess.js +89 -0
- package/src/utils/sourcemaps.js +82 -0
- package/src/utils/svelte-version.js +46 -0
- package/src/utils/vite-plugin-svelte-stats.js +248 -0
- package/src/utils/watch.js +95 -0
- package/types/index.d.ts +228 -0
- package/types/index.d.ts.map +31 -0
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
let useLocalCache = false;
|
|
12
|
+
|
|
13
|
+
/** @type{Map<string,any>} */
|
|
14
|
+
const buildWatchCssCache = new Map();
|
|
15
|
+
return {
|
|
16
|
+
name: 'vite-plugin-svelte:load-compiled-css',
|
|
17
|
+
|
|
18
|
+
configResolved(c) {
|
|
19
|
+
const isDev = c.command === 'serve';
|
|
20
|
+
const isBuildWatch = !!c.build?.watch;
|
|
21
|
+
useLocalCache = isDev || isBuildWatch;
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
resolveId: {
|
|
25
|
+
filter, // same filter in load to ensure minimal work
|
|
26
|
+
handler(id) {
|
|
27
|
+
log.debug(`resolveId resolved virtual css module ${id}`, undefined, 'resolve');
|
|
28
|
+
return id;
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
load: {
|
|
32
|
+
filter,
|
|
33
|
+
async handler(id) {
|
|
34
|
+
const ssr = this.environment.config.consumer === 'server';
|
|
35
|
+
const svelteRequest = api.idParser(id, ssr);
|
|
36
|
+
if (!svelteRequest) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
let cachedCss = this.getModuleInfo(svelteRequest.filename)?.meta.svelte?.css;
|
|
40
|
+
// in `build --watch` or dev ssr reloads getModuleInfo only returns changed module data.
|
|
41
|
+
// To ensure virtual css is loaded unchanged, we cache it here separately
|
|
42
|
+
if (useLocalCache) {
|
|
43
|
+
if (cachedCss) {
|
|
44
|
+
buildWatchCssCache.set(svelteRequest.filename, cachedCss);
|
|
45
|
+
} else {
|
|
46
|
+
cachedCss = buildWatchCssCache.get(svelteRequest.filename);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (cachedCss) {
|
|
51
|
+
const { hasGlobal, ...css } = cachedCss;
|
|
52
|
+
if (hasGlobal === false) {
|
|
53
|
+
// hasGlobal was added in svelte 5.26.0, so make sure it is boolean false
|
|
54
|
+
css.meta ??= {};
|
|
55
|
+
css.meta.vite ??= {};
|
|
56
|
+
css.meta.vite.cssScopeTo = [svelteRequest.filename, 'default'];
|
|
57
|
+
}
|
|
58
|
+
css.moduleType = 'css';
|
|
59
|
+
return css;
|
|
60
|
+
} else {
|
|
61
|
+
log.warn(`failed to load virtual css module ${id}`, undefined, 'load');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -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.filter;
|
|
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,208 @@
|
|
|
1
|
+
import { toRollupError } from '../utils/error.js';
|
|
2
|
+
import { mapToRelative } from '../utils/sourcemaps.js';
|
|
3
|
+
import * as svelte from '@rsvelte/compiler';
|
|
4
|
+
import { log } from '../utils/log.js';
|
|
5
|
+
import { arraify } from '../utils/options.js';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {import('../types/plugin-api.d.ts').PluginAPI} api
|
|
11
|
+
* @returns {import('vite').Plugin}
|
|
12
|
+
*/
|
|
13
|
+
export function preprocess(api) {
|
|
14
|
+
/**
|
|
15
|
+
* @type {import("../types/options.js").ResolvedOptions}
|
|
16
|
+
*/
|
|
17
|
+
let options;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @type {DependenciesCache}
|
|
21
|
+
*/
|
|
22
|
+
let dependenciesCache;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @type {import("../types/compile.d.ts").PreprocessSvelte}
|
|
26
|
+
*/
|
|
27
|
+
let preprocessSvelte;
|
|
28
|
+
|
|
29
|
+
/** @type {import('vite').Plugin} */
|
|
30
|
+
const plugin = {
|
|
31
|
+
name: 'vite-plugin-svelte:preprocess',
|
|
32
|
+
enforce: 'pre',
|
|
33
|
+
configResolved(c) {
|
|
34
|
+
options = api.options;
|
|
35
|
+
if (arraify(options.preprocess).length > 0) {
|
|
36
|
+
preprocessSvelte = createPreprocessSvelte(options, c);
|
|
37
|
+
// @ts-expect-error defined below but filter not in type
|
|
38
|
+
plugin.transform.filter = api.filter;
|
|
39
|
+
} else {
|
|
40
|
+
log.debug(
|
|
41
|
+
`disabling ${plugin.name} because no preprocessor is configured`,
|
|
42
|
+
undefined,
|
|
43
|
+
'preprocess'
|
|
44
|
+
);
|
|
45
|
+
// @ts-expect-error force set undefined to clear memory
|
|
46
|
+
preprocessSvelte = undefined;
|
|
47
|
+
// @ts-expect-error defined below but filter not in type
|
|
48
|
+
plugin.transform.filter = { id: /$./ }; // never match
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
configureServer(server) {
|
|
52
|
+
dependenciesCache = new DependenciesCache(server);
|
|
53
|
+
},
|
|
54
|
+
buildStart() {
|
|
55
|
+
dependenciesCache?.clear();
|
|
56
|
+
},
|
|
57
|
+
transform: {
|
|
58
|
+
async handler(code, id) {
|
|
59
|
+
const ssr = this.environment.config.consumer === 'server';
|
|
60
|
+
const svelteRequest = api.idParser(id, ssr);
|
|
61
|
+
if (!svelteRequest) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const preprocessed = await preprocessSvelte(svelteRequest, code, options);
|
|
66
|
+
dependenciesCache?.update(svelteRequest, preprocessed?.dependencies ?? []);
|
|
67
|
+
if (!preprocessed) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (options.isBuild && this.environment.config.build.watch && preprocessed.dependencies) {
|
|
71
|
+
for (const dep of preprocessed.dependencies) {
|
|
72
|
+
this.addWatchFile(dep);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** @type {import('vite').Rollup.SourceDescription}*/
|
|
77
|
+
const result = { code: preprocessed.code };
|
|
78
|
+
if (preprocessed.map) {
|
|
79
|
+
// @ts-expect-error type differs but should work
|
|
80
|
+
result.map = preprocessed.map;
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
} catch (e) {
|
|
84
|
+
throw toRollupError(e, options);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
return plugin;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* @param {import("../types/options.js").ResolvedOptions} options
|
|
93
|
+
* @param {import("vite").ResolvedConfig} resolvedConfig
|
|
94
|
+
* @returns {import('../types/compile.d.ts').PreprocessSvelte}
|
|
95
|
+
*/
|
|
96
|
+
function createPreprocessSvelte(options, resolvedConfig) {
|
|
97
|
+
/** @type {Array<import('@rsvelte/compiler').PreprocessorGroup>} */
|
|
98
|
+
const preprocessors = arraify(options.preprocess);
|
|
99
|
+
|
|
100
|
+
for (const preprocessor of preprocessors) {
|
|
101
|
+
if (preprocessor.style && '__resolvedConfig' in preprocessor.style) {
|
|
102
|
+
preprocessor.style.__resolvedConfig = resolvedConfig;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** @type {import('../types/compile.d.ts').PreprocessSvelte} */
|
|
107
|
+
return async function preprocessSvelte(svelteRequest, code) {
|
|
108
|
+
const { filename } = svelteRequest;
|
|
109
|
+
let preprocessed;
|
|
110
|
+
if (preprocessors && preprocessors.length > 0) {
|
|
111
|
+
try {
|
|
112
|
+
preprocessed = await svelte.preprocess(code, preprocessors, { filename }); // full filename here so postcss works
|
|
113
|
+
} catch (e) {
|
|
114
|
+
e.message = `Error while preprocessing ${filename}${e.message ? ` - ${e.message}` : ''}`;
|
|
115
|
+
throw e;
|
|
116
|
+
}
|
|
117
|
+
if (typeof preprocessed?.map === 'object') {
|
|
118
|
+
mapToRelative(preprocessed?.map, filename);
|
|
119
|
+
}
|
|
120
|
+
return preprocessed;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @class
|
|
127
|
+
*
|
|
128
|
+
* caches dependencies of preprocessed files and emit change events on dependants
|
|
129
|
+
*/
|
|
130
|
+
class DependenciesCache {
|
|
131
|
+
/** @type {Map<string, string[]>} */
|
|
132
|
+
#dependencies = new Map();
|
|
133
|
+
/** @type {Map<string, Set<string>>} */
|
|
134
|
+
#dependants = new Map();
|
|
135
|
+
|
|
136
|
+
/** @type {import('vite').ViteDevServer} */
|
|
137
|
+
#server;
|
|
138
|
+
/**
|
|
139
|
+
*
|
|
140
|
+
* @param {import('vite').ViteDevServer} server
|
|
141
|
+
*/
|
|
142
|
+
constructor(server) {
|
|
143
|
+
this.#server = server;
|
|
144
|
+
/** @type {(filename: string) => void} */
|
|
145
|
+
const emitChangeEventOnDependants = (filename) => {
|
|
146
|
+
const dependants = this.#dependants.get(filename);
|
|
147
|
+
dependants?.forEach((dependant) => {
|
|
148
|
+
if (fs.existsSync(dependant)) {
|
|
149
|
+
log.debug(
|
|
150
|
+
`emitting virtual change event for "${dependant}" because dependency "${filename}" changed`,
|
|
151
|
+
undefined,
|
|
152
|
+
'hmr'
|
|
153
|
+
);
|
|
154
|
+
server.watcher.emit('change', dependant);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
server.watcher.on('change', emitChangeEventOnDependants);
|
|
159
|
+
server.watcher.on('unlink', emitChangeEventOnDependants);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @param {string} file
|
|
164
|
+
*/
|
|
165
|
+
#ensureWatchedFile(file) {
|
|
166
|
+
const root = this.#server.config.root;
|
|
167
|
+
if (
|
|
168
|
+
file &&
|
|
169
|
+
// only need to watch if out of root
|
|
170
|
+
!file.startsWith(root + '/') &&
|
|
171
|
+
// some rollup plugins use null bytes for private resolved Ids
|
|
172
|
+
!file.includes('\0') &&
|
|
173
|
+
fs.existsSync(file)
|
|
174
|
+
) {
|
|
175
|
+
// resolve file to normalized system path
|
|
176
|
+
this.#server.watcher.add(path.resolve(file));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
clear() {
|
|
181
|
+
this.#dependencies.clear();
|
|
182
|
+
this.#dependants.clear();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
*
|
|
187
|
+
* @param {import('../types/id.d.ts').SvelteRequest} svelteRequest
|
|
188
|
+
* @param {string[]} dependencies
|
|
189
|
+
*/
|
|
190
|
+
update(svelteRequest, dependencies) {
|
|
191
|
+
const id = svelteRequest.normalizedFilename;
|
|
192
|
+
const prevDependencies = this.#dependencies.get(id) || [];
|
|
193
|
+
|
|
194
|
+
this.#dependencies.set(id, dependencies);
|
|
195
|
+
const removed = prevDependencies.filter((d) => !dependencies.includes(d));
|
|
196
|
+
const added = dependencies.filter((d) => !prevDependencies.includes(d));
|
|
197
|
+
added.forEach((d) => {
|
|
198
|
+
this.#ensureWatchedFile(d);
|
|
199
|
+
if (!this.#dependants.has(d)) {
|
|
200
|
+
this.#dependants.set(d, new Set());
|
|
201
|
+
}
|
|
202
|
+
/** @type {Set<string>} */ (this.#dependants.get(d)).add(svelteRequest.filename);
|
|
203
|
+
});
|
|
204
|
+
removed.forEach((d) => {
|
|
205
|
+
/** @type {Set<string>} */ (this.#dependants.get(d)).delete(svelteRequest.filename);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import * as svelte from '@rsvelte/compiler';
|
|
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-ignore not typed on vite
|
|
11
|
+
const { rolldownVersion } = vite;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {NonNullable<import('vite').DepOptimizationOptions['esbuildOptions']>} EsbuildOptions
|
|
15
|
+
* @typedef {NonNullable<EsbuildOptions['plugins']>[number]} EsbuildPlugin
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {NonNullable<import('vite').Rollup.Plugin>} RollupPlugin
|
|
19
|
+
*/
|
|
20
|
+
|
|
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-ignore rolldown types not finished
|
|
46
|
+
optimizeDeps.rolldownOptions = {
|
|
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.rolldownOptions?.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
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {EsbuildPlugin} plugin
|
|
98
|
+
* @param {import('../types/options.d.ts').ResolvedOptions} options
|
|
99
|
+
*/
|
|
100
|
+
function patchESBuildOptimizerPlugin(plugin, options) {
|
|
101
|
+
const components = plugin.name === optimizeSveltePluginName;
|
|
102
|
+
const compileFn = components ? compileSvelte : compileSvelteModule;
|
|
103
|
+
const statsName = components ? 'prebundle library components' : 'prebundle library modules';
|
|
104
|
+
const filter = components ? /\.svelte(?:\?.*)?$/ : /\.svelte\.[jt]s(?:\?.*)?$/;
|
|
105
|
+
plugin.setup = (build) => {
|
|
106
|
+
if (build.initialOptions.plugins?.some((v) => v.name === 'vite:dep-scan')) return;
|
|
107
|
+
|
|
108
|
+
/** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */
|
|
109
|
+
let statsCollection;
|
|
110
|
+
build.onStart(() => {
|
|
111
|
+
statsCollection = options.stats?.startCollection(statsName, {
|
|
112
|
+
logResult: (c) => c.stats.length > 1
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
build.onLoad({ filter }, async ({ path: filename }) => {
|
|
116
|
+
const code = readFileSync(filename, 'utf8');
|
|
117
|
+
try {
|
|
118
|
+
const result = await compileFn(options, { filename, code }, statsCollection);
|
|
119
|
+
const contents = result.map
|
|
120
|
+
? result.code + '//# sourceMappingURL=' + result.map.toUrl()
|
|
121
|
+
: result.code;
|
|
122
|
+
return { contents };
|
|
123
|
+
} catch (e) {
|
|
124
|
+
return { errors: [toESBuildError(e, options)] };
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
build.onEnd(() => {
|
|
128
|
+
statsCollection?.finish();
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @param {RollupPlugin} plugin
|
|
135
|
+
* @param {import('../types/options.d.ts').ResolvedOptions} options
|
|
136
|
+
*/
|
|
137
|
+
function patchRolldownOptimizerPlugin(plugin, options) {
|
|
138
|
+
const components = plugin.name === optimizeSveltePluginName;
|
|
139
|
+
const compileFn = components ? compileSvelte : compileSvelteModule;
|
|
140
|
+
const statsName = components ? 'prebundle library components' : 'prebundle library modules';
|
|
141
|
+
const includeRe = components ? /^[^?#]+\.svelte(?:[?#]|$)/ : /^[^?#]+\.svelte\.[jt]s(?:[?#]|$)/;
|
|
142
|
+
/** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */
|
|
143
|
+
let statsCollection;
|
|
144
|
+
|
|
145
|
+
plugin.options = (opts) => {
|
|
146
|
+
// @ts-expect-error plugins is an array here
|
|
147
|
+
const isScanner = opts.plugins.some(
|
|
148
|
+
(/** @type {{ name: string; }} */ p) => p.name === 'vite:dep-scan:resolve'
|
|
149
|
+
);
|
|
150
|
+
if (isScanner) {
|
|
151
|
+
delete plugin.buildStart;
|
|
152
|
+
delete plugin.transform;
|
|
153
|
+
delete plugin.buildEnd;
|
|
154
|
+
} else {
|
|
155
|
+
plugin.transform = {
|
|
156
|
+
filter: { id: includeRe },
|
|
157
|
+
/**
|
|
158
|
+
* @param {string} code
|
|
159
|
+
* @param {string} filename
|
|
160
|
+
*/
|
|
161
|
+
async handler(code, filename) {
|
|
162
|
+
try {
|
|
163
|
+
return await compileFn(options, { filename, code }, statsCollection);
|
|
164
|
+
} catch (e) {
|
|
165
|
+
throw toRollupError(e, options);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
plugin.buildStart = () => {
|
|
170
|
+
statsCollection = options.stats?.startCollection(statsName, {
|
|
171
|
+
logResult: (c) => c.stats.length > 1
|
|
172
|
+
});
|
|
173
|
+
};
|
|
174
|
+
plugin.buildEnd = () => {
|
|
175
|
+
statsCollection?.finish();
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @param {import('../types/options.d.ts').ResolvedOptions} options
|
|
183
|
+
* @param {{ filename: string, code: string }} input
|
|
184
|
+
* @param {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection} [statsCollection]
|
|
185
|
+
* @returns {Promise<import('../types/compile.d.ts').Code>}
|
|
186
|
+
*/
|
|
187
|
+
async function compileSvelte(options, { filename, code }, statsCollection) {
|
|
188
|
+
let css = options.compilerOptions.css;
|
|
189
|
+
if (css !== 'injected') {
|
|
190
|
+
// TODO ideally we'd be able to externalize prebundled styles too, but for now always put them in the js
|
|
191
|
+
css = 'injected';
|
|
192
|
+
}
|
|
193
|
+
/** @type {import('@rsvelte/compiler').CompileOptions} */
|
|
194
|
+
const compileOptions = {
|
|
195
|
+
dev: true, // default to dev: true because prebundling is only used in dev
|
|
196
|
+
...options.compilerOptions,
|
|
197
|
+
css,
|
|
198
|
+
filename,
|
|
199
|
+
generate: 'client'
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
if (compileOptions.hmr && options.emitCss) {
|
|
203
|
+
const hash = `s-${safeBase64Hash(normalize(filename, options.root))}`;
|
|
204
|
+
compileOptions.cssHash = () => hash;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let preprocessed;
|
|
208
|
+
|
|
209
|
+
if (options.preprocess) {
|
|
210
|
+
try {
|
|
211
|
+
preprocessed = await svelte.preprocess(code, options.preprocess, { filename });
|
|
212
|
+
} catch (e) {
|
|
213
|
+
e.message = `Error while preprocessing ${filename}${e.message ? ` - ${e.message}` : ''}`;
|
|
214
|
+
throw e;
|
|
215
|
+
}
|
|
216
|
+
if (preprocessed.map) compileOptions.sourcemap = preprocessed.map;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const finalCode = preprocessed ? preprocessed.code : code;
|
|
220
|
+
|
|
221
|
+
const dynamicCompileOptions = await options?.dynamicCompileOptions?.({
|
|
222
|
+
filename,
|
|
223
|
+
code: finalCode,
|
|
224
|
+
compileOptions
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
if (dynamicCompileOptions && log.debug.enabled) {
|
|
228
|
+
log.debug(
|
|
229
|
+
`dynamic compile options for ${filename}: ${JSON.stringify(dynamicCompileOptions)}`,
|
|
230
|
+
undefined,
|
|
231
|
+
'compile'
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const finalCompileOptions = dynamicCompileOptions
|
|
236
|
+
? {
|
|
237
|
+
...compileOptions,
|
|
238
|
+
...dynamicCompileOptions
|
|
239
|
+
}
|
|
240
|
+
: compileOptions;
|
|
241
|
+
const endStat = statsCollection?.start(filename);
|
|
242
|
+
const compiled = svelte.compile(finalCode, finalCompileOptions);
|
|
243
|
+
if (endStat) {
|
|
244
|
+
endStat();
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
...compiled.js,
|
|
248
|
+
moduleType: 'js'
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* @param {import('../types/options.d.ts').ResolvedOptions} options
|
|
254
|
+
* @param {{ filename: string; code: string }} input
|
|
255
|
+
* @param {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection} [statsCollection]
|
|
256
|
+
* @returns {Promise<import('../types/compile.d.ts').Code>}
|
|
257
|
+
*/
|
|
258
|
+
async function compileSvelteModule(options, { filename, code }, statsCollection) {
|
|
259
|
+
const endStat = statsCollection?.start(filename);
|
|
260
|
+
const compiled = svelte.compileModule(code, {
|
|
261
|
+
dev: options.compilerOptions?.dev ?? true, // default to dev: true because prebundling is only used in dev
|
|
262
|
+
filename,
|
|
263
|
+
generate: 'client'
|
|
264
|
+
});
|
|
265
|
+
if (endStat) {
|
|
266
|
+
endStat();
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
...compiled.js,
|
|
270
|
+
moduleType: 'js'
|
|
271
|
+
};
|
|
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
|
+
}
|