@staticbolt/core 1.0.0-beta.32 → 1.0.0-beta.33
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/lib/index.d.mts +2 -2
- package/lib/plugins/index.d.mts +93 -32
- package/lib/plugins/index.d.mts.map +1 -1
- package/lib/plugins/index.mjs +1866 -709
- package/lib/plugins/index.mjs.map +1 -1
- package/package.json +5 -3
package/lib/plugins/index.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { A as isTextAssetMetadata, B as checkJsonObject, C as isBinaryAssetMetadata, D as isScriptMetadata, E as isPackageMetadata, G as isTypeScriptScript, H as isEmptyElement, I as safeReadFileSync, K as TYPESCRIPT_TYPE, M as METADATA_TYPES, N as Resolver, O as isStyleMetadata, P as readJsonFile, R as valueOrError, S as filterStyleMetadata, T as isMarkdownMetadata, U as isJavaScript, V as isDynamic, X as splitHtmlLink, Y as isValidRelativePath, Z as DependencyTracker, _ as mergeMaps, b as printFmtError, c as downloadContent, d as hashContent, f as humanReadableBytes, h as isURL, j as isWebManifestMetadata, k as isSvgMetadata, l as escapeHtml, n as bytesToKB, q as isScriptType, s as cloneObject, w as isHtmlMetadata, x as filterScriptMetadata, y as PrintFormattedError, z as checkFileExists } from "../utilities-jK4uUZBV.mjs";
|
|
2
|
-
import { _ as replaceExtension, a as basename, c as isAbsolute, d as join, f as normalize, g as relative, i as createLog, l as isPathMatch, m as parsePatterns, n as CUSTOM_ATTRIBUTES, o as dirname, p as parse$
|
|
2
|
+
import { _ as replaceExtension, a as basename, c as isAbsolute, d as join, f as normalize, g as relative, i as createLog, l as isPathMatch, m as parsePatterns, n as CUSTOM_ATTRIBUTES, o as dirname, p as parse$5, r as Log, s as extname, u as isSubpath, v as resolve } from "../common-D1QTZ8ra.mjs";
|
|
3
3
|
import { _ as minifyHtml, a as formatStyle, c as minifyStylePass, d as minifyScript, f as minifyScriptPass, g as formatHtmlPass, h as formatHtml, i as minifySvgPass, l as formatScript, m as formatMarkdownPass, n as formatSvgPass, o as formatStylePass, p as formatMarkdown, r as minifySvg, s as minifyStyle, t as formatSvg, u as formatScriptPass, v as minifyHtmlPass, y as formatCode } from "../deferred-DTj91vEg.mjs";
|
|
4
4
|
import { t as loadConfigFile } from "../load-config-CsbiJ01A.mjs";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import nodePath from "node:path";
|
|
7
7
|
import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { ResolverFactory } from "oxc-resolver";
|
|
8
9
|
import chalk from "chalk";
|
|
9
10
|
import json5 from "json5";
|
|
10
11
|
import _generator from "@babel/generator";
|
|
@@ -15,6 +16,10 @@ import { globSync } from "glob";
|
|
|
15
16
|
import * as esbuild from "esbuild";
|
|
16
17
|
import _traverse from "@babel/traverse";
|
|
17
18
|
import * as t from "@babel/types";
|
|
19
|
+
import { init, parse as parse$1 } from "cjs-module-lexer";
|
|
20
|
+
import { init as init$1, parse as parse$2 } from "es-module-lexer";
|
|
21
|
+
import * as babelParser from "@babel/parser";
|
|
22
|
+
import { parse as parse$3 } from "@babel/parser";
|
|
18
23
|
import postcssrc from "postcss-load-config";
|
|
19
24
|
import * as babel from "@babel/core";
|
|
20
25
|
import babelPresetEnv from "@babel/preset-env";
|
|
@@ -35,8 +40,7 @@ import remarkRehype from "remark-rehype";
|
|
|
35
40
|
import remarkSmartypants from "remark-smartypants";
|
|
36
41
|
import { unified } from "unified";
|
|
37
42
|
import { visit } from "unist-util-visit";
|
|
38
|
-
import { parse as parse$
|
|
39
|
-
import * as babelParser from "@babel/parser";
|
|
43
|
+
import { parse as parse$4 } from "yaml";
|
|
40
44
|
import fastifyStatic from "@fastify/static";
|
|
41
45
|
import fastifyUrlData from "@fastify/url-data";
|
|
42
46
|
import Fastify from "fastify";
|
|
@@ -137,7 +141,7 @@ function analyzeOutputPlugin(options = {}) {
|
|
|
137
141
|
"workbox-*.js"
|
|
138
142
|
];
|
|
139
143
|
const maxFileSize = options.maxFileSize ?? { ".js": 150 };
|
|
140
|
-
const isLogUnusedFiles = options.logUnusedFiles ??
|
|
144
|
+
const isLogUnusedFiles = options.logUnusedFiles ?? false;
|
|
141
145
|
return {
|
|
142
146
|
name: "analyze-output",
|
|
143
147
|
async postBuild() {
|
|
@@ -158,21 +162,21 @@ function analyzeOutputPlugin(options = {}) {
|
|
|
158
162
|
root: this.outdir
|
|
159
163
|
});
|
|
160
164
|
const missingFiles = [...usedFiles.difference(availableFiles)].filter((file) => !isExcluded(file));
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
165
|
+
if (missingFiles.length > 0) {
|
|
166
|
+
const missingFilesLog = missingFiles.map((missing) => "\n" + chalk.red("File not found: ") + chalk.yellow(relative(this.root, missing)));
|
|
167
|
+
this.log.error(chalk.bold("The following files are missing:") + missingFilesLog.join(""));
|
|
168
|
+
}
|
|
164
169
|
const unusedFiles = availableFiles.difference(usedFiles);
|
|
165
170
|
const unusedFilesLog = [];
|
|
166
171
|
for (const unused of unusedFiles) {
|
|
167
|
-
if (extname(unused) === ".html") continue;
|
|
168
172
|
if (isExcluded(unused)) continue;
|
|
169
173
|
if (isRemoveUnused) unlinkSync(unused);
|
|
170
174
|
unusedFilesLog.push(chalk.yellow(join(basename(this.outdir), relative(this.outdir, unused))));
|
|
171
175
|
}
|
|
172
176
|
const logTitle = isRemoveUnused ? chalk.bold(chalk.red("Removed"), "(" + chalk.yellow(unusedFilesLog.length) + ")", "unused files from the output directory") : chalk.bold("Found", "(" + chalk.yellow(unusedFilesLog.length) + ")", "unused files");
|
|
173
177
|
if (unusedFilesLog.length > 0) this.log.warn(logTitle + (isLogUnusedFiles ? "\n" + unusedFilesLog.join("\n") : ""));
|
|
174
|
-
const
|
|
175
|
-
for (const directory of
|
|
178
|
+
const directories = globSync(this.outdir + "/**/*/");
|
|
179
|
+
for (const directory of directories) {
|
|
176
180
|
let currentPath = directory;
|
|
177
181
|
while (currentPath !== this.outdir) {
|
|
178
182
|
if (!(readdirSync(currentPath).length === 0)) break;
|
|
@@ -181,9 +185,8 @@ function analyzeOutputPlugin(options = {}) {
|
|
|
181
185
|
}
|
|
182
186
|
}
|
|
183
187
|
if (maxFileSize) {
|
|
184
|
-
const
|
|
185
|
-
const
|
|
186
|
-
const filesStat = globSync(`**/*.${patternExtensionPart}`, {
|
|
188
|
+
const patterns = Object.keys(maxFileSize).map((extension) => `**/*${extension}`);
|
|
189
|
+
const filesStat = globSync(patterns, {
|
|
187
190
|
ignore: exclude,
|
|
188
191
|
nodir: true,
|
|
189
192
|
cwd: this.outdir,
|
|
@@ -195,9 +198,9 @@ function analyzeOutputPlugin(options = {}) {
|
|
|
195
198
|
for (const file of sortedFiles) {
|
|
196
199
|
const maxSize = maxFileSize[extname(file.name)];
|
|
197
200
|
if (typeof maxSize !== "number") continue;
|
|
198
|
-
|
|
199
|
-
if (bytesToKB(
|
|
200
|
-
largeFilesLogs.push(chalk.yellow(file.relative()) + ` (${chalk.red.bold(humanReadableBytes(
|
|
201
|
+
const size = file.size ?? 0;
|
|
202
|
+
if (bytesToKB(size) <= maxSize) continue;
|
|
203
|
+
largeFilesLogs.push(chalk.yellow(file.relative()) + ` (${chalk.red.bold(humanReadableBytes(size))} ${chalk.blue(">")} ${chalk.green(humanReadableBytes(maxSize * 1024))})`);
|
|
201
204
|
}
|
|
202
205
|
if (largeFilesLogs.length > 0) this.log.warn(chalk.bold("The following files may be large:\n") + largeFilesLogs.join("\n"));
|
|
203
206
|
}
|
|
@@ -206,38 +209,135 @@ function analyzeOutputPlugin(options = {}) {
|
|
|
206
209
|
}
|
|
207
210
|
|
|
208
211
|
//#endregion
|
|
209
|
-
//#region src/plugins/bundle-packages/
|
|
212
|
+
//#region src/plugins/bundle-packages/shared/helpers.ts
|
|
213
|
+
/** Appends to the array at `key`, creating it when absent. */
|
|
214
|
+
function pushInto(map, key, value) {
|
|
215
|
+
const existing = map.get(key);
|
|
216
|
+
if (!existing) {
|
|
217
|
+
map.set(key, [value]);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
existing.push(value);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Replaces `.js` with `_js` in the directory parts of a package path, so a package like `highlight.js` does not need a folder
|
|
224
|
+
* with the same name as its own bundle.
|
|
225
|
+
*
|
|
226
|
+
* Example: `highlight.js/languages/javascript.js` -> `highlight_js/languages/javascript.js`
|
|
227
|
+
*/
|
|
228
|
+
function normalizePackageName(originalSource) {
|
|
229
|
+
const parts = originalSource.split("/");
|
|
230
|
+
if (parts.length <= 1) return originalSource;
|
|
231
|
+
const fileName = parts.pop();
|
|
232
|
+
return [...parts.map((part) => part.endsWith(".js") ? part.slice(0, -3) + "_js" : part), fileName].join("/");
|
|
233
|
+
}
|
|
234
|
+
/** Extensions a package name can already end in, which are replaced rather than appended to. */
|
|
235
|
+
const JS_EXTENSIONS$1 = /* @__PURE__ */ new Set([
|
|
236
|
+
".js",
|
|
237
|
+
".mjs",
|
|
238
|
+
".cjs"
|
|
239
|
+
]);
|
|
240
|
+
/**
|
|
241
|
+
* Gives a package name the `.js` its bundle is written under.
|
|
242
|
+
*
|
|
243
|
+
* Only a real JavaScript extension is replaced. Treating the last dot-segment of any name as one puts `lodash.debounce` and
|
|
244
|
+
* `lodash.merge` in the same file, where the second overwrites the first and every import of the loser breaks.
|
|
245
|
+
*/
|
|
246
|
+
function withJsExtension(name) {
|
|
247
|
+
const lastSegment = name.slice(name.lastIndexOf("/") + 1);
|
|
248
|
+
const dot = lastSegment.lastIndexOf(".");
|
|
249
|
+
if (dot === -1 || !JS_EXTENSIONS$1.has(lastSegment.slice(dot))) return name + ".js";
|
|
250
|
+
return name.slice(0, name.length - (lastSegment.length - dot)) + ".js";
|
|
251
|
+
}
|
|
252
|
+
/** Where a package's bundle is written. Example: `react-dom/client` -> `packages/react-dom/client.js` */
|
|
253
|
+
function calcPackageOutPath(packageName, packagesDirectory) {
|
|
254
|
+
return join(packagesDirectory, withJsExtension(normalizePackageName(packageName)));
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Output path for an asset shipped by a package, keeping its own extension.
|
|
258
|
+
*
|
|
259
|
+
* Example: `node_modules/highlight.js/styles/default.css` -> `packages/highlight_js/styles/default.css`
|
|
260
|
+
*/
|
|
261
|
+
function calcPackageAssetOutPath(nodeModulesPath, packagesDirectory) {
|
|
262
|
+
const markerIndex = nodeModulesPath.lastIndexOf("node_modules/");
|
|
263
|
+
const packagePath = markerIndex === -1 ? nodeModulesPath : nodeModulesPath.slice(markerIndex + 13);
|
|
264
|
+
return join(packagesDirectory, normalizePackageName(packagePath));
|
|
265
|
+
}
|
|
266
|
+
/** The part of a pattern before its wildcard, or nothing when it has none. */
|
|
267
|
+
function wildcardBase(pattern) {
|
|
268
|
+
if (pattern.endsWith("/**")) return pattern.slice(0, -3);
|
|
269
|
+
if (pattern.endsWith("/*")) return pattern.slice(0, -2);
|
|
270
|
+
}
|
|
271
|
+
/** Whether a package answers to any of the patterns written on a chunk. */
|
|
272
|
+
function matchesPackage(packageName, packages) {
|
|
273
|
+
for (const pattern of packages) {
|
|
274
|
+
if (pattern === packageName) return true;
|
|
275
|
+
const base = wildcardBase(pattern);
|
|
276
|
+
if (base === void 0) continue;
|
|
277
|
+
if (packageName === base || packageName.startsWith(base + "/")) return true;
|
|
278
|
+
}
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
/** Joins a package name with one of its entry points. Example: `packageJoin("react-dom", "./client") // => "react-dom/client"` */
|
|
282
|
+
function packageJoin(name, subPackage) {
|
|
283
|
+
return nodePath.join(name, subPackage).replace(/\\/g, "/").replace(/\/$/, "");
|
|
284
|
+
}
|
|
285
|
+
/** The `node_modules` directory a package was installed into, or nothing with the reason it could not be found. */
|
|
286
|
+
function getNodeModuleDirectory(packageName, root) {
|
|
287
|
+
try {
|
|
288
|
+
const entryPoint = createRequire(root.endsWith(nodePath.sep) ? root : root + nodePath.sep).resolve(packageName);
|
|
289
|
+
const parts = entryPoint.split(nodePath.sep);
|
|
290
|
+
const index = parts.lastIndexOf("node_modules");
|
|
291
|
+
if (index === -1) return { problem: `"${packageName}" resolves to "${entryPoint}", which is not inside a node_modules directory.` };
|
|
292
|
+
return { path: parts.slice(0, index + 1).join(nodePath.sep) };
|
|
293
|
+
} catch {
|
|
294
|
+
return { problem: `"${packageName}" could not be resolved from the project root.` };
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* The path a `resolvePackage` entry points a package export at, if one covers it.
|
|
299
|
+
*
|
|
300
|
+
* An entry that cannot be honoured goes to `onProblem` rather than being printed, so it reaches the build's own reporting.
|
|
301
|
+
*/
|
|
302
|
+
function getCustomPackagePath(root, importPath, isProduction, resolvePackage, onProblem) {
|
|
303
|
+
const environment = isProduction ? "production" : "development";
|
|
304
|
+
for (const [packageName, exports] of Object.entries(resolvePackage)) for (const [entry, stringOrObject] of Object.entries(exports)) {
|
|
305
|
+
if (packageJoin(packageName, entry) !== importPath) continue;
|
|
306
|
+
const relativePath = typeof stringOrObject === "string" ? stringOrObject : stringOrObject[environment];
|
|
307
|
+
if (!relativePath) continue;
|
|
308
|
+
const directory = getNodeModuleDirectory(packageName, root);
|
|
309
|
+
if ("problem" in directory) {
|
|
310
|
+
onProblem(`The "resolvePackage" entry for "${importPath}" was skipped: ${directory.problem}`);
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
return join(directory.path, packageName, relativePath);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/** Whether a path reaches into `node_modules`. */
|
|
317
|
+
function isNodeModulesPath(path) {
|
|
318
|
+
return /(^|\/)node_modules\//.test(path);
|
|
319
|
+
}
|
|
320
|
+
/** Directory of the package a `node_modules` path belongs to, `undefined` for paths outside `node_modules`. */
|
|
321
|
+
function packageDirectory(path) {
|
|
322
|
+
return /^(.*?node_modules\/(?:@[^/]+\/)?[^/]+)(?:\/|$)/.exec(path)?.[1];
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
//#endregion
|
|
326
|
+
//#region src/plugins/bundle-packages/build/bundle-esbuild.ts
|
|
210
327
|
const bundleFunction$1 = valueOrError(esbuild.build);
|
|
328
|
+
/** Bundles one generated entry into a single ES module, keeping the result in memory rather than writing it. */
|
|
211
329
|
async function bundlePackage(options) {
|
|
212
|
-
const {
|
|
213
|
-
const plugins = [{
|
|
214
|
-
name: "esbuild-plugin-packages-resolver",
|
|
215
|
-
setup(build) {
|
|
216
|
-
build.onResolve({ filter: /* @__PURE__ */ new RegExp("^[^./].*") }, async (arguments_) => {
|
|
217
|
-
if (arguments_.path === packageName || arguments_.importer === "<stdin>") return;
|
|
218
|
-
return await onPackageResolve(arguments_.path, arguments_.importer);
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
}];
|
|
222
|
-
if (options.onFileResolve) plugins.push({
|
|
223
|
-
name: "esbuild-plugin-relative-imports-resolver",
|
|
224
|
-
setup(build) {
|
|
225
|
-
build.onResolve({ filter: /^\./ }, (arguments_) => {
|
|
226
|
-
return options.onFileResolve?.(arguments_.path, arguments_.importer);
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
});
|
|
330
|
+
const { contents, resolveDir, outfile, onPackageResolve, production } = options;
|
|
230
331
|
const [bundleResult, bundleError] = await bundleFunction$1({
|
|
231
|
-
stdin:
|
|
232
|
-
contents
|
|
332
|
+
stdin: {
|
|
333
|
+
contents,
|
|
233
334
|
resolveDir
|
|
234
|
-
}
|
|
235
|
-
entryPoints: options.entryPoint ? [options.entryPoint] : void 0,
|
|
335
|
+
},
|
|
236
336
|
outfile,
|
|
237
337
|
absWorkingDir: resolveDir,
|
|
238
338
|
bundle: true,
|
|
239
|
-
minify,
|
|
240
|
-
treeShaking,
|
|
339
|
+
minify: production,
|
|
340
|
+
treeShaking: production,
|
|
241
341
|
write: false,
|
|
242
342
|
legalComments: "none",
|
|
243
343
|
packages: "bundle",
|
|
@@ -246,106 +346,879 @@ async function bundlePackage(options) {
|
|
|
246
346
|
target: "es2022",
|
|
247
347
|
define: { "process.env.NODE_ENV": production ? "\"production\"" : "\"development\"" },
|
|
248
348
|
logLevel: "silent",
|
|
249
|
-
plugins
|
|
349
|
+
plugins: [{
|
|
350
|
+
name: "esbuild-plugin-packages-resolver",
|
|
351
|
+
setup(build) {
|
|
352
|
+
build.onResolve({ filter: /^[^./]/ }, (arguments_) => onPackageResolve(arguments_.path));
|
|
353
|
+
}
|
|
354
|
+
}]
|
|
250
355
|
});
|
|
251
356
|
if (bundleError) return [null, bundleError];
|
|
252
357
|
const outCode = bundleResult?.outputFiles?.[0]?.text;
|
|
253
|
-
if (typeof outCode !== "string") return [null, /* @__PURE__ */ new Error(`
|
|
358
|
+
if (typeof outCode !== "string") return [null, /* @__PURE__ */ new Error(`esbuild produced no output for "${outfile}"`)];
|
|
254
359
|
return [outCode, null];
|
|
255
360
|
}
|
|
256
361
|
|
|
257
362
|
//#endregion
|
|
258
|
-
//#region src/plugins/bundle-packages/
|
|
363
|
+
//#region src/plugins/bundle-packages/shared/name-set.ts
|
|
364
|
+
/** A set of export names, or all of them. Grows only. */
|
|
365
|
+
var NameSet = class NameSet {
|
|
366
|
+
/** `undefined` means all names. */
|
|
367
|
+
#names;
|
|
368
|
+
constructor(names) {
|
|
369
|
+
this.#names = names;
|
|
370
|
+
}
|
|
371
|
+
static all() {
|
|
372
|
+
return new NameSet(void 0);
|
|
373
|
+
}
|
|
374
|
+
static of(names = []) {
|
|
375
|
+
return new NameSet(new Set(names));
|
|
376
|
+
}
|
|
377
|
+
get isAll() {
|
|
378
|
+
return this.#names === void 0;
|
|
379
|
+
}
|
|
380
|
+
has(name) {
|
|
381
|
+
return this.#names ? this.#names.has(name) : true;
|
|
382
|
+
}
|
|
383
|
+
list() {
|
|
384
|
+
return this.#names ? [...this.#names] : [];
|
|
385
|
+
}
|
|
386
|
+
clone() {
|
|
387
|
+
return this.#names ? NameSet.of(this.#names) : NameSet.all();
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Adds another set's names.
|
|
391
|
+
*
|
|
392
|
+
* @returns Whether anything changed
|
|
393
|
+
*/
|
|
394
|
+
widen(other) {
|
|
395
|
+
if (this.isAll) return false;
|
|
396
|
+
if (other.isAll) {
|
|
397
|
+
this.#names = void 0;
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
const names = this.#names;
|
|
401
|
+
let isWidened = false;
|
|
402
|
+
for (const name of other.list()) {
|
|
403
|
+
if (names.has(name)) continue;
|
|
404
|
+
names.add(name);
|
|
405
|
+
isWidened = true;
|
|
406
|
+
}
|
|
407
|
+
return isWidened;
|
|
408
|
+
}
|
|
409
|
+
/** @returns A stable string, for cache keys */
|
|
410
|
+
key() {
|
|
411
|
+
if (!this.#names) return "*";
|
|
412
|
+
return [...this.#names].toSorted((a, b) => a.localeCompare(b)).join(",");
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
/**
|
|
416
|
+
* Widens a map entry, inserting when absent.
|
|
417
|
+
*
|
|
418
|
+
* @returns Whether anything changed
|
|
419
|
+
*/
|
|
420
|
+
function didWidenEntry(map, key, names) {
|
|
421
|
+
const existing = map.get(key);
|
|
422
|
+
if (!existing) {
|
|
423
|
+
map.set(key, names.clone());
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
return existing.widen(names);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
//#endregion
|
|
430
|
+
//#region src/plugins/bundle-packages/plan/resolve-chunks.ts
|
|
431
|
+
/** The packages named directly by a chunk, whichever form it takes. */
|
|
432
|
+
function namedPackages(option) {
|
|
433
|
+
return Array.isArray(option) ? option : option.include;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Works out which packages each chunk holds.
|
|
437
|
+
*
|
|
438
|
+
* For the object form the walk starts at `include` and follows what those packages need, leaving out three kinds of package:
|
|
439
|
+
*
|
|
440
|
+
* - Ones the project imports itself, which keep their own file so a page needing only them does not pull the chunk.
|
|
441
|
+
* - Ones named in `exclude`.
|
|
442
|
+
* - Ones only ever reached through `import()`, since folding those into a chunk ends their laziness.
|
|
443
|
+
*
|
|
444
|
+
* A `require` overrules all three, since the two packages cannot be in separate files at all. An `exclude` naming such a package
|
|
445
|
+
* is refused rather than quietly producing a bundle that throws.
|
|
446
|
+
*/
|
|
447
|
+
function resolveChunks(chunks, discovered, seeds, index, groups) {
|
|
448
|
+
const chunkOf = /* @__PURE__ */ new Map();
|
|
449
|
+
const refused = [];
|
|
450
|
+
const unmatched = [];
|
|
451
|
+
const everyPackage = discovered.keys().toArray();
|
|
452
|
+
const assign = (specifier, chunkName) => {
|
|
453
|
+
if (!chunkOf.get(specifier)?.includes(chunkName)) pushInto(chunkOf, specifier, chunkName);
|
|
454
|
+
};
|
|
455
|
+
for (const [chunkName, option] of Object.entries(chunks)) {
|
|
456
|
+
const named = namedPackages(option);
|
|
457
|
+
const excluded = Array.isArray(option) ? [] : option.exclude ?? [];
|
|
458
|
+
const roots = everyPackage.filter((specifier) => matchesPackage(specifier, named));
|
|
459
|
+
for (const root of roots) assign(root, chunkName);
|
|
460
|
+
unmatched.push(...findUnmatched(chunkName, named, excluded, everyPackage));
|
|
461
|
+
if (Array.isArray(option)) continue;
|
|
462
|
+
const queue = [...roots];
|
|
463
|
+
const seen = new Set(roots);
|
|
464
|
+
while (queue.length > 0) {
|
|
465
|
+
const specifier = queue.shift();
|
|
466
|
+
const closure = index.closureOf(specifier, discovered.get(specifier) ?? NameSet.of());
|
|
467
|
+
if (!closure) continue;
|
|
468
|
+
for (const edge of closure.crossPackage) {
|
|
469
|
+
if (seen.has(edge.specifier)) continue;
|
|
470
|
+
if (!discovered.has(edge.specifier)) continue;
|
|
471
|
+
const isRequired = edge.kind === "require" || edge.isStar;
|
|
472
|
+
if (!isRequired && isLeftOut(edge.specifier, edge.isLazyOnly, named, excluded, seeds)) continue;
|
|
473
|
+
if (isRequired && matchesPackage(edge.specifier, excluded)) refused.push({
|
|
474
|
+
chunk: chunkName,
|
|
475
|
+
specifier: edge.specifier,
|
|
476
|
+
requiredBy: specifier
|
|
477
|
+
});
|
|
478
|
+
seen.add(edge.specifier);
|
|
479
|
+
assign(edge.specifier, chunkName);
|
|
480
|
+
queue.push(edge.specifier);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
pullInWholeGroups(chunkOf, groups, assign);
|
|
485
|
+
const duplicated = [];
|
|
486
|
+
for (const [specifier, names] of chunkOf) {
|
|
487
|
+
if (names.length < 2) continue;
|
|
488
|
+
duplicated.push({
|
|
489
|
+
specifier,
|
|
490
|
+
chunks: [...names]
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
const clashes = [];
|
|
494
|
+
for (const name of Object.keys(chunks)) {
|
|
495
|
+
if (!discovered.has(name)) continue;
|
|
496
|
+
const members = chunkOf.entries().filter(([, chunkNames]) => chunkNames.includes(name)).toArray();
|
|
497
|
+
const hasItself = members.some(([specifier]) => specifier === name);
|
|
498
|
+
clashes.push({
|
|
499
|
+
name,
|
|
500
|
+
otherMembers: hasItself ? members.length - 1 : members.length
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
return {
|
|
504
|
+
chunkOf,
|
|
505
|
+
duplicated,
|
|
506
|
+
refused,
|
|
507
|
+
clashes,
|
|
508
|
+
unmatched,
|
|
509
|
+
pulled: findPulledIn(chunks, chunkOf, seeds)
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Finds packages the project imports that ended up in a chunk without being named by it.
|
|
514
|
+
*
|
|
515
|
+
* The walk skips those so a page wanting only that package does not download the whole chunk, but a `require` or an `export *`
|
|
516
|
+
* overrules it and the package loses its own file — a page importing `shiki` then gets everything `expressive-code` brought.
|
|
517
|
+
*/
|
|
518
|
+
function findPulledIn(chunks, chunkOf, seeds) {
|
|
519
|
+
const byChunk = /* @__PURE__ */ new Map();
|
|
520
|
+
for (const [specifier, chunkNames] of chunkOf) {
|
|
521
|
+
if (!seeds.has(specifier)) continue;
|
|
522
|
+
for (const chunk of chunkNames) {
|
|
523
|
+
const option = chunks[chunk];
|
|
524
|
+
if (!option || Array.isArray(option)) continue;
|
|
525
|
+
if (matchesPackage(specifier, option.include)) continue;
|
|
526
|
+
pushInto(byChunk, chunk, specifier);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return byChunk.entries().map(([chunk, specifiers]) => ({
|
|
530
|
+
chunk,
|
|
531
|
+
specifiers: specifiers.toSorted((a, b) => a.localeCompare(b))
|
|
532
|
+
})).toArray();
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Finds patterns on a chunk that no package answers to.
|
|
536
|
+
*
|
|
537
|
+
* A misspelled name, an uninstalled package and an unused subpath all end in a silently empty chunk, so the pattern is reported
|
|
538
|
+
* rather than the chunk.
|
|
539
|
+
*/
|
|
540
|
+
function findUnmatched(chunk, named, excluded, everyPackage) {
|
|
541
|
+
const found = [];
|
|
542
|
+
const hasNoMatch = (pattern) => everyPackage.every((specifier) => !matchesPackage(specifier, [pattern]));
|
|
543
|
+
for (const pattern of named) {
|
|
544
|
+
if (!hasNoMatch(pattern)) continue;
|
|
545
|
+
found.push({
|
|
546
|
+
chunk,
|
|
547
|
+
pattern,
|
|
548
|
+
isExclusion: false
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
for (const pattern of excluded) {
|
|
552
|
+
if (!hasNoMatch(pattern)) continue;
|
|
553
|
+
found.push({
|
|
554
|
+
chunk,
|
|
555
|
+
pattern,
|
|
556
|
+
isExclusion: true
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
return found;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Adds the rest of a group to whichever chunk took one of its members.
|
|
563
|
+
*
|
|
564
|
+
* A group cannot be split across files at all, so taking one member into a chunk brings the rest with it.
|
|
565
|
+
*/
|
|
566
|
+
function pullInWholeGroups(chunkOf, groups, assign) {
|
|
567
|
+
for (const [specifier, chunkNames] of chunkOf) {
|
|
568
|
+
const group = groups.groupOf.get(specifier);
|
|
569
|
+
if (!group) continue;
|
|
570
|
+
const groupMembers = groups.membersOf.get(group) ?? [];
|
|
571
|
+
for (const member of groupMembers) for (const chunkName of chunkNames) assign(member, chunkName);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
/** Whether a package the walk reached should be left out of the chunk. */
|
|
575
|
+
function isLeftOut(specifier, isLazyOnly, named, excluded, seeds) {
|
|
576
|
+
if (matchesPackage(specifier, named)) return false;
|
|
577
|
+
if (matchesPackage(specifier, excluded)) return true;
|
|
578
|
+
if (seeds.has(specifier)) return true;
|
|
579
|
+
return isLazyOnly;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
//#endregion
|
|
583
|
+
//#region src/plugins/bundle-packages/plan/plan-units.ts
|
|
584
|
+
/** Whether a file holds more than one package, in which case their default exports get renamed. */
|
|
585
|
+
function isSharedUnit(unit) {
|
|
586
|
+
return unit.exports.size > 1;
|
|
587
|
+
}
|
|
588
|
+
/** Follows the imports of the project's packages to find every other package they reach, and what each must export. */
|
|
589
|
+
function discoverPackages(seeds, index) {
|
|
590
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
591
|
+
for (const [specifier, names] of seeds) didWidenEntry(wanted, specifier, names);
|
|
592
|
+
let isSettled = false;
|
|
593
|
+
while (!isSettled) {
|
|
594
|
+
isSettled = true;
|
|
595
|
+
for (const [specifier, names] of wanted) {
|
|
596
|
+
const closure = index.closureOf(specifier, names);
|
|
597
|
+
if (!closure) continue;
|
|
598
|
+
for (const edge of closure.crossPackage) if (didWidenEntry(wanted, edge.specifier, edge.names)) isSettled = false;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return wanted;
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Maps each package to the group it must be bundled with.
|
|
605
|
+
*
|
|
606
|
+
* Two edges cannot cross a file:
|
|
607
|
+
*
|
|
608
|
+
* - `require("x")`, which esbuild replaces with a stub that throws `Dynamic require of "x" is not supported`.
|
|
609
|
+
* - `export * from "x"`, which cannot name what it forwards, so left external the forwarded names disappear.
|
|
610
|
+
*
|
|
611
|
+
* The group takes the name of its lowest member, so the output file name does not change with discovery order.
|
|
612
|
+
*/
|
|
613
|
+
function mustShareGroups(wanted, index) {
|
|
614
|
+
const parent = /* @__PURE__ */ new Map();
|
|
615
|
+
const find = (name) => {
|
|
616
|
+
let current = name;
|
|
617
|
+
while (parent.get(current) !== current) current = parent.get(current) ?? current;
|
|
618
|
+
return current;
|
|
619
|
+
};
|
|
620
|
+
const union = (a, b) => {
|
|
621
|
+
const rootA = find(a);
|
|
622
|
+
const rootB = find(b);
|
|
623
|
+
if (rootA === rootB) return;
|
|
624
|
+
if (rootA < rootB) {
|
|
625
|
+
parent.set(rootB, rootA);
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
parent.set(rootA, rootB);
|
|
629
|
+
};
|
|
630
|
+
for (const name of wanted.keys()) parent.set(name, name);
|
|
631
|
+
for (const [name, names] of wanted) {
|
|
632
|
+
const closure = index.closureOf(name, names);
|
|
633
|
+
if (!closure) continue;
|
|
634
|
+
for (const edge of closure.crossPackage) {
|
|
635
|
+
if (!(edge.kind === "require" || edge.isStar) || !parent.has(edge.specifier)) continue;
|
|
636
|
+
union(name, edge.specifier);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
const groupOf = /* @__PURE__ */ new Map();
|
|
640
|
+
const membersOf = /* @__PURE__ */ new Map();
|
|
641
|
+
for (const name of parent.keys()) {
|
|
642
|
+
const group = find(name);
|
|
643
|
+
groupOf.set(name, group);
|
|
644
|
+
pushInto(membersOf, group, name);
|
|
645
|
+
}
|
|
646
|
+
return {
|
|
647
|
+
groupOf,
|
|
648
|
+
membersOf
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Decides which output file each package goes in.
|
|
653
|
+
*
|
|
654
|
+
* A `chunks` entry wins. Otherwise packages that require each other share one file, and every other package gets its own.
|
|
655
|
+
*
|
|
656
|
+
* Only what the project itself imports is exported. A package pulled in by a file-mate needs no exports: esbuild bundles it
|
|
657
|
+
* either way, and re-exporting it would collide with its file-mates: `version` from both `react` and `react-dom`.
|
|
658
|
+
*/
|
|
659
|
+
function planUnits(discovered, seeds, chunkOf, groups) {
|
|
660
|
+
const units = /* @__PURE__ */ new Map();
|
|
661
|
+
const place = (specifier, unitName) => {
|
|
662
|
+
let unit = units.get(unitName);
|
|
663
|
+
if (!unit) {
|
|
664
|
+
unit = {
|
|
665
|
+
name: unitName,
|
|
666
|
+
exports: /* @__PURE__ */ new Map()
|
|
667
|
+
};
|
|
668
|
+
units.set(unitName, unit);
|
|
669
|
+
}
|
|
670
|
+
const imported = seeds.get(specifier) ?? NameSet.of();
|
|
671
|
+
unit.exports.set(specifier, imported.clone());
|
|
672
|
+
};
|
|
673
|
+
for (const specifier of discovered.keys()) {
|
|
674
|
+
const chunkNames = chunkOf.get(specifier);
|
|
675
|
+
if (!chunkNames || chunkNames.length === 0) {
|
|
676
|
+
place(specifier, groups.groupOf.get(specifier) ?? specifier);
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
for (const chunkName of chunkNames) place(specifier, chunkName);
|
|
680
|
+
}
|
|
681
|
+
return units;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Finds sets of packages that require each other but were spread over several files.
|
|
685
|
+
*
|
|
686
|
+
* Grouping is automatic, so only a `chunks` entry taking part of a group can cause this. Reported per group, not per edge, since
|
|
687
|
+
* the whole group has to move together anyway.
|
|
688
|
+
*/
|
|
689
|
+
function validateUnits(units, groups) {
|
|
690
|
+
const unitOf = unitsBySpecifier(units);
|
|
691
|
+
const problems = [];
|
|
692
|
+
for (const [group, packages] of groups.membersOf) {
|
|
693
|
+
if (packages.length < 2) continue;
|
|
694
|
+
const spread = new Set(packages.flatMap((specifier) => unitOf.get(specifier) ?? []));
|
|
695
|
+
if (spread.size < 2) continue;
|
|
696
|
+
problems.push({
|
|
697
|
+
group,
|
|
698
|
+
members: packages.toSorted((a, b) => a.localeCompare(b)),
|
|
699
|
+
units: [...spread]
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
return problems;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Finds packages that resolve to more than one copy.
|
|
706
|
+
*
|
|
707
|
+
* One output file per package name means only one copy ships and importers of the other silently get it instead. Shipping both
|
|
708
|
+
* would need a second output path and per-importer resolution, so this only reports the conflict.
|
|
709
|
+
*/
|
|
710
|
+
function findVersionConflicts(units, discovered, index) {
|
|
711
|
+
const entriesBySpecifier = /* @__PURE__ */ new Map();
|
|
712
|
+
const record = (specifier, entry) => {
|
|
713
|
+
const existing = entriesBySpecifier.get(specifier) ?? /* @__PURE__ */ new Set();
|
|
714
|
+
existing.add(entry);
|
|
715
|
+
entriesBySpecifier.set(specifier, existing);
|
|
716
|
+
};
|
|
717
|
+
for (const unit of units.values()) for (const specifier of unit.exports.keys()) {
|
|
718
|
+
const closure = index.closureOf(specifier, discovered.get(specifier) ?? NameSet.of());
|
|
719
|
+
if (!closure) continue;
|
|
720
|
+
record(specifier, closure.entry);
|
|
721
|
+
for (const edge of closure.crossPackage) for (const entry of edge.entries) record(edge.specifier, entry);
|
|
722
|
+
}
|
|
723
|
+
const conflicts = [];
|
|
724
|
+
for (const [specifier, entries] of entriesBySpecifier) {
|
|
725
|
+
if (entries.size < 2) continue;
|
|
726
|
+
conflicts.push({
|
|
727
|
+
specifier,
|
|
728
|
+
entries: [...entries]
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
return conflicts;
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Adds the export names that other output files import.
|
|
735
|
+
*
|
|
736
|
+
* `react-dom.js` imports `useState` from `react.js`. Nothing in the project imported it, so `react.js` would not export it.
|
|
737
|
+
*/
|
|
738
|
+
function addCrossUnitNames(units, discovered, index) {
|
|
739
|
+
const unitOf = unitsBySpecifier(units);
|
|
740
|
+
for (const unit of units.values()) for (const specifier of unit.exports.keys()) {
|
|
741
|
+
const closure = index.closureOf(specifier, discovered.get(specifier) ?? NameSet.of());
|
|
742
|
+
if (!closure) continue;
|
|
743
|
+
for (const edge of closure.crossPackage) {
|
|
744
|
+
if (unit.exports.has(edge.specifier)) continue;
|
|
745
|
+
const dependencyUnits = unitOf.get(edge.specifier) ?? [];
|
|
746
|
+
for (const dependencyUnit of dependencyUnits) {
|
|
747
|
+
const target = units.get(dependencyUnit);
|
|
748
|
+
if (!target) continue;
|
|
749
|
+
didWidenEntry(target.exports, edge.specifier, edge.names);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
/** Package name → every output file holding it. More than one means a chunk asked for its own copy. */
|
|
755
|
+
function unitsBySpecifier(units) {
|
|
756
|
+
const unitOf = /* @__PURE__ */ new Map();
|
|
757
|
+
for (const unit of units.values()) for (const specifier of unit.exports.keys()) pushInto(unitOf, specifier, unit.name);
|
|
758
|
+
return unitOf;
|
|
759
|
+
}
|
|
760
|
+
/** Runs discovery, grouping, chunk resolution and the cross-file export pass, which are always used together. */
|
|
761
|
+
function planLayout(seeds, chunks, index) {
|
|
762
|
+
const discovered = discoverPackages(seeds, index);
|
|
763
|
+
const groups = mustShareGroups(discovered, index);
|
|
764
|
+
const plan = resolveChunks(chunks, discovered, seeds, index, groups);
|
|
765
|
+
const units = planUnits(discovered, seeds, plan.chunkOf, groups);
|
|
766
|
+
addCrossUnitNames(units, discovered, index);
|
|
767
|
+
return {
|
|
768
|
+
units,
|
|
769
|
+
discovered,
|
|
770
|
+
plan,
|
|
771
|
+
groups
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
//#endregion
|
|
776
|
+
//#region src/plugins/bundle-packages/build/package-exports.ts
|
|
777
|
+
/** For a package no page imports, present only because a file-mate needs it. */
|
|
778
|
+
const NOTHING_NEEDED = {
|
|
779
|
+
names: NameSet.of(),
|
|
780
|
+
hasDefault: false,
|
|
781
|
+
hasSideEffect: false
|
|
782
|
+
};
|
|
783
|
+
/**
|
|
784
|
+
* Per file, not per package: packages in one file constrain each other. Two can own the same name, and two defaults cannot both
|
|
785
|
+
* be called `default`.
|
|
786
|
+
*/
|
|
787
|
+
function exportsForUnit(unit, context) {
|
|
788
|
+
const isSharedFile = isSharedUnit(unit);
|
|
789
|
+
/** Name → the package in this file that claimed it. */
|
|
790
|
+
const taken = /* @__PURE__ */ new Map();
|
|
791
|
+
const members = [...unit.exports].toSorted(([a], [b]) => a.localeCompare(b));
|
|
792
|
+
const exports = [];
|
|
793
|
+
for (const [specifier, names] of members) {
|
|
794
|
+
const needed = context.neededByPackage.get(specifier) ?? NOTHING_NEEDED;
|
|
795
|
+
exports.push(exportsForPackage({
|
|
796
|
+
specifier,
|
|
797
|
+
names,
|
|
798
|
+
needed,
|
|
799
|
+
isSharedFile,
|
|
800
|
+
taken,
|
|
801
|
+
unit,
|
|
802
|
+
context
|
|
803
|
+
}));
|
|
804
|
+
}
|
|
805
|
+
return exports;
|
|
806
|
+
}
|
|
807
|
+
function exportsForPackage(turn) {
|
|
808
|
+
const { specifier, names, needed, isSharedFile, taken, unit, context } = turn;
|
|
809
|
+
const index = context.index;
|
|
810
|
+
const asked = mergeNeededExports(needed, names);
|
|
811
|
+
const wanted = expandNamesForCjs(specifier, asked.names, index);
|
|
812
|
+
const owned = ownedNames(specifier, index);
|
|
813
|
+
const named = [];
|
|
814
|
+
for (const name of wanted.list()) {
|
|
815
|
+
if (name === "default") continue;
|
|
816
|
+
const keptBy = taken.get(name);
|
|
817
|
+
if (keptBy !== void 0) {
|
|
818
|
+
if (!needed.names.isAll && needed.names.has(name)) context.onNameClash?.({
|
|
819
|
+
unit: unit.name,
|
|
820
|
+
name,
|
|
821
|
+
keptBy,
|
|
822
|
+
lostBy: specifier
|
|
823
|
+
});
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
if (owned && !owned.has(name)) continue;
|
|
827
|
+
taken.set(name, specifier);
|
|
828
|
+
named.push(name);
|
|
829
|
+
}
|
|
830
|
+
const hasStar = wanted.isAll;
|
|
831
|
+
const hasDefault = asked.hasDefault || hasStar && index.hasDefaultExport(specifier);
|
|
832
|
+
const result = {
|
|
833
|
+
specifier,
|
|
834
|
+
hasSideEffect: asked.hasSideEffect,
|
|
835
|
+
hasStar,
|
|
836
|
+
defaultAs: defaultNameFor(specifier, hasDefault, isSharedFile),
|
|
837
|
+
named
|
|
838
|
+
};
|
|
839
|
+
if (!result.hasStar && !result.defaultAs && result.named.length === 0 && !result.hasSideEffect) return {
|
|
840
|
+
...result,
|
|
841
|
+
hasStar: true,
|
|
842
|
+
defaultAs: defaultNameFor(specifier, index.hasDefaultExport(specifier), isSharedFile)
|
|
843
|
+
};
|
|
844
|
+
return result;
|
|
845
|
+
}
|
|
846
|
+
/** Collapses every import of a package into the set of names it must export. */
|
|
847
|
+
function neededExports(specifiers) {
|
|
848
|
+
const names = NameSet.of();
|
|
849
|
+
let hasDefault = false;
|
|
850
|
+
let hasSideEffect = false;
|
|
851
|
+
for (const specifier of specifiers) {
|
|
852
|
+
if (specifier.type === "namespace" || specifier.type === "unknown") {
|
|
853
|
+
names.widen(NameSet.all());
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
if (specifier.type === "default") {
|
|
857
|
+
names.widen(NameSet.of(["default"]));
|
|
858
|
+
hasDefault = true;
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
if (specifier.type === "side-effect") {
|
|
862
|
+
hasSideEffect = true;
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
if (specifier.type !== "named") continue;
|
|
866
|
+
const wanted = specifier.kind === "export" ? specifier.publicName : specifier.name;
|
|
867
|
+
names.widen(NameSet.of([wanted]));
|
|
868
|
+
}
|
|
869
|
+
return {
|
|
870
|
+
names,
|
|
871
|
+
hasDefault,
|
|
872
|
+
hasSideEffect
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
/** Merges what this project imports with what other output files need. */
|
|
876
|
+
function mergeNeededExports(needed, names) {
|
|
877
|
+
const wanted = names.clone();
|
|
878
|
+
wanted.widen(needed.names);
|
|
879
|
+
const isDefaultNamed = !names.isAll && names.has("default");
|
|
880
|
+
return {
|
|
881
|
+
names: wanted,
|
|
882
|
+
hasDefault: needed.hasDefault || isDefaultNamed,
|
|
883
|
+
hasSideEffect: needed.hasSideEffect
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* `export *` carries nothing out of CommonJS, whose exports are assignments rather than syntax, so spell the names out.
|
|
888
|
+
*
|
|
889
|
+
* ES modules keep the star; spelling those out would include `export type` names that do not exist at runtime.
|
|
890
|
+
*/
|
|
891
|
+
function expandNamesForCjs(specifier, names, index) {
|
|
892
|
+
if (!names.isAll || index.formatOf(specifier) !== "cjs") return names;
|
|
893
|
+
const entry = index.resolveFrom(specifier, join(index.root, "package.json"));
|
|
894
|
+
if (!entry) return names;
|
|
895
|
+
return NameSet.of(index.exportsOf(entry));
|
|
896
|
+
}
|
|
897
|
+
/** @returns The names a package publishes, or nothing when they could not be read, since an empty list means the lexer failed */
|
|
898
|
+
function ownedNames(specifier, index) {
|
|
899
|
+
const entry = index.resolveFrom(specifier, join(index.root, "package.json"));
|
|
900
|
+
if (!entry) return;
|
|
901
|
+
const owned = index.exportsOf(entry);
|
|
902
|
+
if (owned.length === 0) return;
|
|
903
|
+
return new Set(owned);
|
|
904
|
+
}
|
|
905
|
+
/** `pointReferencesAtUnits` renames the import sites to match, deriving the name the same way. */
|
|
906
|
+
function defaultNameFor(specifier, hasDefault, isSharedFile) {
|
|
907
|
+
if (!hasDefault) return;
|
|
908
|
+
if (!isSharedFile) return "default";
|
|
909
|
+
return defaultExportAlias(specifier);
|
|
910
|
+
}
|
|
911
|
+
/** Keeps defaults apart when several packages share a file. */
|
|
912
|
+
function defaultExportAlias(packageName) {
|
|
913
|
+
return "default_" + packageName.replace(/@|-|\/|\./g, "_");
|
|
914
|
+
}
|
|
915
|
+
/** Writes the entry esbuild bundles a file from, deciding nothing itself. */
|
|
916
|
+
function printBarrel(exports) {
|
|
917
|
+
let contents = "";
|
|
918
|
+
for (const one of exports) {
|
|
919
|
+
const from = `from '${one.specifier}';`;
|
|
920
|
+
if (one.hasSideEffect) contents += `import '${one.specifier}';`;
|
|
921
|
+
if (one.hasStar) contents += `\nexport * ${from}`;
|
|
922
|
+
if (one.defaultAs) {
|
|
923
|
+
const as = one.defaultAs === "default" ? "default" : `default as ${one.defaultAs}`;
|
|
924
|
+
contents += `\nexport { ${as} } ${from}`;
|
|
925
|
+
}
|
|
926
|
+
if (one.named.length > 0) contents += `\nexport { ${one.named.join(", ")} } ${from}`;
|
|
927
|
+
}
|
|
928
|
+
return contents;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
//#endregion
|
|
932
|
+
//#region src/plugins/bundle-packages/build/build-unit.ts
|
|
933
|
+
/**
|
|
934
|
+
* Bundles one output file, skipping it when the entry has not changed.
|
|
935
|
+
*
|
|
936
|
+
* Compared on the entry itself rather than a summary of it, since a summary missing one input — the kind of import, say — leaves
|
|
937
|
+
* a file built from an entry the pages no longer ask for.
|
|
938
|
+
*/
|
|
939
|
+
async function buildUnit(unit, context, report) {
|
|
940
|
+
const existingMetadata = this.findMetadata({
|
|
941
|
+
type: METADATA_TYPES.Package,
|
|
942
|
+
id: unit.name
|
|
943
|
+
});
|
|
944
|
+
const contents = printBarrel(exportsForUnit(unit, context));
|
|
945
|
+
if (!contents) return;
|
|
946
|
+
if (existingMetadata && context.builtEntries.get(unit.name) === contents) return;
|
|
947
|
+
const packageOutputPath = calcPackageOutPath(unit.name, context.packagesDirectory);
|
|
948
|
+
const packageMetadata = isPackageMetadata(existingMetadata) ? existingMetadata : newPackageMetadata(unit.name, packageOutputPath);
|
|
949
|
+
packageMetadata.directDependencies.clear();
|
|
950
|
+
const [bundledCode, bundleError] = await bundlePackage({
|
|
951
|
+
contents,
|
|
952
|
+
resolveDir: this.root,
|
|
953
|
+
outfile: packageOutputPath,
|
|
954
|
+
production: this.production,
|
|
955
|
+
onPackageResolve: (name) => resolveImport.call(this, name, unit, context, packageOutputPath, packageMetadata)
|
|
956
|
+
});
|
|
957
|
+
if (bundleError) {
|
|
958
|
+
report(`Failed to bundle package "${unit.name}"`, bundleError);
|
|
959
|
+
if (this.production) process.exitCode = 1;
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
packageMetadata.code = bundledCode;
|
|
963
|
+
context.builtEntries.set(unit.name, contents);
|
|
964
|
+
if (!existingMetadata) this.addMetadata(packageMetadata);
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Decides what esbuild does with a bare import it met while bundling.
|
|
968
|
+
*
|
|
969
|
+
* A package belonging to another file is made external and pointed at it. One belonging to this file, or to none, is bundled in,
|
|
970
|
+
* and only then can a `resolvePackage` entry say where it is read from.
|
|
971
|
+
*/
|
|
972
|
+
function resolveImport(importedPackageName, unit, context, packageOutputPath, packageMetadata) {
|
|
973
|
+
const onProblem = (message) => this.log.warn(message);
|
|
974
|
+
const owningUnits = context.unitOf.get(importedPackageName) ?? [];
|
|
975
|
+
const customPath = getCustomPackagePath(this.root, importedPackageName, this.production, context.resolvePackage, onProblem);
|
|
976
|
+
const owningUnit = owningUnits.includes(unit.name) ? unit.name : owningUnits[0];
|
|
977
|
+
if (!owningUnit || owningUnit === unit.name) {
|
|
978
|
+
if (!customPath) return;
|
|
979
|
+
return {
|
|
980
|
+
path: customPath,
|
|
981
|
+
external: false
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
const dependencyOutputPath = calcPackageOutPath(owningUnit, context.packagesDirectory);
|
|
985
|
+
packageMetadata.directDependencies.add(dependencyOutputPath);
|
|
986
|
+
return {
|
|
987
|
+
path: relative(dirname(packageOutputPath), dependencyOutputPath),
|
|
988
|
+
external: true
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
/** A fresh metadata record for an output file that has not been built before. */
|
|
992
|
+
function newPackageMetadata(name, filePath) {
|
|
993
|
+
return {
|
|
994
|
+
type: METADATA_TYPES.Package,
|
|
995
|
+
packageName: name,
|
|
996
|
+
code: "",
|
|
997
|
+
filePath,
|
|
998
|
+
id: name,
|
|
999
|
+
directDependencies: /* @__PURE__ */ new Set()
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
//#endregion
|
|
1004
|
+
//#region src/plugins/bundle-packages/collect/narrow-namespace.ts
|
|
1005
|
+
/**
|
|
1006
|
+
* Records what a binding holding a whole package is used for: the whole namespace when nothing could be narrowed, only the names
|
|
1007
|
+
* actually read, or a side-effect import when the binding is never read.
|
|
1008
|
+
*
|
|
1009
|
+
* `import * as z from "zod"` and `const z = await import("zod")` both come here, so the static and dynamic forms stay in step.
|
|
1010
|
+
*/
|
|
1011
|
+
function recordNamespaceUse(path, localName, specifiers) {
|
|
1012
|
+
const used = namesReadFrom(path, localName);
|
|
1013
|
+
if (used.isAll) {
|
|
1014
|
+
specifiers.push({ type: "namespace" });
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
const names = used.list();
|
|
1018
|
+
if (names.length === 0) {
|
|
1019
|
+
specifiers.push({ type: "side-effect" });
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
for (const name of names) {
|
|
1023
|
+
if (name === "default") {
|
|
1024
|
+
specifiers.push({
|
|
1025
|
+
type: "default",
|
|
1026
|
+
setDefaultName: renameDefaultReads(path, localName)
|
|
1027
|
+
});
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
specifiers.push({
|
|
1031
|
+
type: "named",
|
|
1032
|
+
kind: "import",
|
|
1033
|
+
name,
|
|
1034
|
+
publicName: name
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* The names read off a binding that holds a whole package.
|
|
1040
|
+
*
|
|
1041
|
+
* Narrowed by:
|
|
1042
|
+
*
|
|
1043
|
+
* - Reading a property: `z.string()`
|
|
1044
|
+
* - Destructuring: `const { string, object } = z;`
|
|
1045
|
+
* - Following an alias: `const zod = z;`
|
|
1046
|
+
*
|
|
1047
|
+
* Anything else gives back `NameSet.all()`: a runtime property name, a `...rest`, or the binding used as a value.
|
|
1048
|
+
*/
|
|
1049
|
+
function namesReadFrom(path, importName) {
|
|
1050
|
+
const binding = path.scope.getBinding(importName);
|
|
1051
|
+
if (!binding) return NameSet.all();
|
|
1052
|
+
const names = NameSet.of();
|
|
1053
|
+
for (const referencePath of binding.referencePaths) {
|
|
1054
|
+
const parentPath = referencePath.parentPath;
|
|
1055
|
+
if (!parentPath) continue;
|
|
1056
|
+
const parentNode = parentPath.node;
|
|
1057
|
+
if (t.isMemberExpression(parentNode) && t.isIdentifier(parentNode.property)) {
|
|
1058
|
+
if (parentNode.computed) return NameSet.all();
|
|
1059
|
+
names.widen(NameSet.of([parentNode.property.name]));
|
|
1060
|
+
continue;
|
|
1061
|
+
}
|
|
1062
|
+
if (t.isVariableDeclarator(parentNode) && t.isIdentifier(parentNode.init)) {
|
|
1063
|
+
const id = parentNode.id;
|
|
1064
|
+
if (t.isObjectPattern(id)) {
|
|
1065
|
+
if (id.properties.some((property) => t.isRestElement(property))) return NameSet.all();
|
|
1066
|
+
for (const property of id.properties) {
|
|
1067
|
+
if (!t.isObjectProperty(property) || !t.isIdentifier(property.key)) continue;
|
|
1068
|
+
names.widen(NameSet.of([property.key.name]));
|
|
1069
|
+
}
|
|
1070
|
+
continue;
|
|
1071
|
+
}
|
|
1072
|
+
if (t.isIdentifier(id)) {
|
|
1073
|
+
names.widen(namesReadFrom(parentPath.get("id"), id.name));
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
return NameSet.all();
|
|
1078
|
+
}
|
|
1079
|
+
return names;
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Renames every `binding.default` read, so a package sharing a file keeps the default alias it was given.
|
|
1083
|
+
*
|
|
1084
|
+
* The import stays as is: the binding is the whole namespace, only the one property moves.
|
|
1085
|
+
*/
|
|
1086
|
+
function renameDefaultReads(path, localName) {
|
|
1087
|
+
return (newName) => {
|
|
1088
|
+
const binding = path.scope.getBinding(localName);
|
|
1089
|
+
if (!binding) return;
|
|
1090
|
+
for (const referencePath of binding.referencePaths) {
|
|
1091
|
+
const parentNode = referencePath.parentPath?.node;
|
|
1092
|
+
if (!t.isMemberExpression(parentNode) || !t.isIdentifier(parentNode.property)) continue;
|
|
1093
|
+
if (parentNode.property.name !== "default") continue;
|
|
1094
|
+
parentNode.property = t.identifier(newName);
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
//#endregion
|
|
1100
|
+
//#region src/plugins/bundle-packages/collect/collect-specifiers.ts
|
|
259
1101
|
const traverse$1 = typeof _traverse === "function" ? _traverse : _traverse.default;
|
|
1102
|
+
const NODE_MODULES = "node_modules";
|
|
1103
|
+
/** Pairs the names an import takes with a `source` that reads and writes the AST node holding its path. */
|
|
1104
|
+
function sourceReference(read, write, specifiers) {
|
|
1105
|
+
return {
|
|
1106
|
+
get source() {
|
|
1107
|
+
return read();
|
|
1108
|
+
},
|
|
1109
|
+
set source(newValue) {
|
|
1110
|
+
write(newValue);
|
|
1111
|
+
},
|
|
1112
|
+
specifiers
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Whether any import in a file names a path inside `node_modules`.
|
|
1117
|
+
*
|
|
1118
|
+
* A plain node walk, with no paths or scope built, so it is cheap enough to run over every script before deciding whether the
|
|
1119
|
+
* full traversal is worth it. `resolveSource` has already rewritten package imports into `node_modules` paths, so a file whose
|
|
1120
|
+
* imports never name one cannot reach a package.
|
|
1121
|
+
*/
|
|
1122
|
+
function hasNodeModulesImport(node) {
|
|
1123
|
+
if (!node || typeof node.type !== "string") return false;
|
|
1124
|
+
let isFound = false;
|
|
1125
|
+
t.traverseFast(node, (current) => {
|
|
1126
|
+
if (importSourceOf(current)?.includes(NODE_MODULES)) isFound = true;
|
|
1127
|
+
});
|
|
1128
|
+
return isFound;
|
|
1129
|
+
}
|
|
1130
|
+
/** The literal path one node imports from, whatever form the import takes. */
|
|
1131
|
+
function importSourceOf(node) {
|
|
1132
|
+
if (t.isImportDeclaration(node) || t.isExportAllDeclaration(node) || t.isExportNamedDeclaration(node)) return t.isStringLiteral(node.source) ? node.source.value : void 0;
|
|
1133
|
+
return dynamicImportSource(node)?.value;
|
|
1134
|
+
}
|
|
1135
|
+
/** Every import in a file, with the names it takes and a writable `source` that rewrites its path. */
|
|
260
1136
|
function collectSpecifiersFromAst(ast) {
|
|
261
1137
|
const result = [];
|
|
262
1138
|
traverse$1(ast, {
|
|
263
1139
|
ImportDeclaration(path) {
|
|
264
1140
|
if (!t.isStringLiteral(path.node.source)) return;
|
|
265
|
-
if (
|
|
1141
|
+
if (path.node.importKind === "type") return;
|
|
266
1142
|
const source = path.node.source.value;
|
|
267
1143
|
if (source.startsWith("node:")) return;
|
|
268
1144
|
const specifiers = [];
|
|
269
1145
|
collectImportDeclarationSpecifiers(path, specifiers);
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (path.node) path.node.source = t.stringLiteral(newValue);
|
|
276
|
-
},
|
|
277
|
-
specifiers
|
|
278
|
-
});
|
|
1146
|
+
const write = (newValue) => {
|
|
1147
|
+
if (!path.node) return;
|
|
1148
|
+
path.node.source = t.stringLiteral(newValue);
|
|
1149
|
+
};
|
|
1150
|
+
result.push(sourceReference(() => source, write, specifiers));
|
|
279
1151
|
},
|
|
280
1152
|
ExportAllDeclaration(path) {
|
|
281
1153
|
if (!path.node.source.value) return;
|
|
282
|
-
if (
|
|
1154
|
+
if (path.node.exportKind === "type") return;
|
|
283
1155
|
const specifiers = [];
|
|
284
1156
|
collectExportAllDeclarationSpecifiers(path, specifiers);
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
path.node.source = t.stringLiteral(newValue);
|
|
291
|
-
},
|
|
292
|
-
specifiers
|
|
293
|
-
});
|
|
1157
|
+
const read = () => path.node.source.value;
|
|
1158
|
+
const write = (newValue) => {
|
|
1159
|
+
path.node.source = t.stringLiteral(newValue);
|
|
1160
|
+
};
|
|
1161
|
+
result.push(sourceReference(read, write, specifiers));
|
|
294
1162
|
},
|
|
295
1163
|
ExportNamedDeclaration(path) {
|
|
296
1164
|
if (!t.isStringLiteral(path.node.source)) return;
|
|
297
|
-
if (
|
|
1165
|
+
if (path.node.exportKind === "type") return;
|
|
298
1166
|
const source = path.node.source.value;
|
|
299
1167
|
if (source.startsWith("node:")) return;
|
|
300
1168
|
const specifiers = [];
|
|
301
1169
|
collectExportNamedDeclarationSpecifiers(path, specifiers);
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
set source(newValue) {
|
|
307
|
-
path.node.source = t.stringLiteral(newValue);
|
|
308
|
-
},
|
|
309
|
-
specifiers
|
|
310
|
-
});
|
|
1170
|
+
const write = (newValue) => {
|
|
1171
|
+
path.node.source = t.stringLiteral(newValue);
|
|
1172
|
+
};
|
|
1173
|
+
result.push(sourceReference(() => source, write, specifiers));
|
|
311
1174
|
},
|
|
312
1175
|
CallExpression(path) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const source = path.node.arguments[0].value;
|
|
318
|
-
if (source.startsWith("node:")) return;
|
|
319
|
-
const specifiers = [];
|
|
320
|
-
collectDynamicImportSpecifiers(path, specifiers);
|
|
321
|
-
result.push({
|
|
322
|
-
get source() {
|
|
323
|
-
return source;
|
|
324
|
-
},
|
|
325
|
-
set source(newValue) {
|
|
326
|
-
setSource(newValue);
|
|
327
|
-
},
|
|
328
|
-
specifiers
|
|
329
|
-
});
|
|
1176
|
+
collectDynamicImport(path, result);
|
|
1177
|
+
},
|
|
1178
|
+
ImportExpression(path) {
|
|
1179
|
+
collectDynamicImport(path, result);
|
|
330
1180
|
}
|
|
331
1181
|
});
|
|
332
1182
|
return result;
|
|
333
1183
|
}
|
|
1184
|
+
/** The literal specifier of a dynamic import, for either node shape. */
|
|
1185
|
+
function dynamicImportSource(node) {
|
|
1186
|
+
if (t.isImportExpression(node)) return t.isStringLiteral(node.source) ? node.source : void 0;
|
|
1187
|
+
if (!t.isCallExpression(node) || !t.isImport(node.callee)) return;
|
|
1188
|
+
const argument = node.arguments[0];
|
|
1189
|
+
return t.isStringLiteral(argument) ? argument : void 0;
|
|
1190
|
+
}
|
|
1191
|
+
/** Records one `import()` along with how to rewrite its path. */
|
|
1192
|
+
function collectDynamicImport(path, result) {
|
|
1193
|
+
const literal = dynamicImportSource(path.node);
|
|
1194
|
+
if (!literal) return;
|
|
1195
|
+
const source = literal.value;
|
|
1196
|
+
if (source.startsWith("node:")) return;
|
|
1197
|
+
const write = (newValue) => {
|
|
1198
|
+
if (t.isImportExpression(path.node)) {
|
|
1199
|
+
path.node.source = t.stringLiteral(newValue);
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
path.node.arguments[0] = t.stringLiteral(newValue);
|
|
1203
|
+
};
|
|
1204
|
+
const specifiers = [];
|
|
1205
|
+
collectDynamicImportSpecifiers(path, specifiers);
|
|
1206
|
+
result.push(sourceReference(() => source, write, specifiers));
|
|
1207
|
+
}
|
|
1208
|
+
/** What one `import ... from "x"` takes. No names at all means a side-effect import. */
|
|
334
1209
|
function collectImportDeclarationSpecifiers(path, specifiers) {
|
|
335
1210
|
const node = path.node;
|
|
336
1211
|
if (!t.isStringLiteral(node.source) || node.importKind && node.importKind !== "value") return;
|
|
337
1212
|
let hasFoundSpecifier = false;
|
|
338
|
-
for (
|
|
339
|
-
const specifier = node.specifiers[index];
|
|
1213
|
+
for (const specifier of node.specifiers) {
|
|
340
1214
|
if (t.isImportDefaultSpecifier(specifier)) {
|
|
341
1215
|
const setDefaultName = (newName) => {
|
|
342
|
-
const
|
|
343
|
-
if (
|
|
344
|
-
node.specifiers[
|
|
1216
|
+
const at = node.specifiers.indexOf(specifier);
|
|
1217
|
+
if (at === -1) return;
|
|
1218
|
+
node.specifiers[at] = t.importSpecifier(t.identifier(specifier.local.name), t.identifier(newName));
|
|
345
1219
|
};
|
|
346
1220
|
specifiers.push({
|
|
347
1221
|
type: "default",
|
|
348
|
-
name: specifier.local.name,
|
|
349
1222
|
setDefaultName
|
|
350
1223
|
});
|
|
351
1224
|
hasFoundSpecifier = true;
|
|
@@ -364,60 +1237,30 @@ function collectImportDeclarationSpecifiers(path, specifiers) {
|
|
|
364
1237
|
continue;
|
|
365
1238
|
}
|
|
366
1239
|
if (t.isImportNamespaceSpecifier(specifier)) {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if (name === "default") {
|
|
370
|
-
const capturedLocalName = specifier.local.name;
|
|
371
|
-
const capturedPath = path;
|
|
372
|
-
const setDefaultName = (newName) => {
|
|
373
|
-
const binding = capturedPath.scope.getBinding(capturedLocalName);
|
|
374
|
-
if (!binding) return;
|
|
375
|
-
for (const referencePath of binding.referencePaths) {
|
|
376
|
-
const parentNode = referencePath.parentPath?.node;
|
|
377
|
-
if (t.isMemberExpression(parentNode) && t.isIdentifier(parentNode.property) && parentNode.property.name === "default") parentNode.property = t.identifier(newName);
|
|
378
|
-
}
|
|
379
|
-
};
|
|
380
|
-
specifiers.push({
|
|
381
|
-
type: "default",
|
|
382
|
-
name: capturedLocalName,
|
|
383
|
-
setDefaultName
|
|
384
|
-
});
|
|
385
|
-
hasFoundSpecifier = true;
|
|
386
|
-
continue;
|
|
387
|
-
}
|
|
388
|
-
specifiers.push({
|
|
389
|
-
type: "named",
|
|
390
|
-
kind: "import",
|
|
391
|
-
name,
|
|
392
|
-
publicName: name
|
|
393
|
-
});
|
|
394
|
-
hasFoundSpecifier = true;
|
|
395
|
-
}
|
|
1240
|
+
recordNamespaceUse(path, specifier.local.name, specifiers);
|
|
1241
|
+
hasFoundSpecifier = true;
|
|
396
1242
|
}
|
|
397
1243
|
}
|
|
398
1244
|
if (!hasFoundSpecifier) specifiers.push({ type: "side-effect" });
|
|
399
1245
|
}
|
|
1246
|
+
/** `export * from "x"` can forward any name, so the whole package is taken. */
|
|
400
1247
|
function collectExportAllDeclarationSpecifiers(path, specifiers) {
|
|
401
1248
|
if (!path.node.source.value || path.node.exportKind !== "value") return;
|
|
402
|
-
specifiers.push({
|
|
403
|
-
type: "namespace",
|
|
404
|
-
name: "*"
|
|
405
|
-
});
|
|
1249
|
+
specifiers.push({ type: "namespace" });
|
|
406
1250
|
}
|
|
1251
|
+
/** What one `export { ... } from "x"` takes, using the names before any rename. */
|
|
407
1252
|
function collectExportNamedDeclarationSpecifiers(path, specifiers) {
|
|
408
1253
|
const node = path.node;
|
|
409
1254
|
if (!t.isStringLiteral(node.source) || node.exportKind !== "value") return;
|
|
410
|
-
for (
|
|
411
|
-
const specifier = node.specifiers[index];
|
|
1255
|
+
for (const specifier of node.specifiers) {
|
|
412
1256
|
if (t.isExportDefaultSpecifier(specifier) && t.isIdentifier(specifier.exported)) {
|
|
413
1257
|
const setDefaultName = (newName) => {
|
|
414
|
-
const
|
|
415
|
-
if (
|
|
416
|
-
node.specifiers[
|
|
1258
|
+
const at = node.specifiers.indexOf(specifier);
|
|
1259
|
+
if (at === -1) return;
|
|
1260
|
+
node.specifiers[at] = t.exportSpecifier(t.identifier(newName), t.identifier(specifier.exported.name));
|
|
417
1261
|
};
|
|
418
1262
|
specifiers.push({
|
|
419
1263
|
type: "default",
|
|
420
|
-
name: specifier.exported.name,
|
|
421
1264
|
setDefaultName
|
|
422
1265
|
});
|
|
423
1266
|
continue;
|
|
@@ -432,15 +1275,11 @@ function collectExportNamedDeclarationSpecifiers(path, specifiers) {
|
|
|
432
1275
|
});
|
|
433
1276
|
continue;
|
|
434
1277
|
}
|
|
435
|
-
if (t.isExportNamespaceSpecifier(specifier)) specifiers.push({
|
|
436
|
-
type: "namespace",
|
|
437
|
-
name: t.isIdentifier(specifier.exported) ? specifier.exported.name : specifier.exported.value
|
|
438
|
-
});
|
|
1278
|
+
if (t.isExportNamespaceSpecifier(specifier)) specifiers.push({ type: "namespace" });
|
|
439
1279
|
}
|
|
440
1280
|
}
|
|
441
1281
|
function collectDynamicImportSpecifiers(path, specifiers) {
|
|
442
|
-
if (!
|
|
443
|
-
if (!t.isStringLiteral(path.node.arguments[0])) {
|
|
1282
|
+
if (!dynamicImportSource(path.node)) {
|
|
444
1283
|
specifiers.push({ type: "unknown" });
|
|
445
1284
|
return;
|
|
446
1285
|
}
|
|
@@ -451,17 +1290,17 @@ function collectDynamicImportSpecifiers(path, specifiers) {
|
|
|
451
1290
|
}
|
|
452
1291
|
const parentNode = parentPath.parentPath?.node;
|
|
453
1292
|
if (t.isMemberExpression(parentNode)) {
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
|
|
1293
|
+
const importedName = staticKey(parentNode.property, parentNode.computed);
|
|
1294
|
+
if (!importedName) {
|
|
1295
|
+
specifiers.push({ type: "unknown" });
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
457
1298
|
if (importedName === "default") {
|
|
458
|
-
const capturedMemberExpression = parentNode;
|
|
459
1299
|
const setDefaultName = (newName) => {
|
|
460
|
-
|
|
1300
|
+
parentNode.property = t.identifier(newName);
|
|
461
1301
|
};
|
|
462
1302
|
specifiers.push({
|
|
463
1303
|
type: "default",
|
|
464
|
-
name: importedName,
|
|
465
1304
|
setDefaultName
|
|
466
1305
|
});
|
|
467
1306
|
return;
|
|
@@ -475,322 +1314,769 @@ function collectDynamicImportSpecifiers(path, specifiers) {
|
|
|
475
1314
|
return;
|
|
476
1315
|
}
|
|
477
1316
|
if (t.isVariableDeclarator(parentNode) && t.isObjectPattern(parentNode.id)) {
|
|
478
|
-
|
|
479
|
-
if (!t.isObjectProperty(property)) continue;
|
|
480
|
-
const importedName = getNodeName(property.key);
|
|
481
|
-
if (!importedName) continue;
|
|
482
|
-
const localName = getNodeName(property.value);
|
|
483
|
-
if (importedName === "default") {
|
|
484
|
-
const setDefaultName = (newName) => {
|
|
485
|
-
property.key = t.identifier(newName);
|
|
486
|
-
};
|
|
487
|
-
specifiers.push({
|
|
488
|
-
type: "default",
|
|
489
|
-
name: localName ?? importedName,
|
|
490
|
-
setDefaultName
|
|
491
|
-
});
|
|
492
|
-
continue;
|
|
493
|
-
}
|
|
494
|
-
specifiers.push({
|
|
495
|
-
type: "named",
|
|
496
|
-
kind: "import",
|
|
497
|
-
name: importedName,
|
|
498
|
-
publicName: localName ?? importedName
|
|
499
|
-
});
|
|
500
|
-
}
|
|
1317
|
+
collectDestructuredNames(parentNode.id, specifiers);
|
|
501
1318
|
return;
|
|
502
1319
|
}
|
|
503
1320
|
if (t.isVariableDeclarator(parentNode) && t.isIdentifier(parentNode.id)) {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
1321
|
+
recordNamespaceUse(path, parentNode.id.name, specifiers);
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
if (!parentNode || t.isExpressionStatement(parentNode)) {
|
|
1325
|
+
specifiers.push({ type: "side-effect" });
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
specifiers.push({ type: "unknown" });
|
|
1329
|
+
}
|
|
1330
|
+
/** What `const { a, b: c } = await import('path')` takes, or the whole package when a property cannot be named. */
|
|
1331
|
+
function collectDestructuredNames(pattern, specifiers) {
|
|
1332
|
+
const found = [];
|
|
1333
|
+
for (const property of pattern.properties) {
|
|
1334
|
+
if (!t.isObjectProperty(property)) {
|
|
1335
|
+
specifiers.push({ type: "unknown" });
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
const importedName = staticKey(property.key, property.computed);
|
|
1339
|
+
if (!importedName) {
|
|
1340
|
+
specifiers.push({ type: "unknown" });
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
const localName = t.isIdentifier(property.value) ? property.value.name : void 0;
|
|
1344
|
+
if (importedName === "default") {
|
|
1345
|
+
const setDefaultName = (newName) => {
|
|
1346
|
+
property.key = t.identifier(newName);
|
|
1347
|
+
};
|
|
1348
|
+
found.push({
|
|
1349
|
+
type: "default",
|
|
1350
|
+
setDefaultName
|
|
529
1351
|
});
|
|
1352
|
+
continue;
|
|
530
1353
|
}
|
|
531
|
-
|
|
1354
|
+
found.push({
|
|
1355
|
+
type: "named",
|
|
1356
|
+
kind: "import",
|
|
1357
|
+
name: importedName,
|
|
1358
|
+
publicName: localName ?? importedName
|
|
1359
|
+
});
|
|
532
1360
|
}
|
|
533
|
-
|
|
1361
|
+
specifiers.push(...found);
|
|
1362
|
+
}
|
|
1363
|
+
/** The name a property key stands for, or nothing when only the running program knows it. */
|
|
1364
|
+
function staticKey(node, isComputed) {
|
|
1365
|
+
if (t.isStringLiteral(node)) return node.value;
|
|
1366
|
+
if (isComputed) return null;
|
|
1367
|
+
return getNodeName(node);
|
|
534
1368
|
}
|
|
1369
|
+
/** The name a node stands for, however it was written. */
|
|
1370
|
+
function getNodeName(node) {
|
|
1371
|
+
if (t.isIdentifier(node)) return node.name;
|
|
1372
|
+
if (t.isStringLiteral(node)) return node.value;
|
|
1373
|
+
if (t.isPrivateName(node)) return node.id.name;
|
|
1374
|
+
return null;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
//#endregion
|
|
1378
|
+
//#region src/plugins/bundle-packages/collect/collect-references.ts
|
|
1379
|
+
/** The name of the package an import points into, or nothing if it stays inside the project. */
|
|
1380
|
+
function packageOfSource(source, fromFile, context) {
|
|
1381
|
+
const sourceRootRelative = join(dirname(fromFile), source);
|
|
1382
|
+
if (isNodeModulesPath(sourceRootRelative)) return context.pathPackageMap.get(join(context.root, sourceRootRelative));
|
|
1383
|
+
}
|
|
1384
|
+
/** Records what an import takes from a package, so the package's file exports it. */
|
|
1385
|
+
function recordSpecifiers(packageName, specifiers, context) {
|
|
1386
|
+
const existing = context.specifiersByPackage.get(packageName) ?? [];
|
|
1387
|
+
existing.push(...specifiers);
|
|
1388
|
+
context.specifiersByPackage.set(packageName, existing);
|
|
1389
|
+
}
|
|
1390
|
+
/** Every package import in a script, standalone or inline in a script tag. */
|
|
1391
|
+
function collectScriptReferences(metadata, context) {
|
|
1392
|
+
const references = [];
|
|
1393
|
+
if (!isNodeModulesPath(metadata.filePath) && !hasNodeModulesImport(metadata.ast)) return references;
|
|
1394
|
+
for (const sourceReference of collectSpecifiersFromAst(metadata.ast)) {
|
|
1395
|
+
const packageName = packageOfSource(sourceReference.source, metadata.filePath, context);
|
|
1396
|
+
if (!packageName) {
|
|
1397
|
+
reportUntracedSource(sourceReference.source, metadata.filePath, context);
|
|
1398
|
+
continue;
|
|
1399
|
+
}
|
|
1400
|
+
recordSpecifiers(packageName, sourceReference.specifiers, context);
|
|
1401
|
+
references.push({
|
|
1402
|
+
setSource: (source) => {
|
|
1403
|
+
sourceReference.source = source;
|
|
1404
|
+
},
|
|
1405
|
+
fromFile: metadata.filePath,
|
|
1406
|
+
packageName,
|
|
1407
|
+
specifiers: sourceReference.specifiers
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
return references;
|
|
1411
|
+
}
|
|
1412
|
+
/** Every package import written as a `<script src>` tag, which only runs the package, so it counts as a side-effect import. */
|
|
1413
|
+
function collectScriptTagReferences(metadata, context) {
|
|
1414
|
+
const references = [];
|
|
1415
|
+
for (const scriptTag of metadata.ast.querySelectorAll("script[src]")) {
|
|
1416
|
+
const source = scriptTag.getAttribute("src");
|
|
1417
|
+
if (!source) continue;
|
|
1418
|
+
if (!isScriptType(scriptTag.getAttribute("type"))) continue;
|
|
1419
|
+
const packageName = packageOfSource(source, metadata.filePath, context);
|
|
1420
|
+
if (!packageName) {
|
|
1421
|
+
reportUntracedSource(source, metadata.filePath, context);
|
|
1422
|
+
continue;
|
|
1423
|
+
}
|
|
1424
|
+
const specifiers = [{ type: "side-effect" }];
|
|
1425
|
+
recordSpecifiers(packageName, specifiers, context);
|
|
1426
|
+
references.push({
|
|
1427
|
+
setSource: (newSource) => {
|
|
1428
|
+
scriptTag.setAttribute("src", newSource);
|
|
1429
|
+
},
|
|
1430
|
+
fromFile: metadata.filePath,
|
|
1431
|
+
packageName,
|
|
1432
|
+
specifiers
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
return references;
|
|
1436
|
+
}
|
|
1437
|
+
/** The `href` of every stylesheet link, as sources that write back to the tag. */
|
|
1438
|
+
function styleLinkSources(metadata) {
|
|
1439
|
+
return metadata.ast.querySelectorAll("link[rel=\"stylesheet\"], link[rel=\"preload\"][as=\"style\"]").map((styleLink) => ({
|
|
1440
|
+
get source() {
|
|
1441
|
+
return styleLink.getAttribute("href") ?? "";
|
|
1442
|
+
},
|
|
1443
|
+
set source(value) {
|
|
1444
|
+
styleLink.setAttribute("href", value);
|
|
1445
|
+
}
|
|
1446
|
+
}));
|
|
1447
|
+
}
|
|
1448
|
+
/** Moves the files a package ships next to the package output and points the sources reaching into it at the new place. */
|
|
1449
|
+
async function rebasePackageAssets(fromFile, sources, packagesDirectory, assetByPath) {
|
|
1450
|
+
for (const reference of sources) {
|
|
1451
|
+
const [link, suffix] = splitHtmlLink(reference.source);
|
|
1452
|
+
const sourceRootRelative = join(dirname(fromFile), link);
|
|
1453
|
+
if (!isNodeModulesPath(sourceRootRelative)) continue;
|
|
1454
|
+
const asset = assetByPath.get(join(this.root, sourceRootRelative));
|
|
1455
|
+
if (!asset) continue;
|
|
1456
|
+
const assetOutputPath = calcPackageAssetOutPath(sourceRootRelative, packagesDirectory);
|
|
1457
|
+
if (asset.filePath !== assetOutputPath) {
|
|
1458
|
+
await this.rebase(asset, join(this.root, assetOutputPath));
|
|
1459
|
+
asset.filePath = assetOutputPath;
|
|
1460
|
+
}
|
|
1461
|
+
reference.source = relative(dirname(fromFile), assetOutputPath) + suffix;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
/** Walks every file in the build and collects the imports that reach into `node_modules`. */
|
|
1465
|
+
async function collectReferences(context, packagesDirectory) {
|
|
1466
|
+
const references = [];
|
|
1467
|
+
/** Absolute path → the metadata holding it, so a stylesheet is found without scanning the whole list per link. */
|
|
1468
|
+
const assetByPath = /* @__PURE__ */ new Map();
|
|
1469
|
+
for (const item of this.metadataList) assetByPath.set(join(this.root, item.id), item);
|
|
1470
|
+
for (const inputMetadata of this.metadataList) {
|
|
1471
|
+
for (const { metadata } of filterScriptMetadata(inputMetadata)) references.push(...collectScriptReferences(metadata, context));
|
|
1472
|
+
for (const { metadata } of filterStyleMetadata(inputMetadata)) {
|
|
1473
|
+
const sources = await this.requestSources(metadata);
|
|
1474
|
+
await rebasePackageAssets.call(this, metadata.filePath, sources, packagesDirectory, assetByPath);
|
|
1475
|
+
}
|
|
1476
|
+
if (!isHtmlMetadata(inputMetadata)) continue;
|
|
1477
|
+
references.push(...collectScriptTagReferences(inputMetadata, context));
|
|
1478
|
+
await rebasePackageAssets.call(this, inputMetadata.filePath, styleLinkSources(inputMetadata), packagesDirectory, assetByPath);
|
|
1479
|
+
}
|
|
1480
|
+
return references;
|
|
1481
|
+
}
|
|
1482
|
+
/** Points every import at the file its package ended up in, renaming defaults when a file holds several packages. */
|
|
1483
|
+
function pointReferencesAtUnits(references, units, unitOf, packagesDirectory) {
|
|
1484
|
+
for (const reference of references) {
|
|
1485
|
+
const unitName = unitOf.get(reference.packageName)?.[0] ?? reference.packageName;
|
|
1486
|
+
const packageOutputPath = calcPackageOutPath(unitName, packagesDirectory);
|
|
1487
|
+
reference.setSource(relative(dirname(reference.fromFile), packageOutputPath));
|
|
1488
|
+
const unit = units.get(unitName);
|
|
1489
|
+
if (!unit || !isSharedUnit(unit)) continue;
|
|
1490
|
+
const alias = defaultExportAlias(reference.packageName);
|
|
1491
|
+
for (const specifier of reference.specifiers) if (specifier.type === "default") specifier.setDefaultName(alias);
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
/** Reports an import into `node_modules` whose package could not be named, leaving it out of the bundles. */
|
|
1495
|
+
function reportUntracedSource(source, fromFile, context) {
|
|
1496
|
+
const sourceRootRelative = join(dirname(fromFile), source);
|
|
1497
|
+
if (isNodeModulesPath(sourceRootRelative)) context.reportUntraced(`Could not resolve a package name for "${source}" imported from "${fromFile}".\nThe import is left pointing into node_modules and will not be bundled.`);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
//#endregion
|
|
1501
|
+
//#region src/plugins/bundle-packages/index/read-requires.ts
|
|
1502
|
+
const ENVIRONMENT_GUARD = "process.env.NODE_ENV";
|
|
535
1503
|
/**
|
|
536
|
-
* Collects
|
|
537
|
-
* Covers most cases when importing a package using a name space.
|
|
1504
|
+
* Collects the specifiers a CommonJS file requires, with dead branches removed.
|
|
538
1505
|
*
|
|
539
|
-
*
|
|
1506
|
+
* Packages routinely ship an entry that switches on the build mode:
|
|
540
1507
|
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
*
|
|
1508
|
+
* ```js
|
|
1509
|
+
* if (process.env.NODE_ENV !== "production") {
|
|
1510
|
+
* module.exports = require("./cjs/react.development.js");
|
|
1511
|
+
* } else {
|
|
1512
|
+
* module.exports = require("./cjs/react.production.js");
|
|
1513
|
+
* }
|
|
1514
|
+
* ```
|
|
546
1515
|
*
|
|
547
|
-
*
|
|
1516
|
+
* Esbuild gets `process.env.NODE_ENV` as a define, so it keeps one branch and drops the other. Reading both would report requires
|
|
1517
|
+
* that never reach the output, grouping packages that do not need each other in that mode.
|
|
548
1518
|
*/
|
|
549
|
-
function
|
|
550
|
-
|
|
551
|
-
const
|
|
552
|
-
if (!
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
1519
|
+
function readRequires(code, isProduction) {
|
|
1520
|
+
if (!code.includes(ENVIRONMENT_GUARD)) return matchRequires(code);
|
|
1521
|
+
const ast = tryParse(code);
|
|
1522
|
+
if (!ast) return matchRequires(code);
|
|
1523
|
+
const specifiers = [];
|
|
1524
|
+
for (const node of ast.program.body) collectFromNode(node, isProduction ? "production" : "development", specifiers);
|
|
1525
|
+
return specifiers;
|
|
1526
|
+
}
|
|
1527
|
+
/** Every `require("…")` in the source, without looking at what guards it. */
|
|
1528
|
+
function matchRequires(code) {
|
|
1529
|
+
return Array.from(code.matchAll(/require\(\s*["']([^"']+)["']\s*\)/g), (match) => match[1]);
|
|
1530
|
+
}
|
|
1531
|
+
/** Parses CommonJS, which is a script rather than a module. */
|
|
1532
|
+
function tryParse(code) {
|
|
1533
|
+
try {
|
|
1534
|
+
return parse$3(code, {
|
|
1535
|
+
sourceType: "script",
|
|
1536
|
+
allowReturnOutsideFunction: true,
|
|
1537
|
+
errorRecovery: true
|
|
1538
|
+
});
|
|
1539
|
+
} catch {
|
|
1540
|
+
return null;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
/** Walks one node, following only the branches that survive in this mode. */
|
|
1544
|
+
function collectFromNode(node, mode, specifiers) {
|
|
1545
|
+
if (!node) return;
|
|
1546
|
+
if (t.isCallExpression(node) && t.isIdentifier(node.callee, { name: "require" })) {
|
|
1547
|
+
const argument = node.arguments[0];
|
|
1548
|
+
if (t.isStringLiteral(argument)) specifiers.push(argument.value);
|
|
1549
|
+
}
|
|
1550
|
+
const taken = takenBranch(node, mode);
|
|
1551
|
+
if (taken !== void 0) {
|
|
1552
|
+
collectFromNode(taken, mode, specifiers);
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
const keys = t.VISITOR_KEYS[node.type] ?? [];
|
|
1556
|
+
for (const key of keys) {
|
|
1557
|
+
const value = node[key];
|
|
1558
|
+
if (Array.isArray(value)) {
|
|
1559
|
+
for (const element of value) if (t.isNode(element)) collectFromNode(element, mode, specifiers);
|
|
559
1560
|
continue;
|
|
560
1561
|
}
|
|
561
|
-
if (t.
|
|
562
|
-
const id = parentNode.id;
|
|
563
|
-
if (t.isObjectPattern(id)) {
|
|
564
|
-
for (const property of id.properties) {
|
|
565
|
-
if (t.isRestElement(property) || !t.isIdentifier(property.key)) continue;
|
|
566
|
-
usedNames.add(property.key.name);
|
|
567
|
-
}
|
|
568
|
-
continue;
|
|
569
|
-
}
|
|
570
|
-
if (t.isIdentifier(id)) {
|
|
571
|
-
const aliasUsed = collectUsedNames(parentPath.get("id"), id.name);
|
|
572
|
-
for (const n of aliasUsed) usedNames.add(n);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
1562
|
+
if (t.isNode(value)) collectFromNode(value, mode, specifiers);
|
|
575
1563
|
}
|
|
576
|
-
return usedNames;
|
|
577
1564
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
1565
|
+
/**
|
|
1566
|
+
* @returns The branch a mode check resolves to, `undefined` when the node is not a mode check or its answer is unknown, and
|
|
1567
|
+
* `null` when it resolves to a branch that is not there, such as an `if` with no `else`
|
|
1568
|
+
*/
|
|
1569
|
+
function takenBranch(node, mode) {
|
|
1570
|
+
if (!t.isIfStatement(node) && !t.isConditionalExpression(node)) return;
|
|
1571
|
+
const result = evaluateEnvironmentTest(node.test, mode);
|
|
1572
|
+
if (result === void 0) return;
|
|
1573
|
+
return result ? node.consequent : node.alternate ?? null;
|
|
1574
|
+
}
|
|
1575
|
+
/** Evaluates a comparison between `process.env.NODE_ENV` and a string, or nothing if the test is anything else. */
|
|
1576
|
+
function evaluateEnvironmentTest(test, mode) {
|
|
1577
|
+
if (!t.isBinaryExpression(test)) return;
|
|
1578
|
+
if (![
|
|
1579
|
+
"===",
|
|
1580
|
+
"==",
|
|
1581
|
+
"!==",
|
|
1582
|
+
"!="
|
|
1583
|
+
].includes(test.operator)) return;
|
|
1584
|
+
const left = readComparisonSide(test.left);
|
|
1585
|
+
const right = readComparisonSide(test.right);
|
|
1586
|
+
if (!left || !right) return;
|
|
1587
|
+
if (left.kind === right.kind) return;
|
|
1588
|
+
const isEqual = (left.kind === "literal" ? left.value : right.value) === mode;
|
|
1589
|
+
return test.operator.startsWith("!") ? !isEqual : isEqual;
|
|
1590
|
+
}
|
|
1591
|
+
/** Reads one side of a comparison as either the environment lookup or a string literal. */
|
|
1592
|
+
function readComparisonSide(node) {
|
|
1593
|
+
if (t.isStringLiteral(node)) return {
|
|
1594
|
+
kind: "literal",
|
|
1595
|
+
value: node.value
|
|
1596
|
+
};
|
|
1597
|
+
if (isEnvironmentLookup(node)) return { kind: "environment" };
|
|
1598
|
+
}
|
|
1599
|
+
/** Whether a node is `process.env.NODE_ENV`. */
|
|
1600
|
+
function isEnvironmentLookup(node) {
|
|
1601
|
+
if (!t.isMemberExpression(node) || !t.isIdentifier(node.property, { name: "NODE_ENV" })) return false;
|
|
1602
|
+
const object = node.object;
|
|
1603
|
+
if (!t.isMemberExpression(object) || !t.isIdentifier(object.property, { name: "env" })) return false;
|
|
1604
|
+
return t.isIdentifier(object.object, { name: "process" });
|
|
583
1605
|
}
|
|
584
1606
|
|
|
585
1607
|
//#endregion
|
|
586
|
-
//#region src/plugins/bundle-packages/
|
|
1608
|
+
//#region src/plugins/bundle-packages/index/lex-module.ts
|
|
587
1609
|
/**
|
|
588
|
-
*
|
|
589
|
-
*
|
|
590
|
-
*
|
|
591
|
-
* To resolve this, we replace `.js` with `_js` in the subpackage path only, avoiding conflicts.\
|
|
592
|
-
* Example: `highlight.js/languages/javascript.js` -> `highlight_js/languages/javascript.js`
|
|
1610
|
+
* Reads one file into a record, without resolving anything or opening another file.
|
|
1611
|
+
*
|
|
1612
|
+
* A file with neither imports nor exports lexes as neither ESM nor CJS, and is treated as an ES module exporting nothing.
|
|
593
1613
|
*/
|
|
594
|
-
function
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
1614
|
+
function lexModule(file, code, mtimeMs, isProduction) {
|
|
1615
|
+
const [imports, exports] = parse$2(code, file);
|
|
1616
|
+
const isEsm = imports.length > 0 || exports.length > 0;
|
|
1617
|
+
const record = {
|
|
1618
|
+
file,
|
|
1619
|
+
mtimeMs,
|
|
1620
|
+
format: isEsm ? "esm" : "cjs",
|
|
1621
|
+
reexportOf: /* @__PURE__ */ new Map(),
|
|
1622
|
+
ownExports: [],
|
|
1623
|
+
starSources: [],
|
|
1624
|
+
edges: []
|
|
1625
|
+
};
|
|
1626
|
+
if (isEsm) {
|
|
1627
|
+
readEsmRecord(record, code, imports, exports);
|
|
1628
|
+
return record;
|
|
1629
|
+
}
|
|
1630
|
+
readCjsRecord(record, code, isProduction);
|
|
1631
|
+
return record;
|
|
1632
|
+
}
|
|
1633
|
+
/** Fills a record from the ES module lexer's output. */
|
|
1634
|
+
function readEsmRecord(record, code, imports, exports) {
|
|
1635
|
+
for (const exported of exports) {
|
|
1636
|
+
const owner = imports.find((index) => index.n && exported.s >= index.ss && exported.e <= index.se);
|
|
1637
|
+
if (!owner?.n) {
|
|
1638
|
+
record.ownExports.push(exported.n);
|
|
1639
|
+
continue;
|
|
1640
|
+
}
|
|
1641
|
+
const renames = reexportedNames(code.slice(owner.ss, owner.se));
|
|
1642
|
+
record.reexportOf.set(exported.n, {
|
|
1643
|
+
source: owner.n,
|
|
1644
|
+
importedName: renames.get(exported.n)
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
for (const imported of imports) {
|
|
1648
|
+
if (!imported.n) continue;
|
|
1649
|
+
const statement = code.slice(imported.ss, imported.se);
|
|
1650
|
+
if (/^export\s*\*/.test(statement)) {
|
|
1651
|
+
record.starSources.push(imported.n);
|
|
602
1652
|
continue;
|
|
603
1653
|
}
|
|
604
|
-
|
|
1654
|
+
if (/^export\s*\{/.test(statement)) continue;
|
|
1655
|
+
const isDynamic = imported.d > -1;
|
|
1656
|
+
record.edges.push({
|
|
1657
|
+
specifier: imported.n,
|
|
1658
|
+
kind: "import",
|
|
1659
|
+
names: namesOfImport(statement),
|
|
1660
|
+
isDynamic
|
|
1661
|
+
});
|
|
605
1662
|
}
|
|
606
|
-
return outParts.join("/");
|
|
607
1663
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
const
|
|
611
|
-
|
|
1664
|
+
/** Fills a record from the CommonJS lexer's output, plus the `require()` calls that survive in this mode. */
|
|
1665
|
+
function readCjsRecord(record, code, isProduction) {
|
|
1666
|
+
const parsed = tryParseCjs(code);
|
|
1667
|
+
if (!parsed) {
|
|
1668
|
+
record.format = "esm";
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
const live = new Set(readRequires(code, isProduction));
|
|
1672
|
+
record.ownExports.push(...parsed.exports);
|
|
1673
|
+
for (const reexport of liveReexports(parsed.reexports, live)) record.reexportOf.set(reexport, {
|
|
1674
|
+
source: reexport,
|
|
1675
|
+
importedName: reexport
|
|
1676
|
+
});
|
|
1677
|
+
for (const specifier of live) record.edges.push({
|
|
1678
|
+
specifier,
|
|
1679
|
+
kind: "require",
|
|
1680
|
+
names: void 0,
|
|
1681
|
+
isDynamic: false
|
|
1682
|
+
});
|
|
612
1683
|
}
|
|
613
1684
|
/**
|
|
614
|
-
*
|
|
1685
|
+
* The re-export targets that survive in this mode.
|
|
615
1686
|
*
|
|
616
|
-
*
|
|
1687
|
+
* The lexer always reports the development branch of a mode check, so in production it names a file the build drops. The relative
|
|
1688
|
+
* requires that did survive are the real targets.
|
|
617
1689
|
*/
|
|
618
|
-
function
|
|
619
|
-
const
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
if (specifier.type === "default") {
|
|
631
|
-
isDefaultImport = true;
|
|
632
|
-
newDefaultName = specifier.newDefaultName;
|
|
633
|
-
}
|
|
634
|
-
if (specifier.type === "named" && specifier.kind === "import") named.add(specifier.name);
|
|
635
|
-
if (specifier.type === "named" && specifier.kind === "export") {
|
|
636
|
-
named.delete(specifier.name);
|
|
637
|
-
named.add(specifier.publicName);
|
|
638
|
-
}
|
|
639
|
-
if (specifier.type === "namespace") isNamespace = true;
|
|
640
|
-
else if (specifier.type === "side-effect") hasSideEffect = true;
|
|
1690
|
+
function liveReexports(reported, live) {
|
|
1691
|
+
const kept = reported.filter((reexport) => live.has(reexport));
|
|
1692
|
+
if (kept.length > 0) return kept;
|
|
1693
|
+
if (reported.length === 0) return reported;
|
|
1694
|
+
return live.values().filter((specifier) => specifier.startsWith(".")).toArray();
|
|
1695
|
+
}
|
|
1696
|
+
/** Runs the CommonJS lexer, which throws on ES module syntax. */
|
|
1697
|
+
function tryParseCjs(code) {
|
|
1698
|
+
try {
|
|
1699
|
+
return parse$1(code);
|
|
1700
|
+
} catch {
|
|
1701
|
+
return null;
|
|
641
1702
|
}
|
|
642
|
-
let contents = "";
|
|
643
|
-
if (hasSideEffect) contents += `import '${packageName}';`;
|
|
644
|
-
if (isNamespace) contents += `\nexport * from '${packageName}';`;
|
|
645
|
-
if (isDefaultImport) contents += `\nexport { default${newDefaultName ? ` as ${newDefaultName}` : ""} } from '${packageName}';`;
|
|
646
|
-
if (named.size > 0 && !isNamespace) contents += `\nexport { ${Array.from(named).join(", ")} } from '${packageName}';`;
|
|
647
|
-
return contents;
|
|
648
1703
|
}
|
|
649
|
-
|
|
650
|
-
|
|
1704
|
+
/**
|
|
1705
|
+
* Exported name → the name asked of the source, read off one `export ... from` statement.
|
|
1706
|
+
*
|
|
1707
|
+
* An entry is absent when the whole namespace is taken, which `export * as ns from "x"` does and a brace list never does.
|
|
1708
|
+
*/
|
|
1709
|
+
function reexportedNames(statement) {
|
|
1710
|
+
const renames = /* @__PURE__ */ new Map();
|
|
1711
|
+
const braces = /\{([^}]*)\}/.exec(statement);
|
|
1712
|
+
if (!braces) return renames;
|
|
1713
|
+
const parts = braces[1].split(",");
|
|
1714
|
+
for (const part of parts) {
|
|
1715
|
+
const [imported, exported] = part.split(/\s+as\s+/).map((side) => side.trim().replaceAll(/^["']|["']$/g, ""));
|
|
1716
|
+
if (imported) renames.set(exported || imported, imported);
|
|
1717
|
+
}
|
|
1718
|
+
return renames;
|
|
1719
|
+
}
|
|
1720
|
+
/** The names an import statement binds, or `undefined` for `import * as`, which binds no individual names. */
|
|
1721
|
+
function namesOfImport(statement) {
|
|
1722
|
+
if (/import\s+\*\s+as/.test(statement)) return;
|
|
1723
|
+
const names = [];
|
|
1724
|
+
if (/^import\s+(?!\{)[A-Za-z_$][\w$]*/.test(statement)) names.push("default");
|
|
1725
|
+
const braces = /\{([^}]*)\}/.exec(statement);
|
|
1726
|
+
if (!braces) {
|
|
1727
|
+
if (names.length === 0) return;
|
|
1728
|
+
return names;
|
|
1729
|
+
}
|
|
1730
|
+
const parts = braces[1].split(",");
|
|
1731
|
+
for (const part of parts) {
|
|
1732
|
+
const name = part.trim().split(/\s+as\s+/, 1)[0].trim();
|
|
1733
|
+
if (name) names.push(name);
|
|
1734
|
+
}
|
|
1735
|
+
return names;
|
|
651
1736
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
1737
|
+
|
|
1738
|
+
//#endregion
|
|
1739
|
+
//#region src/plugins/bundle-packages/index/vendor-index.ts
|
|
1740
|
+
/**
|
|
1741
|
+
* Lexes the part of `node_modules` the project uses. No bundler runs. It answers:
|
|
1742
|
+
*
|
|
1743
|
+
* - Which files a specifier pulls in, narrowed to the names asked for.
|
|
1744
|
+
* - Which names a specifier exports, following `export *` and CommonJS re-export chains.
|
|
1745
|
+
* - Which of its imports go to another package, with their names, and which of those are `require` calls.
|
|
1746
|
+
*/
|
|
1747
|
+
var VendorIndex = class VendorIndex {
|
|
1748
|
+
root;
|
|
1749
|
+
#resolver;
|
|
1750
|
+
#resolveCache = /* @__PURE__ */ new Map();
|
|
1751
|
+
#modules = /* @__PURE__ */ new Map();
|
|
1752
|
+
#closures = /* @__PURE__ */ new Map();
|
|
1753
|
+
#isProduction;
|
|
1754
|
+
constructor(root, isProduction) {
|
|
1755
|
+
this.root = root;
|
|
1756
|
+
this.#isProduction = isProduction;
|
|
1757
|
+
this.#resolver = new ResolverFactory({
|
|
1758
|
+
conditionNames: [
|
|
1759
|
+
"browser",
|
|
1760
|
+
"import",
|
|
1761
|
+
"module",
|
|
1762
|
+
"default",
|
|
1763
|
+
isProduction ? "production" : "development"
|
|
1764
|
+
],
|
|
1765
|
+
mainFields: [
|
|
1766
|
+
"browser",
|
|
1767
|
+
"module",
|
|
1768
|
+
"main"
|
|
1769
|
+
],
|
|
1770
|
+
extensions: [
|
|
1771
|
+
".js",
|
|
1772
|
+
".mjs",
|
|
1773
|
+
".cjs",
|
|
1774
|
+
".json"
|
|
1775
|
+
],
|
|
1776
|
+
symlinks: false
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
static async create(root, isProduction) {
|
|
1780
|
+
await init$1;
|
|
1781
|
+
await init();
|
|
1782
|
+
return new VendorIndex(root, isProduction);
|
|
1783
|
+
}
|
|
1784
|
+
/** Resolves a specifier from `fromFile`. Handles relative paths, bare package names and `#subpath` imports. */
|
|
1785
|
+
resolveFrom(specifier, fromFile) {
|
|
1786
|
+
const directory = dirname(fromFile);
|
|
1787
|
+
const key = directory + "\0" + specifier;
|
|
1788
|
+
if (this.#resolveCache.has(key)) return this.#resolveCache.get(key);
|
|
1789
|
+
let resolved;
|
|
1790
|
+
try {
|
|
1791
|
+
resolved = this.#resolver.sync(directory, specifier).path ?? void 0;
|
|
1792
|
+
} catch {
|
|
1793
|
+
resolved = void 0;
|
|
1794
|
+
}
|
|
1795
|
+
this.#resolveCache.set(key, resolved);
|
|
1796
|
+
return resolved;
|
|
1797
|
+
}
|
|
1798
|
+
/** Lexes one file. Re-reads it only when its mtime changed. */
|
|
1799
|
+
lex(file) {
|
|
1800
|
+
const mtimeMs = statSync(file).mtimeMs;
|
|
1801
|
+
const cached = this.#modules.get(file);
|
|
1802
|
+
if (cached && cached.mtimeMs === mtimeMs) return cached;
|
|
1803
|
+
const code = readFileSync(file, "utf8");
|
|
1804
|
+
const record = lexModule(file, code, mtimeMs, this.#isProduction);
|
|
1805
|
+
this.#modules.set(file, record);
|
|
1806
|
+
return record;
|
|
1807
|
+
}
|
|
1808
|
+
/**
|
|
1809
|
+
* Whether the module a specifier resolves to has a default export.
|
|
1810
|
+
*
|
|
1811
|
+
* `export *` does not carry a default, so a package taken whole needs its default re-exported by name or it comes out empty.
|
|
1812
|
+
*/
|
|
1813
|
+
hasDefaultExport(specifier, fromFile = join(this.root, "package.json")) {
|
|
1814
|
+
const entry = this.resolveFrom(specifier, fromFile);
|
|
1815
|
+
if (!entry) return false;
|
|
1816
|
+
const record = this.lex(entry);
|
|
1817
|
+
return record.ownExports.includes("default") || record.reexportOf.has("default");
|
|
1818
|
+
}
|
|
1819
|
+
/** The format of the file a specifier resolves to. */
|
|
1820
|
+
formatOf(specifier, fromFile = join(this.root, "package.json")) {
|
|
1821
|
+
const entry = this.resolveFrom(specifier, fromFile);
|
|
1822
|
+
if (!entry) return;
|
|
1823
|
+
return this.lex(entry).format;
|
|
1824
|
+
}
|
|
1825
|
+
/**
|
|
1826
|
+
* Every name a file exports, following `export *` and CommonJS re-export chains.
|
|
1827
|
+
*
|
|
1828
|
+
* Use this for CommonJS only. In an ES module `export type { X }` and `export { X }` look identical to the lexer, so the list
|
|
1829
|
+
* would include type names, and re-exporting one is a build error.
|
|
1830
|
+
*/
|
|
1831
|
+
exportsOf(file, seen = /* @__PURE__ */ new Set()) {
|
|
1832
|
+
if (seen.has(file)) return [];
|
|
1833
|
+
seen.add(file);
|
|
1834
|
+
const record = this.lex(file);
|
|
1835
|
+
const names = new Set(record.ownExports.filter((name) => name !== "__esModule"));
|
|
1836
|
+
for (const [name, reexport] of record.reexportOf) {
|
|
1837
|
+
if (!isPathReexport(name, reexport)) {
|
|
1838
|
+
names.add(name);
|
|
1839
|
+
continue;
|
|
1840
|
+
}
|
|
1841
|
+
const target = this.resolveFrom(reexport.source, file);
|
|
1842
|
+
if (!target) continue;
|
|
1843
|
+
for (const inner of this.exportsOf(target, seen)) names.add(inner);
|
|
658
1844
|
}
|
|
659
|
-
|
|
660
|
-
const
|
|
661
|
-
if (
|
|
1845
|
+
for (const source of record.starSources) {
|
|
1846
|
+
const target = this.resolveFrom(source, file);
|
|
1847
|
+
if (!target) continue;
|
|
1848
|
+
for (const inner of this.exportsOf(target, seen)) names.add(inner);
|
|
662
1849
|
}
|
|
1850
|
+
names.delete("default");
|
|
1851
|
+
return [...names];
|
|
663
1852
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
1853
|
+
/**
|
|
1854
|
+
* Walks everything `specifier` needs to provide `names`, and collects the imports that leave its package.
|
|
1855
|
+
*
|
|
1856
|
+
* Asking for every name walks the whole package. Naming them follows only the re-export branches that lead to those names,
|
|
1857
|
+
* which is the difference between reading 15 files of `lodash-es` and reading all 600.
|
|
1858
|
+
*/
|
|
1859
|
+
closureOf(specifier, names, fromFile = join(this.root, "package.json")) {
|
|
1860
|
+
const cacheKey = fromFile + "\0" + specifier + "\0" + names.key();
|
|
1861
|
+
if (this.#closures.has(cacheKey)) return this.#closures.get(cacheKey);
|
|
1862
|
+
const closure = this.#walkClosure(specifier, names, fromFile);
|
|
1863
|
+
this.#closures.set(cacheKey, closure);
|
|
1864
|
+
return closure;
|
|
1865
|
+
}
|
|
1866
|
+
#walkClosure(specifier, names, fromFile) {
|
|
1867
|
+
const entry = this.resolveFrom(specifier, fromFile);
|
|
1868
|
+
if (!entry) return;
|
|
1869
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1870
|
+
const crossPackage = /* @__PURE__ */ new Map();
|
|
1871
|
+
const queue = [{
|
|
1872
|
+
file: entry,
|
|
1873
|
+
wanted: names
|
|
1874
|
+
}];
|
|
1875
|
+
while (queue.length > 0) {
|
|
1876
|
+
const { file, wanted } = queue.shift();
|
|
1877
|
+
const key = file + "\0" + wanted.key();
|
|
1878
|
+
if (visited.has(key)) continue;
|
|
1879
|
+
visited.add(key);
|
|
1880
|
+
const record = this.lex(file);
|
|
1881
|
+
for (const [source, wantedNames] of sourcesForNames(record, wanted)) {
|
|
1882
|
+
const target = this.resolveFrom(source, file);
|
|
1883
|
+
if (!target) continue;
|
|
1884
|
+
if (isBareSpecifier(source)) mergeEdge(crossPackage, {
|
|
1885
|
+
specifier: source,
|
|
1886
|
+
kind: "import",
|
|
1887
|
+
names: wantedNames,
|
|
1888
|
+
entry: target,
|
|
1889
|
+
isDynamic: false,
|
|
1890
|
+
isStar: record.starSources.includes(source)
|
|
1891
|
+
});
|
|
1892
|
+
const forward = wantedNames.isAll ? NameSet.all() : NameSet.of([...wantedNames.list(), "default"]);
|
|
1893
|
+
queue.push({
|
|
1894
|
+
file: target,
|
|
1895
|
+
wanted: forward
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
for (const edge of record.edges) {
|
|
1899
|
+
const target = this.resolveFrom(edge.specifier, file);
|
|
1900
|
+
if (!target) continue;
|
|
1901
|
+
if (isBareSpecifier(edge.specifier)) {
|
|
1902
|
+
const names = edge.names ? NameSet.of(edge.names) : NameSet.all();
|
|
1903
|
+
mergeEdge(crossPackage, {
|
|
1904
|
+
specifier: edge.specifier,
|
|
1905
|
+
kind: edge.kind,
|
|
1906
|
+
names,
|
|
1907
|
+
entry: target,
|
|
1908
|
+
isDynamic: edge.isDynamic,
|
|
1909
|
+
isStar: false
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1912
|
+
queue.push({
|
|
1913
|
+
file: target,
|
|
1914
|
+
wanted: NameSet.all()
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
678
1917
|
}
|
|
679
|
-
return
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
1918
|
+
return {
|
|
1919
|
+
entry,
|
|
1920
|
+
crossPackage: crossPackage.values().toArray()
|
|
1921
|
+
};
|
|
1922
|
+
}
|
|
1923
|
+
};
|
|
1924
|
+
/** The `export ... from "x"` branches that can lead to a wanted name → the names to look for in each. */
|
|
1925
|
+
function sourcesForNames(record, wanted) {
|
|
1926
|
+
const sources = /* @__PURE__ */ new Map();
|
|
1927
|
+
for (const [name, reexport] of record.reexportOf) {
|
|
1928
|
+
if (!wanted.has(name) && !isPathReexport(name, reexport)) continue;
|
|
1929
|
+
const asked = reexport.importedName === void 0 ? NameSet.all() : NameSet.of([reexport.importedName]);
|
|
1930
|
+
didWidenEntry(sources, reexport.source, asked);
|
|
683
1931
|
}
|
|
1932
|
+
for (const source of record.starSources) sources.set(source, wanted);
|
|
1933
|
+
return sources;
|
|
684
1934
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
1935
|
+
/**
|
|
1936
|
+
* Whether a re-export names a whole module rather than one of its exports.
|
|
1937
|
+
*
|
|
1938
|
+
* `module.exports = require("./cjs/react.production.js")` is reported by the CommonJS lexer as re-exporting the path itself, so
|
|
1939
|
+
* the names live in the target and this branch is always followed.
|
|
1940
|
+
*/
|
|
1941
|
+
function isPathReexport(name, reexport) {
|
|
1942
|
+
return name === reexport.source && reexport.source.startsWith(".");
|
|
1943
|
+
}
|
|
1944
|
+
/** Merges an edge into the ones already found for the same specifier. */
|
|
1945
|
+
function mergeEdge(edges, found) {
|
|
1946
|
+
const existing = edges.get(found.specifier);
|
|
1947
|
+
if (!existing) {
|
|
1948
|
+
edges.set(found.specifier, {
|
|
1949
|
+
specifier: found.specifier,
|
|
1950
|
+
kind: found.kind,
|
|
1951
|
+
names: found.names.clone(),
|
|
1952
|
+
entries: /* @__PURE__ */ new Set([found.entry]),
|
|
1953
|
+
isLazyOnly: found.isDynamic,
|
|
1954
|
+
isStar: found.isStar
|
|
1955
|
+
});
|
|
1956
|
+
return;
|
|
697
1957
|
}
|
|
1958
|
+
existing.entries.add(found.entry);
|
|
1959
|
+
if (!found.isDynamic) existing.isLazyOnly = false;
|
|
1960
|
+
if (found.isStar) existing.isStar = true;
|
|
1961
|
+
if (found.kind === "require") existing.kind = "require";
|
|
1962
|
+
existing.names.widen(found.names);
|
|
698
1963
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
1964
|
+
/**
|
|
1965
|
+
* Whether a specifier can land in a different output file than the one importing it.
|
|
1966
|
+
*
|
|
1967
|
+
* Only bare specifiers can; `./x` and `#internal` resolve inside their own package and travel with it. Same package is not the
|
|
1968
|
+
* same file though: `react/jsx-runtime` is a subpath with a file of its own, and its development build requires `react` back.
|
|
1969
|
+
*/
|
|
1970
|
+
function isBareSpecifier(specifier) {
|
|
1971
|
+
return !specifier.startsWith(".") && !specifier.startsWith("#") && !specifier.startsWith("/");
|
|
705
1972
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
1973
|
+
|
|
1974
|
+
//#endregion
|
|
1975
|
+
//#region src/plugins/bundle-packages/shared/report-problems.ts
|
|
1976
|
+
/** `a, b` becomes `"a", "b"`. */
|
|
1977
|
+
function quoted(names, separator = ", ") {
|
|
1978
|
+
return names.map((name) => `"${name}"`).join(separator);
|
|
709
1979
|
}
|
|
710
1980
|
/**
|
|
711
|
-
*
|
|
712
|
-
*
|
|
1981
|
+
* Reports a `chunks` grouping that split packages joined by a `require` or an `export *`.
|
|
1982
|
+
*
|
|
1983
|
+
* Grouping is automatic, so only a hand-written entry can cause this. One message per group, since the whole group has to move
|
|
1984
|
+
* together anyway.
|
|
713
1985
|
*/
|
|
714
|
-
function
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
1986
|
+
function reportLayoutProblems(units, groups, report) {
|
|
1987
|
+
for (const problem of validateUnits(units, groups)) {
|
|
1988
|
+
const chunkName = problem.units.find((name) => !problem.members.includes(name)) ?? problem.group;
|
|
1989
|
+
report(`These packages cannot be in separate files, but they were spread over ${quoted(problem.units)}. A "require" between them throws when the bundle runs, and an "export *" between them loses the names it forwards with no error at all.\nPut all of them in one chunk:\n bundlePackagesPlugin({ chunks: { "${chunkName}": [${quoted(problem.members)}] } })`);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
/** Warns about anything in `chunks` that could not be honoured as written. */
|
|
1993
|
+
function reportChunkPlan(plan, warn) {
|
|
1994
|
+
for (const { chunk, pattern, isExclusion } of plan.unmatched) warn(`The chunk "${chunk}" ${isExclusion ? "excludes" : "asks for"} "${pattern}", which no package in this build answers to. Check the spelling, or that the package is installed and something imports it.`);
|
|
1995
|
+
for (const clash of plan.clashes) warn(`The chunk "${clash.name}" is named after a package this build uses, so that package's own file is now the chunk and carries ${clash.otherMembers} other package(s) with it. Every page importing "${clash.name}" downloads all of them. Give the chunk a name no package has.`);
|
|
1996
|
+
for (const { chunk, specifiers } of plan.pulled) warn(`${quoted(specifiers)} would each have had a file of their own, since this project imports them directly, but "${chunk}" holds packages they cannot be separated from and took them too. A page importing only one of them now downloads the whole chunk. Drop the chunk to get separate files back.`);
|
|
1997
|
+
for (const refused of plan.refused) warn(`"${refused.chunk}" excludes "${refused.specifier}", but "${refused.requiredBy}" requires it at runtime and the two cannot be in separate files. It was kept in the chunk.`);
|
|
1998
|
+
if (plan.duplicated.length === 0) return;
|
|
1999
|
+
const names = quoted(plan.duplicated.map((duplicate) => duplicate.specifier));
|
|
2000
|
+
warn(`The chunks ${quoted([...new Set(plan.duplicated.flatMap((duplicate) => duplicate.chunks))], " and ")} both pull in ${names}, so each is built into both files. Every import of them points at one of the two, which leaves the other carrying packages nothing loads. Name an overlapping package in one chunk only.`);
|
|
2001
|
+
}
|
|
2002
|
+
/**
|
|
2003
|
+
* One output file cannot export a name twice, so a page importing it from the losing package silently reads the other's value.
|
|
2004
|
+
*
|
|
2005
|
+
* Only a `chunks` entry can put two directly-imported packages in one file, so naming the chunk is the way out.
|
|
2006
|
+
*/
|
|
2007
|
+
function nameClashMessage(clash) {
|
|
2008
|
+
return `"${clash.keptBy}" and "${clash.lostBy}" both export "${clash.name}" and share the output file "${clash.unit}", so only one of them can publish it. "${clash.keptBy}" keeps it, and every page importing "${clash.name}" from "${clash.lostBy}" silently reads "${clash.keptBy}"'s value instead. Put them in separate chunks.`;
|
|
722
2009
|
}
|
|
723
2010
|
/**
|
|
724
|
-
*
|
|
2011
|
+
* Warns about a package `node_modules` holds more than one copy of.
|
|
725
2012
|
*
|
|
726
|
-
*
|
|
727
|
-
* @param collected - Newly gathered specifiers to screen for novelty.
|
|
728
|
-
* @returns Specifiers in `collected` that are absent from `existing`.
|
|
2013
|
+
* One output file per package name means only one copy ships. Only a deduped install or a version override can fix that.
|
|
729
2014
|
*/
|
|
730
|
-
function
|
|
731
|
-
const
|
|
732
|
-
|
|
2015
|
+
function reportVersionConflicts(root, units, discovered, index, warn) {
|
|
2016
|
+
for (const conflict of findVersionConflicts(units, discovered, index)) {
|
|
2017
|
+
const copies = conflict.entries.map((entry) => ` ${relative(root, entry)}`).join("\n");
|
|
2018
|
+
warn(`"${conflict.specifier}" resolves to more than one copy, and only one of them can be bundled:\n${copies}\nWhichever importer wanted the other copy will get this one instead. Dedupe the install to be sure which.`);
|
|
2019
|
+
}
|
|
733
2020
|
}
|
|
734
2021
|
|
|
735
2022
|
//#endregion
|
|
736
2023
|
//#region src/plugins/bundle-packages/bundle-packages-plugin.ts
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
2024
|
+
const JS_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2025
|
+
".js",
|
|
2026
|
+
".mjs",
|
|
2027
|
+
".cjs",
|
|
2028
|
+
".jsx",
|
|
2029
|
+
".ts",
|
|
2030
|
+
".mts",
|
|
2031
|
+
".cts",
|
|
2032
|
+
".tsx"
|
|
2033
|
+
]);
|
|
740
2034
|
/**
|
|
741
|
-
* Bundles every
|
|
742
|
-
*
|
|
743
|
-
* to what is actually used.
|
|
2035
|
+
* Bundles every `node_modules` package imported anywhere in the project into a file under `packagesDir`, and rewrites the imports
|
|
2036
|
+
* to point at it.
|
|
744
2037
|
*
|
|
745
2038
|
* Notes:
|
|
746
2039
|
*
|
|
747
|
-
* -
|
|
748
|
-
* -
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
* -
|
|
753
|
-
* bundled from it.
|
|
754
|
-
* - "chunks" merges packages into one file and renames their default imports to "default_<package>" so they share a module scope,
|
|
755
|
-
* which is what CommonJS packages need to work in the browser.
|
|
756
|
-
* - A package first discovered while bundling another one is bundled right away, then re-bundled once its specifiers are known.
|
|
2040
|
+
* - Minification and tree-shaking are production-only.
|
|
2041
|
+
* - `<script src>` into `node_modules` is redirected too, and counts as a side-effect import.
|
|
2042
|
+
* - A stylesheet shipped by a package, linked from HTML or `@import`ed from CSS, is copied next to the package output, keeping its
|
|
2043
|
+
* path inside the package.
|
|
2044
|
+
* - Dots in output paths become underscores (`highlight.js` → `highlight_js`) so a package cannot collide with its own bundle.
|
|
2045
|
+
* - Packages that `require` each other are bundled together.
|
|
757
2046
|
*/
|
|
758
2047
|
function bundlePackagesPlugin(options = {}) {
|
|
759
|
-
const
|
|
2048
|
+
const packagesDirectory = options.packagesDir ?? "./packages";
|
|
760
2049
|
const resolvePackage = options.resolvePackage ?? {};
|
|
761
2050
|
const chunks = options.chunks ?? {};
|
|
762
|
-
/**
|
|
2051
|
+
/** Package specifier → specifiers imported from it. */
|
|
763
2052
|
const specifiersByPackage = /* @__PURE__ */ new Map();
|
|
764
|
-
/**
|
|
2053
|
+
/** Resolved `node_modules` path → package specifier. */
|
|
765
2054
|
const pathPackageMap = /* @__PURE__ */ new Map();
|
|
766
|
-
/**
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
".jsx",
|
|
773
|
-
".ts",
|
|
774
|
-
".mts",
|
|
775
|
-
".cts",
|
|
776
|
-
".tsx"
|
|
777
|
-
]);
|
|
2055
|
+
/** Output file → entry it was last built from. */
|
|
2056
|
+
const builtEntries = /* @__PURE__ */ new Map();
|
|
2057
|
+
/** Every package import found so far. */
|
|
2058
|
+
const allReferences = [];
|
|
2059
|
+
/** Warnings already printed. */
|
|
2060
|
+
const reported = /* @__PURE__ */ new Set();
|
|
778
2061
|
const printFormattedError = PrintFormattedError.create({ function: bundlePackagesPlugin });
|
|
2062
|
+
let index;
|
|
779
2063
|
return {
|
|
780
2064
|
name: "bundle-packages",
|
|
2065
|
+
async setup() {
|
|
2066
|
+
index = await VendorIndex.create(this.root, this.production);
|
|
2067
|
+
},
|
|
781
2068
|
resolveSource(source, filePath) {
|
|
782
2069
|
if (!isValidRelativePath(source)) return;
|
|
783
2070
|
const [link] = splitHtmlLink(source);
|
|
784
2071
|
const target = join(dirname(filePath), link);
|
|
785
|
-
if (isNodeModulesPath(target
|
|
2072
|
+
if (isNodeModulesPath(target) && packageDirectory(target) !== packageDirectory(filePath)) return {
|
|
786
2073
|
source,
|
|
787
2074
|
dependencyID: void 0
|
|
788
2075
|
};
|
|
789
2076
|
const resolveResult = this.resolver.resolve(source, filePath);
|
|
790
2077
|
if (!resolveResult) return;
|
|
791
2078
|
if (!isAbsolute(resolveResult.path)) return;
|
|
792
|
-
if (!resolveResult.isPackage) return;
|
|
793
|
-
if (!JS_EXTENSIONS.has(extname(resolveResult.path))) return;
|
|
2079
|
+
if (!resolveResult.isPackage || !JS_EXTENSIONS.has(extname(resolveResult.path))) return;
|
|
794
2080
|
if (!resolveResult.path.startsWith(this.root)) this.log.warn(`Resolved package "${resolveResult.path}" outside of the root directory.`);
|
|
795
2081
|
pathPackageMap.set(resolveResult.path, source);
|
|
796
2082
|
const absFileDirectory = join(this.root, dirname(filePath));
|
|
@@ -800,272 +2086,49 @@ function bundlePackagesPlugin(options = {}) {
|
|
|
800
2086
|
};
|
|
801
2087
|
},
|
|
802
2088
|
async postTransform() {
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
if (!packageName) {
|
|
847
|
-
printFormattedError(`Could not resolve package name for script src "${source}" (in "${inputMetadata.filePath}"). The script tag will not be included in the bundled packages.`);
|
|
848
|
-
continue;
|
|
849
|
-
}
|
|
850
|
-
const packageOutputPath = calcPackageOutPath(packageName, packagesOutputDirection);
|
|
851
|
-
const newSource = relative(dirname(inputMetadata.filePath), packageOutputPath);
|
|
852
|
-
scriptTag.setAttribute("src", newSource);
|
|
853
|
-
const existingSpecifiers = specifiersByPackage.get(packageName) ?? [];
|
|
854
|
-
existingSpecifiers.push({ type: "side-effect" });
|
|
855
|
-
specifiersByPackage.set(packageName, existingSpecifiers);
|
|
856
|
-
}
|
|
857
|
-
const styleLinks = inputMetadata.ast.querySelectorAll("link[rel=\"stylesheet\"], link[rel=\"preload\"][as=\"style\"]");
|
|
858
|
-
for (const styleLink of styleLinks) {
|
|
859
|
-
const source = styleLink.getAttribute("href");
|
|
860
|
-
if (!source) continue;
|
|
861
|
-
const sourceRootRelative = join(dirname(inputMetadata.filePath), source);
|
|
862
|
-
if (!isNodeModulesPath(sourceRootRelative, root)) continue;
|
|
863
|
-
const assetOutputPath = calcPackageAssetOutPath(sourceRootRelative, packagesOutputDirection);
|
|
864
|
-
const assetPath = join(root, sourceRootRelative);
|
|
865
|
-
const asset = this.metadataList.find((metadata) => join(root, metadata.id) === assetPath);
|
|
866
|
-
if (asset && asset.filePath !== assetOutputPath) {
|
|
867
|
-
await this.rebase(asset, join(root, assetOutputPath));
|
|
868
|
-
asset.filePath = assetOutputPath;
|
|
869
|
-
}
|
|
870
|
-
styleLink.setAttribute("href", relative(dirname(inputMetadata.filePath), assetOutputPath));
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
/**
|
|
875
|
-
* Tracks packages that need to be re-bundled. During bundling, we might detect that a package imports another package whose
|
|
876
|
-
* specifiers we have already collected. This means we need to recollect the specifiers from the discovered package and
|
|
877
|
-
* re-bundle the current package.
|
|
878
|
-
*/
|
|
879
|
-
const pendingReBundles = [];
|
|
880
|
-
for (const [chunkName, packages] of Object.entries(chunks)) {
|
|
881
|
-
if (packages.length === 0) continue;
|
|
882
|
-
let contents = "";
|
|
883
|
-
for (const [packageName, specifiers] of specifiersByPackage) {
|
|
884
|
-
if (specifiers.length === 0) continue;
|
|
885
|
-
if (!matchesPackage(packageName, packages)) continue;
|
|
886
|
-
contents += generateStdinString(specifiers, packageName);
|
|
887
|
-
}
|
|
888
|
-
if (!contents) continue;
|
|
889
|
-
const bundledChunk = this.findMetadata({
|
|
890
|
-
type: METADATA_TYPES.Package,
|
|
891
|
-
id: chunkName
|
|
892
|
-
});
|
|
893
|
-
if (bundledChunk && chunkContents.get(chunkName) === contents) continue;
|
|
894
|
-
const packageOutputPath = calcPackageOutPath(chunkName, packagesOutputDirection);
|
|
895
|
-
const packageMetadata = isPackageMetadata(bundledChunk) ? bundledChunk : {
|
|
896
|
-
type: METADATA_TYPES.Package,
|
|
897
|
-
packageName: chunkName,
|
|
898
|
-
code: "",
|
|
899
|
-
filePath: packageOutputPath,
|
|
900
|
-
id: chunkName,
|
|
901
|
-
directDependencies: /* @__PURE__ */ new Set()
|
|
902
|
-
};
|
|
903
|
-
packageMetadata.directDependencies.clear();
|
|
904
|
-
const [bundledCode, bundleError] = await bundlePackage({
|
|
905
|
-
contents,
|
|
906
|
-
resolveDir: this.root,
|
|
907
|
-
outfile: packageOutputPath,
|
|
908
|
-
packageName: chunkName,
|
|
909
|
-
minify: this.production,
|
|
910
|
-
production: this.production,
|
|
911
|
-
treeShaking: this.production,
|
|
912
|
-
onPackageResolve: (importedPackageName) => {
|
|
913
|
-
const packageChunkName = getChunkName(importedPackageName, chunks);
|
|
914
|
-
if (packageChunkName === chunkName) {
|
|
915
|
-
const customPath = getCustomPackagePath(this.root, importedPackageName, this.production, resolvePackage);
|
|
916
|
-
if (customPath) return {
|
|
917
|
-
path: customPath,
|
|
918
|
-
external: false
|
|
919
|
-
};
|
|
920
|
-
return;
|
|
921
|
-
}
|
|
922
|
-
const dependencyOutputPath = calcPackageOutPath(packageChunkName ?? importedPackageName, packagesOutputDirection);
|
|
923
|
-
packageMetadata.directDependencies.add(dependencyOutputPath);
|
|
924
|
-
pendingReBundles.push({
|
|
925
|
-
dependencyName: importedPackageName,
|
|
926
|
-
dependencyOutputPath,
|
|
927
|
-
importerName: chunkName,
|
|
928
|
-
importerOutputPath: packageOutputPath
|
|
929
|
-
});
|
|
930
|
-
if (!specifiersByPackage.has(importedPackageName)) specifiersByPackage.set(importedPackageName, [{ type: "unknown" }]);
|
|
931
|
-
return {
|
|
932
|
-
path: relative(dirname(packageOutputPath), dependencyOutputPath),
|
|
933
|
-
external: true
|
|
934
|
-
};
|
|
935
|
-
}
|
|
936
|
-
});
|
|
937
|
-
if (bundleError) {
|
|
938
|
-
printFormattedError(`Failed to bundle package "${chunkName}"`, bundleError);
|
|
939
|
-
continue;
|
|
940
|
-
}
|
|
941
|
-
packageMetadata.code = bundledCode;
|
|
942
|
-
chunkContents.set(chunkName, contents);
|
|
943
|
-
if (!bundledChunk) this.addMetadata(packageMetadata);
|
|
944
|
-
}
|
|
945
|
-
for (const [packageName, specifiers] of specifiersByPackage) {
|
|
946
|
-
if (specifiers.length === 0) continue;
|
|
947
|
-
if (this.findMetadata({
|
|
948
|
-
type: METADATA_TYPES.Package,
|
|
949
|
-
id: packageName
|
|
950
|
-
})) continue;
|
|
951
|
-
if (getChunkName(packageName, chunks)) continue;
|
|
952
|
-
const packageOutputPath = calcPackageOutPath(packageName, packagesOutputDirection);
|
|
953
|
-
const packageMetadata = {
|
|
954
|
-
type: METADATA_TYPES.Package,
|
|
955
|
-
packageName,
|
|
956
|
-
code: "",
|
|
957
|
-
filePath: packageOutputPath,
|
|
958
|
-
id: packageName,
|
|
959
|
-
directDependencies: /* @__PURE__ */ new Set()
|
|
960
|
-
};
|
|
961
|
-
let isLoadAsIs = !this.production;
|
|
962
|
-
for (const [index, specifier] of Array.from(specifiers).entries()) {
|
|
963
|
-
if (specifier.type !== "unknown") continue;
|
|
964
|
-
isLoadAsIs = true;
|
|
965
|
-
specifiers.splice(index, 1);
|
|
966
|
-
}
|
|
967
|
-
const contents = isLoadAsIs ? void 0 : generateStdinString(specifiers, packageName);
|
|
968
|
-
const [bundledCode, bundleError] = await bundlePackage({
|
|
969
|
-
contents,
|
|
970
|
-
entryPoint: isLoadAsIs ? packageName : void 0,
|
|
971
|
-
resolveDir: this.root,
|
|
972
|
-
outfile: packageOutputPath,
|
|
973
|
-
packageName,
|
|
974
|
-
minify: this.production,
|
|
975
|
-
production: this.production,
|
|
976
|
-
treeShaking: this.production,
|
|
977
|
-
onPackageResolve: (importedPackageName) => {
|
|
978
|
-
const packageOrChunkName = getChunkName(importedPackageName, chunks) ?? importedPackageName;
|
|
979
|
-
const dependencyOutputPath = calcPackageOutPath(packageOrChunkName, packagesOutputDirection);
|
|
980
|
-
packageMetadata.directDependencies.add(dependencyOutputPath);
|
|
981
|
-
pendingReBundles.push({
|
|
982
|
-
dependencyName: importedPackageName,
|
|
983
|
-
dependencyOutputPath,
|
|
984
|
-
importerName: packageName,
|
|
985
|
-
importerOutputPath: packageOutputPath
|
|
986
|
-
});
|
|
987
|
-
if (!specifiersByPackage.has(importedPackageName)) specifiersByPackage.set(importedPackageName, [{ type: "unknown" }]);
|
|
988
|
-
return {
|
|
989
|
-
path: relative(dirname(packageOutputPath), dependencyOutputPath),
|
|
990
|
-
external: true
|
|
991
|
-
};
|
|
992
|
-
}
|
|
993
|
-
});
|
|
994
|
-
if (bundleError) {
|
|
995
|
-
printFormattedError(`Failed to bundle package "${packageName}"`, bundleError);
|
|
996
|
-
continue;
|
|
997
|
-
}
|
|
998
|
-
packageMetadata.code = bundledCode;
|
|
999
|
-
this.addMetadata(packageMetadata);
|
|
1000
|
-
}
|
|
1001
|
-
if (!this.production) return;
|
|
1002
|
-
for (const { dependencyOutputPath, dependencyName, importerName, importerOutputPath } of pendingReBundles) {
|
|
1003
|
-
const packageToBundle = this.metadataList.find((m) => m.filePath === dependencyOutputPath);
|
|
1004
|
-
if (!isPackageMetadata(packageToBundle)) {
|
|
1005
|
-
printFormattedError(`Re-bundle failed: could not find bundled asset for "${dependencyOutputPath}".`, `The package "${dependencyName}" is not bundled.`);
|
|
1006
|
-
continue;
|
|
1007
|
-
}
|
|
1008
|
-
const importerBundle = this.metadataList.find((m) => m.filePath === importerOutputPath);
|
|
1009
|
-
if (!isPackageMetadata(importerBundle)) {
|
|
1010
|
-
printFormattedError(`Re-bundle failed: could not find bundled asset for "${importerOutputPath}".`, `The package "${importerName}" is not bundled.`);
|
|
1011
|
-
continue;
|
|
1012
|
-
}
|
|
1013
|
-
const importerScript = await this.load(importerBundle.filePath, {
|
|
1014
|
-
code: importerBundle.code,
|
|
1015
|
-
type: "js"
|
|
1016
|
-
});
|
|
1017
|
-
if (!isScriptMetadata(importerScript)) {
|
|
1018
|
-
printFormattedError(`Re-bundle failed: could not parse asset "${importerOutputPath}" as a script.`, `The package "${importerName}" may be missing specifiers.`);
|
|
1019
|
-
continue;
|
|
1020
|
-
}
|
|
1021
|
-
const sources = collectSpecifiersFromAst(importerScript.ast);
|
|
1022
|
-
const collectedSpecifiers = [];
|
|
1023
|
-
for (const sourceReference of sources) {
|
|
1024
|
-
const source = sourceReference.source;
|
|
1025
|
-
if (join(dirname(importerScript.filePath), source) === dependencyOutputPath) {
|
|
1026
|
-
const specifiers = sourceReference.specifiers;
|
|
1027
|
-
collectedSpecifiers.push(...specifiers);
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
if (collectedSpecifiers.length === 0) {
|
|
1031
|
-
printFormattedError(`Re-bundle failed: could not find an import of "${dependencyOutputPath}" inside "${importerOutputPath}".`, `\nThe dependency "${dependencyName}" may be missing specifiers.`);
|
|
1032
|
-
continue;
|
|
1033
|
-
}
|
|
1034
|
-
if (collectedSpecifiers.some((specifier) => specifier.type === "unknown")) {
|
|
1035
|
-
this.log.warn(`Cannot tree-shake the package "${dependencyName}" correctly. The package "${importerName}" has dynamic imports with unresolved specifiers.`);
|
|
1036
|
-
continue;
|
|
1037
|
-
}
|
|
1038
|
-
const allSpecifiers = specifiersByPackage.get(dependencyName) ?? [];
|
|
1039
|
-
const newSpecifiers = diffSpecifiers(allSpecifiers, collectedSpecifiers);
|
|
1040
|
-
if (newSpecifiers.length === 0) continue;
|
|
1041
|
-
allSpecifiers.push(...newSpecifiers);
|
|
1042
|
-
specifiersByPackage.set(dependencyName, allSpecifiers);
|
|
1043
|
-
packageToBundle.directDependencies.clear();
|
|
1044
|
-
const contents = generateStdinString(allSpecifiers, dependencyName);
|
|
1045
|
-
const [reBundledCode, reBundleError] = await bundlePackage({
|
|
1046
|
-
contents,
|
|
1047
|
-
resolveDir: this.root,
|
|
1048
|
-
outfile: dependencyOutputPath,
|
|
1049
|
-
packageName: dependencyName,
|
|
1050
|
-
production: this.production,
|
|
1051
|
-
minify: true,
|
|
1052
|
-
treeShaking: true,
|
|
1053
|
-
onPackageResolve: (importedPackageName) => {
|
|
1054
|
-
const packageOrChunkName = getChunkName(importedPackageName, chunks) ?? importedPackageName;
|
|
1055
|
-
const dependencyOutputPath = calcPackageOutPath(packageOrChunkName, packagesOutputDirection);
|
|
1056
|
-
packageToBundle.directDependencies.add(dependencyOutputPath);
|
|
1057
|
-
return {
|
|
1058
|
-
path: relative(dirname(packageToBundle.filePath), dependencyOutputPath),
|
|
1059
|
-
external: true
|
|
1060
|
-
};
|
|
1061
|
-
}
|
|
1062
|
-
});
|
|
1063
|
-
if (reBundleError) {
|
|
1064
|
-
printFormattedError(`Failed to re-bundle package "${dependencyName}"`, reBundleError);
|
|
1065
|
-
continue;
|
|
1066
|
-
}
|
|
1067
|
-
packageToBundle.code = reBundledCode;
|
|
1068
|
-
}
|
|
2089
|
+
if (!index) return;
|
|
2090
|
+
const collectContext = {
|
|
2091
|
+
root: this.root,
|
|
2092
|
+
pathPackageMap,
|
|
2093
|
+
specifiersByPackage,
|
|
2094
|
+
reportUntraced: printFormattedError
|
|
2095
|
+
};
|
|
2096
|
+
const references = await collectReferences.call(this, collectContext, packagesDirectory);
|
|
2097
|
+
allReferences.push(...references);
|
|
2098
|
+
if (specifiersByPackage.size === 0) return;
|
|
2099
|
+
const neededByPackage = /* @__PURE__ */ new Map();
|
|
2100
|
+
const seeds = /* @__PURE__ */ new Map();
|
|
2101
|
+
for (const [specifier, specifiers] of specifiersByPackage) {
|
|
2102
|
+
const needed = neededExports(specifiers);
|
|
2103
|
+
neededByPackage.set(specifier, needed);
|
|
2104
|
+
seeds.set(specifier, needed.names);
|
|
2105
|
+
}
|
|
2106
|
+
const { units, discovered, plan, groups } = planLayout(seeds, chunks, index);
|
|
2107
|
+
const unitOf = unitsBySpecifier(units);
|
|
2108
|
+
const warn = (message) => {
|
|
2109
|
+
if (reported.has(message)) return;
|
|
2110
|
+
reported.add(message);
|
|
2111
|
+
this.log.warn(message);
|
|
2112
|
+
};
|
|
2113
|
+
const fail = (message) => {
|
|
2114
|
+
if (reported.has(message)) return;
|
|
2115
|
+
reported.add(message);
|
|
2116
|
+
printFormattedError(message);
|
|
2117
|
+
};
|
|
2118
|
+
reportLayoutProblems(units, groups, fail);
|
|
2119
|
+
reportChunkPlan(plan, warn);
|
|
2120
|
+
reportVersionConflicts(this.root, units, discovered, index, warn);
|
|
2121
|
+
pointReferencesAtUnits(allReferences, units, unitOf, packagesDirectory);
|
|
2122
|
+
const buildContext = {
|
|
2123
|
+
packagesDirectory,
|
|
2124
|
+
unitOf,
|
|
2125
|
+
neededByPackage,
|
|
2126
|
+
onNameClash: (clash) => warn(nameClashMessage(clash)),
|
|
2127
|
+
resolvePackage,
|
|
2128
|
+
index,
|
|
2129
|
+
builtEntries
|
|
2130
|
+
};
|
|
2131
|
+
for (const unit of units.values()) await buildUnit.call(this, unit, buildContext, printFormattedError);
|
|
1069
2132
|
}
|
|
1070
2133
|
};
|
|
1071
2134
|
}
|
|
@@ -4169,25 +5232,28 @@ var StyleBundler = class {
|
|
|
4169
5232
|
* Bundles `metadata.ast` in place.
|
|
4170
5233
|
*
|
|
4171
5234
|
* @param bundleAbsolutePath Where the bundle ends up, which imported `url()` references are rebased onto.
|
|
5235
|
+
* @param isExternal Marks a resolved import path as external: the `@import` is kept instead of inlined, at any depth.
|
|
4172
5236
|
*/
|
|
4173
|
-
async bundle(metadata, bundleAbsolutePath) {
|
|
4174
|
-
return await new BundleRun(this.#app, this.#loaded, bundleAbsolutePath).bundle(metadata);
|
|
5237
|
+
async bundle(metadata, bundleAbsolutePath, isExternal) {
|
|
5238
|
+
return await new BundleRun(this.#app, this.#loaded, bundleAbsolutePath, isExternal).bundle(metadata);
|
|
4175
5239
|
}
|
|
4176
5240
|
};
|
|
4177
5241
|
var BundleRun = class {
|
|
4178
5242
|
#app;
|
|
4179
5243
|
#loaded;
|
|
4180
5244
|
#bundleAbsolutePath;
|
|
5245
|
+
#isExternal;
|
|
4181
5246
|
#entries = [];
|
|
4182
5247
|
#occurrences = [];
|
|
4183
5248
|
#diagnostics = [];
|
|
4184
5249
|
#reportedNodes = /* @__PURE__ */ new Set();
|
|
4185
5250
|
#charsetParams;
|
|
4186
5251
|
#hasKeptImports = false;
|
|
4187
|
-
constructor(app, loaded, bundleAbsolutePath) {
|
|
5252
|
+
constructor(app, loaded, bundleAbsolutePath, isExternal) {
|
|
4188
5253
|
this.#app = app;
|
|
4189
5254
|
this.#loaded = loaded;
|
|
4190
5255
|
this.#bundleAbsolutePath = bundleAbsolutePath;
|
|
5256
|
+
this.#isExternal = isExternal;
|
|
4191
5257
|
}
|
|
4192
5258
|
async bundle(metadata) {
|
|
4193
5259
|
const absolutePath = join(this.#app.root, metadata.filePath);
|
|
@@ -4308,6 +5374,7 @@ var BundleRun = class {
|
|
|
4308
5374
|
this.#error(node, filePath, `Failed to resolve "${prelude.url}"`);
|
|
4309
5375
|
return keep;
|
|
4310
5376
|
}
|
|
5377
|
+
if (this.#isExternal?.(absolutePath)) return keep;
|
|
4311
5378
|
if (ancestors.has(absolutePath)) return { action: "drop" };
|
|
4312
5379
|
const metadata = await this.#load(absolutePath);
|
|
4313
5380
|
if (!metadata) {
|
|
@@ -4472,95 +5539,183 @@ function keptImportPrelude(text, prelude, chain) {
|
|
|
4472
5539
|
//#endregion
|
|
4473
5540
|
//#region src/plugins/html-bundle-style/html-bundle-style-plugin.ts
|
|
4474
5541
|
/**
|
|
4475
|
-
* Inlines the "@import" tree of a stylesheet, so the tag ends up self-contained. Opt-in per tag through the "bundle" attribute
|
|
4476
|
-
*
|
|
5542
|
+
* Inlines the "@import" tree of a stylesheet, so the tag ends up self-contained. Opt-in per tag through the "bundle" attribute on
|
|
5543
|
+
* a "<link rel=stylesheet>", a "<link rel=preload as=style>" or a "<style>" tag.
|
|
5544
|
+
*
|
|
5545
|
+
* Attributes (names configurable via HtmlBundleStyleOptions):
|
|
5546
|
+
*
|
|
5547
|
+
* - "bundle": bundle this tag.
|
|
5548
|
+
* - "bundle-out": output path for a link tag, resolved from the output directory, not from the HTML file.
|
|
5549
|
+
* - "bundle-externals" / "bundle-externals-exclude": semicolon-separated globs, override the plugin defaults for this tag.
|
|
4477
5550
|
*
|
|
4478
|
-
*
|
|
4479
|
-
*
|
|
5551
|
+
* A "<link>" is bundled to "{filename}.bundle.css" next to the original (or to "bundle-out") and "href" is rewritten to it; the
|
|
5552
|
+
* source file is never touched. A "<style>" tag gets the resolved CSS as its content, in place.
|
|
4480
5553
|
*
|
|
4481
5554
|
* Notes:
|
|
4482
5555
|
*
|
|
4483
|
-
* -
|
|
4484
|
-
* -
|
|
4485
|
-
* everything it needs.
|
|
4486
|
-
* - A linked stylesheet is bundled once even when several pages link to it; they share the same file.
|
|
5556
|
+
* - Bundling only runs in production; in dev the attributes are just stripped. They never reach the output either way.
|
|
5557
|
+
* - If the target output path is already bundled, the work is skipped and "href" is simply pointed at it.
|
|
4487
5558
|
* - Imports resolve the way every other source does: relative paths, path aliases and packages, through the build's resolver.
|
|
4488
5559
|
* - Imported files are pulled from the build (so transforms other plugins made are included) and only read from disk when they are
|
|
4489
|
-
* not part of it. They stay in the output as separate files
|
|
4490
|
-
*
|
|
5560
|
+
* not part of it. They stay in the output as separate files. Their "url()" references are rebased onto the bundle's location.
|
|
5561
|
+
* - An import matching the externals globs is kept as an "@import" (hoisted, conditions folded in, path rebased) and the file it
|
|
5562
|
+
* points at stays in the output.
|
|
4491
5563
|
* - Conditions ("layer", "supports()", media) wrap the imported rules, a file imported twice under the same conditions is kept
|
|
4492
5564
|
* where it was imported last, as in a browser, and imports from other origins are hoisted to the top.
|
|
5565
|
+
* - Combining with html-inline-style: register this plugin first, so a tag carrying both attributes bundles first and the inlined
|
|
5566
|
+
* content is the bundle.
|
|
4493
5567
|
*/
|
|
4494
5568
|
function htmlBundleStylePlugin(options = {}) {
|
|
5569
|
+
const defaultExternals = options.externals ?? [];
|
|
5570
|
+
const defaultExternalsExclude = options.externalsExclude ?? [];
|
|
4495
5571
|
const bundleAttribute = options.bundleAttribute ?? "bundle";
|
|
4496
|
-
const
|
|
4497
|
-
const
|
|
5572
|
+
const bundleOutAttribute = options.bundleOutAttribute ?? "bundle-out";
|
|
5573
|
+
const externalsAttribute = options.externalsAttribute ?? "bundle-externals";
|
|
5574
|
+
const externalsExcludeAttribute = options.externalsExcludeAttribute ?? "bundle-externals-exclude";
|
|
5575
|
+
const query = `link[rel="stylesheet"][${bundleAttribute}], link[rel="preload"][as="style"][${bundleAttribute}], style[${bundleAttribute}]`;
|
|
5576
|
+
/** Bundled `<style bundle>` tags by content, rebase target and externals, so a tag repeated through a layout is built once. */
|
|
5577
|
+
const bundledStyleTags = /* @__PURE__ */ new Map();
|
|
5578
|
+
const printFmtError = PrintFormattedError.create({ function: htmlBundleStylePlugin });
|
|
5579
|
+
const report = (diagnostics) => {
|
|
5580
|
+
for (const { level, message, node, filePath } of diagnostics) printFmtError(message, {
|
|
5581
|
+
node,
|
|
5582
|
+
filePath,
|
|
5583
|
+
level
|
|
5584
|
+
});
|
|
5585
|
+
};
|
|
4498
5586
|
let bundler;
|
|
4499
5587
|
return {
|
|
4500
5588
|
name: "html-bundle-style",
|
|
4501
|
-
|
|
5589
|
+
preTransform() {
|
|
5590
|
+
bundledStyleTags.clear();
|
|
4502
5591
|
bundler = new StyleBundler(this);
|
|
4503
5592
|
},
|
|
4504
5593
|
async transform(metadata) {
|
|
4505
|
-
if (!this.production) return;
|
|
4506
5594
|
if (!isHtmlMetadata(metadata)) return;
|
|
5595
|
+
const filePath = metadata.filePath;
|
|
4507
5596
|
const elements = metadata.ast.querySelectorAll(query);
|
|
4508
5597
|
for (const node of elements) {
|
|
5598
|
+
const bundleOutValue = node.getAttribute(bundleOutAttribute);
|
|
5599
|
+
const bundleOut = bundleOutValue ? normalize(bundleOutValue.replace(/^\/+/, "")) : null;
|
|
5600
|
+
const externalsAttributeValue = node.getAttribute(externalsAttribute);
|
|
5601
|
+
const externalsExcludeAttributeValue = node.getAttribute(externalsExcludeAttribute);
|
|
4509
5602
|
node.removeAttribute(bundleAttribute);
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
5603
|
+
node.removeAttribute(bundleOutAttribute);
|
|
5604
|
+
node.removeAttribute(externalsAttribute);
|
|
5605
|
+
node.removeAttribute(externalsExcludeAttribute);
|
|
5606
|
+
if (!this.production) continue;
|
|
5607
|
+
const externals = parsePatterns(externalsAttributeValue) ?? defaultExternals;
|
|
5608
|
+
const externalsIgnore = parsePatterns(externalsExcludeAttributeValue) ?? defaultExternalsExclude;
|
|
5609
|
+
const isExternal = externals.length === 0 ? void 0 : (absolutePath) => isPathMatch(absolutePath, {
|
|
5610
|
+
include: externals,
|
|
5611
|
+
ignore: externalsIgnore,
|
|
5612
|
+
root: this.root
|
|
5613
|
+
});
|
|
5614
|
+
if (node.tagName.toLowerCase() === "link") {
|
|
5615
|
+
const href = node.getAttribute("href");
|
|
5616
|
+
if (!href) {
|
|
5617
|
+
printFmtError("The link tag does not have an \"href\" attribute", {
|
|
5618
|
+
node,
|
|
5619
|
+
filePath
|
|
5620
|
+
});
|
|
5621
|
+
continue;
|
|
4520
5622
|
}
|
|
4521
|
-
const
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
5623
|
+
const [link, suffix] = splitHtmlLink(href);
|
|
5624
|
+
const sourcePath = join(dirname(filePath), link);
|
|
5625
|
+
const outfile = bundleOut ?? replaceExtension(sourcePath, ".bundle.css");
|
|
5626
|
+
if (this.findMetadata({ filePath: outfile })) {
|
|
5627
|
+
this.log.debug(`The stylesheet "${sourcePath}" is already bundled in "${outfile}", skipping...`);
|
|
5628
|
+
node.setAttribute("href", relative(dirname(filePath), outfile) + suffix);
|
|
5629
|
+
continue;
|
|
5630
|
+
}
|
|
5631
|
+
const sourceMetadata = this.findMetadata({ filePath: sourcePath });
|
|
5632
|
+
if (!isStyleMetadata(sourceMetadata)) {
|
|
5633
|
+
printFmtError(`Could not find style metadata for "${href}".`, {
|
|
5634
|
+
node,
|
|
5635
|
+
filePath
|
|
5636
|
+
});
|
|
5637
|
+
continue;
|
|
5638
|
+
}
|
|
5639
|
+
const bundleAbsolutePath = join(this.root, outfile);
|
|
5640
|
+
const clone = this.clone(sourceMetadata);
|
|
5641
|
+
await this.rebase(clone, bundleAbsolutePath);
|
|
5642
|
+
report(await bundler.bundle(clone, bundleAbsolutePath, isExternal));
|
|
5643
|
+
const bundledMetadata = await this.load(outfile, {
|
|
5644
|
+
code: await this.stringify(clone),
|
|
5645
|
+
type: "css"
|
|
5646
|
+
});
|
|
5647
|
+
if (!isStyleMetadata(bundledMetadata)) {
|
|
5648
|
+
printFmtError("Failed to load the bundled stylesheet", {
|
|
5649
|
+
node,
|
|
5650
|
+
filePath
|
|
5651
|
+
});
|
|
5652
|
+
continue;
|
|
5653
|
+
}
|
|
5654
|
+
this.addMetadata(bundledMetadata);
|
|
5655
|
+
node.setAttribute("href", relative(dirname(filePath), outfile) + suffix);
|
|
5656
|
+
continue;
|
|
5657
|
+
}
|
|
5658
|
+
const metadataID = node.getAttribute(CUSTOM_ATTRIBUTES.MetadataID);
|
|
5659
|
+
const styleMetadata = metadataID ? metadata.stylesMetadataList.get(metadataID) : void 0;
|
|
5660
|
+
if (!metadataID || !styleMetadata) {
|
|
5661
|
+
printFmtError("Could not find style metadata.", {
|
|
4528
5662
|
node,
|
|
4529
|
-
filePath
|
|
5663
|
+
filePath
|
|
4530
5664
|
});
|
|
4531
5665
|
continue;
|
|
4532
5666
|
}
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
5667
|
+
const cacheKey = [
|
|
5668
|
+
metadataID,
|
|
5669
|
+
dirname(filePath),
|
|
5670
|
+
externals.join(";"),
|
|
5671
|
+
externalsIgnore.join(";")
|
|
5672
|
+
].join("\0");
|
|
5673
|
+
const cached = bundledStyleTags.get(cacheKey);
|
|
5674
|
+
if (cached) {
|
|
5675
|
+
metadata.stylesMetadataList.set(metadataID, this.clone(cached));
|
|
5676
|
+
continue;
|
|
5677
|
+
}
|
|
5678
|
+
report(await bundler.bundle(styleMetadata, join(this.root, filePath), isExternal));
|
|
5679
|
+
bundledStyleTags.set(cacheKey, styleMetadata);
|
|
4545
5680
|
}
|
|
4546
5681
|
},
|
|
4547
5682
|
lspHtmlData() {
|
|
4548
|
-
const description = `Adds the \`${bundleAttribute}\` attribute, which
|
|
4549
|
-
const
|
|
5683
|
+
const description = `Adds the \`${bundleAttribute}\` attribute and its options, which inline the stylesheet's \`@import\` tree at build time.`;
|
|
5684
|
+
const bundle = {
|
|
4550
5685
|
name: bundleAttribute,
|
|
4551
5686
|
description: "Enable style bundling.",
|
|
4552
5687
|
valueSet: "v"
|
|
4553
|
-
}
|
|
5688
|
+
};
|
|
5689
|
+
const externals = {
|
|
5690
|
+
name: externalsAttribute,
|
|
5691
|
+
description: `Glob patterns for imports to keep as external \`@import\`s instead of inlining them, matched against the resolved import path. (semicolon-separated)\n\nDefaults to "${defaultExternals.join(";")}".`
|
|
5692
|
+
};
|
|
5693
|
+
const externalsExclude = {
|
|
5694
|
+
name: externalsExcludeAttribute,
|
|
5695
|
+
description: `Glob patterns that exempt imports from being treated as external, even if they match \`${externalsAttribute}\`. (semicolon-separated)\n\nDefaults to "${defaultExternalsExclude.join(";")}".`
|
|
5696
|
+
};
|
|
4554
5697
|
return {
|
|
4555
5698
|
version: 1.1,
|
|
4556
5699
|
tags: [{
|
|
4557
5700
|
name: "link",
|
|
4558
5701
|
description,
|
|
4559
|
-
attributes
|
|
5702
|
+
attributes: [
|
|
5703
|
+
bundle,
|
|
5704
|
+
{
|
|
5705
|
+
name: bundleOutAttribute,
|
|
5706
|
+
description: "Specify the output path for the bundled stylesheet relative to the output directory, regardless of the HTML file's location.\nDefaults to `{filename}.bundle.css` next to the original file."
|
|
5707
|
+
},
|
|
5708
|
+
externals,
|
|
5709
|
+
externalsExclude
|
|
5710
|
+
]
|
|
4560
5711
|
}, {
|
|
4561
5712
|
name: "style",
|
|
4562
5713
|
description,
|
|
4563
|
-
attributes
|
|
5714
|
+
attributes: [
|
|
5715
|
+
bundle,
|
|
5716
|
+
externals,
|
|
5717
|
+
externalsExclude
|
|
5718
|
+
]
|
|
4564
5719
|
}]
|
|
4565
5720
|
};
|
|
4566
5721
|
}
|
|
@@ -4913,6 +6068,8 @@ async function loadScriptMetadata(source, filePath) {
|
|
|
4913
6068
|
* - A local file has to be part of the build already, otherwise its metadata is not found and nothing is inlined.
|
|
4914
6069
|
* - The inlined copy is rebased onto the HTML file, so relative "url()" and "@import" paths keep resolving, and the original file
|
|
4915
6070
|
* stays in the output unless something removes it.
|
|
6071
|
+
* - Combining with html-bundle-style: register that plugin first, so a link carrying both attributes is bundled before it is
|
|
6072
|
+
* inlined and the inlined content is the bundle.
|
|
4916
6073
|
*/
|
|
4917
6074
|
function htmlInlineStylePlugin(options = {}) {
|
|
4918
6075
|
const inlineAttribute = options.inlineAttribute ?? "inline";
|
|
@@ -6307,7 +7464,7 @@ function isSelfReference(sourceAbsolute, filePathAbsolute) {
|
|
|
6307
7464
|
function calculatePagePath(originalPath, pagesDirectory) {
|
|
6308
7465
|
if (extname(originalPath) === ".md") originalPath = replaceExtension(originalPath, ".html");
|
|
6309
7466
|
if (!isSubpath(pagesDirectory, dirname(originalPath))) return normalize(originalPath);
|
|
6310
|
-
const fileName = parse$
|
|
7467
|
+
const fileName = parse$5(originalPath).name;
|
|
6311
7468
|
const extension = extname(originalPath);
|
|
6312
7469
|
let newPath = cutLeadingSubpath(originalPath, pagesDirectory);
|
|
6313
7470
|
if (extension && fileName !== "index") newPath = join(dirname(newPath), fileName, "index" + extension);
|
|
@@ -7327,7 +8484,7 @@ function coreMarkdownPlugin(options = {}) {
|
|
|
7327
8484
|
const frontmatter = {};
|
|
7328
8485
|
const firstNode = root.children[0];
|
|
7329
8486
|
if (firstNode?.type === "yaml") {
|
|
7330
|
-
const parsed = parse$
|
|
8487
|
+
const parsed = parse$4(firstNode.value) ?? {};
|
|
7331
8488
|
Object.assign(frontmatter, parsed);
|
|
7332
8489
|
}
|
|
7333
8490
|
return [{
|