@sveltejs/vite-plugin-svelte 1.2.0 → 1.3.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/vite-plugin-svelte",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "license": "MIT",
5
5
  "author": "dominikg",
6
6
  "files": [
@@ -47,7 +47,7 @@
47
47
  "kleur": "^4.1.5",
48
48
  "magic-string": "^0.26.7",
49
49
  "svelte-hmr": "^0.15.1",
50
- "vitefu": "^0.2.1"
50
+ "vitefu": "^0.2.2"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "diff-match-patch": "^1.0.5",
@@ -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
  ];
@@ -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
- async function compileSvelte(
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) {
@@ -18,3 +18,5 @@ export const SVELTE_HMR_IMPORTS = [
18
18
  'svelte-hmr/runtime/proxy-adapter-dom.js',
19
19
  'svelte-hmr'
20
20
  ];
21
+
22
+ export const SVELTE_EXPORT_CONDITIONS = ['svelte'];
@@ -1,4 +1,4 @@
1
- import { promises as fs } from 'fs';
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 = await fs.readFile(filename, 'utf8');
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
  }
@@ -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 { SVELTE_HMR_IMPORTS, SVELTE_IMPORTS, SVELTE_RESOLVE_MAIN_FIELDS } from './constants';
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: viteEnv.command === 'build',
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.extensions = options.extensions ?? ['.svelte'];
362
- // Add esbuild plugin to prebundle Svelte files.
363
- // Currently a placeholder as more information is needed after Vite config is resolved,
364
- // the real Svelte plugin is added in `patchResolvedViteConfig()`
365
- extraViteConfig.optimizeDeps.esbuildOptions = {
366
- plugins: [{ name: facadeEsbuildSveltePluginName, setup: () => {} }]
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
- return !!pkgJson.svelte;
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
- * Force Vite to pre-bundle Svelte libraries
621
+ * Enable support for Vite's dependency optimization to prebundle Svelte libraries.
555
622
  *
556
- * @default false
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,217 @@
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 { dirname } from 'path';
6
+ import { performance } from 'perf_hooks';
7
+ import { normalizePath } from 'vite';
8
+
9
+ interface Stat {
10
+ file: string;
11
+ pkg?: string;
12
+ start: number;
13
+ end: number;
14
+ }
15
+
16
+ export interface StatCollection {
17
+ name: string;
18
+ options: CollectionOptions;
19
+ //eslint-disable-next-line no-unused-vars
20
+ start: (file: string) => () => void;
21
+ stats: Stat[];
22
+ packageStats?: PackageStats[];
23
+ collectionStart: number;
24
+ duration?: number;
25
+ finish: () => Promise<void> | void;
26
+ finished: boolean;
27
+ }
28
+
29
+ interface PackageStats {
30
+ pkg: string;
31
+ files: number;
32
+ duration: number;
33
+ }
34
+
35
+ export interface CollectionOptions {
36
+ //eslint-disable-next-line no-unused-vars
37
+ logInProgress: (collection: StatCollection, now: number) => boolean;
38
+ //eslint-disable-next-line no-unused-vars
39
+ logResult: (collection: StatCollection) => boolean;
40
+ }
41
+
42
+ const defaultCollectionOptions: CollectionOptions = {
43
+ // log after 500ms and more than one file processed
44
+ logInProgress: (c, now) => now - c.collectionStart > 500 && c.stats.length > 1,
45
+ // always log results
46
+ logResult: () => true
47
+ };
48
+
49
+ function humanDuration(n: number) {
50
+ // 99.9ms 0.10s
51
+ return n < 100 ? `${n.toFixed(1)}ms` : `${(n / 1000).toFixed(2)}s`;
52
+ }
53
+
54
+ function formatPackageStats(pkgStats: PackageStats[]) {
55
+ const statLines = pkgStats.map((pkgStat) => {
56
+ const duration = pkgStat.duration;
57
+ const avg = duration / pkgStat.files;
58
+ return [pkgStat.pkg, `${pkgStat.files}`, humanDuration(duration), humanDuration(avg)];
59
+ });
60
+ statLines.unshift(['package', 'files', 'time', 'avg']);
61
+ const columnWidths = statLines.reduce(
62
+ (widths: number[], row) => {
63
+ for (let i = 0; i < row.length; i++) {
64
+ const cell = row[i];
65
+ if (widths[i] < cell.length) {
66
+ widths[i] = cell.length;
67
+ }
68
+ }
69
+ return widths;
70
+ },
71
+ statLines[0].map(() => 0)
72
+ );
73
+
74
+ const table = statLines
75
+ .map((row: string[]) =>
76
+ row
77
+ .map((cell: string, i: number) => {
78
+ if (i === 0) {
79
+ return cell.padEnd(columnWidths[i], ' ');
80
+ } else {
81
+ return cell.padStart(columnWidths[i], ' ');
82
+ }
83
+ })
84
+ .join('\t')
85
+ )
86
+ .join('\n');
87
+ return table;
88
+ }
89
+
90
+ /**
91
+ * utility to get the package name a file belongs to
92
+ *
93
+ * @param {string} file to find package for
94
+ * @returns {path:string,name:string} tuple of path,name where name is the parsed package name and path is the normalized path to it
95
+ */
96
+ async function getClosestNamedPackage(file: string): Promise<{ name: string; path: string }> {
97
+ let name = '$unknown';
98
+ let path = await findClosestPkgJsonPath(file, (pkgPath) => {
99
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
100
+ if (pkg.name != null) {
101
+ name = pkg.name;
102
+ return true;
103
+ }
104
+ return false;
105
+ });
106
+ // return normalized path with appended '/' so .startsWith works for future file checks
107
+ path = normalizePath(dirname(path ?? file)) + '/';
108
+ return { name, path };
109
+ }
110
+
111
+ export class VitePluginSvelteStats {
112
+ // package directory -> package name
113
+ private _packages: { path: string; name: string }[] = [];
114
+ private _collections: StatCollection[] = [];
115
+ startCollection(name: string, opts?: Partial<CollectionOptions>) {
116
+ const options = {
117
+ ...defaultCollectionOptions,
118
+ ...opts
119
+ };
120
+ const stats: Stat[] = [];
121
+ const collectionStart = performance.now();
122
+ const _this = this;
123
+ let hasLoggedProgress = false;
124
+ const collection: StatCollection = {
125
+ name,
126
+ options,
127
+ stats,
128
+ collectionStart,
129
+ finished: false,
130
+ start(file) {
131
+ if (collection.finished) {
132
+ throw new Error('called after finish() has been used');
133
+ }
134
+ file = normalizePath(file);
135
+ const start = performance.now();
136
+ const stat: Stat = { file, start, end: start };
137
+ return () => {
138
+ const now = performance.now();
139
+ stat.end = now;
140
+ stats.push(stat);
141
+ if (!hasLoggedProgress && options.logInProgress(collection, now)) {
142
+ hasLoggedProgress = true;
143
+ log.info(`${name} in progress ...`);
144
+ }
145
+ };
146
+ },
147
+ async finish() {
148
+ await _this._finish(collection);
149
+ }
150
+ };
151
+ _this._collections.push(collection);
152
+ return collection;
153
+ }
154
+
155
+ public async finishAll() {
156
+ await Promise.all(this._collections.map((c) => c.finish()));
157
+ }
158
+
159
+ private async _finish(collection: StatCollection) {
160
+ try {
161
+ collection.finished = true;
162
+ const now = performance.now();
163
+ collection.duration = now - collection.collectionStart;
164
+ const logResult = collection.options.logResult(collection);
165
+ if (logResult) {
166
+ await this._aggregateStatsResult(collection);
167
+ log.info(`${collection.name} done.`, formatPackageStats(collection.packageStats!));
168
+ }
169
+ // cut some ties to free it for garbage collection
170
+ const index = this._collections.indexOf(collection);
171
+ this._collections.splice(index, 1);
172
+ collection.stats.length = 0;
173
+ collection.stats = [];
174
+ if (collection.packageStats) {
175
+ collection.packageStats.length = 0;
176
+ collection.packageStats = [];
177
+ }
178
+ collection.start = () => () => {};
179
+ collection.finish = () => {};
180
+ } catch (e) {
181
+ // this should not happen, but stats taking also should not break the process
182
+ log.debug.once(`failed to finish stats for ${collection.name}`, e);
183
+ }
184
+ }
185
+
186
+ private async _aggregateStatsResult(collection: StatCollection) {
187
+ const stats = collection.stats;
188
+ for (const stat of stats) {
189
+ let pkg = this._packages.find((p) => stat.file.startsWith(p.path));
190
+ if (!pkg) {
191
+ pkg = await getClosestNamedPackage(stat.file);
192
+ this._packages.push(pkg);
193
+ }
194
+ stat.pkg = pkg.name;
195
+ }
196
+
197
+ // group stats
198
+ const grouped: { [key: string]: PackageStats } = {};
199
+ stats.forEach((stat) => {
200
+ const pkg = stat.pkg!;
201
+ let group = grouped[pkg];
202
+ if (!group) {
203
+ group = grouped[pkg] = {
204
+ files: 0,
205
+ duration: 0,
206
+ pkg
207
+ };
208
+ }
209
+ group.files += 1;
210
+ group.duration += stat.end - stat.start;
211
+ });
212
+
213
+ const groups = Object.values(grouped);
214
+ groups.sort((a, b) => b.duration - a.duration);
215
+ collection.packageStats = groups;
216
+ }
217
+ }