@sveltejs/vite-plugin-svelte 1.2.0 → 1.3.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/dist/index.cjs +452 -209
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.js +444 -201
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +4 -1
- package/src/utils/compile.ts +36 -3
- package/src/utils/constants.ts +2 -0
- package/src/utils/esbuild.ts +19 -7
- package/src/utils/options.ts +90 -13
- package/src/utils/vite-plugin-svelte-stats.ts +206 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sveltejs/vite-plugin-svelte",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "dominikg",
|
|
6
6
|
"files": [
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"diff-match-patch": "^1.0.5",
|
|
66
66
|
"esbuild": "^0.15.14",
|
|
67
67
|
"rollup": "^2.79.1",
|
|
68
|
-
"svelte": "3.53.1",
|
|
68
|
+
"svelte": "^3.53.1",
|
|
69
69
|
"tsup": "^6.5.0",
|
|
70
70
|
"vite": "^3.2.3"
|
|
71
71
|
},
|
package/src/index.ts
CHANGED
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
patchResolvedViteConfig,
|
|
16
16
|
preResolveOptions
|
|
17
17
|
} from './utils/options';
|
|
18
|
-
import { VitePluginSvelteCache } from './utils/vite-plugin-svelte-cache';
|
|
19
18
|
|
|
20
19
|
import { ensureWatchedFile, setupWatchers } from './utils/watch';
|
|
21
20
|
import { resolveViaPackageJsonSvelte } from './utils/resolve';
|
|
@@ -23,6 +22,7 @@ import { PartialResolvedId } from 'rollup';
|
|
|
23
22
|
import { toRollupError } from './utils/error';
|
|
24
23
|
import { saveSvelteMetadata } from './utils/optimizer';
|
|
25
24
|
import { svelteInspector } from './ui/inspector/plugin';
|
|
25
|
+
import { VitePluginSvelteCache } from './utils/vite-plugin-svelte-cache';
|
|
26
26
|
|
|
27
27
|
interface PluginAPI {
|
|
28
28
|
/**
|
|
@@ -228,6 +228,9 @@ export function svelte(inlineOptions?: Partial<Options>): Plugin[] {
|
|
|
228
228
|
throw toRollupError(e, options);
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
|
+
},
|
|
232
|
+
async buildEnd() {
|
|
233
|
+
await options.stats?.finishAll();
|
|
231
234
|
}
|
|
232
235
|
}
|
|
233
236
|
];
|
package/src/utils/compile.ts
CHANGED
|
@@ -5,11 +5,13 @@ import { createMakeHot } from 'svelte-hmr';
|
|
|
5
5
|
import { SvelteRequest } from './id';
|
|
6
6
|
import { safeBase64Hash } from './hash';
|
|
7
7
|
import { log } from './log';
|
|
8
|
+
import { StatCollection } from './vite-plugin-svelte-stats';
|
|
8
9
|
|
|
9
10
|
const scriptLangRE = /<script [^>]*lang=["']?([^"' >]+)["']?[^>]*>/;
|
|
10
11
|
|
|
11
|
-
const _createCompileSvelte = (makeHot: Function) =>
|
|
12
|
-
|
|
12
|
+
const _createCompileSvelte = (makeHot: Function) => {
|
|
13
|
+
let stats: StatCollection | undefined;
|
|
14
|
+
return async function compileSvelte(
|
|
13
15
|
svelteRequest: SvelteRequest,
|
|
14
16
|
code: string,
|
|
15
17
|
options: Partial<ResolvedOptions>
|
|
@@ -18,6 +20,31 @@ const _createCompileSvelte = (makeHot: Function) =>
|
|
|
18
20
|
const { emitCss = true } = options;
|
|
19
21
|
const dependencies = [];
|
|
20
22
|
|
|
23
|
+
if (options.stats) {
|
|
24
|
+
if (options.isBuild) {
|
|
25
|
+
if (!stats) {
|
|
26
|
+
// build is either completely ssr or csr, create stats collector on first compile
|
|
27
|
+
// it is then finished in the buildEnd hook.
|
|
28
|
+
stats = options.stats.startCollection(`${ssr ? 'ssr' : 'dom'} compile`, {
|
|
29
|
+
logInProgress: () => false
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
// dev time ssr, it's a ssr request and there are no stats, assume new page load and start collecting
|
|
34
|
+
if (ssr && !stats) {
|
|
35
|
+
stats = options.stats.startCollection('ssr compile');
|
|
36
|
+
}
|
|
37
|
+
// stats are being collected but this isn't an ssr request, assume page loaded and stop collecting
|
|
38
|
+
if (!ssr && stats) {
|
|
39
|
+
stats.finish();
|
|
40
|
+
stats = undefined;
|
|
41
|
+
}
|
|
42
|
+
// TODO find a way to trace dom compile during dev
|
|
43
|
+
// problem: we need to call finish at some point but have no way to tell if page load finished
|
|
44
|
+
// also they for hmr updates too
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
21
48
|
const compileOptions: CompileOptions = {
|
|
22
49
|
...options.compilerOptions,
|
|
23
50
|
filename,
|
|
@@ -67,7 +94,13 @@ const _createCompileSvelte = (makeHot: Function) =>
|
|
|
67
94
|
...dynamicCompileOptions
|
|
68
95
|
}
|
|
69
96
|
: compileOptions;
|
|
97
|
+
|
|
98
|
+
const endStat = stats?.start(filename);
|
|
70
99
|
const compiled = compile(finalCode, finalCompileOptions);
|
|
100
|
+
if (endStat) {
|
|
101
|
+
endStat();
|
|
102
|
+
}
|
|
103
|
+
|
|
71
104
|
const hasCss = compiled.css?.code?.trim().length > 0;
|
|
72
105
|
// compiler might not emit css with mode none or it may be empty
|
|
73
106
|
if (emitCss && hasCss) {
|
|
@@ -99,7 +132,7 @@ const _createCompileSvelte = (makeHot: Function) =>
|
|
|
99
132
|
dependencies
|
|
100
133
|
};
|
|
101
134
|
};
|
|
102
|
-
|
|
135
|
+
};
|
|
103
136
|
function buildMakeHot(options: ResolvedOptions) {
|
|
104
137
|
const needsMakeHot = options.hot !== false && options.isServe && !options.isProduction;
|
|
105
138
|
if (needsMakeHot) {
|
package/src/utils/constants.ts
CHANGED
package/src/utils/esbuild.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
2
|
import { compile, preprocess } from 'svelte/compiler';
|
|
3
3
|
import { DepOptimizationOptions } from 'vite';
|
|
4
4
|
import { Compiled } from './compile';
|
|
@@ -6,6 +6,7 @@ import { log } from './log';
|
|
|
6
6
|
import { CompileOptions, ResolvedOptions } from './options';
|
|
7
7
|
import { toESBuildError } from './error';
|
|
8
8
|
import { atLeastSvelte } from './svelte-version';
|
|
9
|
+
import { StatCollection } from './vite-plugin-svelte-stats';
|
|
9
10
|
|
|
10
11
|
type EsbuildOptions = NonNullable<DepOptimizationOptions['esbuildOptions']>;
|
|
11
12
|
type EsbuildPlugin = NonNullable<EsbuildOptions['plugins']>[number];
|
|
@@ -23,23 +24,32 @@ export function esbuildSveltePlugin(options: ResolvedOptions): EsbuildPlugin {
|
|
|
23
24
|
|
|
24
25
|
const svelteExtensions = (options.extensions ?? ['.svelte']).map((ext) => ext.slice(1));
|
|
25
26
|
const svelteFilter = new RegExp(`\\.(` + svelteExtensions.join('|') + `)(\\?.*)?$`);
|
|
26
|
-
|
|
27
|
+
let statsCollection: StatCollection | undefined;
|
|
28
|
+
build.onStart(() => {
|
|
29
|
+
statsCollection = options.stats?.startCollection('prebundle libraries', {
|
|
30
|
+
logResult: (c) => c.stats.length > 1
|
|
31
|
+
});
|
|
32
|
+
});
|
|
27
33
|
build.onLoad({ filter: svelteFilter }, async ({ path: filename }) => {
|
|
28
|
-
const code =
|
|
34
|
+
const code = readFileSync(filename, 'utf8');
|
|
29
35
|
try {
|
|
30
|
-
const contents = await compileSvelte(options, { filename, code });
|
|
36
|
+
const contents = await compileSvelte(options, { filename, code }, statsCollection);
|
|
31
37
|
return { contents };
|
|
32
38
|
} catch (e) {
|
|
33
39
|
return { errors: [toESBuildError(e, options)] };
|
|
34
40
|
}
|
|
35
41
|
});
|
|
42
|
+
build.onEnd(() => {
|
|
43
|
+
statsCollection?.finish();
|
|
44
|
+
});
|
|
36
45
|
}
|
|
37
46
|
};
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
async function compileSvelte(
|
|
41
50
|
options: ResolvedOptions,
|
|
42
|
-
{ filename, code }: { filename: string; code: string }
|
|
51
|
+
{ filename, code }: { filename: string; code: string },
|
|
52
|
+
statsCollection?: StatCollection
|
|
43
53
|
): Promise<string> {
|
|
44
54
|
let css = options.compilerOptions.css;
|
|
45
55
|
if (css !== 'none') {
|
|
@@ -83,8 +93,10 @@ async function compileSvelte(
|
|
|
83
93
|
...dynamicCompileOptions
|
|
84
94
|
}
|
|
85
95
|
: compileOptions;
|
|
86
|
-
|
|
96
|
+
const endStat = statsCollection?.start(filename);
|
|
87
97
|
const compiled = compile(finalCode, finalCompileOptions) as Compiled;
|
|
88
|
-
|
|
98
|
+
if (endStat) {
|
|
99
|
+
endStat();
|
|
100
|
+
}
|
|
89
101
|
return compiled.js.code + '//# sourceMappingURL=' + compiled.js.map.toUrl();
|
|
90
102
|
}
|
package/src/utils/options.ts
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
import { ConfigEnv, ResolvedConfig, UserConfig, ViteDevServer, normalizePath } from 'vite';
|
|
3
3
|
import { log } from './log';
|
|
4
4
|
import { loadSvelteConfig } from './load-svelte-config';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
SVELTE_EXPORT_CONDITIONS,
|
|
7
|
+
SVELTE_HMR_IMPORTS,
|
|
8
|
+
SVELTE_IMPORTS,
|
|
9
|
+
SVELTE_RESOLVE_MAIN_FIELDS
|
|
10
|
+
} from './constants';
|
|
6
11
|
// eslint-disable-next-line node/no-missing-import
|
|
7
12
|
import type { CompileOptions, Warning } from 'svelte/types/compiler/interfaces';
|
|
8
13
|
import type {
|
|
@@ -27,6 +32,7 @@ import {
|
|
|
27
32
|
} from 'vitefu';
|
|
28
33
|
import { atLeastSvelte } from './svelte-version';
|
|
29
34
|
import { isCommonDepWithoutSvelteField } from './dependencies';
|
|
35
|
+
import { VitePluginSvelteStats } from './vite-plugin-svelte-stats';
|
|
30
36
|
|
|
31
37
|
// svelte 3.53.0 changed compilerOptions.css from boolean to string | boolen, use string when available
|
|
32
38
|
const cssAsString = atLeastSvelte('3.53.0');
|
|
@@ -129,9 +135,11 @@ export async function preResolveOptions(
|
|
|
129
135
|
...viteUserConfig,
|
|
130
136
|
root: resolveViteRoot(viteUserConfig)
|
|
131
137
|
};
|
|
138
|
+
const isBuild = viteEnv.command === 'build';
|
|
132
139
|
const defaultOptions: Partial<Options> = {
|
|
133
140
|
extensions: ['.svelte'],
|
|
134
|
-
emitCss: true
|
|
141
|
+
emitCss: true,
|
|
142
|
+
prebundleSvelteLibraries: !isBuild
|
|
135
143
|
};
|
|
136
144
|
const svelteConfig = convertPluginOptions(
|
|
137
145
|
await loadSvelteConfig(viteConfigWithResolvedRoot, inlineOptions)
|
|
@@ -139,7 +147,7 @@ export async function preResolveOptions(
|
|
|
139
147
|
|
|
140
148
|
const extraOptions: Partial<PreResolvedOptions> = {
|
|
141
149
|
root: viteConfigWithResolvedRoot.root!,
|
|
142
|
-
isBuild
|
|
150
|
+
isBuild,
|
|
143
151
|
isServe: viteEnv.command === 'serve',
|
|
144
152
|
isDebug: process.env.DEBUG != null
|
|
145
153
|
};
|
|
@@ -203,6 +211,14 @@ export function resolveOptions(
|
|
|
203
211
|
addExtraPreprocessors(merged, viteConfig);
|
|
204
212
|
enforceOptionsForHmr(merged);
|
|
205
213
|
enforceOptionsForProduction(merged);
|
|
214
|
+
// mergeConfigs would mangle functions on the stats class, so do this afterwards
|
|
215
|
+
const isLogLevelInfo = [undefined, 'info'].includes(viteConfig.logLevel);
|
|
216
|
+
const disableCompileStats = merged.experimental?.disableCompileStats;
|
|
217
|
+
const statsEnabled =
|
|
218
|
+
disableCompileStats !== true && disableCompileStats !== (merged.isBuild ? 'build' : 'dev');
|
|
219
|
+
if (statsEnabled && isLogLevelInfo) {
|
|
220
|
+
merged.stats = new VitePluginSvelteStats();
|
|
221
|
+
}
|
|
206
222
|
return merged;
|
|
207
223
|
}
|
|
208
224
|
|
|
@@ -315,7 +331,8 @@ export async function buildExtraViteConfig(
|
|
|
315
331
|
const extraViteConfig: Partial<UserConfig> = {
|
|
316
332
|
resolve: {
|
|
317
333
|
mainFields: [...SVELTE_RESOLVE_MAIN_FIELDS],
|
|
318
|
-
dedupe: [...SVELTE_IMPORTS, ...SVELTE_HMR_IMPORTS]
|
|
334
|
+
dedupe: [...SVELTE_IMPORTS, ...SVELTE_HMR_IMPORTS],
|
|
335
|
+
conditions: [...SVELTE_EXPORT_CONDITIONS]
|
|
319
336
|
}
|
|
320
337
|
// this option is still awaiting a PR in vite to be supported
|
|
321
338
|
// see https://github.com/sveltejs/vite-plugin-svelte/issues/60
|
|
@@ -358,12 +375,17 @@ export async function buildExtraViteConfig(
|
|
|
358
375
|
|
|
359
376
|
// handle prebundling for svelte files
|
|
360
377
|
if (options.prebundleSvelteLibraries) {
|
|
361
|
-
extraViteConfig.optimizeDeps
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
378
|
+
extraViteConfig.optimizeDeps = {
|
|
379
|
+
...extraViteConfig.optimizeDeps,
|
|
380
|
+
// Experimental Vite API to allow these extensions to be scanned and prebundled
|
|
381
|
+
// @ts-ignore
|
|
382
|
+
extensions: options.extensions ?? ['.svelte'],
|
|
383
|
+
// Add esbuild plugin to prebundle Svelte files.
|
|
384
|
+
// Currently a placeholder as more information is needed after Vite config is resolved,
|
|
385
|
+
// the real Svelte plugin is added in `patchResolvedViteConfig()`
|
|
386
|
+
esbuildOptions: {
|
|
387
|
+
plugins: [{ name: facadeEsbuildSveltePluginName, setup: () => {} }]
|
|
388
|
+
}
|
|
367
389
|
};
|
|
368
390
|
}
|
|
369
391
|
|
|
@@ -377,9 +399,44 @@ export async function buildExtraViteConfig(
|
|
|
377
399
|
log.debug('enabling "experimental.hmrPartialAccept" in vite config');
|
|
378
400
|
extraViteConfig.experimental = { hmrPartialAccept: true };
|
|
379
401
|
}
|
|
402
|
+
validateViteConfig(extraViteConfig, config, options);
|
|
380
403
|
return extraViteConfig;
|
|
381
404
|
}
|
|
382
405
|
|
|
406
|
+
function validateViteConfig(
|
|
407
|
+
extraViteConfig: Partial<UserConfig>,
|
|
408
|
+
config: UserConfig,
|
|
409
|
+
options: PreResolvedOptions
|
|
410
|
+
) {
|
|
411
|
+
const { prebundleSvelteLibraries, isBuild } = options;
|
|
412
|
+
if (prebundleSvelteLibraries) {
|
|
413
|
+
const isEnabled = (option: 'dev' | 'build' | boolean) =>
|
|
414
|
+
option !== true && option !== (isBuild ? 'build' : 'dev');
|
|
415
|
+
const logWarning = (name: string, value: 'dev' | 'build' | boolean, recommendation: string) =>
|
|
416
|
+
log.warn.once(
|
|
417
|
+
`Incompatible options: \`prebundleSvelteLibraries: true\` and vite \`${name}: ${JSON.stringify(
|
|
418
|
+
value
|
|
419
|
+
)}\` ${isBuild ? 'during build.' : '.'} ${recommendation}`
|
|
420
|
+
);
|
|
421
|
+
const viteOptimizeDepsDisabled = config.optimizeDeps?.disabled ?? 'build'; // fall back to vite default
|
|
422
|
+
const isOptimizeDepsEnabled = isEnabled(viteOptimizeDepsDisabled);
|
|
423
|
+
if (!isBuild && !isOptimizeDepsEnabled) {
|
|
424
|
+
logWarning(
|
|
425
|
+
'optimizeDeps.disabled',
|
|
426
|
+
viteOptimizeDepsDisabled,
|
|
427
|
+
'Forcing `optimizeDeps.disabled: "build"`. Disable prebundleSvelteLibraries or update your vite config to enable optimizeDeps during dev.'
|
|
428
|
+
);
|
|
429
|
+
extraViteConfig.optimizeDeps!.disabled = 'build';
|
|
430
|
+
} else if (isBuild && isOptimizeDepsEnabled) {
|
|
431
|
+
logWarning(
|
|
432
|
+
'optimizeDeps.disabled',
|
|
433
|
+
viteOptimizeDepsDisabled,
|
|
434
|
+
'Disable optimizeDeps or prebundleSvelteLibraries for build if you experience errors.'
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
383
440
|
async function buildExtraConfigForDependencies(options: PreResolvedOptions, config: UserConfig) {
|
|
384
441
|
// extra handling for svelte dependencies in the project
|
|
385
442
|
const depsConfig = await crawlFrameworkPkgs({
|
|
@@ -387,7 +444,17 @@ async function buildExtraConfigForDependencies(options: PreResolvedOptions, conf
|
|
|
387
444
|
isBuild: options.isBuild,
|
|
388
445
|
viteUserConfig: config,
|
|
389
446
|
isFrameworkPkgByJson(pkgJson) {
|
|
390
|
-
|
|
447
|
+
let hasSvelteCondition = false;
|
|
448
|
+
if (typeof pkgJson.exports === 'object') {
|
|
449
|
+
// use replacer as a simple way to iterate over nested keys
|
|
450
|
+
JSON.stringify(pkgJson.exports, (key, value) => {
|
|
451
|
+
if (SVELTE_EXPORT_CONDITIONS.includes(key)) {
|
|
452
|
+
hasSvelteCondition = true;
|
|
453
|
+
}
|
|
454
|
+
return value;
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
return hasSvelteCondition || !!pkgJson.svelte;
|
|
391
458
|
},
|
|
392
459
|
isSemiFrameworkPkgByJson(pkgJson) {
|
|
393
460
|
return !!pkgJson.dependencies?.svelte || !!pkgJson.peerDependencies?.svelte;
|
|
@@ -551,9 +618,11 @@ export interface PluginOptions {
|
|
|
551
618
|
disableDependencyReinclusion?: boolean | string[];
|
|
552
619
|
|
|
553
620
|
/**
|
|
554
|
-
*
|
|
621
|
+
* Enable support for Vite's dependency optimization to prebundle Svelte libraries.
|
|
555
622
|
*
|
|
556
|
-
*
|
|
623
|
+
* To disable prebundling for a specific library, add it to `optimizeDeps.exclude`.
|
|
624
|
+
*
|
|
625
|
+
* @default true for dev, false for build
|
|
557
626
|
*/
|
|
558
627
|
prebundleSvelteLibraries?: boolean;
|
|
559
628
|
|
|
@@ -655,6 +724,13 @@ export interface ExperimentalOptions {
|
|
|
655
724
|
*
|
|
656
725
|
*/
|
|
657
726
|
sendWarningsToBrowser?: boolean;
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* disable svelte compile statistics
|
|
730
|
+
*
|
|
731
|
+
* @default false
|
|
732
|
+
*/
|
|
733
|
+
disableCompileStats?: 'dev' | 'build' | boolean;
|
|
658
734
|
}
|
|
659
735
|
|
|
660
736
|
export interface InspectorOptions {
|
|
@@ -741,6 +817,7 @@ export interface PreResolvedOptions extends Options {
|
|
|
741
817
|
export interface ResolvedOptions extends PreResolvedOptions {
|
|
742
818
|
isProduction: boolean;
|
|
743
819
|
server?: ViteDevServer;
|
|
820
|
+
stats?: VitePluginSvelteStats;
|
|
744
821
|
}
|
|
745
822
|
|
|
746
823
|
export type {
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { log } from './log';
|
|
2
|
+
//eslint-disable-next-line node/no-missing-import
|
|
3
|
+
import { findClosestPkgJsonPath } from 'vitefu';
|
|
4
|
+
import { readFileSync } from 'fs';
|
|
5
|
+
import { performance } from 'perf_hooks';
|
|
6
|
+
|
|
7
|
+
interface Stat {
|
|
8
|
+
file: string;
|
|
9
|
+
pkg?: string;
|
|
10
|
+
start: number;
|
|
11
|
+
end: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface StatCollection {
|
|
15
|
+
name: string;
|
|
16
|
+
options: CollectionOptions;
|
|
17
|
+
//eslint-disable-next-line no-unused-vars
|
|
18
|
+
start: (file: string) => () => void;
|
|
19
|
+
stats: Stat[];
|
|
20
|
+
packageStats?: PackageStats[];
|
|
21
|
+
collectionStart: number;
|
|
22
|
+
duration?: number;
|
|
23
|
+
finish: () => Promise<void> | void;
|
|
24
|
+
finished: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface PackageStats {
|
|
28
|
+
pkg: string;
|
|
29
|
+
files: number;
|
|
30
|
+
duration: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface CollectionOptions {
|
|
34
|
+
//eslint-disable-next-line no-unused-vars
|
|
35
|
+
logInProgress: (collection: StatCollection, now: number) => boolean;
|
|
36
|
+
//eslint-disable-next-line no-unused-vars
|
|
37
|
+
logResult: (collection: StatCollection) => boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const defaultCollectionOptions: CollectionOptions = {
|
|
41
|
+
// log after 500ms and more than one file processed
|
|
42
|
+
logInProgress: (c, now) => now - c.collectionStart > 500 && c.stats.length > 1,
|
|
43
|
+
// always log results
|
|
44
|
+
logResult: () => true
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function humanDuration(n: number) {
|
|
48
|
+
// 99.9ms 0.10s
|
|
49
|
+
return n < 100 ? `${n.toFixed(1)}ms` : `${(n / 1000).toFixed(2)}s`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function formatPackageStats(pkgStats: PackageStats[]) {
|
|
53
|
+
const statLines = pkgStats.map((pkgStat) => {
|
|
54
|
+
const duration = pkgStat.duration;
|
|
55
|
+
const avg = duration / pkgStat.files;
|
|
56
|
+
return [pkgStat.pkg, `${pkgStat.files}`, humanDuration(duration), humanDuration(avg)];
|
|
57
|
+
});
|
|
58
|
+
statLines.unshift(['package', 'files', 'time', 'avg']);
|
|
59
|
+
const columnWidths = statLines.reduce(
|
|
60
|
+
(widths: number[], row) => {
|
|
61
|
+
for (let i = 0; i < row.length; i++) {
|
|
62
|
+
const cell = row[i];
|
|
63
|
+
if (widths[i] < cell.length) {
|
|
64
|
+
widths[i] = cell.length;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return widths;
|
|
68
|
+
},
|
|
69
|
+
statLines[0].map(() => 0)
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const table = statLines
|
|
73
|
+
.map((row: string[]) =>
|
|
74
|
+
row
|
|
75
|
+
.map((cell: string, i: number) => {
|
|
76
|
+
if (i === 0) {
|
|
77
|
+
return cell.padEnd(columnWidths[i], ' ');
|
|
78
|
+
} else {
|
|
79
|
+
return cell.padStart(columnWidths[i], ' ');
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
.join('\t')
|
|
83
|
+
)
|
|
84
|
+
.join('\n');
|
|
85
|
+
return table;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export class VitePluginSvelteStats {
|
|
89
|
+
// package directory -> package name
|
|
90
|
+
private _packages: { path: string; name: string }[] = [];
|
|
91
|
+
private _collections: StatCollection[] = [];
|
|
92
|
+
startCollection(name: string, opts?: Partial<CollectionOptions>) {
|
|
93
|
+
const options = {
|
|
94
|
+
...defaultCollectionOptions,
|
|
95
|
+
...opts
|
|
96
|
+
};
|
|
97
|
+
const stats: Stat[] = [];
|
|
98
|
+
const collectionStart = performance.now();
|
|
99
|
+
const _this = this;
|
|
100
|
+
let hasLoggedProgress = false;
|
|
101
|
+
const collection: StatCollection = {
|
|
102
|
+
name,
|
|
103
|
+
options,
|
|
104
|
+
stats,
|
|
105
|
+
collectionStart,
|
|
106
|
+
finished: false,
|
|
107
|
+
start(file) {
|
|
108
|
+
if (collection.finished) {
|
|
109
|
+
throw new Error('called after finish() has been used');
|
|
110
|
+
}
|
|
111
|
+
const start = performance.now();
|
|
112
|
+
const stat: Stat = { file, start, end: start };
|
|
113
|
+
return () => {
|
|
114
|
+
const now = performance.now();
|
|
115
|
+
stat.end = now;
|
|
116
|
+
stats.push(stat);
|
|
117
|
+
if (!hasLoggedProgress && options.logInProgress(collection, now)) {
|
|
118
|
+
hasLoggedProgress = true;
|
|
119
|
+
log.info(`${name} in progress ...`);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
async finish() {
|
|
124
|
+
await _this._finish(collection);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
_this._collections.push(collection);
|
|
128
|
+
return collection;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public async finishAll() {
|
|
132
|
+
await Promise.all(this._collections.map((c) => c.finish()));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private async _finish(collection: StatCollection) {
|
|
136
|
+
collection.finished = true;
|
|
137
|
+
const now = performance.now();
|
|
138
|
+
collection.duration = now - collection.collectionStart;
|
|
139
|
+
const logResult = collection.options.logResult(collection);
|
|
140
|
+
if (logResult) {
|
|
141
|
+
await this._aggregateStatsResult(collection);
|
|
142
|
+
log.info(`${collection.name} done.`, formatPackageStats(collection.packageStats!));
|
|
143
|
+
}
|
|
144
|
+
// cut some ties to free it for garbage collection
|
|
145
|
+
const index = this._collections.indexOf(collection);
|
|
146
|
+
this._collections.splice(index, 1);
|
|
147
|
+
collection.stats.length = 0;
|
|
148
|
+
collection.stats = [];
|
|
149
|
+
if (collection.packageStats) {
|
|
150
|
+
collection.packageStats.length = 0;
|
|
151
|
+
collection.packageStats = [];
|
|
152
|
+
}
|
|
153
|
+
collection.start = () => () => {};
|
|
154
|
+
collection.finish = () => {};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private async _aggregateStatsResult(collection: StatCollection) {
|
|
158
|
+
const stats = collection.stats;
|
|
159
|
+
for (const stat of stats) {
|
|
160
|
+
let pkg = this._packages.find((p) => stat.file.startsWith(p.path));
|
|
161
|
+
if (!pkg) {
|
|
162
|
+
// check for package.json first
|
|
163
|
+
let pkgPath = await findClosestPkgJsonPath(stat.file);
|
|
164
|
+
if (pkgPath) {
|
|
165
|
+
let path = pkgPath?.replace(/package.json$/, '');
|
|
166
|
+
let name = JSON.parse(readFileSync(pkgPath, 'utf-8')).name;
|
|
167
|
+
if (!name) {
|
|
168
|
+
// some packages have nameless nested package.json
|
|
169
|
+
pkgPath = await findClosestPkgJsonPath(path);
|
|
170
|
+
if (pkgPath) {
|
|
171
|
+
path = pkgPath?.replace(/package.json$/, '');
|
|
172
|
+
name = JSON.parse(readFileSync(pkgPath, 'utf-8')).name;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (path && name) {
|
|
176
|
+
pkg = { path, name };
|
|
177
|
+
this._packages.push(pkg);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// TODO is it possible that we want to track files where there is no named packge.json as parent?
|
|
182
|
+
// what do we want to do for that, try to find common root paths for different stats?
|
|
183
|
+
stat.pkg = pkg?.name ?? '$unknown';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// group stats
|
|
187
|
+
const grouped: { [key: string]: PackageStats } = {};
|
|
188
|
+
stats.forEach((stat) => {
|
|
189
|
+
const pkg = stat.pkg!;
|
|
190
|
+
let group = grouped[pkg];
|
|
191
|
+
if (!group) {
|
|
192
|
+
group = grouped[pkg] = {
|
|
193
|
+
files: 0,
|
|
194
|
+
duration: 0,
|
|
195
|
+
pkg
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
group.files += 1;
|
|
199
|
+
group.duration += stat.end - stat.start;
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const groups = Object.values(grouped);
|
|
203
|
+
groups.sort((a, b) => b.duration - a.duration);
|
|
204
|
+
collection.packageStats = groups;
|
|
205
|
+
}
|
|
206
|
+
}
|