@trebired/bundler 2.0.0 → 3.0.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/CHANGELOG.md +12 -0
- package/README.md +207 -287
- package/dist/core/asset-manifest.d.ts.map +1 -1
- package/dist/core/asset-manifest.js +70 -72
- package/dist/core/asset-manifest.js.map +1 -1
- package/dist/core/build.d.ts.map +1 -1
- package/dist/core/build.js +5 -10
- package/dist/core/build.js.map +1 -1
- package/dist/core/derive-manifest.d.ts.map +1 -1
- package/dist/core/derive-manifest.js +4 -0
- package/dist/core/derive-manifest.js.map +1 -1
- package/dist/core/discovery.d.ts +13 -17
- package/dist/core/discovery.d.ts.map +1 -1
- package/dist/core/discovery.js +276 -277
- package/dist/core/discovery.js.map +1 -1
- package/dist/core/esbuild-options.d.ts +1 -3
- package/dist/core/esbuild-options.d.ts.map +1 -1
- package/dist/core/esbuild-options.js +4 -21
- package/dist/core/esbuild-options.js.map +1 -1
- package/dist/core/manifest.d.ts +2 -2
- package/dist/core/manifest.d.ts.map +1 -1
- package/dist/core/manifest.js +2 -11
- package/dist/core/manifest.js.map +1 -1
- package/dist/core/shared.d.ts +3 -9
- package/dist/core/shared.d.ts.map +1 -1
- package/dist/core/shared.js +5 -22
- package/dist/core/shared.js.map +1 -1
- package/dist/core/watch.d.ts.map +1 -1
- package/dist/core/watch.js +20 -32
- package/dist/core/watch.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/plugins/virtual-entries.js +1 -1
- package/dist/plugins/virtual-entries.js.map +1 -1
- package/dist/types.d.ts +67 -32
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/core/discovery.js
CHANGED
|
@@ -1,31 +1,15 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import fsp from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
const DEFAULT_IGNORE_DIRS = [".git", "coverage", "dist", "node_modules"];
|
|
4
|
+
import { walkImportGraph } from "./import-graph.js";
|
|
6
5
|
const DEFAULT_DISCOVERY_BUNDLE_MAX_SIZE = 50 * 1024 * 1024;
|
|
7
|
-
const
|
|
8
|
-
{
|
|
9
|
-
name: "scripts",
|
|
10
|
-
extensions: [".js", ".ts"],
|
|
11
|
-
loader: "ts",
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
name: "styles",
|
|
15
|
-
extensions: [".css", ".scss"],
|
|
16
|
-
loader: "css",
|
|
17
|
-
},
|
|
18
|
-
];
|
|
19
|
-
const NORMALIZED_DISCOVERY_BUNDLE_GROUPS = DEFAULT_DISCOVERY_BUNDLE_GROUPS.map((group) => ({
|
|
20
|
-
...group,
|
|
21
|
-
extensions: new Set(group.extensions),
|
|
22
|
-
}));
|
|
6
|
+
const DEFAULT_IGNORE_DIRS = [".git", "coverage", "dist", "node_modules"];
|
|
23
7
|
const VIRTUAL_ENTRY_PREFIX = "trebired-virtual:";
|
|
24
8
|
function toPosixPath(value) {
|
|
25
9
|
return value.replace(/\\/g, "/");
|
|
26
10
|
}
|
|
27
11
|
function normalizePathValue(value) {
|
|
28
|
-
return toPosixPath(String(value || "").trim()).replace(/^\.\/+/, "");
|
|
12
|
+
return toPosixPath(String(value || "").trim()).replace(/^\.\/+/, "").replace(/^\/+|\/+$/g, "");
|
|
29
13
|
}
|
|
30
14
|
function globToRegExp(pattern) {
|
|
31
15
|
const normalized = normalizePathValue(pattern);
|
|
@@ -74,9 +58,6 @@ function matchesAnyPattern(value, patterns) {
|
|
|
74
58
|
return globToRegExp(normalizedPattern).test(normalized);
|
|
75
59
|
});
|
|
76
60
|
}
|
|
77
|
-
function normalizeStringList(values) {
|
|
78
|
-
return (values || []).map(normalizePathValue).filter(Boolean);
|
|
79
|
-
}
|
|
80
61
|
function parseBundleMaxSize(value) {
|
|
81
62
|
if (typeof value === "number") {
|
|
82
63
|
if (!Number.isFinite(value) || value <= 0) {
|
|
@@ -106,9 +87,36 @@ function parseBundleMaxSize(value) {
|
|
|
106
87
|
}
|
|
107
88
|
return resolved;
|
|
108
89
|
}
|
|
90
|
+
function normalizeStringList(values) {
|
|
91
|
+
return (values || []).map(normalizePathValue).filter(Boolean);
|
|
92
|
+
}
|
|
93
|
+
function normalizeDiscoverRule(rule) {
|
|
94
|
+
const key = normalizePathValue(rule.key);
|
|
95
|
+
const include = normalizeStringList(rule.include);
|
|
96
|
+
const exclude = normalizeStringList(rule.exclude);
|
|
97
|
+
if (!key) {
|
|
98
|
+
throw new Error("bundler-discover-rule-missing-key");
|
|
99
|
+
}
|
|
100
|
+
if (include.length === 0) {
|
|
101
|
+
throw new Error(`bundler-discover-rule-missing-include :: ${key}`);
|
|
102
|
+
}
|
|
103
|
+
if (rule.strategy !== "bundle" && rule.maxBundleSize != null) {
|
|
104
|
+
throw new Error(`bundler-discover-rule-invalid-max-size-strategy :: ${key}`);
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
exclude,
|
|
108
|
+
include,
|
|
109
|
+
key,
|
|
110
|
+
maxBundleSize: rule.strategy === "bundle" ? parseBundleMaxSize(rule.maxBundleSize) : undefined,
|
|
111
|
+
strategy: rule.strategy,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
109
114
|
function normalizeDiscoverOptions(rootDir, discover) {
|
|
110
115
|
const list = Array.isArray(discover) ? discover : discover ? [discover] : [];
|
|
111
|
-
|
|
116
|
+
if (list.length === 0) {
|
|
117
|
+
throw new Error("bundler-missing-discover");
|
|
118
|
+
}
|
|
119
|
+
const normalized = list
|
|
112
120
|
.map((item) => item && typeof item === "object" ? item : null)
|
|
113
121
|
.filter(Boolean)
|
|
114
122
|
.map((item) => {
|
|
@@ -116,109 +124,30 @@ function normalizeDiscoverOptions(rootDir, discover) {
|
|
|
116
124
|
if (!dir) {
|
|
117
125
|
throw new Error("bundler-discover-missing-dir");
|
|
118
126
|
}
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
127
|
+
const rules = (item.rules || []).map(normalizeDiscoverRule);
|
|
128
|
+
if (rules.length === 0) {
|
|
129
|
+
throw new Error(`bundler-discover-missing-rules :: ${dir}`);
|
|
130
|
+
}
|
|
123
131
|
return {
|
|
124
132
|
dir,
|
|
125
133
|
dirAbs: path.resolve(rootDir, dir),
|
|
126
|
-
exclude: normalizeStringList(item.exclude),
|
|
127
|
-
extensions,
|
|
128
134
|
ignoreDirs: new Set([
|
|
129
135
|
...DEFAULT_IGNORE_DIRS,
|
|
130
136
|
...normalizeStringList(item.ignoreDirs),
|
|
131
137
|
].map((value) => path.basename(value))),
|
|
132
|
-
|
|
133
|
-
maxBundleSize: parseBundleMaxSize(item.maxBundleSize),
|
|
134
|
-
namePrefix: normalizePathValue(item.namePrefix || ""),
|
|
138
|
+
rules,
|
|
135
139
|
};
|
|
136
140
|
});
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return entries
|
|
143
|
-
.map((value) => String(value || "").trim())
|
|
144
|
-
.filter(Boolean)
|
|
145
|
-
.map((value) => {
|
|
146
|
-
const rel = normalizePathValue(value);
|
|
147
|
-
const ext = path.extname(rel);
|
|
148
|
-
const noExt = ext ? rel.slice(0, -ext.length) : rel;
|
|
149
|
-
return {
|
|
150
|
-
name: noExt,
|
|
151
|
-
path: path.resolve(rootDir, rel),
|
|
152
|
-
source: "manual",
|
|
153
|
-
};
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
if (typeof entries === "object") {
|
|
157
|
-
return Object.entries(entries)
|
|
158
|
-
.map(([key, value]) => ({
|
|
159
|
-
name: normalizePathValue(key),
|
|
160
|
-
path: path.resolve(rootDir, String(value || "").trim()),
|
|
161
|
-
source: "manual",
|
|
162
|
-
}))
|
|
163
|
-
.filter((entry) => Boolean(entry.name && entry.path));
|
|
164
|
-
}
|
|
165
|
-
return [];
|
|
166
|
-
}
|
|
167
|
-
function normalizeVirtualEntries(virtualEntries) {
|
|
168
|
-
if (!virtualEntries || typeof virtualEntries !== "object")
|
|
169
|
-
return [];
|
|
170
|
-
return Object.entries(virtualEntries)
|
|
171
|
-
.map(([key, value]) => ({
|
|
172
|
-
name: normalizePathValue(key),
|
|
173
|
-
path: `${VIRTUAL_ENTRY_PREFIX}${normalizePathValue(key)}`,
|
|
174
|
-
source: "virtual",
|
|
175
|
-
contents: String(value || ""),
|
|
176
|
-
}))
|
|
177
|
-
.filter((entry) => Boolean(entry.name));
|
|
178
|
-
}
|
|
179
|
-
function resolveEntryPriority(record) {
|
|
180
|
-
if (record.source === "manual")
|
|
181
|
-
return 0;
|
|
182
|
-
if (record.source === "virtual")
|
|
183
|
-
return 1;
|
|
184
|
-
return 2;
|
|
185
|
-
}
|
|
186
|
-
function compareEntries(a, b) {
|
|
187
|
-
return resolveEntryPriority(a) - resolveEntryPriority(b)
|
|
188
|
-
|| a.name.localeCompare(b.name)
|
|
189
|
-
|| a.path.localeCompare(b.path);
|
|
190
|
-
}
|
|
191
|
-
function dedupeEntriesBySourcePath(records) {
|
|
192
|
-
const duplicates = [];
|
|
193
|
-
const keptByPath = new Map();
|
|
194
|
-
for (const record of [...records].sort(compareEntries)) {
|
|
195
|
-
if (record.source === "virtual") {
|
|
196
|
-
const virtualKey = `virtual:${record.name}`;
|
|
197
|
-
if (!keptByPath.has(virtualKey)) {
|
|
198
|
-
keptByPath.set(virtualKey, record);
|
|
141
|
+
const seenRuleKeys = new Set();
|
|
142
|
+
for (const config of normalized) {
|
|
143
|
+
for (const rule of config.rules) {
|
|
144
|
+
if (seenRuleKeys.has(rule.key)) {
|
|
145
|
+
throw new Error(`bundler-discover-duplicate-rule-key :: ${rule.key}`);
|
|
199
146
|
}
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
const key = toPosixPath(path.resolve(record.path));
|
|
203
|
-
const existing = keptByPath.get(key);
|
|
204
|
-
if (!existing) {
|
|
205
|
-
keptByPath.set(key, record);
|
|
206
|
-
continue;
|
|
147
|
+
seenRuleKeys.add(rule.key);
|
|
207
148
|
}
|
|
208
|
-
duplicates.push({
|
|
209
|
-
dropped: record,
|
|
210
|
-
kept: existing,
|
|
211
|
-
});
|
|
212
149
|
}
|
|
213
|
-
return
|
|
214
|
-
duplicates: duplicates.sort((a, b) => compareEntries(a.dropped, b.dropped)),
|
|
215
|
-
records: Array.from(keptByPath.values()).sort((a, b) => a.name.localeCompare(b.name) || a.path.localeCompare(b.path)),
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
function buildDiscoveredEntryName(args) {
|
|
219
|
-
const ext = path.extname(args.relativePath);
|
|
220
|
-
const withoutExt = ext ? args.relativePath.slice(0, -ext.length) : args.relativePath;
|
|
221
|
-
return normalizePathValue([args.config.namePrefix, withoutExt].filter(Boolean).join("/"));
|
|
150
|
+
return normalized;
|
|
222
151
|
}
|
|
223
152
|
function createStableBundleId(value) {
|
|
224
153
|
let hash = 2166136261;
|
|
@@ -228,10 +157,6 @@ function createStableBundleId(value) {
|
|
|
228
157
|
}
|
|
229
158
|
return (hash >>> 0).toString(36);
|
|
230
159
|
}
|
|
231
|
-
function resolveBundleGroup(entry) {
|
|
232
|
-
const ext = path.extname(entry.path).toLowerCase();
|
|
233
|
-
return NORMALIZED_DISCOVERY_BUNDLE_GROUPS.find((group) => group.extensions.has(ext));
|
|
234
|
-
}
|
|
235
160
|
function toRootImportSpecifier(rootDir, absPath) {
|
|
236
161
|
const rel = normalizePathValue(path.relative(rootDir, absPath));
|
|
237
162
|
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
@@ -239,205 +164,279 @@ function toRootImportSpecifier(rootDir, absPath) {
|
|
|
239
164
|
function buildBundleContents(args) {
|
|
240
165
|
if (args.loader === "css") {
|
|
241
166
|
return args.files
|
|
242
|
-
.map((file) => `@import ${JSON.stringify(toRootImportSpecifier(args.rootDir, file.
|
|
167
|
+
.map((file) => `@import ${JSON.stringify(toRootImportSpecifier(args.rootDir, file.absPath))};`)
|
|
243
168
|
.join("\n");
|
|
244
169
|
}
|
|
245
170
|
return args.files
|
|
246
|
-
.map((file) => `import ${JSON.stringify(toRootImportSpecifier(args.rootDir, file.
|
|
171
|
+
.map((file) => `import ${JSON.stringify(toRootImportSpecifier(args.rootDir, file.absPath))};`)
|
|
247
172
|
.join("\n");
|
|
248
173
|
}
|
|
249
|
-
function
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
for (const entry of args.entries) {
|
|
254
|
-
const nextSize = currentSize + entry.bytes;
|
|
255
|
-
if (current.length > 0 && nextSize > args.maxSize) {
|
|
256
|
-
chunks.push(current);
|
|
257
|
-
current = [];
|
|
258
|
-
currentSize = 0;
|
|
259
|
-
}
|
|
260
|
-
current.push(entry);
|
|
261
|
-
currentSize += entry.bytes;
|
|
262
|
-
}
|
|
263
|
-
if (current.length > 0) {
|
|
264
|
-
chunks.push(current);
|
|
265
|
-
}
|
|
266
|
-
return chunks;
|
|
174
|
+
function buildEntryKey(args) {
|
|
175
|
+
const ext = path.extname(args.rootRel);
|
|
176
|
+
const withoutExt = ext ? args.rootRel.slice(0, -ext.length) : args.rootRel;
|
|
177
|
+
return `entry:${args.ruleKey}:${normalizePathValue(withoutExt)}`;
|
|
267
178
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const group = resolveBundleGroup(record);
|
|
271
|
-
if (!group) {
|
|
272
|
-
return {
|
|
273
|
-
group: undefined,
|
|
274
|
-
record,
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
const stats = await fsp.stat(record.path);
|
|
278
|
-
const bytes = Math.max(stats.size, 1);
|
|
279
|
-
if (bytes > args.config.maxBundleSize) {
|
|
280
|
-
throw new Error(`bundler-discover-bundle-file-too-large :: ${normalizePathValue(path.relative(args.rootDir, record.path))}`);
|
|
281
|
-
}
|
|
282
|
-
return {
|
|
283
|
-
group,
|
|
284
|
-
record: {
|
|
285
|
-
...record,
|
|
286
|
-
bytes,
|
|
287
|
-
},
|
|
288
|
-
};
|
|
289
|
-
}));
|
|
290
|
-
const passthrough = [];
|
|
291
|
-
const grouped = new Map();
|
|
292
|
-
for (const item of resolved) {
|
|
293
|
-
if (!item.group) {
|
|
294
|
-
passthrough.push(item.record);
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
const existing = grouped.get(item.group.name);
|
|
298
|
-
if (existing) {
|
|
299
|
-
existing.files.push(item.record);
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
grouped.set(item.group.name, {
|
|
303
|
-
files: [item.record],
|
|
304
|
-
group: item.group,
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
const bundledRecords = [];
|
|
308
|
-
for (const [groupName, value] of Array.from(grouped.entries()).sort(([a], [b]) => a.localeCompare(b))) {
|
|
309
|
-
const bundleId = createStableBundleId(JSON.stringify({
|
|
310
|
-
dir: args.config.dir,
|
|
311
|
-
exclude: args.config.exclude,
|
|
312
|
-
extensions: args.config.extensions,
|
|
313
|
-
groupName,
|
|
314
|
-
include: args.config.include,
|
|
315
|
-
maxBundleSize: args.config.maxBundleSize,
|
|
316
|
-
namePrefix: args.config.namePrefix,
|
|
317
|
-
}));
|
|
318
|
-
const chunks = splitEntriesByMaxSize({
|
|
319
|
-
entries: value.files.sort((a, b) => a.name.localeCompare(b.name) || a.path.localeCompare(b.path)),
|
|
320
|
-
maxSize: args.config.maxBundleSize,
|
|
321
|
-
});
|
|
322
|
-
chunks.forEach((chunk, index) => {
|
|
323
|
-
const suffix = chunks.length > 1 ? `-${index + 1}` : "";
|
|
324
|
-
const name = `bundle-${bundleId}${suffix}`;
|
|
325
|
-
bundledRecords.push({
|
|
326
|
-
contents: buildBundleContents({
|
|
327
|
-
files: chunk,
|
|
328
|
-
loader: value.group.loader,
|
|
329
|
-
rootDir: args.rootDir,
|
|
330
|
-
}),
|
|
331
|
-
name,
|
|
332
|
-
path: `${VIRTUAL_ENTRY_PREFIX}${name}`,
|
|
333
|
-
source: "virtual",
|
|
334
|
-
virtualLoader: value.group.loader,
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
return [...passthrough, ...bundledRecords]
|
|
339
|
-
.sort((a, b) => a.name.localeCompare(b.name) || a.path.localeCompare(b.path));
|
|
179
|
+
function buildBundleEntryKey(ruleKey, part) {
|
|
180
|
+
return `bundle:${ruleKey}:${part}`;
|
|
340
181
|
}
|
|
341
|
-
|
|
182
|
+
function resolveBundleLoader(filePath) {
|
|
183
|
+
if (/\.(?:css|scss)$/i.test(filePath))
|
|
184
|
+
return "css";
|
|
185
|
+
if (/\.(?:[mc]?[jt]sx?)$/i.test(filePath))
|
|
186
|
+
return "ts";
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
async function scanDiscoveredFiles(config, rootDir) {
|
|
342
190
|
if (!fs.existsSync(config.dirAbs))
|
|
343
191
|
return [];
|
|
344
|
-
const
|
|
192
|
+
const files = [];
|
|
345
193
|
const visit = async (currentAbs) => {
|
|
346
194
|
const entries = await fsp.readdir(currentAbs, { withFileTypes: true });
|
|
347
195
|
for (const entry of entries) {
|
|
348
196
|
const abs = path.join(currentAbs, entry.name);
|
|
349
|
-
const
|
|
350
|
-
if (!
|
|
197
|
+
const discoverRel = normalizePathValue(path.relative(config.dirAbs, abs));
|
|
198
|
+
if (!discoverRel)
|
|
351
199
|
continue;
|
|
352
200
|
if (entry.isDirectory()) {
|
|
353
201
|
if (config.ignoreDirs.has(entry.name))
|
|
354
202
|
continue;
|
|
355
|
-
if (matchesAnyPattern(relFromDiscover, config.exclude))
|
|
356
|
-
continue;
|
|
357
203
|
await visit(abs);
|
|
358
204
|
continue;
|
|
359
205
|
}
|
|
360
206
|
if (!entry.isFile())
|
|
361
207
|
continue;
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
continue;
|
|
367
|
-
if (matchesAnyPattern(relFromDiscover, config.exclude))
|
|
368
|
-
continue;
|
|
369
|
-
records.push({
|
|
370
|
-
name: buildDiscoveredEntryName({
|
|
371
|
-
config,
|
|
372
|
-
relativePath: relFromDiscover,
|
|
373
|
-
}),
|
|
374
|
-
path: abs,
|
|
375
|
-
source: "discover",
|
|
208
|
+
files.push({
|
|
209
|
+
absPath: abs,
|
|
210
|
+
discoverRel,
|
|
211
|
+
rootRel: normalizePathValue(path.relative(rootDir, abs)),
|
|
376
212
|
});
|
|
377
213
|
}
|
|
378
214
|
};
|
|
379
215
|
await visit(config.dirAbs);
|
|
380
|
-
return
|
|
216
|
+
return files.sort((a, b) => a.rootRel.localeCompare(b.rootRel));
|
|
381
217
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
218
|
+
function classifyFile(args) {
|
|
219
|
+
return args.config.rules.find((rule) => {
|
|
220
|
+
if (!matchesAnyPattern(args.file.discoverRel, rule.include))
|
|
221
|
+
return false;
|
|
222
|
+
if (matchesAnyPattern(args.file.discoverRel, rule.exclude))
|
|
223
|
+
return false;
|
|
224
|
+
return true;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
function splitByMaxSize(args) {
|
|
228
|
+
const chunks = [];
|
|
229
|
+
let current = [];
|
|
230
|
+
let currentSize = 0;
|
|
231
|
+
for (const file of args.files) {
|
|
232
|
+
const nextSize = currentSize + file.bytes;
|
|
233
|
+
if (current.length > 0 && nextSize > args.maxBundleSize) {
|
|
234
|
+
chunks.push(current);
|
|
235
|
+
current = [];
|
|
236
|
+
currentSize = 0;
|
|
237
|
+
}
|
|
238
|
+
current.push(file);
|
|
239
|
+
currentSize += file.bytes;
|
|
240
|
+
}
|
|
241
|
+
if (current.length > 0) {
|
|
242
|
+
chunks.push(current);
|
|
243
|
+
}
|
|
244
|
+
return chunks;
|
|
245
|
+
}
|
|
246
|
+
async function validateGroupedBootImports(args) {
|
|
247
|
+
const groupedScriptSources = new Set(args.entries
|
|
248
|
+
.filter((entry) => entry.strategy === "bundle" && entry.virtualLoader === "ts")
|
|
249
|
+
.flatMap((entry) => entry.ownedSources));
|
|
250
|
+
if (groupedScriptSources.size === 0)
|
|
251
|
+
return;
|
|
252
|
+
const bootEntries = args.entries.filter((entry) => {
|
|
253
|
+
if (entry.strategy !== "entry" || !entry.entrySource)
|
|
254
|
+
return false;
|
|
255
|
+
return /\.(?:client\.tsx?|defer\.ts)$/i.test(entry.entrySource);
|
|
256
|
+
});
|
|
257
|
+
for (const bootEntry of bootEntries) {
|
|
258
|
+
const graph = await walkImportGraph({
|
|
259
|
+
entries: bootEntry.entrySource,
|
|
260
|
+
rootDir: args.rootDir,
|
|
391
261
|
});
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
262
|
+
const groupedDependency = Object.keys(graph.files)
|
|
263
|
+
.sort()
|
|
264
|
+
.find((sourcePath) => sourcePath !== bootEntry.entrySource && groupedScriptSources.has(sourcePath));
|
|
265
|
+
if (groupedDependency) {
|
|
266
|
+
throw new Error(`bundler-discover-entry-imports-grouped-source :: ${bootEntry.entrySource} -> ${groupedDependency}`);
|
|
267
|
+
}
|
|
398
268
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
269
|
+
}
|
|
270
|
+
async function resolveBundlerEntries(options, rootDir, settings = {}) {
|
|
271
|
+
const configs = normalizeDiscoverOptions(rootDir, options.discover);
|
|
272
|
+
const resolvedEntries = [];
|
|
273
|
+
const rules = new Map();
|
|
274
|
+
const sourceOwners = new Map();
|
|
275
|
+
const emittedNames = new Set();
|
|
276
|
+
const emittedKeys = new Set();
|
|
277
|
+
for (const config of configs) {
|
|
278
|
+
const files = await scanDiscoveredFiles(config, rootDir);
|
|
279
|
+
const matchedByRule = new Map();
|
|
280
|
+
for (const rule of config.rules) {
|
|
281
|
+
rules.set(rule.key, {
|
|
282
|
+
entryKeys: [],
|
|
283
|
+
ignoredSources: [],
|
|
284
|
+
ruleKey: rule.key,
|
|
285
|
+
strategy: rule.strategy,
|
|
286
|
+
});
|
|
287
|
+
matchedByRule.set(rule.key, []);
|
|
288
|
+
}
|
|
289
|
+
for (const file of files) {
|
|
290
|
+
const matchedRule = classifyFile({
|
|
291
|
+
config,
|
|
292
|
+
file,
|
|
293
|
+
});
|
|
294
|
+
if (!matchedRule) {
|
|
295
|
+
throw new Error(`bundler-discover-unmatched-file :: ${file.rootRel}`);
|
|
296
|
+
}
|
|
297
|
+
matchedByRule.get(matchedRule.key).push(file);
|
|
298
|
+
}
|
|
299
|
+
for (const rule of config.rules) {
|
|
300
|
+
const matchedFiles = (matchedByRule.get(rule.key) || []).sort((a, b) => a.rootRel.localeCompare(b.rootRel));
|
|
301
|
+
const ruleRecord = rules.get(rule.key);
|
|
302
|
+
if (rule.strategy === "ignore") {
|
|
303
|
+
ruleRecord.ignoredSources = matchedFiles.map((file) => file.rootRel);
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (rule.strategy === "entry") {
|
|
307
|
+
for (const file of matchedFiles) {
|
|
308
|
+
const ext = path.extname(file.rootRel);
|
|
309
|
+
const withoutExt = ext ? file.rootRel.slice(0, -ext.length) : file.rootRel;
|
|
310
|
+
const record = {
|
|
311
|
+
entrySource: file.rootRel,
|
|
312
|
+
key: buildEntryKey({
|
|
313
|
+
rootRel: file.rootRel,
|
|
314
|
+
ruleKey: rule.key,
|
|
315
|
+
}),
|
|
316
|
+
kind: "entry",
|
|
317
|
+
name: normalizePathValue(withoutExt),
|
|
318
|
+
ownedSources: [file.rootRel],
|
|
319
|
+
path: file.absPath,
|
|
320
|
+
ruleKey: rule.key,
|
|
321
|
+
source: "discover",
|
|
322
|
+
strategy: "entry",
|
|
323
|
+
};
|
|
324
|
+
if (emittedKeys.has(record.key)) {
|
|
325
|
+
throw new Error(`bundler-discover-entry-key-conflict :: ${record.key}`);
|
|
326
|
+
}
|
|
327
|
+
if (emittedNames.has(record.name)) {
|
|
328
|
+
throw new Error(`bundler-discover-output-name-conflict :: ${record.name}`);
|
|
329
|
+
}
|
|
330
|
+
emittedKeys.add(record.key);
|
|
331
|
+
emittedNames.add(record.name);
|
|
332
|
+
resolvedEntries.push(record);
|
|
333
|
+
ruleRecord.entryKeys.push(record.key);
|
|
334
|
+
sourceOwners.set(file.rootRel, record.key);
|
|
335
|
+
}
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
const filesWithStats = await Promise.all(matchedFiles.map(async (file) => {
|
|
339
|
+
const stats = await fsp.stat(file.absPath);
|
|
340
|
+
const bytes = Math.max(stats.size, 1);
|
|
341
|
+
if (bytes > rule.maxBundleSize) {
|
|
342
|
+
throw new Error(`bundler-discover-bundle-file-too-large :: ${file.rootRel}`);
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
...file,
|
|
346
|
+
bytes,
|
|
347
|
+
};
|
|
348
|
+
}));
|
|
349
|
+
if (filesWithStats.length === 0) {
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
const loaders = new Set(filesWithStats.map((file) => resolveBundleLoader(file.rootRel)).filter(Boolean));
|
|
353
|
+
if (loaders.size !== 1) {
|
|
354
|
+
throw new Error(`bundler-discover-rule-mixed-loaders :: ${rule.key}`);
|
|
355
|
+
}
|
|
356
|
+
const loader = Array.from(loaders)[0];
|
|
357
|
+
const stableId = createStableBundleId(JSON.stringify({
|
|
358
|
+
dir: config.dir,
|
|
359
|
+
ruleKey: rule.key,
|
|
360
|
+
sources: filesWithStats.map((file) => file.rootRel),
|
|
361
|
+
}));
|
|
362
|
+
const chunks = splitByMaxSize({
|
|
363
|
+
files: filesWithStats.sort((a, b) => a.rootRel.localeCompare(b.rootRel)),
|
|
364
|
+
maxBundleSize: rule.maxBundleSize,
|
|
365
|
+
});
|
|
366
|
+
chunks.forEach((chunk, index) => {
|
|
367
|
+
const part = index + 1;
|
|
368
|
+
const name = chunks.length === 1 ? `bundle-${stableId}` : `bundle-${stableId}-${part}`;
|
|
369
|
+
const key = buildBundleEntryKey(rule.key, part);
|
|
370
|
+
const ownedSources = chunk.map((file) => file.rootRel);
|
|
371
|
+
if (emittedKeys.has(key)) {
|
|
372
|
+
throw new Error(`bundler-discover-entry-key-conflict :: ${key}`);
|
|
373
|
+
}
|
|
374
|
+
if (emittedNames.has(name)) {
|
|
375
|
+
throw new Error(`bundler-discover-output-name-conflict :: ${name}`);
|
|
376
|
+
}
|
|
377
|
+
emittedKeys.add(key);
|
|
378
|
+
emittedNames.add(name);
|
|
379
|
+
resolvedEntries.push({
|
|
380
|
+
contents: buildBundleContents({
|
|
381
|
+
files: chunk,
|
|
382
|
+
loader,
|
|
383
|
+
rootDir,
|
|
384
|
+
}),
|
|
385
|
+
key,
|
|
386
|
+
kind: "bundle",
|
|
387
|
+
name,
|
|
388
|
+
ownedSources,
|
|
389
|
+
path: `${VIRTUAL_ENTRY_PREFIX}${name}`,
|
|
390
|
+
ruleKey: rule.key,
|
|
391
|
+
source: "internal",
|
|
392
|
+
strategy: "bundle",
|
|
393
|
+
virtualLoader: loader,
|
|
394
|
+
});
|
|
395
|
+
ruleRecord.entryKeys.push(key);
|
|
396
|
+
for (const sourcePath of ownedSources) {
|
|
397
|
+
sourceOwners.set(sourcePath, key);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
405
400
|
}
|
|
406
|
-
if (existing.path === record.path)
|
|
407
|
-
continue;
|
|
408
|
-
throw new Error(`bundler-entry-name-conflict :: ${record.name}`);
|
|
409
401
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
402
|
+
await validateGroupedBootImports({
|
|
403
|
+
entries: resolvedEntries,
|
|
404
|
+
rootDir,
|
|
405
|
+
});
|
|
406
|
+
if (resolvedEntries.length === 0 && !settings.allowEmpty) {
|
|
407
|
+
throw new Error("bundler-missing-entries");
|
|
408
|
+
}
|
|
409
|
+
const discovery = {
|
|
410
|
+
entries: resolvedEntries.sort((a, b) => a.key.localeCompare(b.key)),
|
|
411
|
+
rules: Object.fromEntries(Array.from(rules.entries()).sort(([a], [b]) => a.localeCompare(b))),
|
|
412
|
+
sourceOwners: Object.fromEntries(Array.from(sourceOwners.entries()).sort(([a], [b]) => a.localeCompare(b))),
|
|
413
|
+
};
|
|
419
414
|
return {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
415
|
+
...discovery,
|
|
416
|
+
signature: JSON.stringify({
|
|
417
|
+
entries: discovery.entries.map((entry) => ({
|
|
418
|
+
entrySource: entry.entrySource,
|
|
419
|
+
key: entry.key,
|
|
420
|
+
kind: entry.kind,
|
|
421
|
+
name: entry.name,
|
|
422
|
+
ownedSources: entry.ownedSources,
|
|
423
|
+
path: entry.source === "internal" ? `virtual:${entry.name}` : entry.path,
|
|
424
|
+
ruleKey: entry.ruleKey,
|
|
425
|
+
strategy: entry.strategy,
|
|
426
|
+
})),
|
|
427
|
+
rules: discovery.rules,
|
|
428
|
+
sourceOwners: discovery.sourceOwners,
|
|
429
|
+
}),
|
|
423
430
|
};
|
|
424
431
|
}
|
|
425
432
|
function toEntryPointMap(records, rootDir) {
|
|
426
433
|
return Object.fromEntries(records.map((record) => [
|
|
427
434
|
record.name,
|
|
428
|
-
record.source === "
|
|
435
|
+
record.source === "internal"
|
|
429
436
|
? record.path
|
|
430
437
|
: normalizePathValue(path.relative(rootDir, record.path)),
|
|
431
438
|
]));
|
|
432
439
|
}
|
|
433
|
-
function toPublicEntryMap(records, rootDir) {
|
|
434
|
-
return Object.fromEntries(records.map((record) => [
|
|
435
|
-
record.name,
|
|
436
|
-
record.source === "virtual"
|
|
437
|
-
? `virtual:${record.name}`
|
|
438
|
-
: normalizePathValue(path.relative(rootDir, record.path)),
|
|
439
|
-
]));
|
|
440
|
-
}
|
|
441
440
|
function normalizeManifestOptions(manifest) {
|
|
442
441
|
if (!manifest) {
|
|
443
442
|
return { enabled: false };
|
|
@@ -466,5 +465,5 @@ function normalizeDiscoverRoots(rootDir, discover) {
|
|
|
466
465
|
});
|
|
467
466
|
return Array.from(new Set(roots));
|
|
468
467
|
}
|
|
469
|
-
export { normalizeDiscoverRoots, normalizeManifestOptions, resolveBundlerEntries,
|
|
468
|
+
export { normalizeDiscoverRoots, normalizeManifestOptions, resolveBundlerEntries, toEntryPointMap, toPosixPath, VIRTUAL_ENTRY_PREFIX, };
|
|
470
469
|
//# sourceMappingURL=discovery.js.map
|