@sveltejs/vite-plugin-svelte 1.0.0-next.9 → 1.0.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/README.md +16 -50
- package/dist/index.cjs +2014 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +231 -214
- package/dist/index.js +1522 -395
- package/dist/index.js.map +1 -0
- package/package.json +48 -33
- package/src/handle-hot-update.ts +117 -0
- package/src/index.ts +242 -0
- package/src/ui/inspector/Inspector.svelte +245 -0
- package/src/ui/inspector/load-inspector.js +15 -0
- package/src/ui/inspector/plugin.ts +106 -0
- package/src/utils/__tests__/dependencies.spec.ts +43 -0
- package/src/utils/__tests__/sourcemap.spec.ts +25 -0
- package/src/utils/compile.ts +159 -0
- package/src/utils/constants.ts +20 -0
- package/src/utils/dependencies.ts +241 -0
- package/src/utils/error.ts +95 -0
- package/src/utils/esbuild.ts +84 -0
- package/src/utils/hash.ts +32 -0
- package/src/utils/id.ts +135 -0
- package/src/utils/load-svelte-config.ts +115 -0
- package/src/utils/log.ts +211 -0
- package/src/utils/optimizer.ts +45 -0
- package/src/utils/options.ts +707 -0
- package/src/utils/preprocess.ts +252 -0
- package/src/utils/resolve.ts +57 -0
- package/src/utils/sourcemap.ts +58 -0
- package/src/utils/vite-plugin-svelte-cache.ts +127 -0
- package/src/utils/watch.ts +110 -0
- package/CHANGELOG.md +0 -44
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { CompileOptions, ResolvedOptions } from './options';
|
|
2
|
+
import { compile, preprocess, walk } from 'svelte/compiler';
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
import { createMakeHot } from 'svelte-hmr';
|
|
5
|
+
import { SvelteRequest } from './id';
|
|
6
|
+
import { safeBase64Hash } from './hash';
|
|
7
|
+
import { log } from './log';
|
|
8
|
+
|
|
9
|
+
const scriptLangRE = /<script [^>]*lang=["']?([^"' >]+)["']?[^>]*>/;
|
|
10
|
+
|
|
11
|
+
const _createCompileSvelte = (makeHot: Function) =>
|
|
12
|
+
async function compileSvelte(
|
|
13
|
+
svelteRequest: SvelteRequest,
|
|
14
|
+
code: string,
|
|
15
|
+
options: Partial<ResolvedOptions>
|
|
16
|
+
): Promise<CompileData> {
|
|
17
|
+
const { filename, normalizedFilename, cssId, ssr } = svelteRequest;
|
|
18
|
+
const { emitCss = true } = options;
|
|
19
|
+
const dependencies = [];
|
|
20
|
+
|
|
21
|
+
const compileOptions: CompileOptions = {
|
|
22
|
+
...options.compilerOptions,
|
|
23
|
+
filename,
|
|
24
|
+
generate: ssr ? 'ssr' : 'dom',
|
|
25
|
+
format: 'esm'
|
|
26
|
+
};
|
|
27
|
+
if (options.hot && options.emitCss) {
|
|
28
|
+
const hash = `s-${safeBase64Hash(normalizedFilename)}`;
|
|
29
|
+
log.debug(`setting cssHash ${hash} for ${normalizedFilename}`);
|
|
30
|
+
compileOptions.cssHash = () => hash;
|
|
31
|
+
}
|
|
32
|
+
if (ssr && compileOptions.enableSourcemap !== false) {
|
|
33
|
+
if (typeof compileOptions.enableSourcemap === 'object') {
|
|
34
|
+
compileOptions.enableSourcemap.css = false;
|
|
35
|
+
} else {
|
|
36
|
+
compileOptions.enableSourcemap = { js: true, css: false };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let preprocessed;
|
|
41
|
+
|
|
42
|
+
if (options.preprocess) {
|
|
43
|
+
try {
|
|
44
|
+
preprocessed = await preprocess(code, options.preprocess, { filename });
|
|
45
|
+
} catch (e) {
|
|
46
|
+
e.message = `Error while preprocessing ${filename}${e.message ? ` - ${e.message}` : ''}`;
|
|
47
|
+
throw e;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (preprocessed.dependencies) dependencies.push(...preprocessed.dependencies);
|
|
51
|
+
if (preprocessed.map) compileOptions.sourcemap = preprocessed.map;
|
|
52
|
+
}
|
|
53
|
+
const finalCode = preprocessed ? preprocessed.code : code;
|
|
54
|
+
const dynamicCompileOptions = await options.experimental?.dynamicCompileOptions?.({
|
|
55
|
+
filename,
|
|
56
|
+
code: finalCode,
|
|
57
|
+
compileOptions
|
|
58
|
+
});
|
|
59
|
+
if (dynamicCompileOptions && log.debug.enabled) {
|
|
60
|
+
log.debug(
|
|
61
|
+
`dynamic compile options for ${filename}: ${JSON.stringify(dynamicCompileOptions)}`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
const finalCompileOptions = dynamicCompileOptions
|
|
65
|
+
? {
|
|
66
|
+
...compileOptions,
|
|
67
|
+
...dynamicCompileOptions
|
|
68
|
+
}
|
|
69
|
+
: compileOptions;
|
|
70
|
+
const compiled = compile(finalCode, finalCompileOptions);
|
|
71
|
+
|
|
72
|
+
if (emitCss && compiled.css.code) {
|
|
73
|
+
// TODO properly update sourcemap?
|
|
74
|
+
compiled.js.code += `\nimport ${JSON.stringify(cssId)};\n`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// only apply hmr when not in ssr context and hot options are set
|
|
78
|
+
if (!ssr && makeHot) {
|
|
79
|
+
compiled.js.code = makeHot({
|
|
80
|
+
id: filename,
|
|
81
|
+
compiledCode: compiled.js.code,
|
|
82
|
+
hotOptions: options.hot,
|
|
83
|
+
compiled,
|
|
84
|
+
originalCode: code,
|
|
85
|
+
compileOptions: finalCompileOptions
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
compiled.js.dependencies = dependencies;
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
filename,
|
|
93
|
+
normalizedFilename,
|
|
94
|
+
lang: code.match(scriptLangRE)?.[1] || 'js',
|
|
95
|
+
// @ts-ignore
|
|
96
|
+
compiled,
|
|
97
|
+
ssr,
|
|
98
|
+
dependencies
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
function buildMakeHot(options: ResolvedOptions) {
|
|
103
|
+
const needsMakeHot = options.hot !== false && options.isServe && !options.isProduction;
|
|
104
|
+
if (needsMakeHot) {
|
|
105
|
+
// @ts-ignore
|
|
106
|
+
const hotApi = options?.hot?.hotApi;
|
|
107
|
+
// @ts-ignore
|
|
108
|
+
const adapter = options?.hot?.adapter;
|
|
109
|
+
return createMakeHot({
|
|
110
|
+
walk,
|
|
111
|
+
hotApi,
|
|
112
|
+
adapter,
|
|
113
|
+
hotOptions: { noOverlay: true, ...(options.hot as object) }
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function createCompileSvelte(options: ResolvedOptions) {
|
|
119
|
+
const makeHot = buildMakeHot(options);
|
|
120
|
+
return _createCompileSvelte(makeHot);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface Code {
|
|
124
|
+
code: string;
|
|
125
|
+
map?: any;
|
|
126
|
+
dependencies?: any[];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface Compiled {
|
|
130
|
+
js: Code;
|
|
131
|
+
css: Code;
|
|
132
|
+
ast: any; // TODO type
|
|
133
|
+
warnings: any[]; // TODO type
|
|
134
|
+
vars: {
|
|
135
|
+
name: string;
|
|
136
|
+
export_name: string;
|
|
137
|
+
injected: boolean;
|
|
138
|
+
module: boolean;
|
|
139
|
+
mutated: boolean;
|
|
140
|
+
reassigned: boolean;
|
|
141
|
+
referenced: boolean;
|
|
142
|
+
writable: boolean;
|
|
143
|
+
referenced_from_script: boolean;
|
|
144
|
+
}[];
|
|
145
|
+
stats: {
|
|
146
|
+
timings: {
|
|
147
|
+
total: number;
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface CompileData {
|
|
153
|
+
filename: string;
|
|
154
|
+
normalizedFilename: string;
|
|
155
|
+
lang: string;
|
|
156
|
+
compiled: Compiled;
|
|
157
|
+
ssr: boolean | undefined;
|
|
158
|
+
dependencies: string[];
|
|
159
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const VITE_RESOLVE_MAIN_FIELDS = ['module', 'jsnext:main', 'jsnext'];
|
|
2
|
+
|
|
3
|
+
export const SVELTE_RESOLVE_MAIN_FIELDS = ['svelte', ...VITE_RESOLVE_MAIN_FIELDS];
|
|
4
|
+
|
|
5
|
+
export const SVELTE_IMPORTS = [
|
|
6
|
+
'svelte/animate',
|
|
7
|
+
'svelte/easing',
|
|
8
|
+
'svelte/internal',
|
|
9
|
+
'svelte/motion',
|
|
10
|
+
'svelte/ssr',
|
|
11
|
+
'svelte/store',
|
|
12
|
+
'svelte/transition',
|
|
13
|
+
'svelte'
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export const SVELTE_HMR_IMPORTS = [
|
|
17
|
+
'svelte-hmr/runtime/hot-api-esm.js',
|
|
18
|
+
'svelte-hmr/runtime/proxy-adapter-dom.js',
|
|
19
|
+
'svelte-hmr'
|
|
20
|
+
];
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { log } from './log';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { createRequire } from 'module';
|
|
5
|
+
|
|
6
|
+
export function findRootSvelteDependencies(root: string, cwdFallback = true): SvelteDependency[] {
|
|
7
|
+
log.debug(`findSvelteDependencies: searching svelte dependencies in ${root}`);
|
|
8
|
+
const pkgFile = path.join(root, 'package.json');
|
|
9
|
+
if (!fs.existsSync(pkgFile)) {
|
|
10
|
+
if (cwdFallback) {
|
|
11
|
+
const cwd = process.cwd();
|
|
12
|
+
if (root !== cwd) {
|
|
13
|
+
log.debug(`no package.json found in vite root ${root}`);
|
|
14
|
+
return findRootSvelteDependencies(cwd, false);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
log.warn(`no package.json found, findRootSvelteDependencies failed`);
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const pkg = parsePkg(root);
|
|
22
|
+
if (!pkg) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const deps = [
|
|
27
|
+
...Object.keys(pkg.dependencies || {}),
|
|
28
|
+
...Object.keys(pkg.devDependencies || {})
|
|
29
|
+
].filter((dep) => !is_common_without_svelte_field(dep));
|
|
30
|
+
|
|
31
|
+
return getSvelteDependencies(deps, root);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getSvelteDependencies(
|
|
35
|
+
deps: string[],
|
|
36
|
+
pkgDir: string,
|
|
37
|
+
path: string[] = []
|
|
38
|
+
): SvelteDependency[] {
|
|
39
|
+
const result = [];
|
|
40
|
+
const localRequire = createRequire(`${pkgDir}/package.json`);
|
|
41
|
+
const resolvedDeps = deps
|
|
42
|
+
.map((dep) => resolveDependencyData(dep, localRequire))
|
|
43
|
+
.filter(Boolean) as DependencyData[];
|
|
44
|
+
for (const { pkg, dir } of resolvedDeps) {
|
|
45
|
+
const type = getSvelteDependencyType(pkg);
|
|
46
|
+
if (!type) continue;
|
|
47
|
+
result.push({ name: pkg.name, type, pkg, dir, path });
|
|
48
|
+
// continue crawling for component libraries so we can optimize them, js libraries are fine
|
|
49
|
+
if (type === 'component-library' && pkg.dependencies) {
|
|
50
|
+
let dependencyNames = Object.keys(pkg.dependencies);
|
|
51
|
+
const circular = dependencyNames.filter((name) => path.includes(name));
|
|
52
|
+
if (circular.length > 0) {
|
|
53
|
+
log.warn.enabled &&
|
|
54
|
+
log.warn(
|
|
55
|
+
`skipping circular svelte dependencies in automated vite optimizeDeps handling`,
|
|
56
|
+
circular.map((x) => path.concat(x).join('>'))
|
|
57
|
+
);
|
|
58
|
+
dependencyNames = dependencyNames.filter((name) => !path.includes(name));
|
|
59
|
+
}
|
|
60
|
+
if (path.length === 3) {
|
|
61
|
+
log.debug.once(`encountered deep svelte dependency tree: ${path.join('>')}`);
|
|
62
|
+
}
|
|
63
|
+
result.push(...getSvelteDependencies(dependencyNames, dir, path.concat(pkg.name)));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function resolveDependencyData(
|
|
70
|
+
dep: string,
|
|
71
|
+
localRequire: NodeRequire
|
|
72
|
+
): DependencyData | void {
|
|
73
|
+
try {
|
|
74
|
+
const pkgJson = `${dep}/package.json`;
|
|
75
|
+
const pkg = localRequire(pkgJson);
|
|
76
|
+
const dir = path.dirname(localRequire.resolve(pkgJson));
|
|
77
|
+
return { dir, pkg };
|
|
78
|
+
} catch (e) {
|
|
79
|
+
log.debug.once(`dependency ${dep} does not export package.json`, e);
|
|
80
|
+
// walk up from default export until we find package.json with name=dep
|
|
81
|
+
try {
|
|
82
|
+
let dir = path.dirname(localRequire.resolve(dep));
|
|
83
|
+
while (dir) {
|
|
84
|
+
const pkg = parsePkg(dir, true);
|
|
85
|
+
if (pkg && pkg.name === dep) {
|
|
86
|
+
return { dir, pkg };
|
|
87
|
+
}
|
|
88
|
+
const parent = path.dirname(dir);
|
|
89
|
+
if (parent === dir) {
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
dir = parent;
|
|
93
|
+
}
|
|
94
|
+
} catch (e) {
|
|
95
|
+
log.debug.once(`error while trying to find package.json of ${dep}`, e);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
log.debug.once(`failed to resolve ${dep}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function parsePkg(dir: string, silent = false): Pkg | void {
|
|
102
|
+
const pkgFile = path.join(dir, 'package.json');
|
|
103
|
+
try {
|
|
104
|
+
return JSON.parse(fs.readFileSync(pkgFile, 'utf-8'));
|
|
105
|
+
} catch (e) {
|
|
106
|
+
!silent && log.warn.enabled && log.warn(`failed to parse ${pkgFile}`, e);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getSvelteDependencyType(pkg: Pkg): SvelteDependencyType | undefined {
|
|
111
|
+
if (isSvelteComponentLib(pkg)) {
|
|
112
|
+
return 'component-library';
|
|
113
|
+
} else if (isSvelteLib(pkg)) {
|
|
114
|
+
return 'js-library';
|
|
115
|
+
} else {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function isSvelteComponentLib(pkg: Pkg) {
|
|
121
|
+
return !!pkg.svelte;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isSvelteLib(pkg: Pkg) {
|
|
125
|
+
return !!pkg.dependencies?.svelte || !!pkg.peerDependencies?.svelte;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const COMMON_DEPENDENCIES_WITHOUT_SVELTE_FIELD = [
|
|
129
|
+
'@lukeed/uuid',
|
|
130
|
+
'@playwright/test',
|
|
131
|
+
'@sveltejs/vite-plugin-svelte',
|
|
132
|
+
'@sveltejs/kit',
|
|
133
|
+
'autoprefixer',
|
|
134
|
+
'cookie',
|
|
135
|
+
'dotenv',
|
|
136
|
+
'esbuild',
|
|
137
|
+
'eslint',
|
|
138
|
+
'jest',
|
|
139
|
+
'mdsvex',
|
|
140
|
+
'playwright',
|
|
141
|
+
'postcss',
|
|
142
|
+
'prettier',
|
|
143
|
+
'svelte',
|
|
144
|
+
'svelte-check',
|
|
145
|
+
'svelte-hmr',
|
|
146
|
+
'svelte-preprocess',
|
|
147
|
+
'tslib',
|
|
148
|
+
'typescript',
|
|
149
|
+
'vite',
|
|
150
|
+
'vitest',
|
|
151
|
+
'__vite-browser-external' // see https://github.com/sveltejs/vite-plugin-svelte/issues/362
|
|
152
|
+
];
|
|
153
|
+
const COMMON_PREFIXES_WITHOUT_SVELTE_FIELD = [
|
|
154
|
+
'@fontsource/',
|
|
155
|
+
'@postcss-plugins/',
|
|
156
|
+
'@rollup/',
|
|
157
|
+
'@sveltejs/adapter-',
|
|
158
|
+
'@types/',
|
|
159
|
+
'@typescript-eslint/',
|
|
160
|
+
'eslint-',
|
|
161
|
+
'jest-',
|
|
162
|
+
'postcss-plugin-',
|
|
163
|
+
'prettier-plugin-',
|
|
164
|
+
'rollup-plugin-',
|
|
165
|
+
'vite-plugin-'
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Test for common dependency names that tell us it is not a package including a svelte field, eg. eslint + plugins.
|
|
170
|
+
*
|
|
171
|
+
* This speeds up the find process as we don't have to try and require the package.json for all of them
|
|
172
|
+
*
|
|
173
|
+
* @param dependency {string}
|
|
174
|
+
* @returns {boolean} true if it is a dependency without a svelte field
|
|
175
|
+
*/
|
|
176
|
+
export function is_common_without_svelte_field(dependency: string): boolean {
|
|
177
|
+
return (
|
|
178
|
+
COMMON_DEPENDENCIES_WITHOUT_SVELTE_FIELD.includes(dependency) ||
|
|
179
|
+
COMMON_PREFIXES_WITHOUT_SVELTE_FIELD.some(
|
|
180
|
+
(prefix) =>
|
|
181
|
+
prefix.startsWith('@')
|
|
182
|
+
? dependency.startsWith(prefix)
|
|
183
|
+
: dependency.substring(dependency.lastIndexOf('/') + 1).startsWith(prefix) // check prefix omitting @scope/
|
|
184
|
+
)
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function needsOptimization(dep: string, localRequire: NodeRequire): boolean {
|
|
189
|
+
const depData = resolveDependencyData(dep, localRequire);
|
|
190
|
+
if (!depData) return false;
|
|
191
|
+
const pkg = depData.pkg;
|
|
192
|
+
// only optimize if is cjs, using the below as heuristic
|
|
193
|
+
// see https://github.com/sveltejs/vite-plugin-svelte/issues/162
|
|
194
|
+
const hasEsmFields = pkg.module || pkg.exports;
|
|
195
|
+
if (hasEsmFields) return false;
|
|
196
|
+
if (pkg.main) {
|
|
197
|
+
// ensure entry is js so vite can prebundle it
|
|
198
|
+
// see https://github.com/sveltejs/vite-plugin-svelte/issues/233
|
|
199
|
+
const entryExt = path.extname(pkg.main);
|
|
200
|
+
return !entryExt || entryExt === '.js' || entryExt === '.cjs';
|
|
201
|
+
} else {
|
|
202
|
+
// check if has implicit index.js entrypoint
|
|
203
|
+
// https://github.com/sveltejs/vite-plugin-svelte/issues/281
|
|
204
|
+
try {
|
|
205
|
+
localRequire.resolve(`${dep}/index.js`);
|
|
206
|
+
return true;
|
|
207
|
+
} catch {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
interface DependencyData {
|
|
214
|
+
dir: string;
|
|
215
|
+
pkg: Pkg;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface SvelteDependency {
|
|
219
|
+
name: string;
|
|
220
|
+
type: SvelteDependencyType;
|
|
221
|
+
dir: string;
|
|
222
|
+
pkg: Pkg;
|
|
223
|
+
path: string[];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// component-library => exports svelte components
|
|
227
|
+
// js-library => only uses svelte api, no components
|
|
228
|
+
export type SvelteDependencyType = 'component-library' | 'js-library';
|
|
229
|
+
|
|
230
|
+
export interface Pkg {
|
|
231
|
+
name: string;
|
|
232
|
+
svelte?: string;
|
|
233
|
+
dependencies?: DependencyList;
|
|
234
|
+
devDependencies?: DependencyList;
|
|
235
|
+
peerDependencies?: DependencyList;
|
|
236
|
+
[key: string]: any;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface DependencyList {
|
|
240
|
+
[key: string]: string;
|
|
241
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { RollupError } from 'rollup';
|
|
2
|
+
import { ResolvedOptions, Warning } from './options';
|
|
3
|
+
import { buildExtendedLogMessage } from './log';
|
|
4
|
+
import { PartialMessage } from 'esbuild';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* convert an error thrown by svelte.compile to a RollupError so that vite displays it in a user friendly way
|
|
8
|
+
* @param error a svelte compiler error, which is a mix of Warning and an error
|
|
9
|
+
* @returns {RollupError} the converted error
|
|
10
|
+
*/
|
|
11
|
+
export function toRollupError(error: Warning & Error, options: ResolvedOptions): RollupError {
|
|
12
|
+
const { filename, frame, start, code, name, stack } = error;
|
|
13
|
+
const rollupError: RollupError = {
|
|
14
|
+
name, // needed otherwise sveltekit coalesce_to_error turns it into a string
|
|
15
|
+
id: filename,
|
|
16
|
+
message: buildExtendedLogMessage(error), // include filename:line:column so that it's clickable
|
|
17
|
+
frame: formatFrameForVite(frame),
|
|
18
|
+
code,
|
|
19
|
+
stack: options.isBuild || options.isDebug || !frame ? stack : ''
|
|
20
|
+
};
|
|
21
|
+
if (start) {
|
|
22
|
+
rollupError.loc = {
|
|
23
|
+
line: start.line,
|
|
24
|
+
column: start.column,
|
|
25
|
+
file: filename
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return rollupError;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* convert an error thrown by svelte.compile to an esbuild PartialMessage
|
|
33
|
+
* @param error a svelte compiler error, which is a mix of Warning and an error
|
|
34
|
+
* @returns {PartialMessage} the converted error
|
|
35
|
+
*/
|
|
36
|
+
export function toESBuildError(error: Warning & Error, options: ResolvedOptions): PartialMessage {
|
|
37
|
+
const { filename, frame, start, stack } = error;
|
|
38
|
+
const partialMessage: PartialMessage = {
|
|
39
|
+
text: buildExtendedLogMessage(error)
|
|
40
|
+
};
|
|
41
|
+
if (start) {
|
|
42
|
+
partialMessage.location = {
|
|
43
|
+
line: start.line,
|
|
44
|
+
column: start.column,
|
|
45
|
+
file: filename,
|
|
46
|
+
lineText: lineFromFrame(start.line, frame) // needed to get a meaningful error message on cli
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (options.isBuild || options.isDebug || !frame) {
|
|
50
|
+
partialMessage.detail = stack;
|
|
51
|
+
}
|
|
52
|
+
return partialMessage;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* extract line with number from codeframe
|
|
57
|
+
*/
|
|
58
|
+
function lineFromFrame(lineNo: number, frame?: string): string {
|
|
59
|
+
if (!frame) {
|
|
60
|
+
return '';
|
|
61
|
+
}
|
|
62
|
+
const lines = frame.split('\n');
|
|
63
|
+
const errorLine = lines.find((line) => line.trimStart().startsWith(`${lineNo}: `));
|
|
64
|
+
return errorLine ? errorLine.substring(errorLine.indexOf(': ') + 3) : '';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* vite error overlay expects a specific format to show frames
|
|
69
|
+
* this reformats svelte frame (colon separated, less whitespace)
|
|
70
|
+
* to one that vite displays on overlay ( pipe separated, more whitespace)
|
|
71
|
+
* e.g.
|
|
72
|
+
* ```
|
|
73
|
+
* 1: foo
|
|
74
|
+
* 2: bar;
|
|
75
|
+
* ^
|
|
76
|
+
* 3: baz
|
|
77
|
+
* ```
|
|
78
|
+
* to
|
|
79
|
+
* ```
|
|
80
|
+
* 1 | foo
|
|
81
|
+
* 2 | bar;
|
|
82
|
+
* ^
|
|
83
|
+
* 3 | baz
|
|
84
|
+
* ```
|
|
85
|
+
* @see https://github.com/vitejs/vite/blob/96591bf9989529de839ba89958755eafe4c445ae/packages/vite/src/client/overlay.ts#L116
|
|
86
|
+
*/
|
|
87
|
+
function formatFrameForVite(frame?: string): string {
|
|
88
|
+
if (!frame) {
|
|
89
|
+
return '';
|
|
90
|
+
}
|
|
91
|
+
return frame
|
|
92
|
+
.split('\n')
|
|
93
|
+
.map((line) => (line.match(/^\s+\^/) ? ' ' + line : ' ' + line.replace(':', ' | ')))
|
|
94
|
+
.join('\n');
|
|
95
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { compile, preprocess } from 'svelte/compiler';
|
|
3
|
+
import { DepOptimizationOptions } from 'vite';
|
|
4
|
+
import { Compiled } from './compile';
|
|
5
|
+
import { log } from './log';
|
|
6
|
+
import { CompileOptions, ResolvedOptions } from './options';
|
|
7
|
+
import { toESBuildError } from './error';
|
|
8
|
+
|
|
9
|
+
type EsbuildOptions = NonNullable<DepOptimizationOptions['esbuildOptions']>;
|
|
10
|
+
type EsbuildPlugin = NonNullable<EsbuildOptions['plugins']>[number];
|
|
11
|
+
|
|
12
|
+
export const facadeEsbuildSveltePluginName = 'vite-plugin-svelte:facade';
|
|
13
|
+
|
|
14
|
+
export function esbuildSveltePlugin(options: ResolvedOptions): EsbuildPlugin {
|
|
15
|
+
return {
|
|
16
|
+
name: 'vite-plugin-svelte:optimize-svelte',
|
|
17
|
+
setup(build) {
|
|
18
|
+
// Skip in scanning phase as Vite already handles scanning Svelte files.
|
|
19
|
+
// Otherwise this would heavily slow down the scanning phase.
|
|
20
|
+
if (build.initialOptions.plugins?.some((v) => v.name === 'vite:dep-scan')) return;
|
|
21
|
+
|
|
22
|
+
const svelteExtensions = (options.extensions ?? ['.svelte']).map((ext) => ext.slice(1));
|
|
23
|
+
const svelteFilter = new RegExp(`\\.(` + svelteExtensions.join('|') + `)(\\?.*)?$`);
|
|
24
|
+
|
|
25
|
+
build.onLoad({ filter: svelteFilter }, async ({ path: filename }) => {
|
|
26
|
+
const code = await fs.readFile(filename, 'utf8');
|
|
27
|
+
try {
|
|
28
|
+
const contents = await compileSvelte(options, { filename, code });
|
|
29
|
+
return { contents };
|
|
30
|
+
} catch (e) {
|
|
31
|
+
return { errors: [toESBuildError(e, options)] };
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function compileSvelte(
|
|
39
|
+
options: ResolvedOptions,
|
|
40
|
+
{ filename, code }: { filename: string; code: string }
|
|
41
|
+
): Promise<string> {
|
|
42
|
+
const compileOptions: CompileOptions = {
|
|
43
|
+
...options.compilerOptions,
|
|
44
|
+
css: true,
|
|
45
|
+
filename,
|
|
46
|
+
format: 'esm',
|
|
47
|
+
generate: 'dom'
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
let preprocessed;
|
|
51
|
+
|
|
52
|
+
if (options.preprocess) {
|
|
53
|
+
try {
|
|
54
|
+
preprocessed = await preprocess(code, options.preprocess, { filename });
|
|
55
|
+
} catch (e) {
|
|
56
|
+
e.message = `Error while preprocessing ${filename}${e.message ? ` - ${e.message}` : ''}`;
|
|
57
|
+
throw e;
|
|
58
|
+
}
|
|
59
|
+
if (preprocessed.map) compileOptions.sourcemap = preprocessed.map;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const finalCode = preprocessed ? preprocessed.code : code;
|
|
63
|
+
|
|
64
|
+
const dynamicCompileOptions = await options.experimental?.dynamicCompileOptions?.({
|
|
65
|
+
filename,
|
|
66
|
+
code: finalCode,
|
|
67
|
+
compileOptions
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (dynamicCompileOptions && log.debug.enabled) {
|
|
71
|
+
log.debug(`dynamic compile options for ${filename}: ${JSON.stringify(dynamicCompileOptions)}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const finalCompileOptions = dynamicCompileOptions
|
|
75
|
+
? {
|
|
76
|
+
...compileOptions,
|
|
77
|
+
...dynamicCompileOptions
|
|
78
|
+
}
|
|
79
|
+
: compileOptions;
|
|
80
|
+
|
|
81
|
+
const compiled = compile(finalCode, finalCompileOptions) as Compiled;
|
|
82
|
+
|
|
83
|
+
return compiled.js.code + '//# sourceMappingURL=' + compiled.js.map.toUrl();
|
|
84
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
|
|
3
|
+
const hashes = Object.create(null);
|
|
4
|
+
|
|
5
|
+
//TODO shorter?
|
|
6
|
+
const hash_length = 12;
|
|
7
|
+
|
|
8
|
+
export function safeBase64Hash(input: string) {
|
|
9
|
+
if (hashes[input]) {
|
|
10
|
+
return hashes[input];
|
|
11
|
+
}
|
|
12
|
+
//TODO if performance really matters, use a faster one like xx-hash etc.
|
|
13
|
+
// should be evenly distributed because short input length and similarities in paths could cause collisions otherwise
|
|
14
|
+
// OR DON'T USE A HASH AT ALL, what about a simple counter?
|
|
15
|
+
const md5 = crypto.createHash('md5');
|
|
16
|
+
md5.update(input);
|
|
17
|
+
const hash = toSafe(md5.digest('base64')).slice(0, hash_length);
|
|
18
|
+
hashes[input] = hash;
|
|
19
|
+
return hash;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const replacements: { [key: string]: string } = {
|
|
23
|
+
'+': '-',
|
|
24
|
+
'/': '_',
|
|
25
|
+
'=': ''
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const replaceRE = new RegExp(`[${Object.keys(replacements).join('')}]`, 'g');
|
|
29
|
+
|
|
30
|
+
function toSafe(base64: string) {
|
|
31
|
+
return base64.replace(replaceRE, (x) => replacements[x]);
|
|
32
|
+
}
|