@sveltejs/vite-plugin-svelte 1.1.1 → 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 +732 -540
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.js +664 -468
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
- package/src/index.ts +31 -15
- package/src/utils/__tests__/svelte-version.spec.ts +102 -0
- package/src/utils/compile.ts +39 -5
- package/src/utils/constants.ts +2 -0
- package/src/utils/dependencies.ts +17 -173
- package/src/utils/esbuild.ts +26 -8
- package/src/utils/options.ts +214 -138
- package/src/utils/resolve.ts +6 -7
- package/src/utils/svelte-version.ts +37 -0
- package/src/utils/vite-plugin-svelte-stats.ts +206 -0
- package/src/utils/__tests__/dependencies.spec.ts +0 -43
|
@@ -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
|
+
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { findRootSvelteDependencies, needsOptimization } from '../dependencies';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import { createRequire } from 'module';
|
|
5
|
-
import { fileURLToPath } from 'url';
|
|
6
|
-
const __dir = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const e2eTestRoot = path.resolve(__dir, '../../../../../packages/e2e-tests');
|
|
8
|
-
|
|
9
|
-
describe('dependencies', () => {
|
|
10
|
-
describe('findRootSvelteDependencies', () => {
|
|
11
|
-
it('should find svelte dependencies in packages/e2e-test/hmr', () => {
|
|
12
|
-
const deps = findRootSvelteDependencies(path.resolve('packages/e2e-tests/hmr'));
|
|
13
|
-
expect(deps).toHaveLength(1);
|
|
14
|
-
expect(deps[0].name).toBe('e2e-test-dep-svelte-simple');
|
|
15
|
-
expect(deps[0].path).toEqual([]);
|
|
16
|
-
});
|
|
17
|
-
it('should find nested svelte dependencies in packages/e2e-test/package-json-svelte-field', () => {
|
|
18
|
-
const deps = findRootSvelteDependencies(path.join(e2eTestRoot, 'package-json-svelte-field'));
|
|
19
|
-
expect(deps).toHaveLength(3);
|
|
20
|
-
const hybrid = deps.find((dep) => dep.name === 'e2e-test-dep-svelte-hybrid');
|
|
21
|
-
expect(hybrid).toBeTruthy();
|
|
22
|
-
expect(hybrid.path).toHaveLength(0);
|
|
23
|
-
const nested = deps.find((dep) => dep.name === 'e2e-test-dep-svelte-nested');
|
|
24
|
-
expect(nested).toBeTruthy();
|
|
25
|
-
expect(nested.path).toHaveLength(0);
|
|
26
|
-
const simple = deps.find((dep) => dep.name === 'e2e-test-dep-svelte-simple');
|
|
27
|
-
expect(simple).toBeTruthy();
|
|
28
|
-
expect(simple.path).toHaveLength(1);
|
|
29
|
-
expect(simple.path[0]).toBe('e2e-test-dep-svelte-nested');
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
describe('needsOptimization', () => {
|
|
33
|
-
it('should optimize cjs deps only', () => {
|
|
34
|
-
const testDepsPath = path.join(e2eTestRoot, 'dependencies/package.json');
|
|
35
|
-
const localRequire = createRequire(testDepsPath);
|
|
36
|
-
expect(needsOptimization('e2e-test-dep-cjs-and-esm', localRequire)).toBe(false);
|
|
37
|
-
expect(needsOptimization('e2e-test-dep-cjs-only', localRequire)).toBe(true);
|
|
38
|
-
expect(needsOptimization('e2e-test-dep-esm-only', localRequire)).toBe(false);
|
|
39
|
-
expect(needsOptimization('e2e-test-dep-index-only', localRequire)).toBe(true);
|
|
40
|
-
expect(needsOptimization('e2e-test-dep-types-only', localRequire)).toBe(false);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
});
|