@jsenv/core 40.0.10 → 40.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,19 @@
1
1
  import { parseHtml, injectHtmlNodeAsEarlyAsPossible, createHtmlNode, stringifyHtmlAst, applyBabelPlugins, generateUrlForInlineContent, parseJsWithAcorn, visitHtmlNodes, analyzeScriptNode, getHtmlNodeText, getHtmlNodeAttribute, getHtmlNodePosition, getUrlForContentInsideHtml, setHtmlNodeAttributes, setHtmlNodeText, parseCssUrls, getHtmlNodeAttributePosition, parseSrcSet, removeHtmlNodeText, parseJsUrls, getUrlForContentInsideJs, analyzeLinkNode, injectJsenvScript, findHtmlNode, removeHtmlNode, insertHtmlNodeAfter } from "@jsenv/ast";
2
- import { lookupPackageDirectory$1 as lookupPackageDirectory, registerDirectoryLifecycle$1 as registerDirectoryLifecycle, urlToRelativeUrl$1 as urlToRelativeUrl, createDetailedMessage$1 as createDetailedMessage, stringifyUrlSite$1 as stringifyUrlSite, generateContentFrame$1 as generateContentFrame, validateResponseIntegrity$1 as validateResponseIntegrity, urlIsInsideOf$1 as urlIsInsideOf, ensureWindowsDriveLetter$1 as ensureWindowsDriveLetter, setUrlFilename$1 as setUrlFilename, moveUrl$1 as moveUrl, getCallerPosition$1 as getCallerPosition, urlToBasename$1 as urlToBasename, urlToExtension$1 as urlToExtension, asSpecifierWithoutSearch$1 as asSpecifierWithoutSearch, asUrlWithoutSearch$1 as asUrlWithoutSearch, injectQueryParamsIntoSpecifier$1 as injectQueryParamsIntoSpecifier, bufferToEtag$1 as bufferToEtag, isFileSystemPath$1 as isFileSystemPath, urlToPathname$1 as urlToPathname, setUrlBasename$1 as setUrlBasename, urlToFileSystemPath$1 as urlToFileSystemPath, writeFileSync$1 as writeFileSync, createLogger$1 as createLogger, URL_META$1 as URL_META, applyNodeEsmResolution$1 as applyNodeEsmResolution, normalizeUrl$1 as normalizeUrl, ANSI$1 as ANSI, CONTENT_TYPE$1 as CONTENT_TYPE, distributePercentages, humanizeFileSize, urlToFilename$1 as urlToFilename, DATA_URL$1 as DATA_URL, normalizeImportMap$1 as normalizeImportMap, composeTwoImportMaps$1 as composeTwoImportMaps, resolveImport$1 as resolveImport, JS_QUOTES$1 as JS_QUOTES, readCustomConditionsFromProcessArgs$1 as readCustomConditionsFromProcessArgs, defaultLookupPackageScope$1 as defaultLookupPackageScope, defaultReadPackageJson$1 as defaultReadPackageJson, readEntryStatSync$1 as readEntryStatSync, ensurePathnameTrailingSlash$1 as ensurePathnameTrailingSlash, comparePathnames$1 as comparePathnames, applyFileSystemMagicResolution$1 as applyFileSystemMagicResolution, getExtensionsToTry$1 as getExtensionsToTry, setUrlExtension$1 as setUrlExtension, jsenvPluginTranspilation$1 as jsenvPluginTranspilation, UNICODE, escapeRegexpSpecialChars, injectQueryParamIntoSpecifierWithoutEncoding, renderUrlOrRelativeUrlFilename, assertAndNormalizeDirectoryUrl$1 as assertAndNormalizeDirectoryUrl, Abort, raceProcessTeardownEvents, jsenvPluginBundling, jsenvPluginMinification, ensureEmptyDirectory, jsenvPluginJsModuleFallback, clearDirectorySync, createTaskLog$1 as createTaskLog } from "../jsenv_core_packages.js";
2
+ import { startMonitoringCpuUsage, startMonitoringMemoryUsage } from "@jsenv/os-metrics";
3
+ import { memoryUsage } from "node:process";
3
4
  import { readFileSync, existsSync, readdirSync, lstatSync, realpathSync } from "node:fs";
4
- import { RUNTIME_COMPAT } from "@jsenv/runtime-compat";
5
+ import { lookupPackageDirectory$1 as lookupPackageDirectory, registerDirectoryLifecycle$1 as registerDirectoryLifecycle, urlToRelativeUrl$1 as urlToRelativeUrl, createDetailedMessage$1 as createDetailedMessage, stringifyUrlSite$1 as stringifyUrlSite, generateContentFrame$1 as generateContentFrame, validateResponseIntegrity$1 as validateResponseIntegrity, urlIsInsideOf$1 as urlIsInsideOf, ensureWindowsDriveLetter$1 as ensureWindowsDriveLetter, setUrlFilename$1 as setUrlFilename, moveUrl$1 as moveUrl, getCallerPosition$1 as getCallerPosition, urlToBasename$1 as urlToBasename, urlToExtension$1 as urlToExtension, asSpecifierWithoutSearch$1 as asSpecifierWithoutSearch, asUrlWithoutSearch$1 as asUrlWithoutSearch, injectQueryParamsIntoSpecifier$1 as injectQueryParamsIntoSpecifier, bufferToEtag$1 as bufferToEtag, isFileSystemPath$1 as isFileSystemPath, urlToPathname$1 as urlToPathname, setUrlBasename$1 as setUrlBasename, urlToFileSystemPath$1 as urlToFileSystemPath, writeFileSync$1 as writeFileSync, createLogger$1 as createLogger, URL_META$1 as URL_META, applyNodeEsmResolution$1 as applyNodeEsmResolution, RUNTIME_COMPAT$1 as RUNTIME_COMPAT, normalizeUrl$1 as normalizeUrl, ANSI$1 as ANSI, CONTENT_TYPE$1 as CONTENT_TYPE, urlToFilename$1 as urlToFilename, DATA_URL$1 as DATA_URL, normalizeImportMap$1 as normalizeImportMap, composeTwoImportMaps$1 as composeTwoImportMaps, resolveImport$1 as resolveImport, JS_QUOTES$1 as JS_QUOTES, defaultLookupPackageScope$1 as defaultLookupPackageScope, defaultReadPackageJson$1 as defaultReadPackageJson, readCustomConditionsFromProcessArgs$1 as readCustomConditionsFromProcessArgs, readEntryStatSync$1 as readEntryStatSync, ensurePathnameTrailingSlash$1 as ensurePathnameTrailingSlash, compareFileUrls$1 as compareFileUrls, applyFileSystemMagicResolution$1 as applyFileSystemMagicResolution, getExtensionsToTry$1 as getExtensionsToTry, setUrlExtension$1 as setUrlExtension, jsenvPluginTranspilation$1 as jsenvPluginTranspilation, renderTable, humanizeFileSize, humanizeDuration, renderDetails, renderBigSection, distributePercentages, humanizeMemory, comparePathnames, UNICODE, escapeRegexpSpecialChars, injectQueryParamIntoSpecifierWithoutEncoding, renderUrlOrRelativeUrlFilename, assertAndNormalizeDirectoryUrl$1 as assertAndNormalizeDirectoryUrl, Abort, raceProcessTeardownEvents, inferRuntimeCompatFromClosestPackage, browserDefaultRuntimeCompat, nodeDefaultRuntimeCompat, clearDirectorySync, createTaskLog$1 as createTaskLog, jsenvPluginBundling, jsenvPluginMinification, ensureEmptyDirectory, jsenvPluginJsModuleFallback, createDynamicLog } from "../jsenv_core_packages.js";
5
6
  import { pathToFileURL } from "node:url";
6
7
  import { generateSourcemapFileUrl, createMagicSource, composeTwoSourcemaps, generateSourcemapDataUrl, SOURCEMAP } from "@jsenv/sourcemap";
7
8
  import { performance } from "node:perf_hooks";
8
9
  import { jsenvPluginSupervisor } from "@jsenv/plugin-supervisor";
9
10
  import { WebSocketResponse, pickContentType } from "@jsenv/server";
10
11
  import { createHash } from "node:crypto";
11
- import "string-width";
12
- import "node:process";
12
+ import "strip-ansi";
13
+ import "../jsenv_core_node_modules.js";
13
14
  import "node:os";
14
15
  import "node:tty";
16
+ import "node:util";
15
17
  import "node:path";
16
18
  import "node:module";
17
19
  import "@jsenv/js-module-fallback";
@@ -481,7 +483,7 @@ const assertFetchedContentCompliance = ({ urlInfo, content }) => {
481
483
  }
482
484
  const { expectedType } = urlInfo.firstReference;
483
485
  if (expectedType && urlInfo.type !== expectedType) {
484
- if (urlInfo.type === "asset" && urlInfo.context.build) ; else {
486
+ if (urlInfo.type === "entry_build" && urlInfo.context.build) ; else {
485
487
  throw new Error(
486
488
  `type must be "${expectedType}", got "${urlInfo.type}" on ${urlInfo.url}`,
487
489
  );
@@ -2624,7 +2626,7 @@ const shouldHandleSourcemap = (urlInfo) => {
2624
2626
  };
2625
2627
 
2626
2628
  const inlineContentClientFileUrl = new URL(
2627
- "../client/inline_content/inline_content.js",
2629
+ "./client/inline_content.js",
2628
2630
  import.meta.url,
2629
2631
  ).href;
2630
2632
 
@@ -2677,7 +2679,7 @@ const createKitchen = ({
2677
2679
  sourcemaps,
2678
2680
  outDirectoryUrl,
2679
2681
  },
2680
- resolve: (specifier, importer) => {
2682
+ resolve: (specifier, importer = rootDirectoryUrl) => {
2681
2683
  const { url, packageDirectoryUrl, packageJson } = applyNodeEsmResolution({
2682
2684
  conditions: packageConditions,
2683
2685
  parentUrl: importer,
@@ -3335,208 +3337,27 @@ const inferUrlInfoType = (urlInfo) => {
3335
3337
  return expectedType || "other";
3336
3338
  };
3337
3339
 
3338
- const createUrlGraphSummary = (
3339
- urlGraph,
3340
- { title = "graph summary" } = {},
3340
+ const jsenvPluginDirectoryReferenceEffect = (
3341
+ directoryReferenceEffect = "error",
3342
+ { rootDirectoryUrl },
3341
3343
  ) => {
3342
- const graphReport = createUrlGraphReport(urlGraph);
3343
- return `--- ${title} ---
3344
- ${createRepartitionMessage(graphReport)}
3345
- --------------------`;
3346
- };
3347
-
3348
- const createUrlGraphReport = (urlGraph) => {
3349
- const countGroups = {
3350
- sourcemaps: 0,
3351
- html: 0,
3352
- css: 0,
3353
- js: 0,
3354
- json: 0,
3355
- other: 0,
3356
- total: 0,
3357
- };
3358
- const sizeGroups = {
3359
- sourcemaps: 0,
3360
- html: 0,
3361
- css: 0,
3362
- js: 0,
3363
- json: 0,
3364
- other: 0,
3365
- total: 0,
3366
- };
3367
-
3368
- GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
3369
- urlGraph.rootUrlInfo,
3370
- (urlInfo) => {
3371
- // ignore:
3372
- // - ignored files: we don't know their content
3373
- // - inline files and data files: they are already taken into account in the file where they appear
3374
- if (urlInfo.url.startsWith("ignore:")) {
3375
- return;
3376
- }
3377
- if (urlInfo.isInline) {
3378
- return;
3379
- }
3380
- if (urlInfo.url.startsWith("data:")) {
3381
- return;
3382
- }
3383
-
3384
- // file loaded via import assertion are already inside the graph
3385
- // their js module equivalent are ignored to avoid counting it twice
3386
- // in the build graph the file targeted by import assertion will likely be gone
3387
- // and only the js module remain (likely bundled)
3388
- if (
3389
- urlInfo.searchParams.has("as_json_module") ||
3390
- urlInfo.searchParams.has("as_css_module") ||
3391
- urlInfo.searchParams.has("as_text_module")
3392
- ) {
3393
- return;
3394
- }
3395
-
3396
- const urlContentSize = Buffer.byteLength(urlInfo.content);
3397
- const category = determineCategory(urlInfo);
3398
- if (category === "sourcemap") {
3399
- countGroups.sourcemaps++;
3400
- sizeGroups.sourcemaps += urlContentSize;
3401
- return;
3402
- }
3403
- countGroups.total++;
3404
- sizeGroups.total += urlContentSize;
3405
- if (category === "html") {
3406
- countGroups.html++;
3407
- sizeGroups.html += urlContentSize;
3408
- return;
3409
- }
3410
- if (category === "css") {
3411
- countGroups.css++;
3412
- sizeGroups.css += urlContentSize;
3413
- return;
3414
- }
3415
- if (category === "js") {
3416
- countGroups.js++;
3417
- sizeGroups.js += urlContentSize;
3418
- return;
3419
- }
3420
- if (category === "json") {
3421
- countGroups.json++;
3422
- sizeGroups.json += urlContentSize;
3423
- return;
3424
- }
3425
- countGroups.other++;
3426
- sizeGroups.other += urlContentSize;
3427
- return;
3428
- },
3429
- );
3430
-
3431
- const sizesToDistribute = {};
3432
- Object.keys(sizeGroups).forEach((groupName) => {
3433
- if (groupName !== "sourcemaps" && groupName !== "total") {
3434
- sizesToDistribute[groupName] = sizeGroups[groupName];
3435
- }
3436
- });
3437
- const percentageGroups = distributePercentages(sizesToDistribute);
3438
-
3439
- return {
3440
- // sourcemaps are special, there size are ignored
3441
- // so there is no "percentage" associated
3442
- sourcemaps: {
3443
- count: countGroups.sourcemaps,
3444
- size: sizeGroups.sourcemaps,
3445
- percentage: undefined,
3446
- },
3447
-
3448
- html: {
3449
- count: countGroups.html,
3450
- size: sizeGroups.html,
3451
- percentage: percentageGroups.html,
3452
- },
3453
- css: {
3454
- count: countGroups.css,
3455
- size: sizeGroups.css,
3456
- percentage: percentageGroups.css,
3457
- },
3458
- js: {
3459
- count: countGroups.js,
3460
- size: sizeGroups.js,
3461
- percentage: percentageGroups.js,
3462
- },
3463
- json: {
3464
- count: countGroups.json,
3465
- size: sizeGroups.json,
3466
- percentage: percentageGroups.json,
3467
- },
3468
- other: {
3469
- count: countGroups.other,
3470
- size: sizeGroups.other,
3471
- percentage: percentageGroups.other,
3472
- },
3473
- total: {
3474
- count: countGroups.total,
3475
- size: sizeGroups.total,
3476
- percentage: 100,
3477
- },
3478
- };
3479
- };
3480
-
3481
- const determineCategory = (urlInfo) => {
3482
- if (urlInfo.type === "sourcemap") {
3483
- return "sourcemap";
3484
- }
3485
- if (urlInfo.type === "html") {
3486
- return "html";
3487
- }
3488
- if (urlInfo.type === "css") {
3489
- return "css";
3490
- }
3491
- if (urlInfo.type === "js_module" || urlInfo.type === "js_classic") {
3492
- return "js";
3493
- }
3494
- if (urlInfo.type === "json") {
3495
- return "json";
3496
- }
3497
- return "other";
3498
- };
3499
-
3500
- const createRepartitionMessage = ({ html, css, js, json, other, total }) => {
3501
- const addPart = (name, { count, size, percentage }) => {
3502
- parts.push(
3503
- `${ANSI.color(`${name}:`, ANSI.GREY)} ${count} (${humanizeFileSize(
3504
- size,
3505
- )} / ${percentage} %)`,
3344
+ let getDirectoryReferenceEffect;
3345
+ if (typeof directoryReferenceEffect === "string") {
3346
+ getDirectoryReferenceEffect = () => directoryReferenceEffect;
3347
+ } else if (typeof directoryReferenceEffect === "function") {
3348
+ getDirectoryReferenceEffect = directoryReferenceEffect;
3349
+ } else if (typeof directoryReferenceEffect === "object") {
3350
+ const associations = URL_META.resolveAssociations(
3351
+ { effect: directoryReferenceEffect },
3352
+ rootDirectoryUrl,
3506
3353
  );
3507
- };
3508
-
3509
- const parts = [];
3510
- // if (sourcemaps.count) {
3511
- // parts.push(
3512
- // `${ANSI.color(`sourcemaps:`, ANSI.GREY)} ${
3513
- // sourcemaps.count
3514
- // } (${humanizeFileSize(sourcemaps.size)})`,
3515
- // )
3516
- // }
3517
- if (html.count) {
3518
- addPart("html ", html);
3519
- }
3520
- if (css.count) {
3521
- addPart("css ", css);
3522
- }
3523
- if (js.count) {
3524
- addPart("js ", js);
3525
- }
3526
- if (json.count) {
3527
- addPart("json ", json);
3528
- }
3529
- if (other.count) {
3530
- addPart("other", other);
3354
+ getDirectoryReferenceEffect = (reference) => {
3355
+ const { url } = reference;
3356
+ const meta = URL_META.applyAssociations({ url, associations });
3357
+ return meta.effect || "error";
3358
+ };
3531
3359
  }
3532
- addPart("total", total);
3533
- return `- ${parts.join(`
3534
- - `)}`;
3535
- };
3536
3360
 
3537
- const jsenvPluginDirectoryReferenceEffect = (
3538
- directoryReferenceEffect = "error",
3539
- ) => {
3540
3361
  return {
3541
3362
  name: "jsenv:directory_reference_effect",
3542
3363
  appliesDuring: "*",
@@ -3573,12 +3394,8 @@ const jsenvPluginDirectoryReferenceEffect = (
3573
3394
  actionForDirectory = "copy";
3574
3395
  } else if (reference.type === "http_request") {
3575
3396
  actionForDirectory = "preserve";
3576
- } else if (typeof directoryReferenceEffect === "string") {
3577
- actionForDirectory = directoryReferenceEffect;
3578
- } else if (typeof directoryReferenceEffect === "function") {
3579
- actionForDirectory = directoryReferenceEffect(reference);
3580
3397
  } else {
3581
- actionForDirectory = "error";
3398
+ actionForDirectory = getDirectoryReferenceEffect(reference);
3582
3399
  }
3583
3400
  reference.actionForDirectory = actionForDirectory;
3584
3401
  if (actionForDirectory !== "copy") {
@@ -3857,7 +3674,7 @@ const jsenvPluginInlining = () => {
3857
3674
 
3858
3675
  const jsenvPluginHtmlSyntaxErrorFallback = () => {
3859
3676
  const htmlSyntaxErrorFileUrl = import.meta.resolve(
3860
- "../client/html_syntax_error/html_syntax_error.html",
3677
+ "./client/html_syntax_error.html",
3861
3678
  );
3862
3679
 
3863
3680
  return {
@@ -5320,7 +5137,7 @@ const parseAndTransformJsReferences = async (
5320
5137
  let filenameHint;
5321
5138
  if (
5322
5139
  externalReferenceInfo.subtype === "import_dynamic" &&
5323
- isBareSpecifier(externalReferenceInfo.specifier)
5140
+ isBareSpecifier$2(externalReferenceInfo.specifier)
5324
5141
  ) {
5325
5142
  filenameHint = `${externalReferenceInfo.specifier}.js`;
5326
5143
  }
@@ -5410,7 +5227,7 @@ const parseAndTransformJsReferences = async (
5410
5227
  return { content, sourcemap };
5411
5228
  };
5412
5229
 
5413
- const isBareSpecifier = (specifier) => {
5230
+ const isBareSpecifier$2 = (specifier) => {
5414
5231
  if (
5415
5232
  specifier[0] === "/" ||
5416
5233
  specifier.startsWith("./") ||
@@ -5524,7 +5341,6 @@ const jsenvPluginReferenceAnalysis = ({
5524
5341
  inlineContent = true,
5525
5342
  inlineConvertedScript = false,
5526
5343
  fetchInlineUrls = true,
5527
- directoryReferenceEffect,
5528
5344
  }) => {
5529
5345
  return [
5530
5346
  jsenvPluginDirectoryReferenceAnalysis(),
@@ -5625,18 +5441,18 @@ const jsenvPluginInlineContentFetcher = () => {
5625
5441
 
5626
5442
 
5627
5443
  const createNodeEsmResolver = ({
5628
- build,
5629
5444
  runtimeCompat,
5630
- packageConditions,
5445
+ rootDirectoryUrl,
5446
+ packageConditions = {},
5631
5447
  preservesSymlink,
5632
5448
  }) => {
5633
- const nodeRuntimeEnabled = Object.keys(runtimeCompat).includes("node");
5634
- // https://nodejs.org/api/esm.html#resolver-algorithm-specification
5635
- packageConditions = packageConditions || [
5636
- ...(build ? [] : readCustomConditionsFromProcessArgs()),
5637
- nodeRuntimeEnabled ? "node" : "browser",
5638
- "import",
5639
- ];
5449
+ const buildPackageConditions = createBuildPackageConditions(
5450
+ packageConditions,
5451
+ {
5452
+ rootDirectoryUrl,
5453
+ runtimeCompat,
5454
+ },
5455
+ );
5640
5456
 
5641
5457
  return (reference) => {
5642
5458
  if (reference.type === "package_json") {
@@ -5657,10 +5473,12 @@ const createNodeEsmResolver = ({
5657
5473
  if (!parentUrl.startsWith("file:")) {
5658
5474
  return null; // let it to jsenv_web_resolution
5659
5475
  }
5476
+ const { specifier } = reference;
5477
+ const conditions = buildPackageConditions(specifier, parentUrl);
5660
5478
  const { url, type, isMain, packageDirectoryUrl } = applyNodeEsmResolution({
5661
- conditions: packageConditions,
5479
+ conditions,
5662
5480
  parentUrl,
5663
- specifier: reference.specifier,
5481
+ specifier,
5664
5482
  preservesSymlink,
5665
5483
  });
5666
5484
  // try to give a more meaningful filename after build
@@ -5724,6 +5542,107 @@ const createNodeEsmResolver = ({
5724
5542
  };
5725
5543
  };
5726
5544
 
5545
+ const createBuildPackageConditions = (
5546
+ packageConditions,
5547
+ { rootDirectoryUrl, runtimeCompat },
5548
+ ) => {
5549
+ const nodeRuntimeEnabled = Object.keys(runtimeCompat).includes("node");
5550
+ // https://nodejs.org/api/esm.html#resolver-algorithm-specification
5551
+ const processArgConditions = readCustomConditionsFromProcessArgs();
5552
+ const packageConditionsDefaultResolvers = {};
5553
+ for (const processArgCondition of processArgConditions) {
5554
+ packageConditionsDefaultResolvers[processArgCondition] = true;
5555
+ }
5556
+ const packageConditionResolvers = {
5557
+ ...packageConditionsDefaultResolvers,
5558
+ development: (specifier, importer) => {
5559
+ if (isBareSpecifier$1(specifier)) {
5560
+ const { url } = applyNodeEsmResolution({
5561
+ specifier,
5562
+ parentUrl: importer,
5563
+ });
5564
+ return !url.includes("/node_modules/");
5565
+ }
5566
+ return !importer.includes("/node_modules/");
5567
+ },
5568
+ node: nodeRuntimeEnabled,
5569
+ browser: !nodeRuntimeEnabled,
5570
+ import: true,
5571
+ };
5572
+ for (const condition of Object.keys(packageConditions)) {
5573
+ const value = packageConditions[condition];
5574
+ let customResolver;
5575
+ if (typeof value === "object") {
5576
+ const associations = URL_META.resolveAssociations(
5577
+ { applies: value },
5578
+ (pattern) => {
5579
+ if (isBareSpecifier$1(pattern)) {
5580
+ try {
5581
+ if (pattern.endsWith("/")) {
5582
+ // avoid package path not exported
5583
+ const { packageDirectoryUrl } = applyNodeEsmResolution({
5584
+ specifier: pattern.slice(0, -1),
5585
+ parentUrl: rootDirectoryUrl,
5586
+ });
5587
+ return packageDirectoryUrl;
5588
+ }
5589
+ const { url } = applyNodeEsmResolution({
5590
+ specifier: pattern,
5591
+ parentUrl: rootDirectoryUrl,
5592
+ });
5593
+ return url;
5594
+ } catch {
5595
+ return new URL(pattern, rootDirectoryUrl);
5596
+ }
5597
+ }
5598
+ return new URL(pattern, rootDirectoryUrl);
5599
+ },
5600
+ );
5601
+ customResolver = (specifier, importer) => {
5602
+ if (isBareSpecifier$1(specifier)) {
5603
+ const { url } = applyNodeEsmResolution({
5604
+ specifier,
5605
+ parentUrl: importer,
5606
+ });
5607
+ const { applies } = URL_META.applyAssociations({ url, associations });
5608
+ return applies;
5609
+ }
5610
+ return URL_META.applyAssociations({ url: importer, associations })
5611
+ .applies;
5612
+ };
5613
+ } else if (typeof value === "function") {
5614
+ customResolver = value;
5615
+ } else {
5616
+ customResolver = () => value;
5617
+ }
5618
+ const existing = packageConditionResolvers[condition];
5619
+ if (existing) {
5620
+ packageConditionResolvers[condition] = (...args) => {
5621
+ const customResult = customResolver(...args);
5622
+ return customResult === undefined ? existing(...args) : customResult;
5623
+ };
5624
+ } else {
5625
+ packageConditionResolvers[condition] = customResolver;
5626
+ }
5627
+ }
5628
+
5629
+ return (specifier, importer) => {
5630
+ const conditions = [];
5631
+ for (const conditionCandidate of Object.keys(packageConditionResolvers)) {
5632
+ const packageConditionResolver =
5633
+ packageConditionResolvers[conditionCandidate];
5634
+ if (typeof packageConditionResolver === "function") {
5635
+ if (packageConditionResolver(specifier, importer)) {
5636
+ conditions.push(conditionCandidate);
5637
+ }
5638
+ } else if (packageConditionResolver) {
5639
+ conditions.push(conditionCandidate);
5640
+ }
5641
+ }
5642
+ return conditions;
5643
+ };
5644
+ };
5645
+
5727
5646
  const addRelationshipWithPackageJson = ({
5728
5647
  reference,
5729
5648
  packageJsonUrl,
@@ -5758,9 +5677,51 @@ const addRelationshipWithPackageJson = ({
5758
5677
  }
5759
5678
  };
5760
5679
 
5761
- const jsenvPluginNodeEsmResolution = (resolutionConfig = {}) => {
5680
+ const isBareSpecifier$1 = (specifier) => {
5681
+ if (
5682
+ specifier[0] === "/" ||
5683
+ specifier.startsWith("./") ||
5684
+ specifier.startsWith("../")
5685
+ ) {
5686
+ return false;
5687
+ }
5688
+ try {
5689
+ // eslint-disable-next-line no-new
5690
+ new URL(specifier);
5691
+ return false;
5692
+ } catch {
5693
+ return true;
5694
+ }
5695
+ };
5696
+
5697
+ const jsenvPluginNodeEsmResolution = (
5698
+ resolutionConfig = {},
5699
+ packageConditions,
5700
+ ) => {
5762
5701
  let nodeEsmResolverDefault;
5763
- const resolvers = {};
5702
+ const resolverMap = new Map();
5703
+ let anyTypeResolver;
5704
+
5705
+ const resolverFromObject = (
5706
+ { preservesSymlink, ...rest },
5707
+ { kitchenContext, urlType },
5708
+ ) => {
5709
+ const unexpectedKeys = Object.keys(rest);
5710
+ if (unexpectedKeys.length) {
5711
+ throw new TypeError(
5712
+ `${unexpectedKeys.join(
5713
+ ",",
5714
+ )}: there is no such configuration on "${urlType}"`,
5715
+ );
5716
+ }
5717
+ return createNodeEsmResolver({
5718
+ build: kitchenContext.build,
5719
+ runtimeCompat: kitchenContext.runtimeCompat,
5720
+ rootDirectoryUrl: kitchenContext.rootDirectoryUrl,
5721
+ packageConditions,
5722
+ preservesSymlink,
5723
+ });
5724
+ };
5764
5725
 
5765
5726
  return {
5766
5727
  name: "jsenv:node_esm_resolution",
@@ -5769,47 +5730,38 @@ const jsenvPluginNodeEsmResolution = (resolutionConfig = {}) => {
5769
5730
  nodeEsmResolverDefault = createNodeEsmResolver({
5770
5731
  build: kitchenContext.build,
5771
5732
  runtimeCompat: kitchenContext.runtimeCompat,
5733
+ rootDirectoryUrl: kitchenContext.rootDirectoryUrl,
5772
5734
  preservesSymlink: true,
5735
+ packageConditions,
5773
5736
  });
5774
- Object.keys(resolutionConfig).forEach((urlType) => {
5737
+ for (const urlType of Object.keys(resolutionConfig)) {
5738
+ let resolver;
5775
5739
  const config = resolutionConfig[urlType];
5776
5740
  if (config === true) {
5777
- resolvers[urlType] = (...args) => nodeEsmResolverDefault(...args);
5741
+ resolver = nodeEsmResolverDefault;
5778
5742
  } else if (config === false) {
5779
- resolvers[urlType] = () => null;
5743
+ // resolverMap.set(urlType, () => null);
5744
+ continue;
5780
5745
  } else if (typeof config === "object") {
5781
- const {
5782
- runtimeCompat,
5783
- packageConditions,
5784
- preservesSymlink,
5785
- ...rest
5786
- } = config;
5787
- const unexpectedKeys = Object.keys(rest);
5788
- if (unexpectedKeys.length) {
5789
- throw new TypeError(
5790
- `${unexpectedKeys.join(
5791
- ",",
5792
- )}: there is no such configuration on "${urlType}"`,
5793
- );
5794
- }
5795
- resolvers[urlType] = createNodeEsmResolver({
5796
- build: kitchenContext.build,
5797
- runtimeCompat,
5798
- packageConditions,
5799
- preservesSymlink,
5800
- });
5746
+ resolver = resolverFromObject(config, { kitchenContext, urlType });
5801
5747
  } else {
5802
5748
  throw new TypeError(
5803
- `config must be true, false or an object, got ${config} on "${urlType}"`,
5749
+ `The value "${config}" for ${urlType} in nodeEsmResolution is invalid: it must be true, false or an object.`,
5804
5750
  );
5805
5751
  }
5806
- });
5807
5752
 
5808
- if (resolvers.js_module === undefined) {
5809
- resolvers.js_module = nodeEsmResolverDefault;
5753
+ if (urlType === "*") {
5754
+ anyTypeResolver = resolver;
5755
+ } else {
5756
+ resolverMap.set(urlType, resolver);
5757
+ }
5758
+ }
5759
+
5760
+ if (!resolverMap.has("js_module")) {
5761
+ resolverMap.set("js_module", nodeEsmResolverDefault);
5810
5762
  }
5811
- if (resolvers.js_classic === undefined) {
5812
- resolvers.js_classic = (reference) => {
5763
+ if (!resolverMap.has("js_classic")) {
5764
+ resolverMap.set("js_classic", (reference) => {
5813
5765
  if (reference.subtype === "self_import_scripts_arg") {
5814
5766
  return nodeEsmResolverDefault(reference);
5815
5767
  }
@@ -5818,7 +5770,7 @@ const jsenvPluginNodeEsmResolution = (resolutionConfig = {}) => {
5818
5770
  return nodeEsmResolverDefault(reference);
5819
5771
  }
5820
5772
  return null;
5821
- };
5773
+ });
5822
5774
  }
5823
5775
  },
5824
5776
  resolveReference: (reference) => {
@@ -5828,8 +5780,14 @@ const jsenvPluginNodeEsmResolution = (resolutionConfig = {}) => {
5828
5780
  return result;
5829
5781
  }
5830
5782
  const urlType = urlTypeFromReference(reference);
5831
- const resolver = resolvers[urlType];
5832
- return resolver ? resolver(reference) : null;
5783
+ const resolver = resolverMap.get(urlType);
5784
+ if (resolver) {
5785
+ return resolver(reference);
5786
+ }
5787
+ if (anyTypeResolver) {
5788
+ return anyTypeResolver(reference);
5789
+ }
5790
+ return null;
5833
5791
  },
5834
5792
  // when specifier is prefixed by "file:///@ignore/"
5835
5793
  // we return an empty js module
@@ -6071,7 +6029,7 @@ return {
6071
6029
 
6072
6030
 
6073
6031
  const htmlFileUrlForDirectory = import.meta.resolve(
6074
- "../client/directory_listing/directory_listing.html",
6032
+ "./client/directory_listing.html",
6075
6033
  );
6076
6034
 
6077
6035
  const jsenvPluginDirectoryListing = ({
@@ -6426,9 +6384,8 @@ const getDirectoryContentItems = ({
6426
6384
  fileUrls.push(fileUrlObject);
6427
6385
  }
6428
6386
  }
6429
- fileUrls.sort((a, b) => {
6430
- return comparePathnames(a.pathname, b.pathname);
6431
- });
6387
+ fileUrls.sort(compareFileUrls);
6388
+
6432
6389
  const items = [];
6433
6390
  for (const fileUrl of fileUrls) {
6434
6391
  const urlRelativeToCurrentDirectory = urlToRelativeUrl(
@@ -7433,7 +7390,7 @@ const htmlNodeCanHotReload = (node) => {
7433
7390
 
7434
7391
  const jsenvPluginImportMetaHot = () => {
7435
7392
  const importMetaHotClientFileUrl = import.meta.resolve(
7436
- "../client/import_meta_hot/import_meta_hot.js",
7393
+ "./client/import_meta_hot.js",
7437
7394
  );
7438
7395
 
7439
7396
  return {
@@ -7545,7 +7502,7 @@ import.meta.hot = createImportMetaHot(import.meta.url);
7545
7502
  };
7546
7503
 
7547
7504
  const jsenvPluginAutoreloadClient = () => {
7548
- const autoreloadClientFileUrl = import.meta.resolve("../client/autoreload/autoreload.js");
7505
+ const autoreloadClientFileUrl = import.meta.resolve("./client/autoreload.js");
7549
7506
 
7550
7507
  return {
7551
7508
  name: "jsenv:autoreload_client",
@@ -8064,7 +8021,7 @@ const jsenvPluginRibbon = ({
8064
8021
  rootDirectoryUrl,
8065
8022
  htmlInclude = "/**/*.html",
8066
8023
  }) => {
8067
- const ribbonClientFileUrl = import.meta.resolve("../client/ribbon/ribbon.js");
8024
+ const ribbonClientFileUrl = import.meta.resolve("./client/ribbon.js");
8068
8025
  const associations = URL_META.resolveAssociations(
8069
8026
  {
8070
8027
  ribbon: {
@@ -8152,6 +8109,7 @@ const getCorePlugins = ({
8152
8109
 
8153
8110
  referenceAnalysis = {},
8154
8111
  nodeEsmResolution = {},
8112
+ packageConditions,
8155
8113
  magicExtensions,
8156
8114
  magicDirectoryIndex,
8157
8115
  directoryListing = true,
@@ -8222,10 +8180,12 @@ const getCorePlugins = ({
8222
8180
  },
8223
8181
  },
8224
8182
  ...(nodeEsmResolution
8225
- ? [jsenvPluginNodeEsmResolution(nodeEsmResolution)]
8183
+ ? [jsenvPluginNodeEsmResolution(nodeEsmResolution, packageConditions)]
8226
8184
  : []),
8227
8185
  jsenvPluginWebResolution(),
8228
- jsenvPluginDirectoryReferenceEffect(directoryReferenceEffect),
8186
+ jsenvPluginDirectoryReferenceEffect(directoryReferenceEffect, {
8187
+ rootDirectoryUrl,
8188
+ }),
8229
8189
  jsenvPluginVersionSearchParam(),
8230
8190
 
8231
8191
  // "jsenvPluginSupervisor" MUST be after "jsenvPluginInlining" as it needs inline script to be cooked
@@ -8246,180 +8206,274 @@ const getCorePlugins = ({
8246
8206
  ];
8247
8207
  };
8248
8208
 
8249
- // default runtimeCompat corresponds to
8250
- // "we can keep <script type="module"> intact":
8251
- // so script_type_module + dynamic_import + import_meta
8252
- const defaultRuntimeCompat = {
8253
- // android: "8",
8254
- chrome: "64",
8255
- edge: "79",
8256
- firefox: "67",
8257
- ios: "12",
8258
- opera: "51",
8259
- safari: "11.3",
8260
- samsung: "9.2",
8209
+ const humanizeProcessCpuUsage = (ratio) => {
8210
+ const percentageAsNumber = ratio * 100;
8211
+ const percentageAsNumberRounded = Math.round(percentageAsNumber);
8212
+ const percentage = `${percentageAsNumberRounded}%`;
8213
+ return percentage;
8261
8214
  };
8262
- const logsDefault = {
8263
- level: "info",
8264
- disabled: false,
8265
- animation: true,
8215
+ const humanizeProcessMemoryUsage = (value) => {
8216
+ return humanizeMemory(value, { short: true, decimals: 0 });
8266
8217
  };
8267
- const getDefaultBase = (runtimeCompat) =>
8268
- runtimeCompat.node ? "./" : "/";
8269
-
8270
- const createBuildUrlsGenerator = ({
8271
- logger,
8272
- sourceDirectoryUrl,
8273
- buildDirectoryUrl,
8274
- assetsDirectory,
8218
+ const renderBuildDoneLog = ({
8219
+ duration,
8220
+ buildFileContents,
8221
+ processCpuUsage,
8222
+ processMemoryUsage,
8275
8223
  }) => {
8276
- const cache = {};
8277
- const getUrlName = (url, urlInfo) => {
8278
- if (!urlInfo) {
8279
- return urlToFilename(url);
8224
+ const buildContentReport = createBuildContentReport(buildFileContents);
8225
+
8226
+ let title = "";
8227
+ let content = "";
8228
+ const lines = [];
8229
+
8230
+ const filesWrittenCount = buildContentReport.total.count;
8231
+ if (filesWrittenCount === 1) {
8232
+ title = `1 file written`;
8233
+ } else {
8234
+ title = `${filesWrittenCount} files written`;
8235
+ const keys = Object.keys(buildContentReport);
8236
+ const rows = [];
8237
+ let y = 0;
8238
+ let highestPercentage = 0;
8239
+ let highestPercentageY = 0;
8240
+ for (const key of keys) {
8241
+ if (key === "sourcemaps") {
8242
+ continue;
8243
+ }
8244
+ if (key === "total") {
8245
+ continue;
8246
+ }
8247
+ const { count, size, percentage } = buildContentReport[key];
8248
+ if (count === 0) {
8249
+ continue;
8250
+ }
8251
+ const row = [
8252
+ {
8253
+ value: key,
8254
+ borderTop: {},
8255
+ borderBottom: {},
8256
+ },
8257
+ {
8258
+ value: count,
8259
+ borderTop: {},
8260
+ borderBottom: {},
8261
+ },
8262
+ {
8263
+ value: size,
8264
+ format: "size",
8265
+ borderTop: {},
8266
+ borderBottom: {},
8267
+ },
8268
+ {
8269
+ value: percentage,
8270
+ format: "percentage",
8271
+ unit: "%",
8272
+ borderTop: {},
8273
+ borderBottom: {},
8274
+ },
8275
+ ];
8276
+ if (percentage > highestPercentage) {
8277
+ highestPercentage = percentage;
8278
+ highestPercentageY = y;
8279
+ }
8280
+ rows.push(row);
8281
+ y++;
8282
+ }
8283
+ if (rows.length > 1) {
8284
+ const rowWithHighestPercentage = rows[highestPercentageY];
8285
+ for (const cell of rowWithHighestPercentage) {
8286
+ cell.bold = true;
8287
+ }
8288
+ const table = renderTable(rows, {
8289
+ borderCollapse: true,
8290
+ ansi: true,
8291
+ });
8292
+ content += table;
8293
+ content += "\n";
8280
8294
  }
8281
- if (urlInfo.filenameHint) {
8282
- return urlInfo.filenameHint;
8283
- }
8284
- return urlToFilename(url);
8285
- };
8295
+ }
8286
8296
 
8287
- const buildUrlCache = new Map();
8297
+ let sizeLine = `total size: `;
8298
+ sizeLine += humanizeFileSize(buildContentReport.total.size);
8299
+ lines.push(sizeLine);
8288
8300
 
8289
- const associateBuildUrl = (url, buildUrl) => {
8290
- buildUrlCache.set(url, buildUrl);
8291
- logger.debug(`associate a build url
8292
- ${ANSI.color(url, ANSI.GREY)} ->
8293
- ${ANSI.color(buildUrl, ANSI.MAGENTA)}
8294
- `);
8301
+ let durationLine = `duration: `;
8302
+ durationLine += humanizeDuration(duration, { short: true });
8303
+ lines.push(durationLine);
8304
+
8305
+ // cpu usage
8306
+ let cpuUsageLine = "cpu: ";
8307
+ cpuUsageLine += `${humanizeProcessCpuUsage(processCpuUsage.end)}`;
8308
+ cpuUsageLine += renderDetails({
8309
+ med: humanizeProcessCpuUsage(processCpuUsage.median),
8310
+ min: humanizeProcessCpuUsage(processCpuUsage.min),
8311
+ max: humanizeProcessCpuUsage(processCpuUsage.max),
8312
+ });
8313
+ lines.push(cpuUsageLine);
8314
+
8315
+ // memory usage
8316
+ let memoryUsageLine = "memory: ";
8317
+ memoryUsageLine += `${humanizeProcessMemoryUsage(processMemoryUsage.end)}`;
8318
+ memoryUsageLine += renderDetails({
8319
+ med: humanizeProcessMemoryUsage(processMemoryUsage.median),
8320
+ min: humanizeProcessMemoryUsage(processMemoryUsage.min),
8321
+ max: humanizeProcessMemoryUsage(processMemoryUsage.max),
8322
+ });
8323
+ lines.push(memoryUsageLine);
8324
+
8325
+ content += lines.join("\n");
8326
+ return `${renderBigSection({
8327
+ title,
8328
+ content,
8329
+ })}`;
8330
+ };
8331
+
8332
+ const createBuildContentReport = (buildFileContents) => {
8333
+ const countGroups = {
8334
+ sourcemaps: 0,
8335
+ html: 0,
8336
+ css: 0,
8337
+ js: 0,
8338
+ json: 0,
8339
+ other: 0,
8340
+ total: 0,
8341
+ };
8342
+ const sizeGroups = {
8343
+ sourcemaps: 0,
8344
+ html: 0,
8345
+ css: 0,
8346
+ js: 0,
8347
+ json: 0,
8348
+ other: 0,
8349
+ total: 0,
8295
8350
  };
8296
8351
 
8297
- const generate = (url, { urlInfo, ownerUrlInfo }) => {
8298
- const buildUrlFromCache = buildUrlCache.get(url);
8299
- if (buildUrlFromCache) {
8300
- return buildUrlFromCache;
8352
+ for (const buildRelativeUrl of Object.keys(buildFileContents)) {
8353
+ const content = buildFileContents[buildRelativeUrl];
8354
+ const contentSize = Buffer.byteLength(content);
8355
+ const category = determineCategory(buildRelativeUrl);
8356
+ if (category === "sourcemap") {
8357
+ countGroups.sourcemaps++;
8358
+ sizeGroups.sourcemaps += contentSize;
8359
+ continue;
8301
8360
  }
8302
- if (urlIsInsideOf(url, buildDirectoryUrl)) {
8303
- buildUrlCache.set(url, url);
8304
- return url;
8361
+ countGroups.total++;
8362
+ sizeGroups.total += contentSize;
8363
+ if (category === "html") {
8364
+ countGroups.html++;
8365
+ sizeGroups.html += contentSize;
8366
+ continue;
8305
8367
  }
8306
- if (
8307
- urlInfo.type === "directory" ||
8308
- (urlInfo.type === undefined && urlInfo.typeHint === "directory")
8309
- ) {
8310
- let directoryPath;
8311
- if (url === sourceDirectoryUrl) {
8312
- directoryPath = "";
8313
- } else if (urlInfo.filenameHint) {
8314
- directoryPath = urlInfo.filenameHint;
8315
- } else {
8316
- directoryPath = urlToRelativeUrl(url, sourceDirectoryUrl);
8317
- }
8318
- const { search } = new URL(url);
8319
- const buildUrl = `${buildDirectoryUrl}${directoryPath}${search}`;
8320
- associateBuildUrl(url, buildUrl);
8321
- return buildUrl;
8368
+ if (category === "css") {
8369
+ countGroups.css++;
8370
+ sizeGroups.css += contentSize;
8371
+ continue;
8322
8372
  }
8323
-
8324
- const directoryPath = determineDirectoryPath({
8325
- sourceDirectoryUrl,
8326
- assetsDirectory,
8327
- urlInfo,
8328
- ownerUrlInfo,
8329
- });
8330
- let names = cache[directoryPath];
8331
- if (!names) {
8332
- names = [];
8333
- cache[directoryPath] = names;
8373
+ if (category === "js") {
8374
+ countGroups.js++;
8375
+ sizeGroups.js += contentSize;
8376
+ continue;
8334
8377
  }
8335
- const urlObject = new URL(url);
8336
- let { search, hash } = urlObject;
8337
- let name = getUrlName(url, urlInfo);
8338
- let [basename, extension] = splitFileExtension(name);
8339
- extension = extensionMappings[extension] || extension;
8340
- let nameCandidate = `${basename}${extension}`; // reconstruct name in case extension was normalized
8341
- let integer = 1;
8342
- while (true) {
8343
- if (!names.includes(nameCandidate)) {
8344
- names.push(nameCandidate);
8345
- break;
8346
- }
8347
- integer++;
8348
- nameCandidate = `${basename}${integer}${extension}`;
8378
+ if (category === "json") {
8379
+ countGroups.json++;
8380
+ sizeGroups.json += contentSize;
8381
+ continue;
8349
8382
  }
8350
- const buildUrl = `${buildDirectoryUrl}${directoryPath}${nameCandidate}${search}${hash}`;
8351
- associateBuildUrl(url, buildUrl);
8352
- return buildUrl;
8353
- };
8383
+ countGroups.other++;
8384
+ sizeGroups.other += contentSize;
8385
+ continue;
8386
+ }
8354
8387
 
8355
- return {
8356
- generate,
8357
- };
8358
- };
8388
+ const sizesToDistribute = {};
8389
+ for (const groupName of Object.keys(sizeGroups)) {
8390
+ if (groupName !== "sourcemaps" && groupName !== "total") {
8391
+ sizesToDistribute[groupName] = sizeGroups[groupName];
8392
+ }
8393
+ }
8394
+ const percentageGroups = distributePercentages(sizesToDistribute);
8359
8395
 
8360
- // It's best to generate files with an extension representing what is inside the file
8361
- // and after build js files contains solely js (js or typescript is gone).
8362
- // This way a static file server is already configured to server the correct content-type
8363
- // (otherwise one would have to configure that ".jsx" is "text/javascript")
8364
- // To keep in mind: if you have "user.jsx" and "user.js" AND both file are not bundled
8365
- // you end up with "dist/js/user.js" and "dist/js/user2.js"
8366
- const extensionMappings = {
8367
- ".jsx": ".js",
8368
- ".ts": ".js",
8369
- ".tsx": ".js",
8370
- };
8396
+ return {
8397
+ // sourcemaps are special, there size are ignored
8398
+ // so there is no "percentage" associated
8399
+ sourcemaps: {
8400
+ count: countGroups.sourcemaps,
8401
+ size: sizeGroups.sourcemaps,
8402
+ percentage: undefined,
8403
+ },
8371
8404
 
8372
- const splitFileExtension = (filename) => {
8373
- const dotLastIndex = filename.lastIndexOf(".");
8374
- if (dotLastIndex === -1) {
8375
- return [filename, ""];
8376
- }
8377
- return [filename.slice(0, dotLastIndex), filename.slice(dotLastIndex)];
8405
+ html: {
8406
+ count: countGroups.html,
8407
+ size: sizeGroups.html,
8408
+ percentage: percentageGroups.html,
8409
+ },
8410
+ css: {
8411
+ count: countGroups.css,
8412
+ size: sizeGroups.css,
8413
+ percentage: percentageGroups.css,
8414
+ },
8415
+ js: {
8416
+ count: countGroups.js,
8417
+ size: sizeGroups.js,
8418
+ percentage: percentageGroups.js,
8419
+ },
8420
+ json: {
8421
+ count: countGroups.json,
8422
+ size: sizeGroups.json,
8423
+ percentage: percentageGroups.json,
8424
+ },
8425
+ other: {
8426
+ count: countGroups.other,
8427
+ size: sizeGroups.other,
8428
+ percentage: percentageGroups.other,
8429
+ },
8430
+ total: {
8431
+ count: countGroups.total,
8432
+ size: sizeGroups.total,
8433
+ percentage: 100,
8434
+ },
8435
+ };
8378
8436
  };
8379
8437
 
8380
- const determineDirectoryPath = ({
8381
- sourceDirectoryUrl,
8382
- assetsDirectory,
8383
- urlInfo,
8384
- ownerUrlInfo,
8385
- }) => {
8386
- if (urlInfo.dirnameHint) {
8387
- return urlInfo.dirnameHint;
8388
- }
8389
- if (urlInfo.type === "directory") {
8390
- return "";
8391
- }
8392
- if (urlInfo.isInline) {
8393
- const parentDirectoryPath = determineDirectoryPath({
8394
- sourceDirectoryUrl,
8395
- assetsDirectory,
8396
- urlInfo: ownerUrlInfo || urlInfo.firstReference.ownerUrlInfo,
8397
- });
8398
- return parentDirectoryPath;
8399
- }
8400
- const dynamicImportId = urlInfo.searchParams.get("dynamic_import_id");
8401
- if (dynamicImportId) {
8402
- return `${assetsDirectory}${dynamicImportId}/`;
8403
- }
8404
- if (urlInfo.isEntryPoint && !urlInfo.isDynamicEntryPoint) {
8405
- return "";
8406
- }
8407
- if (urlInfo.type === "importmap") {
8408
- return "";
8438
+ const determineCategory = (buildRelativeUrl) => {
8439
+ if (buildRelativeUrl.endsWith(".map")) {
8440
+ return "sourcemap";
8409
8441
  }
8410
- if (urlInfo.type === "html") {
8411
- return `${assetsDirectory}html/`;
8442
+ if (buildRelativeUrl.endsWith(".html")) {
8443
+ return "html";
8412
8444
  }
8413
- if (urlInfo.type === "css") {
8414
- return `${assetsDirectory}css/`;
8445
+ if (buildRelativeUrl.endsWith(".css")) {
8446
+ return "css";
8415
8447
  }
8416
- if (urlInfo.type === "js_module" || urlInfo.type === "js_classic") {
8417
- return `${assetsDirectory}js/`;
8448
+ if (
8449
+ buildRelativeUrl.endsWith(".js") ||
8450
+ buildRelativeUrl.endsWith(".mjs") ||
8451
+ buildRelativeUrl.endsWith(".cjs")
8452
+ ) {
8453
+ return "js";
8418
8454
  }
8419
- if (urlInfo.type === "json") {
8420
- return `${assetsDirectory}json/`;
8455
+ if (buildRelativeUrl.endsWith(".json")) {
8456
+ return "json";
8421
8457
  }
8422
- return `${assetsDirectory}other/`;
8458
+ return "other";
8459
+ };
8460
+
8461
+ // default runtimeCompat corresponds to
8462
+ // "we can keep <script type="module"> intact":
8463
+ // so script_type_module + dynamic_import + import_meta
8464
+ const defaultRuntimeCompat = {
8465
+ // android: "8",
8466
+ chrome: "64",
8467
+ edge: "79",
8468
+ firefox: "67",
8469
+ ios: "12",
8470
+ opera: "51",
8471
+ safari: "11.3",
8472
+ samsung: "9.2",
8473
+ };
8474
+ const logsDefault = {
8475
+ level: "info",
8476
+ animated: true,
8423
8477
  };
8424
8478
 
8425
8479
  // https://bundlers.tooling.report/hashing/avoid-cascade/
@@ -8555,8 +8609,9 @@ const createBuildSpecifierManager = ({
8555
8609
  logger,
8556
8610
  sourceDirectoryUrl,
8557
8611
  buildDirectoryUrl,
8558
- base,
8559
8612
  assetsDirectory,
8613
+ buildUrlsGenerator,
8614
+ base,
8560
8615
  length = 8,
8561
8616
 
8562
8617
  versioning,
@@ -8564,12 +8619,6 @@ const createBuildSpecifierManager = ({
8564
8619
  versionLength,
8565
8620
  canUseImportmap,
8566
8621
  }) => {
8567
- const buildUrlsGenerator = createBuildUrlsGenerator({
8568
- logger,
8569
- sourceDirectoryUrl,
8570
- buildDirectoryUrl,
8571
- assetsDirectory,
8572
- });
8573
8622
  const placeholderAPI = createPlaceholderAPI({
8574
8623
  length,
8575
8624
  });
@@ -8599,6 +8648,7 @@ const createBuildSpecifierManager = ({
8599
8648
  buildUrl = buildUrlsGenerator.generate(url, {
8600
8649
  urlInfo,
8601
8650
  ownerUrlInfo: reference.ownerUrlInfo,
8651
+ assetsDirectory,
8602
8652
  });
8603
8653
  }
8604
8654
 
@@ -8673,6 +8723,12 @@ const createBuildSpecifierManager = ({
8673
8723
  resolveReference: (reference) => {
8674
8724
  const { ownerUrlInfo } = reference;
8675
8725
  if (ownerUrlInfo.remapReference && !reference.isInline) {
8726
+ if (reference.specifier.startsWith("file:")) {
8727
+ const rawUrlInfo = rawKitchen.graph.getUrlInfo(reference.specifier);
8728
+ if (rawUrlInfo && rawUrlInfo.type === "entry_build") {
8729
+ return reference.specifier; // we want to ignore it
8730
+ }
8731
+ }
8676
8732
  const newSpecifier = ownerUrlInfo.remapReference(reference);
8677
8733
  reference.specifier = newSpecifier;
8678
8734
  }
@@ -8705,6 +8761,14 @@ const createBuildSpecifierManager = ({
8705
8761
  return url;
8706
8762
  },
8707
8763
  redirectReference: (reference) => {
8764
+ // don't think this is needed because we'll find the rawUrlInfo
8765
+ // which contains the filenameHint
8766
+ // const otherEntryBuildInfo = getOtherEntryBuildInfo(reference.url);
8767
+ // if (otherEntryBuildInfo) {
8768
+ // reference.filenameHint = otherEntryBuildInfo.entryUrlInfo.filenameHint;
8769
+ // return null;
8770
+ // }
8771
+
8708
8772
  let referenceBeforeInlining = reference;
8709
8773
  if (
8710
8774
  referenceBeforeInlining.isInline &&
@@ -8790,7 +8854,6 @@ const createBuildSpecifierManager = ({
8790
8854
  firstReference = firstReference.prev;
8791
8855
  }
8792
8856
  const rawUrl = firstReference.rawUrl || firstReference.url;
8793
- const rawUrlInfo = rawKitchen.graph.getUrlInfo(rawUrl);
8794
8857
  const bundleInfo = bundleInfoMap.get(rawUrl);
8795
8858
  if (bundleInfo) {
8796
8859
  finalUrlInfo.remapReference = bundleInfo.remapReference;
@@ -8807,7 +8870,21 @@ const createBuildSpecifierManager = ({
8807
8870
  data: bundleInfo.data,
8808
8871
  };
8809
8872
  }
8873
+ const rawUrlInfo = rawKitchen.graph.getUrlInfo(rawUrl);
8810
8874
  if (rawUrlInfo) {
8875
+ if (rawUrlInfo.type === "entry_build") {
8876
+ const otherEntryBuildInfo = rawUrlInfo.otherEntryBuildInfo;
8877
+ if (
8878
+ // we need to wait ONLY if we are versioning
8879
+ // and only IF the reference can't be remapped globally or by importmap
8880
+ // otherwise we can reference the file right away
8881
+ versioning &&
8882
+ getReferenceVersioningInfo(firstReference).type === "inline"
8883
+ ) {
8884
+ await otherEntryBuildInfo.promise;
8885
+ }
8886
+ finalUrlInfo.otherEntryBuildInfo = otherEntryBuildInfo;
8887
+ }
8811
8888
  return rawUrlInfo;
8812
8889
  }
8813
8890
  // reference injected during "shape":
@@ -9038,6 +9115,15 @@ const createBuildSpecifierManager = ({
9038
9115
  if (urlInfo.url.startsWith("ignore:")) {
9039
9116
  return;
9040
9117
  }
9118
+ if (urlInfo.type === "entry_build") {
9119
+ const otherEntryBuildInfo = urlInfo.otherEntryBuildInfo;
9120
+ const entryUrlInfoVersion =
9121
+ otherEntryBuildInfo.buildFileVersions[
9122
+ otherEntryBuildInfo.buildRelativeUrl
9123
+ ];
9124
+ contentOnlyVersionMap.set(urlInfo, entryUrlInfoVersion);
9125
+ return;
9126
+ }
9041
9127
  let content = urlInfo.content;
9042
9128
  if (urlInfo.type === "html") {
9043
9129
  content = stringifyHtmlAst(
@@ -9077,6 +9163,9 @@ const createBuildSpecifierManager = ({
9077
9163
  const getSetOfUrlInfoInfluencingVersion = (urlInfo) => {
9078
9164
  const placeholderInfluencingVersionSet = new Set();
9079
9165
  const visitContainedPlaceholders = (urlInfo) => {
9166
+ if (urlInfo.type === "entry_build") {
9167
+ return;
9168
+ }
9080
9169
  const referencedContentVersion = contentOnlyVersionMap.get(urlInfo);
9081
9170
  if (!referencedContentVersion) {
9082
9171
  // ignored while traversing graph (not used anymore, inline, ...)
@@ -9121,20 +9210,24 @@ const createBuildSpecifierManager = ({
9121
9210
  contentOnlyUrlInfo,
9122
9211
  contentOnlyVersion,
9123
9212
  ] of contentOnlyVersionMap) {
9124
- const setOfUrlInfoInfluencingVersion =
9125
- getSetOfUrlInfoInfluencingVersion(contentOnlyUrlInfo);
9126
9213
  const versionPartSet = new Set();
9127
- versionPartSet.add(contentOnlyVersion);
9128
- for (const urlInfoInfluencingVersion of setOfUrlInfoInfluencingVersion) {
9129
- const otherUrlInfoContentVersion = contentOnlyVersionMap.get(
9130
- urlInfoInfluencingVersion,
9131
- );
9132
- if (!otherUrlInfoContentVersion) {
9133
- throw new Error(
9134
- `cannot find content version for ${urlInfoInfluencingVersion.url} (used by ${contentOnlyUrlInfo.url})`,
9214
+ if (contentOnlyUrlInfo.type === "entry_build") {
9215
+ versionPartSet.add(contentOnlyVersion);
9216
+ } else {
9217
+ const setOfUrlInfoInfluencingVersion =
9218
+ getSetOfUrlInfoInfluencingVersion(contentOnlyUrlInfo);
9219
+ versionPartSet.add(contentOnlyVersion);
9220
+ for (const urlInfoInfluencingVersion of setOfUrlInfoInfluencingVersion) {
9221
+ const otherUrlInfoContentVersion = contentOnlyVersionMap.get(
9222
+ urlInfoInfluencingVersion,
9135
9223
  );
9224
+ if (!otherUrlInfoContentVersion) {
9225
+ throw new Error(
9226
+ `cannot find content version for ${urlInfoInfluencingVersion.url} (used by ${contentOnlyUrlInfo.url})`,
9227
+ );
9228
+ }
9229
+ versionPartSet.add(otherUrlInfoContentVersion);
9136
9230
  }
9137
- versionPartSet.add(otherUrlInfoContentVersion);
9138
9231
  }
9139
9232
  const version = generateVersion(versionPartSet, versionLength);
9140
9233
  versionMap.set(contentOnlyUrlInfo, version);
@@ -9536,6 +9629,7 @@ const createBuildSpecifierManager = ({
9536
9629
  const buildManifest = {};
9537
9630
  const buildContents = {};
9538
9631
  const buildInlineRelativeUrlSet = new Set();
9632
+ const buildFileVersions = {};
9539
9633
  GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
9540
9634
  finalKitchen.graph.rootUrlInfo,
9541
9635
  (urlInfo) => {
@@ -9549,6 +9643,9 @@ const createBuildSpecifierManager = ({
9549
9643
  ) {
9550
9644
  return;
9551
9645
  }
9646
+ if (urlInfo.type === "entry_build") {
9647
+ return;
9648
+ }
9552
9649
  const buildSpecifier = buildUrlToBuildSpecifierMap.get(buildUrl);
9553
9650
  const buildSpecifierVersioned = versioning
9554
9651
  ? buildSpecifierToBuildSpecifierVersionedMap.get(buildSpecifier)
@@ -9557,6 +9654,8 @@ const createBuildSpecifierManager = ({
9557
9654
  buildUrl,
9558
9655
  buildDirectoryUrl,
9559
9656
  );
9657
+ buildFileVersions[buildRelativeUrl] = versionMap.get(urlInfo);
9658
+
9560
9659
  let contentKey;
9561
9660
  // if to guard for html where versioned build specifier is not generated
9562
9661
  if (buildSpecifierVersioned) {
@@ -9595,7 +9694,12 @@ const createBuildSpecifierManager = ({
9595
9694
  }
9596
9695
  });
9597
9696
 
9598
- return { buildFileContents, buildInlineContents, buildManifest };
9697
+ return {
9698
+ buildFileContents,
9699
+ buildInlineContents,
9700
+ buildManifest,
9701
+ buildFileVersions,
9702
+ };
9599
9703
  },
9600
9704
  };
9601
9705
  };
@@ -9818,29 +9922,187 @@ const asBuildUrlVersioned = ({
9818
9922
  return `${buildDirectoryUrl}${pathname}${search}${hash}`;
9819
9923
  };
9820
9924
 
9821
- const ensureUnixLineBreaks = (stringOrBuffer) => {
9822
- if (typeof stringOrBuffer === "string") {
9823
- const stringWithLinuxBreaks = stringOrBuffer.replace(/\r\n/g, "\n");
9824
- return stringWithLinuxBreaks;
9825
- }
9826
- return ensureUnixLineBreaksOnBuffer(stringOrBuffer);
9827
- };
9925
+ // import { ANSI } from "@jsenv/humanize";
9828
9926
 
9829
- // https://github.com/nodejs/help/issues/1738#issuecomment-458460503
9830
- const ensureUnixLineBreaksOnBuffer = (buffer) => {
9831
- const int32Array = new Int32Array(buffer, 0, buffer.length);
9832
- const int32ArrayWithLineBreaksNormalized = int32Array.filter(
9833
- (element, index, typedArray) => {
9834
- if (element === 0x0d) {
9835
- if (typedArray[index + 1] === 0x0a) {
9836
- // Windows -> Unix
9837
- return false;
9838
- }
9839
- // Mac OS -> Unix
9840
- typedArray[index] = 0x0a;
9841
- }
9842
- return true;
9843
- },
9927
+ const createBuildUrlsGenerator = ({
9928
+ // logger,
9929
+ sourceDirectoryUrl,
9930
+ buildDirectoryUrl,
9931
+ }) => {
9932
+ const getUrlName = (url, urlInfo) => {
9933
+ if (!urlInfo) {
9934
+ return urlToFilename(url);
9935
+ }
9936
+ if (urlInfo.filenameHint) {
9937
+ return urlInfo.filenameHint;
9938
+ }
9939
+ return urlToFilename(url);
9940
+ };
9941
+
9942
+ const buildUrlMap = new Map();
9943
+ const associateBuildUrl = (url, buildUrl) => {
9944
+ buildUrlMap.set(url, buildUrl);
9945
+ // logger.debug(`associate a build url
9946
+ // ${ANSI.color(url, ANSI.GREY)} ->
9947
+ // ${ANSI.color(buildUrl, ANSI.MAGENTA)}
9948
+ // `);
9949
+ };
9950
+
9951
+ const nameSetPerDirectoryMap = new Map();
9952
+ const generate = (url, { urlInfo, ownerUrlInfo, assetsDirectory }) => {
9953
+ const buildUrlFromMap = buildUrlMap.get(url);
9954
+ if (buildUrlFromMap) {
9955
+ return buildUrlFromMap;
9956
+ }
9957
+ if (urlIsInsideOf(url, buildDirectoryUrl)) {
9958
+ associateBuildUrl(url, url);
9959
+ return url;
9960
+ }
9961
+ if (urlInfo.type === "entry_build") {
9962
+ const buildUrl = new URL(urlInfo.filenameHint, buildDirectoryUrl).href;
9963
+ associateBuildUrl(url, buildUrl);
9964
+ return buildUrl;
9965
+ }
9966
+ if (
9967
+ urlInfo.type === "directory" ||
9968
+ (urlInfo.type === undefined && urlInfo.typeHint === "directory")
9969
+ ) {
9970
+ let directoryPath;
9971
+ if (url === sourceDirectoryUrl) {
9972
+ directoryPath = "";
9973
+ } else if (urlInfo.filenameHint) {
9974
+ directoryPath = urlInfo.filenameHint;
9975
+ } else {
9976
+ directoryPath = urlToRelativeUrl(url, sourceDirectoryUrl);
9977
+ }
9978
+ const { search } = new URL(url);
9979
+ const buildUrl = `${buildDirectoryUrl}${directoryPath}${search}`;
9980
+ associateBuildUrl(url, buildUrl);
9981
+ return buildUrl;
9982
+ }
9983
+
9984
+ const directoryPath = determineDirectoryPath({
9985
+ sourceDirectoryUrl,
9986
+ assetsDirectory,
9987
+ urlInfo,
9988
+ ownerUrlInfo,
9989
+ });
9990
+ let nameSet = nameSetPerDirectoryMap.get(directoryPath);
9991
+ if (!nameSet) {
9992
+ nameSet = new Set();
9993
+ nameSetPerDirectoryMap.set(directoryPath, nameSet);
9994
+ }
9995
+ const urlObject = new URL(url);
9996
+ let { search, hash } = urlObject;
9997
+ let urlName = getUrlName(url, urlInfo);
9998
+ let [basename, extension] = splitFileExtension(urlName);
9999
+ extension = extensionMappings[extension] || extension;
10000
+ let nameCandidate = `${basename}${extension}`; // reconstruct name in case extension was normalized
10001
+ let integer = 1;
10002
+ while (nameSet.has(nameCandidate)) {
10003
+ integer++;
10004
+ nameCandidate = `${basename}${integer}${extension}`;
10005
+ }
10006
+ const name = nameCandidate;
10007
+ nameSet.add(name);
10008
+ const buildUrl = `${buildDirectoryUrl}${directoryPath}${name}${search}${hash}`;
10009
+ associateBuildUrl(url, buildUrl);
10010
+ return buildUrl;
10011
+ };
10012
+
10013
+ return {
10014
+ generate,
10015
+ };
10016
+ };
10017
+
10018
+ // It's best to generate files with an extension representing what is inside the file
10019
+ // and after build js files contains solely js (js or typescript is gone).
10020
+ // This way a static file server is already configured to server the correct content-type
10021
+ // (otherwise one would have to configure that ".jsx" is "text/javascript")
10022
+ // To keep in mind: if you have "user.jsx" and "user.js" AND both file are not bundled
10023
+ // you end up with "dist/js/user.js" and "dist/js/user2.js"
10024
+ const extensionMappings = {
10025
+ ".jsx": ".js",
10026
+ ".ts": ".js",
10027
+ ".tsx": ".js",
10028
+ };
10029
+
10030
+ const splitFileExtension = (filename) => {
10031
+ const dotLastIndex = filename.lastIndexOf(".");
10032
+ if (dotLastIndex === -1) {
10033
+ return [filename, ""];
10034
+ }
10035
+ return [filename.slice(0, dotLastIndex), filename.slice(dotLastIndex)];
10036
+ };
10037
+
10038
+ const determineDirectoryPath = ({
10039
+ sourceDirectoryUrl,
10040
+ assetsDirectory,
10041
+ urlInfo,
10042
+ ownerUrlInfo,
10043
+ }) => {
10044
+ if (urlInfo.dirnameHint) {
10045
+ return urlInfo.dirnameHint;
10046
+ }
10047
+ if (urlInfo.type === "directory") {
10048
+ return "";
10049
+ }
10050
+ if (urlInfo.isInline) {
10051
+ const parentDirectoryPath = determineDirectoryPath({
10052
+ sourceDirectoryUrl,
10053
+ assetsDirectory,
10054
+ urlInfo: ownerUrlInfo || urlInfo.firstReference.ownerUrlInfo,
10055
+ });
10056
+ return parentDirectoryPath;
10057
+ }
10058
+ const dynamicImportId = urlInfo.searchParams.get("dynamic_import_id");
10059
+ if (dynamicImportId) {
10060
+ return `${assetsDirectory}${dynamicImportId}/`;
10061
+ }
10062
+ if (urlInfo.isEntryPoint && !urlInfo.isDynamicEntryPoint) {
10063
+ return "";
10064
+ }
10065
+ if (urlInfo.type === "importmap") {
10066
+ return "";
10067
+ }
10068
+ if (urlInfo.type === "html") {
10069
+ return `${assetsDirectory}html/`;
10070
+ }
10071
+ if (urlInfo.type === "css") {
10072
+ return `${assetsDirectory}css/`;
10073
+ }
10074
+ if (urlInfo.type === "js_module" || urlInfo.type === "js_classic") {
10075
+ return `${assetsDirectory}js/`;
10076
+ }
10077
+ if (urlInfo.type === "json") {
10078
+ return `${assetsDirectory}json/`;
10079
+ }
10080
+ return `${assetsDirectory}other/`;
10081
+ };
10082
+
10083
+ const ensureUnixLineBreaks = (stringOrBuffer) => {
10084
+ if (typeof stringOrBuffer === "string") {
10085
+ const stringWithLinuxBreaks = stringOrBuffer.replace(/\r\n/g, "\n");
10086
+ return stringWithLinuxBreaks;
10087
+ }
10088
+ return ensureUnixLineBreaksOnBuffer(stringOrBuffer);
10089
+ };
10090
+
10091
+ // https://github.com/nodejs/help/issues/1738#issuecomment-458460503
10092
+ const ensureUnixLineBreaksOnBuffer = (buffer) => {
10093
+ const int32Array = new Int32Array(buffer, 0, buffer.length);
10094
+ const int32ArrayWithLineBreaksNormalized = int32Array.filter(
10095
+ (element, index, typedArray) => {
10096
+ if (element === 0x0d) {
10097
+ if (typedArray[index + 1] === 0x0a) {
10098
+ // Windows -> Unix
10099
+ return false;
10100
+ }
10101
+ // Mac OS -> Unix
10102
+ typedArray[index] = 0x0a;
10103
+ }
10104
+ return true;
10105
+ },
9844
10106
  );
9845
10107
  return Buffer.from(int32ArrayWithLineBreaksNormalized);
9846
10108
  };
@@ -9926,80 +10188,6 @@ const jsenvPluginMappings = (mappings) => {
9926
10188
  // url: import.meta.resolve("emoji-regex/index.js"),
9927
10189
  // });
9928
10190
 
9929
- const jsenvPluginSubbuilds = (
9930
- subBuildParamsArray,
9931
- { parentBuildParams, onCustomBuildDirectory, buildStart },
9932
- ) => {
9933
- if (subBuildParamsArray.length === 0) {
9934
- return [];
9935
- }
9936
- return subBuildParamsArray.map((subBuildParams, index) => {
9937
- const defaultChildBuildParams = {};
9938
- const childBuildParams = {
9939
- ...parentBuildParams,
9940
- logs: {
9941
- level: "warn",
9942
- disabled: true,
9943
- },
9944
- ...defaultChildBuildParams,
9945
- ...subBuildParams,
9946
- outDirectoryUrl: new URL(
9947
- `./subbuild_${index}/`,
9948
- parentBuildParams.outDirectoryUrl,
9949
- ),
9950
- };
9951
- const subBuildDirectoryUrl = subBuildParams.buildDirectoryUrl;
9952
- if (subBuildDirectoryUrl) {
9953
- const subBuildRelativeUrl = urlToRelativeUrl(
9954
- subBuildDirectoryUrl,
9955
- parentBuildParams.buildDirectoryUrl,
9956
- );
9957
- childBuildParams.base =
9958
- parentBuildParams.base === "./"
9959
- ? `./`
9960
- : subBuildParams.base ||
9961
- getDefaultBase(
9962
- childBuildParams.runtimeCompat || defaultRuntimeCompat,
9963
- );
9964
- onCustomBuildDirectory(subBuildRelativeUrl);
9965
- }
9966
- const buildPromise = buildStart(childBuildParams, index);
9967
- const entryPointBuildUrlMap = new Map();
9968
- const entryPointSourceUrlSet = new Set();
9969
- const entryPointBuildUrlSet = new Set();
9970
- const childBuildEntryPoints = childBuildParams.entryPoints;
9971
- for (const key of Object.keys(childBuildEntryPoints)) {
9972
- const entryPointUrl = new URL(key, childBuildParams.sourceDirectoryUrl)
9973
- .href;
9974
- const entryPointBuildUrl = new URL(
9975
- childBuildEntryPoints[key],
9976
- childBuildParams.buildDirectoryUrl,
9977
- ).href;
9978
- entryPointBuildUrlMap.set(entryPointUrl, entryPointBuildUrl);
9979
- entryPointSourceUrlSet.add(entryPointUrl);
9980
- entryPointBuildUrlSet.add(entryPointBuildUrl);
9981
- }
9982
-
9983
- return {
9984
- name: `jsenv:subbuild_${index}`,
9985
- redirectReference: (reference) => {
9986
- const entryPointBuildUrl = entryPointBuildUrlMap.get(reference.url);
9987
- if (!entryPointBuildUrl) {
9988
- return null;
9989
- }
9990
- return entryPointBuildUrl;
9991
- },
9992
- fetchUrlContent: async (urlInfo) => {
9993
- if (!entryPointBuildUrlSet.has(urlInfo.url)) {
9994
- return;
9995
- }
9996
- await buildPromise;
9997
- urlInfo.typeHint = "asset"; // this ensure the rest of jsenv do not scan or modify the content of this file
9998
- },
9999
- };
10000
- });
10001
- };
10002
-
10003
10191
  /*
10004
10192
  * Build is split in 3 steps:
10005
10193
  * 1. craft
@@ -10032,8 +10220,8 @@ const jsenvPluginSubbuilds = (
10032
10220
  * Keys are relative to sourceDirectoryUrl
10033
10221
  * @param {object} params.runtimeCompat
10034
10222
  * Code generated will be compatible with these runtimes
10035
- * @param {string} [params.assetsDirectory=""]
10036
- * Directory where asset files will be written
10223
+ * @param {string} [params.assetsDirectory]
10224
+ * Directory where asset files will be written. By default sibling to the entry build file.
10037
10225
  * @param {string|url} [params.base=""]
10038
10226
  * Urls in build file contents will be prefixed with this string
10039
10227
  * @param {boolean|object} [params.bundling=true]
@@ -10055,54 +10243,29 @@ const jsenvPluginSubbuilds = (
10055
10243
  * Map build file paths without versioning to versioned file paths
10056
10244
  */
10057
10245
  const build = async ({
10058
- signal = new AbortController().signal,
10059
- handleSIGINT = true,
10060
- logs = logsDefault,
10061
10246
  sourceDirectoryUrl,
10062
10247
  buildDirectoryUrl,
10063
10248
  entryPoints = {},
10064
- assetsDirectory = "",
10065
- runtimeCompat = defaultRuntimeCompat,
10066
- base = getDefaultBase(runtimeCompat),
10067
- ignore,
10249
+ logs,
10068
10250
 
10069
- mappings,
10070
- subbuilds = [],
10071
- plugins = [],
10072
- referenceAnalysis = {},
10073
- nodeEsmResolution,
10074
- magicExtensions,
10075
- magicDirectoryIndex,
10076
- directoryReferenceEffect,
10077
- scenarioPlaceholders,
10078
- injections,
10079
- transpilation = {},
10080
- bundling = true,
10081
- minification = !runtimeCompat.node,
10082
- versioning = !runtimeCompat.node,
10083
- versioningMethod = "search_param", // "filename", "search_param"
10084
- versioningViaImportmap = true,
10085
- versionLength = 8,
10086
- lineBreakNormalization = process.platform === "win32",
10251
+ outDirectoryUrl,
10252
+ buildDirectoryCleanPatterns = { "**/*": true },
10253
+ returnBuildInlineContents,
10254
+ returnBuildManifest,
10255
+ returnBuildFileVersions,
10256
+ signal = new AbortController().signal,
10257
+ handleSIGINT = true,
10258
+
10259
+ writeOnFileSystem = true,
10087
10260
 
10261
+ watch = false,
10088
10262
  sourceFilesConfig = {},
10089
10263
  cooldownBetweenFileEvents,
10090
- watch = false,
10091
- http = false,
10092
10264
 
10093
- buildDirectoryCleanPatterns = {
10094
- "**/*": true,
10095
- },
10096
- sourcemaps = "none",
10097
- sourcemapsSourcesContent,
10098
- writeOnFileSystem = true,
10099
- outDirectoryUrl,
10100
- assetManifest = versioningMethod === "filename",
10101
- assetManifestFileRelativeUrl = "asset-manifest.json",
10102
- returnBuildInlineContents,
10103
- returnBuildManifest,
10104
10265
  ...rest
10105
10266
  }) => {
10267
+ const entryPointArray = [];
10268
+
10106
10269
  // param validation
10107
10270
  {
10108
10271
  const unexpectedParamNames = Object.keys(rest);
@@ -10111,555 +10274,511 @@ const build = async ({
10111
10274
  `${unexpectedParamNames.join(",")}: there is no such param`,
10112
10275
  );
10113
10276
  }
10114
- // logs
10277
+ // source and build directory
10115
10278
  {
10116
- if (typeof logs !== "object") {
10117
- throw new TypeError(`logs must be an object, got ${logs}`);
10118
- }
10119
- const unexpectedLogsKeys = Object.keys(logs).filter(
10120
- (key) => !Object.hasOwn(logsDefault, key),
10279
+ sourceDirectoryUrl = assertAndNormalizeDirectoryUrl(
10280
+ sourceDirectoryUrl,
10281
+ "sourceDirectoryUrl",
10121
10282
  );
10122
- if (unexpectedLogsKeys.length > 0) {
10123
- throw new TypeError(
10124
- `${unexpectedLogsKeys.join(",")}: no such key on logs`,
10125
- );
10126
- }
10127
- logs = { ...logsDefault, ...logs };
10128
- }
10129
- sourceDirectoryUrl = assertAndNormalizeDirectoryUrl(
10130
- sourceDirectoryUrl,
10131
- "sourceDirectoryUrl",
10132
- );
10133
- buildDirectoryUrl = assertAndNormalizeDirectoryUrl(
10134
- buildDirectoryUrl,
10135
- "buildDirectoryUrl",
10136
- );
10137
- if (outDirectoryUrl === undefined) {
10138
- if (
10139
- process.env.CAPTURING_SIDE_EFFECTS ||
10140
- (false)
10141
- ) {
10142
- outDirectoryUrl = new URL("../.jsenv_b/", sourceDirectoryUrl);
10143
- } else {
10144
- const packageDirectoryUrl = lookupPackageDirectory(sourceDirectoryUrl);
10145
- if (packageDirectoryUrl) {
10146
- outDirectoryUrl = `${packageDirectoryUrl}.jsenv/`;
10147
- }
10148
- }
10149
- } else if (outDirectoryUrl) {
10150
- outDirectoryUrl = assertAndNormalizeDirectoryUrl(
10151
- outDirectoryUrl,
10152
- "outDirectoryUrl",
10283
+ buildDirectoryUrl = assertAndNormalizeDirectoryUrl(
10284
+ buildDirectoryUrl,
10285
+ "buildDirectoryUrl",
10153
10286
  );
10154
10287
  }
10155
-
10156
- if (typeof entryPoints !== "object" || entryPoints === null) {
10157
- throw new TypeError(`entryPoints must be an object, got ${entryPoints}`);
10158
- }
10159
- const keys = Object.keys(entryPoints);
10160
- keys.forEach((key) => {
10161
- if (!key.startsWith("./")) {
10162
- throw new TypeError(
10163
- `entryPoints keys must start with "./", found ${key}`,
10164
- );
10165
- }
10166
- const value = entryPoints[key];
10167
- if (typeof value !== "string") {
10168
- throw new TypeError(
10169
- `entryPoints values must be strings, found "${value}" on key "${key}"`,
10170
- );
10171
- }
10172
- if (value.includes("/")) {
10288
+ // entry points
10289
+ {
10290
+ if (typeof entryPoints !== "object" || entryPoints === null) {
10173
10291
  throw new TypeError(
10174
- `entryPoints values must be plain strings (no "/"), found "${value}" on key "${key}"`,
10292
+ `The value "${entryPoints}" for "entryPoints" is invalid: it must be an object.`,
10175
10293
  );
10176
10294
  }
10177
- });
10178
- if (!["filename", "search_param"].includes(versioningMethod)) {
10179
- throw new TypeError(
10180
- `versioningMethod must be "filename" or "search_param", got ${versioning}`,
10181
- );
10182
- }
10183
- if (bundling === true) {
10184
- bundling = {};
10185
- }
10186
- if (minification === true) {
10187
- minification = {};
10188
- }
10189
- }
10190
-
10191
- if (assetsDirectory && assetsDirectory[assetsDirectory.length - 1] !== "/") {
10192
- assetsDirectory = `${assetsDirectory}/`;
10193
- }
10194
-
10195
- const operation = Abort.startOperation();
10196
- operation.addAbortSignal(signal);
10197
- if (handleSIGINT) {
10198
- operation.addAbortSource((abort) => {
10199
- return raceProcessTeardownEvents(
10295
+ const keys = Object.keys(entryPoints);
10296
+ const isSingleEntryPoint = keys.length === 1;
10297
+ for (const key of keys) {
10298
+ // key (sourceRelativeUrl)
10299
+ let sourceUrl;
10300
+ let runtimeType;
10200
10301
  {
10201
- },
10202
- abort,
10203
- );
10204
- });
10205
- }
10302
+ if (isBareSpecifier(key)) {
10303
+ const packageConditions = ["development", "node", "import"];
10304
+ try {
10305
+ const { url, type } = applyNodeEsmResolution({
10306
+ conditions: packageConditions,
10307
+ parentUrl: sourceDirectoryUrl,
10308
+ specifier: key,
10309
+ });
10310
+ if (type === "field:browser") {
10311
+ runtimeType = "browser";
10312
+ }
10313
+ sourceUrl = url;
10314
+ } catch (e) {
10315
+ throw new Error(
10316
+ `The key "${key}" in "entryPoints" is invalid: it cannot be resolved.`,
10317
+ { cause: e },
10318
+ );
10319
+ }
10320
+ } else {
10321
+ if (!key.startsWith("./")) {
10322
+ throw new TypeError(
10323
+ `The key "${key}" in "entryPoints" is invalid: it must start with "./".`,
10324
+ );
10325
+ }
10206
10326
 
10207
- const runBuild = async ({ signal, logLevel }) => {
10208
- const logger = createLogger({ logLevel });
10209
- const createBuildTask = (label) => {
10210
- return createTaskLog(label, {
10211
- disabled:
10212
- logs.disabled || (!logger.levels.debug && !logger.levels.info),
10213
- animated: logs.animation && !logger.levels.debug,
10214
- });
10215
- };
10327
+ try {
10328
+ sourceUrl = new URL(key, sourceDirectoryUrl).href;
10329
+ } catch {
10330
+ throw new TypeError(
10331
+ `The key "${key}" in "entryPoints" is invalid: it must be a relative url.`,
10332
+ );
10333
+ }
10334
+ }
10335
+ if (!urlIsInsideOf(sourceUrl, sourceDirectoryUrl)) {
10336
+ throw new Error(
10337
+ `The key "${key}" in "entryPoints" is invalid: it must be inside the source directory at ${sourceDirectoryUrl}.`,
10338
+ );
10339
+ }
10216
10340
 
10217
- const buildOperation = Abort.startOperation();
10218
- buildOperation.addAbortSignal(signal);
10219
- const entryPointKeys = Object.keys(entryPoints);
10220
- if (entryPointKeys.length === 1) {
10221
- logger.info(`
10222
- build "${entryPointKeys[0]}"`);
10223
- } else {
10224
- logger.info(`
10225
- build ${entryPointKeys.length} entry points`);
10341
+ if (!runtimeType) {
10342
+ const ext = urlToExtension(sourceUrl);
10343
+ if (ext === ".html" || ext === ".css") {
10344
+ runtimeType = "browser";
10345
+ }
10346
+ }
10347
+ }
10348
+
10349
+ // value (entryPointParams)
10350
+ const value = entryPoints[key];
10351
+ {
10352
+ if (value === null || typeof value !== "object") {
10353
+ throw new TypeError(
10354
+ `The value "${value}" in "entryPoints" is invalid: it must be an object.`,
10355
+ );
10356
+ }
10357
+ const forEntryPointOrEmpty = isSingleEntryPoint
10358
+ ? ""
10359
+ : ` for entry point "${key}"`;
10360
+ const unexpectedEntryPointParamNames = Object.keys(value).filter(
10361
+ (key) => !Object.hasOwn(entryPointDefaultParams, key),
10362
+ );
10363
+ if (unexpectedEntryPointParamNames.length) {
10364
+ throw new TypeError(
10365
+ `The value${forEntryPointOrEmpty} contains unknown keys: ${unexpectedEntryPointParamNames.join(",")}.`,
10366
+ );
10367
+ }
10368
+ const { versioningMethod } = value;
10369
+ if (versioningMethod !== undefined) {
10370
+ if (!["filename", "search_param"].includes(versioningMethod)) {
10371
+ throw new TypeError(
10372
+ `The versioningMethod "${versioningMethod}"${forEntryPointOrEmpty} is invalid: it must be "filename" or "search_param".`,
10373
+ );
10374
+ }
10375
+ }
10376
+ const { buildRelativeUrl } = value;
10377
+ if (buildRelativeUrl !== undefined) {
10378
+ let buildUrl;
10379
+ try {
10380
+ buildUrl = new URL(buildRelativeUrl, buildDirectoryUrl);
10381
+ } catch {
10382
+ throw new TypeError(
10383
+ `The buildRelativeUrl "${buildRelativeUrl}"${forEntryPointOrEmpty} is invalid: it must be a relative url.`,
10384
+ );
10385
+ }
10386
+ if (!urlIsInsideOf(buildUrl, buildDirectoryUrl)) {
10387
+ throw new Error(
10388
+ `The buildRelativeUrl "${buildRelativeUrl}"${forEntryPointOrEmpty} is invalid: it must be inside the build directory at ${buildDirectoryUrl}.`,
10389
+ );
10390
+ }
10391
+ }
10392
+ const { runtimeCompat } = value;
10393
+ if (runtimeCompat !== undefined) {
10394
+ if (runtimeCompat === null || typeof runtimeCompat !== "object") {
10395
+ throw new TypeError(
10396
+ `The runtimeCompat "${runtimeCompat}"${forEntryPointOrEmpty} is invalid: it must be an object.`,
10397
+ );
10398
+ }
10399
+ }
10400
+ }
10401
+
10402
+ entryPointArray.push({
10403
+ key,
10404
+ sourceUrl,
10405
+ sourceRelativeUrl: `./${urlToRelativeUrl(sourceUrl, sourceDirectoryUrl)}`,
10406
+ params: { ...value },
10407
+ runtimeType,
10408
+ });
10409
+ }
10226
10410
  }
10227
- let explicitJsModuleConversion = false;
10228
- for (const entryPointKey of entryPointKeys) {
10229
- if (entryPointKey.includes("?js_module_fallback")) {
10230
- explicitJsModuleConversion = true;
10231
- break;
10411
+ // logs
10412
+ if (logs === undefined) {
10413
+ logs = logsDefault;
10414
+ } else {
10415
+ if (typeof logs !== "object") {
10416
+ throw new TypeError(
10417
+ `The value "${logs}" is invalid for param logs: it must be an object.`,
10418
+ );
10232
10419
  }
10233
- if (entryPointKey.includes("?as_js_classic")) {
10234
- explicitJsModuleConversion = true;
10235
- break;
10420
+ const unexpectedLogsKeys = Object.keys(logs).filter(
10421
+ (key) => !Object.hasOwn(logsDefault, key),
10422
+ );
10423
+ if (unexpectedLogsKeys.length > 0) {
10424
+ throw new TypeError(
10425
+ `The param logs have unknown params: ${unexpectedLogsKeys.join(",")}.`,
10426
+ );
10236
10427
  }
10237
10428
  }
10238
- const entryUrls = [];
10239
- const contextSharedDuringBuild = {
10240
- buildStep: "craft",
10241
- buildDirectoryUrl,
10242
- assetsDirectory,
10243
- versioning,
10244
- versioningViaImportmap,
10245
- };
10246
- const rawKitchen = createKitchen({
10247
- signal,
10248
- logLevel: logs.level,
10249
- rootDirectoryUrl: sourceDirectoryUrl,
10250
- ignore,
10251
- // during first pass (craft) we keep "ignore:" when a reference is ignored
10252
- // so that the second pass (shape) properly ignore those urls
10253
- ignoreProtocol: "keep",
10254
- build: true,
10255
- runtimeCompat,
10256
- initialContext: contextSharedDuringBuild,
10257
- sourcemaps,
10258
- sourcemapsSourcesContent,
10259
- outDirectoryUrl: outDirectoryUrl
10260
- ? new URL("craft/", outDirectoryUrl)
10261
- : undefined,
10429
+ if (outDirectoryUrl !== undefined) {
10430
+ outDirectoryUrl = assertAndNormalizeDirectoryUrl(
10431
+ outDirectoryUrl,
10432
+ "outDirectoryUrl",
10433
+ );
10434
+ }
10435
+ }
10436
+
10437
+ const operation = Abort.startOperation();
10438
+ operation.addAbortSignal(signal);
10439
+ if (handleSIGINT) {
10440
+ operation.addAbortSource((abort) => {
10441
+ return raceProcessTeardownEvents(
10442
+ {
10443
+ },
10444
+ abort,
10445
+ );
10262
10446
  });
10447
+ }
10263
10448
 
10264
- let subbuildResults = [];
10449
+ const cpuMonitoring = startMonitoringCpuUsage();
10450
+ operation.addEndCallback(cpuMonitoring.stop);
10451
+ const [processCpuUsageMonitoring] = cpuMonitoring;
10452
+ const memoryMonitoring = startMonitoringMemoryUsage();
10453
+ const [processMemoryUsageMonitoring] = memoryMonitoring;
10454
+ const interval = setInterval(() => {
10455
+ processCpuUsageMonitoring.measure();
10456
+ processMemoryUsageMonitoring.measure();
10457
+ }, 500).unref();
10458
+ operation.addEndCallback(() => {
10459
+ clearInterval(interval);
10460
+ });
10265
10461
 
10266
- const rawPluginStore = createPluginStore([
10267
- ...(mappings ? [jsenvPluginMappings(mappings)] : []),
10268
- ...jsenvPluginSubbuilds(subbuilds, {
10269
- parentBuildParams: {
10270
- sourceDirectoryUrl,
10271
- buildDirectoryUrl,
10272
- runtimeCompat,
10273
- bundling,
10274
- minification,
10275
- versioning,
10276
- versioningMethod,
10277
- outDirectoryUrl,
10278
- base,
10279
- },
10280
- onCustomBuildDirectory: (subBuildRelativeUrl) => {
10281
- buildDirectoryCleanPatterns = {
10282
- ...buildDirectoryCleanPatterns,
10283
- [`${subBuildRelativeUrl}**/*`]: false,
10284
- };
10285
- },
10286
- buildStart: async (params, index) => {
10287
- const result = await build({
10288
- ...params,
10289
- signal,
10290
- handleSIGINT: false,
10291
- });
10292
- subbuildResults[index] = result;
10293
- return result;
10294
- },
10295
- }),
10296
- ...plugins,
10297
- ...(bundling ? [jsenvPluginBundling(bundling)] : []),
10298
- ...(minification ? [jsenvPluginMinification(minification)] : []),
10299
- ...getCorePlugins({
10300
- rootDirectoryUrl: sourceDirectoryUrl,
10301
- runtimeCompat,
10302
- referenceAnalysis,
10303
- nodeEsmResolution,
10304
- magicExtensions,
10305
- magicDirectoryIndex,
10306
- directoryReferenceEffect,
10307
- injections,
10308
- transpilation: {
10309
- babelHelpersAsImport: !explicitJsModuleConversion,
10310
- ...transpilation,
10311
- jsModuleFallback: false,
10312
- },
10313
- inlining: false,
10314
- http,
10315
- scenarioPlaceholders,
10316
- }),
10317
- ]);
10318
- const rawPluginController = createPluginController(
10319
- rawPluginStore,
10320
- rawKitchen,
10321
- );
10322
- rawKitchen.setPluginController(rawPluginController);
10462
+ const logLevel = logs.level;
10463
+ const logger = createLogger({ logLevel });
10464
+ const animatedLogEnabled =
10465
+ logs.animated &&
10466
+ // canEraseProcessStdout
10467
+ process.stdout.isTTY &&
10468
+ // if there is an error during execution npm will mess up the output
10469
+ // (happens when npm runs several command in a workspace)
10470
+ // so we enable hot replace only when !process.exitCode (no error so far)
10471
+ process.exitCode !== 1;
10472
+ let startBuildLogs = () => {};
10473
+
10474
+ const renderEntyPointBuildDoneLog = (
10475
+ entryBuildInfo,
10476
+ { sourceUrlToLog, buildUrlToLog },
10477
+ ) => {
10478
+ let content = "";
10479
+ content += `${UNICODE.OK} ${ANSI.color(sourceUrlToLog, ANSI.GREY)} ${ANSI.color("->", ANSI.GREY)} ${ANSI.color(buildUrlToLog, "")}`;
10480
+ // content += " ";
10481
+ // content += ANSI.color("(", ANSI.GREY);
10482
+ // content += ANSI.color(
10483
+ // humanizeDuration(entryBuildInfo.duration, { short: true }),
10484
+ // ANSI.GREY,
10485
+ // );
10486
+ // content += ANSI.color(")", ANSI.GREY);
10487
+ content += "\n";
10488
+ return content;
10489
+ };
10490
+ const renderBuildEndLog = ({ duration, buildFileContents }) => {
10491
+ // tell how many files are generated in build directory
10492
+ // tell the repartition?
10493
+ // this is not really useful for single build right?
10494
+
10495
+ processCpuUsageMonitoring.end();
10496
+ processMemoryUsageMonitoring.end();
10497
+
10498
+ return renderBuildDoneLog({
10499
+ duration,
10500
+ buildFileContents,
10501
+ processCpuUsage: processCpuUsageMonitoring.info,
10502
+ processMemoryUsage: processMemoryUsageMonitoring.info,
10503
+ });
10504
+ };
10323
10505
 
10324
- {
10325
- const generateSourceGraph = createBuildTask("generate source graph");
10326
- try {
10327
- if (outDirectoryUrl) {
10328
- await ensureEmptyDirectory(new URL(`craft/`, outDirectoryUrl));
10329
- }
10330
- const rawRootUrlInfo = rawKitchen.graph.rootUrlInfo;
10331
- await rawRootUrlInfo.dependencies.startCollecting(() => {
10332
- Object.keys(entryPoints).forEach((key) => {
10333
- const entryReference = rawRootUrlInfo.dependencies.found({
10334
- trace: { message: `"${key}" in entryPoints parameter` },
10335
- isEntryPoint: true,
10336
- type: "entry_point",
10337
- specifier: key,
10338
- filenameHint: entryPoints[key],
10339
- });
10340
- entryUrls.push(entryReference.url);
10341
- });
10506
+ if (animatedLogEnabled) {
10507
+ startBuildLogs = () => {
10508
+ const startMs = Date.now();
10509
+ let dynamicLog = createDynamicLog();
10510
+ const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
10511
+ let frameIndex = 0;
10512
+ let oneWrite = false;
10513
+ const memoryHeapUsedAtStart = memoryUsage().heapUsed;
10514
+ const renderDynamicLog = () => {
10515
+ frameIndex = frameIndex === frames.length - 1 ? 0 : frameIndex + 1;
10516
+ let dynamicLogContent = "";
10517
+ dynamicLogContent += `${frames[frameIndex]} `;
10518
+ dynamicLogContent += `building ${entryPointArray.length} entry points`;
10519
+
10520
+ const msEllapsed = Date.now() - startMs;
10521
+ const infos = [];
10522
+ const duration = humanizeDuration(msEllapsed, {
10523
+ short: true,
10524
+ decimals: 0,
10525
+ rounded: false,
10342
10526
  });
10343
- await rawRootUrlInfo.cookDependencies({
10344
- operation: buildOperation,
10527
+ infos.push(ANSI.color(duration, ANSI.GREY));
10528
+ let memoryUsageColor = ANSI.GREY;
10529
+ const memoryHeapUsed = memoryUsage().heapUsed;
10530
+ if (memoryHeapUsed > 2.5 * memoryHeapUsedAtStart) {
10531
+ memoryUsageColor = ANSI.YELLOW;
10532
+ } else if (memoryHeapUsed > 1.5 * memoryHeapUsedAtStart) {
10533
+ memoryUsageColor = null;
10534
+ }
10535
+ const memoryHeapUsedFormatted = humanizeMemory(memoryHeapUsed, {
10536
+ short: true,
10537
+ decimals: 0,
10345
10538
  });
10346
- } catch (e) {
10347
- generateSourceGraph.fail();
10348
- throw e;
10349
- }
10350
- generateSourceGraph.done();
10351
- }
10352
-
10353
- const finalKitchen = createKitchen({
10354
- name: "shape",
10355
- logLevel: logs.level,
10356
- rootDirectoryUrl: sourceDirectoryUrl,
10357
- // here most plugins are not there
10358
- // - no external plugin
10359
- // - no plugin putting reference.mustIgnore on https urls
10360
- // At this stage it's only about redirecting urls to the build directory
10361
- // consequently only a subset or urls are supported
10362
- supportedProtocols: ["file:", "data:", "virtual:", "ignore:"],
10363
- ignore,
10364
- ignoreProtocol: "remove",
10365
- build: true,
10366
- runtimeCompat,
10367
- initialContext: contextSharedDuringBuild,
10368
- sourcemaps,
10369
- sourcemapsComment: "relative",
10370
- sourcemapsSourcesContent,
10371
- outDirectoryUrl: outDirectoryUrl
10372
- ? new URL("shape/", outDirectoryUrl)
10373
- : undefined,
10374
- });
10375
- const buildSpecifierManager = createBuildSpecifierManager({
10376
- rawKitchen,
10377
- finalKitchen,
10378
- logger,
10379
- sourceDirectoryUrl,
10380
- buildDirectoryUrl,
10381
- base,
10382
- assetsDirectory,
10539
+ infos.push(ANSI.color(memoryHeapUsedFormatted, memoryUsageColor));
10383
10540
 
10384
- versioning,
10385
- versioningMethod,
10386
- versionLength,
10387
- canUseImportmap:
10388
- versioningViaImportmap &&
10389
- entryUrls.every((finalEntryUrl) => {
10390
- const entryUrlInfo = rawKitchen.graph.getUrlInfo(finalEntryUrl);
10391
- return entryUrlInfo.type === "html";
10392
- }) &&
10393
- rawKitchen.context.isSupportedOnCurrentClients("importmap"),
10394
- });
10395
- const finalPluginStore = createPluginStore([
10396
- jsenvPluginReferenceAnalysis({
10397
- ...referenceAnalysis,
10398
- fetchInlineUrls: false,
10399
- // inlineContent: false,
10400
- }),
10401
- jsenvPluginDirectoryReferenceEffect(directoryReferenceEffect),
10402
- ...(lineBreakNormalization ? [jsenvPluginLineBreakNormalization()] : []),
10403
- jsenvPluginJsModuleFallback({
10404
- remapImportSpecifier: (specifier, parentUrl) => {
10405
- return buildSpecifierManager.remapPlaceholder(specifier, parentUrl);
10406
- },
10407
- }),
10408
- jsenvPluginInlining(),
10409
- {
10410
- name: "jsenv:optimize",
10411
- appliesDuring: "build",
10412
- transformUrlContent: async (urlInfo) => {
10413
- await rawKitchen.pluginController.callAsyncHooks(
10414
- "optimizeUrlContent",
10415
- urlInfo,
10416
- (optimizeReturnValue) => {
10417
- urlInfo.mutateContent(optimizeReturnValue);
10418
- },
10419
- );
10420
- },
10421
- },
10422
- buildSpecifierManager.jsenvPluginMoveToBuildDirectory,
10423
- ]);
10424
- const finalPluginController = createPluginController(
10425
- finalPluginStore,
10426
- finalKitchen,
10427
- {
10428
- initialPuginsMeta: rawKitchen.pluginController.pluginsMeta,
10429
- },
10430
- );
10431
- finalKitchen.setPluginController(finalPluginController);
10541
+ const infoFormatted = infos.join(ANSI.color(`/`, ANSI.GREY));
10542
+ dynamicLogContent += ` ${ANSI.color(
10543
+ "[",
10544
+ ANSI.GREY,
10545
+ )}${infoFormatted}${ANSI.color("]", ANSI.GREY)}`;
10432
10546
 
10433
- const bundlers = {};
10434
- {
10435
- for (const plugin of rawKitchen.pluginController.activePlugins) {
10436
- const bundle = plugin.bundle;
10437
- if (!bundle) {
10438
- continue;
10439
- }
10440
- if (typeof bundle !== "object") {
10441
- throw new Error(
10442
- `bundle must be an object, found "${bundle}" on plugin named "${plugin.name}"`,
10443
- );
10444
- }
10445
- for (const type of Object.keys(bundle)) {
10446
- const bundleFunction = bundle[type];
10447
- if (!bundleFunction) {
10448
- continue;
10449
- }
10450
- const bundlerForThatType = bundlers[type];
10451
- if (bundlerForThatType) {
10452
- // first plugin to define a bundle hook wins
10453
- continue;
10454
- }
10455
- bundlers[type] = {
10456
- plugin,
10457
- bundleFunction: bundle[type],
10458
- urlInfoMap: new Map(),
10459
- };
10460
- }
10461
- }
10462
- const addToBundlerIfAny = (rawUrlInfo) => {
10463
- const bundler = bundlers[rawUrlInfo.type];
10464
- if (bundler) {
10465
- bundler.urlInfoMap.set(rawUrlInfo.url, rawUrlInfo);
10547
+ if (oneWrite) {
10548
+ dynamicLogContent = `\n${dynamicLogContent}`;
10466
10549
  }
10550
+ dynamicLogContent = `${dynamicLogContent}\n`;
10551
+ return dynamicLogContent;
10467
10552
  };
10468
- // ignore unused urls thanks to "forEachUrlInfoStronglyReferenced"
10469
- // it avoid bundling things that are not actually used
10470
- // happens for:
10471
- // - js import assertions
10472
- // - conversion to js classic using ?as_js_classic or ?js_module_fallback
10473
- GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
10474
- rawKitchen.graph.rootUrlInfo,
10475
- (rawUrlInfo) => {
10476
- if (rawUrlInfo.isEntryPoint) {
10477
- addToBundlerIfAny(rawUrlInfo);
10478
- }
10479
- if (rawUrlInfo.type === "html") {
10480
- for (const referenceToOther of rawUrlInfo.referenceToOthersSet) {
10481
- if (
10482
- referenceToOther.isResourceHint &&
10483
- referenceToOther.expectedType === "js_module"
10484
- ) {
10485
- const referencedUrlInfo = referenceToOther.urlInfo;
10486
- if (
10487
- referencedUrlInfo &&
10488
- // something else than the resource hint is using this url
10489
- referencedUrlInfo.referenceFromOthersSet.size > 0
10490
- ) {
10491
- addToBundlerIfAny(referencedUrlInfo);
10492
- continue;
10493
- }
10494
- }
10495
- if (referenceToOther.isWeak) {
10496
- continue;
10497
- }
10498
- const referencedUrlInfo = referenceToOther.urlInfo;
10499
- if (referencedUrlInfo.isInline) {
10500
- if (referencedUrlInfo.type !== "js_module") {
10501
- continue;
10502
- }
10503
- addToBundlerIfAny(referencedUrlInfo);
10504
- continue;
10505
- }
10506
- addToBundlerIfAny(referencedUrlInfo);
10507
- }
10508
- return;
10509
- }
10510
- // File referenced with
10511
- // - new URL("./file.js", import.meta.url)
10512
- // - import.meta.resolve("./file.js")
10513
- // are entry points that should be bundled
10514
- // For instance we will bundle service worker/workers detected like this
10515
- if (rawUrlInfo.type === "js_module") {
10516
- for (const referenceToOther of rawUrlInfo.referenceToOthersSet) {
10517
- if (
10518
- referenceToOther.type === "js_url" ||
10519
- referenceToOther.subtype === "import_meta_resolve"
10520
- ) {
10521
- const referencedUrlInfo = referenceToOther.urlInfo;
10522
- let isAlreadyBundled = false;
10523
- for (const referenceFromOther of referencedUrlInfo.referenceFromOthersSet) {
10524
- if (referenceFromOther.url === referencedUrlInfo.url) {
10525
- if (
10526
- referenceFromOther.subtype === "import_dynamic" ||
10527
- referenceFromOther.type === "script"
10528
- ) {
10529
- isAlreadyBundled = true;
10530
- break;
10531
- }
10532
- }
10533
- }
10534
- if (!isAlreadyBundled) {
10535
- addToBundlerIfAny(referencedUrlInfo);
10536
- }
10537
- continue;
10538
- }
10539
- if (referenceToOther.type === "js_inline_content") ;
10540
- }
10541
- }
10553
+ dynamicLog.update(renderDynamicLog());
10554
+ const interval = setInterval(() => {
10555
+ dynamicLog.update(renderDynamicLog());
10556
+ }, 150).unref();
10557
+ signal.addEventListener("abort", () => {
10558
+ clearInterval(interval);
10559
+ });
10560
+ return {
10561
+ onEntryPointBuildStart: (
10562
+ entryBuildInfo,
10563
+ { sourceUrlToLog, buildUrlToLog },
10564
+ ) => {
10565
+ return () => {
10566
+ oneWrite = true;
10567
+ dynamicLog.clearDuringFunctionCall((write) => {
10568
+ const log = renderEntyPointBuildDoneLog(entryBuildInfo, {
10569
+ sourceUrlToLog,
10570
+ buildUrlToLog,
10571
+ });
10572
+ write(log);
10573
+ }, renderDynamicLog());
10574
+ };
10542
10575
  },
10543
- );
10544
- for (const type of Object.keys(bundlers)) {
10545
- const bundler = bundlers[type];
10546
- const urlInfosToBundle = Array.from(bundler.urlInfoMap.values());
10547
- if (urlInfosToBundle.length === 0) {
10548
- continue;
10549
- }
10550
- const bundleTask = createBuildTask(`bundle "${type}"`);
10551
- try {
10552
- await buildSpecifierManager.applyBundling({
10553
- bundler,
10554
- urlInfosToBundle,
10555
- });
10556
- } catch (e) {
10557
- bundleTask.fail();
10558
- throw e;
10559
- }
10560
- bundleTask.done();
10576
+ onBuildEnd: ({ buildFileContents, duration }) => {
10577
+ clearInterval(interval);
10578
+ dynamicLog.update("");
10579
+ dynamicLog.destroy();
10580
+ dynamicLog = null;
10581
+ logger.info("");
10582
+ logger.info(renderBuildEndLog({ duration, buildFileContents }));
10583
+ },
10584
+ };
10585
+ };
10586
+ } else {
10587
+ startBuildLogs = () => {
10588
+ if (entryPointArray.length === 1) {
10589
+ const [singleEntryPoint] = entryPointArray;
10590
+ logger.info(`building ${singleEntryPoint.key}`);
10591
+ } else {
10592
+ logger.info(`building ${entryPointArray.length} entry points`);
10561
10593
  }
10562
- }
10594
+ logger.info("");
10595
+ return {
10596
+ onEntryPointBuildStart: (
10597
+ entryBuildInfo,
10598
+ { sourceUrlToLog, buildUrlToLog },
10599
+ ) => {
10600
+ return () => {
10601
+ logger.info(
10602
+ renderEntyPointBuildDoneLog(entryBuildInfo, {
10603
+ sourceUrlToLog,
10604
+ buildUrlToLog,
10605
+ }),
10606
+ );
10607
+ };
10608
+ },
10609
+ onBuildEnd: ({ buildFileContents, duration }) => {
10610
+ logger.info("");
10611
+ logger.info(renderBuildEndLog({ duration, buildFileContents }));
10612
+ },
10613
+ };
10614
+ };
10615
+ }
10563
10616
 
10564
- {
10565
- finalKitchen.context.buildStep = "shape";
10566
- const generateBuildGraph = createBuildTask("generate build graph");
10567
- try {
10568
- if (outDirectoryUrl) {
10569
- await ensureEmptyDirectory(new URL(`shape/`, outDirectoryUrl));
10570
- }
10571
- const finalRootUrlInfo = finalKitchen.graph.rootUrlInfo;
10572
- await finalRootUrlInfo.dependencies.startCollecting(() => {
10573
- entryUrls.forEach((entryUrl) => {
10574
- finalRootUrlInfo.dependencies.found({
10575
- trace: { message: `entryPoint` },
10576
- isEntryPoint: true,
10577
- type: "entry_point",
10578
- specifier: entryUrl,
10579
- });
10580
- });
10581
- });
10582
- await finalRootUrlInfo.cookDependencies({
10583
- operation: buildOperation,
10584
- });
10585
- } catch (e) {
10586
- generateBuildGraph.fail();
10587
- throw e;
10588
- }
10589
- generateBuildGraph.done();
10590
- }
10617
+ // we want to start building the entry point that are deeper
10618
+ // - they are more likely to be small
10619
+ // - they are more likely to be referenced by highter files that will depend on them
10620
+ entryPointArray.sort((a, b) => {
10621
+ return compareFileUrls(a.sourceUrl, b.sourceUrl);
10622
+ });
10591
10623
 
10592
- {
10593
- finalKitchen.context.buildStep = "refine";
10624
+ const packageDirectoryUrl = lookupPackageDirectory(sourceDirectoryUrl);
10625
+ if (outDirectoryUrl === undefined) {
10626
+ if (
10627
+ process.env.CAPTURING_SIDE_EFFECTS ||
10628
+ (false)
10629
+ ) {
10630
+ outDirectoryUrl = new URL("../.jsenv_b/", sourceDirectoryUrl).href;
10631
+ } else if (packageDirectoryUrl) {
10632
+ outDirectoryUrl = `${packageDirectoryUrl}.jsenv/`;
10633
+ }
10634
+ }
10635
+ let rootPackageDirectoryUrl = packageDirectoryUrl;
10636
+ if (packageDirectoryUrl) {
10637
+ const parentPackageDirectoryUrl = lookupPackageDirectory(
10638
+ new URL("../", packageDirectoryUrl),
10639
+ );
10640
+ if (parentPackageDirectoryUrl) {
10641
+ rootPackageDirectoryUrl = parentPackageDirectoryUrl;
10642
+ }
10643
+ }
10594
10644
 
10595
- const htmlRefineSet = new Set();
10596
- const registerHtmlRefine = (htmlRefine) => {
10597
- htmlRefineSet.add(htmlRefine);
10598
- };
10645
+ const runBuild = async ({ signal }) => {
10646
+ const startDate = Date.now();
10647
+ const { onBuildEnd, onEntryPointBuildStart } = startBuildLogs();
10599
10648
 
10600
- {
10601
- await buildSpecifierManager.replacePlaceholders();
10602
- }
10649
+ const buildUrlsGenerator = createBuildUrlsGenerator({
10650
+ sourceDirectoryUrl,
10651
+ buildDirectoryUrl,
10652
+ });
10603
10653
 
10604
- /*
10605
- * Update <link rel="preload"> and friends after build (once we know everything)
10606
- * - Used to remove resource hint targeting an url that is no longer used:
10607
- * - because of bundlings
10608
- * - because of import assertions transpilation (file is inlined into JS)
10609
- */
10610
- {
10611
- buildSpecifierManager.prepareResyncResourceHints({
10612
- registerHtmlRefine,
10613
- });
10654
+ let someEntryPointUseNode = false;
10655
+ for (const entryPoint of entryPointArray) {
10656
+ let { runtimeCompat } = entryPoint.params;
10657
+ if (runtimeCompat === undefined) {
10658
+ const runtimeCompatFromPackage = inferRuntimeCompatFromClosestPackage(
10659
+ entryPoint.sourceUrl,
10660
+ {
10661
+ runtimeType: entryPoint.runtimeType,
10662
+ },
10663
+ );
10664
+ if (runtimeCompatFromPackage) {
10665
+ entryPoint.params.runtimeCompat = runtimeCompat =
10666
+ runtimeCompatFromPackage;
10667
+ } else {
10668
+ entryPoint.params.runtimeCompat = runtimeCompat =
10669
+ entryPoint.runtimeType === "browser"
10670
+ ? browserDefaultRuntimeCompat
10671
+ : nodeDefaultRuntimeCompat;
10672
+ }
10614
10673
  }
10615
-
10616
- {
10617
- GRAPH_VISITOR.forEach(finalKitchen.graph, (urlInfo) => {
10618
- if (!urlInfo.url.startsWith("file:")) {
10619
- return;
10620
- }
10621
- if (urlInfo.type !== "html") {
10622
- return;
10623
- }
10624
- const htmlAst = parseHtml({
10625
- html: urlInfo.content,
10626
- url: urlInfo.url,
10627
- storeOriginalPositions: false,
10628
- });
10629
- for (const htmlRefine of htmlRefineSet) {
10630
- const htmlMutationCallbackSet = new Set();
10631
- const registerHtmlMutation = (callback) => {
10632
- htmlMutationCallbackSet.add(callback);
10633
- };
10634
- htmlRefine(htmlAst, { registerHtmlMutation });
10635
- for (const htmlMutationCallback of htmlMutationCallbackSet) {
10636
- htmlMutationCallback();
10637
- }
10638
- }
10639
- // cleanup jsenv attributes from html as a last step
10640
- urlInfo.content = stringifyHtmlAst(htmlAst, {
10641
- cleanupJsenvAttributes: true,
10642
- cleanupPositionAttributes: true,
10643
- });
10644
- });
10674
+ if (!someEntryPointUseNode && "node" in runtimeCompat) {
10675
+ someEntryPointUseNode = true;
10645
10676
  }
10677
+ }
10646
10678
 
10647
- {
10648
- const inject = buildSpecifierManager.prepareServiceWorkerUrlInjection();
10649
- if (inject) {
10650
- const urlsInjectionInSw = createBuildTask(
10651
- "inject urls in service worker",
10652
- );
10653
- await inject();
10654
- urlsInjectionInSw.done();
10655
- buildOperation.throwIfAborted();
10656
- }
10679
+ const entryBuildInfoMap = new Map();
10680
+ let entryPointIndex = 0;
10681
+ const entryOutDirSet = new Set();
10682
+ for (const entryPoint of entryPointArray) {
10683
+ let entryOutDirCandidate = `entry_${urlToBasename(entryPoint.sourceRelativeUrl)}/`;
10684
+ let entryInteger = 1;
10685
+ while (entryOutDirSet.has(entryOutDirCandidate)) {
10686
+ entryInteger++;
10687
+ entryOutDirCandidate = `entry_${urlToBasename(entryPoint.sourceRelativeUrl)}_${entryInteger}/`;
10657
10688
  }
10689
+ const entryOutDirname = entryOutDirCandidate;
10690
+ entryOutDirSet.add(entryOutDirname);
10691
+ const entryOutDirectoryUrl = new URL(entryOutDirname, outDirectoryUrl);
10692
+ const { entryReference, buildEntryPoint } = await prepareEntryPointBuild(
10693
+ {
10694
+ signal,
10695
+ sourceDirectoryUrl,
10696
+ buildDirectoryUrl,
10697
+ outDirectoryUrl: entryOutDirectoryUrl,
10698
+ sourceRelativeUrl: entryPoint.sourceRelativeUrl,
10699
+ buildUrlsGenerator,
10700
+ someEntryPointUseNode,
10701
+ },
10702
+ entryPoint.params,
10703
+ );
10704
+ const entryPointBuildRelativeUrl = entryPoint.params.buildRelativeUrl;
10705
+ const entryBuildInfo = {
10706
+ index: entryPointIndex,
10707
+ entryReference,
10708
+ entryUrlInfo: entryReference.urlInfo,
10709
+ buildRelativeUrl: entryPointBuildRelativeUrl,
10710
+ buildFileContents: undefined,
10711
+ buildFileVersions: undefined,
10712
+ buildInlineContents: undefined,
10713
+ buildManifest: undefined,
10714
+ duration: null,
10715
+ buildEntryPoint: () => {
10716
+ const sourceUrl = new URL(
10717
+ entryPoint.sourceRelativeUrl,
10718
+ sourceDirectoryUrl,
10719
+ );
10720
+ const buildUrl = new URL(
10721
+ entryPointBuildRelativeUrl,
10722
+ buildDirectoryUrl,
10723
+ );
10724
+ const sourceUrlToLog = rootPackageDirectoryUrl
10725
+ ? urlToRelativeUrl(sourceUrl, rootPackageDirectoryUrl)
10726
+ : entryPoint.key;
10727
+ const buildUrlToLog = rootPackageDirectoryUrl
10728
+ ? urlToRelativeUrl(buildUrl, rootPackageDirectoryUrl)
10729
+ : entryPointBuildRelativeUrl;
10730
+
10731
+ const entryPointBuildStartMs = Date.now();
10732
+ const onEntryPointBuildEnd = onEntryPointBuildStart(entryBuildInfo, {
10733
+ sourceUrlToLog,
10734
+ buildUrlToLog,
10735
+ });
10736
+ const promise = (async () => {
10737
+ const result = await buildEntryPoint({
10738
+ getOtherEntryBuildInfo: (url) => {
10739
+ if (url === entryReference.url) {
10740
+ return null;
10741
+ }
10742
+ const otherEntryBuildInfo = entryBuildInfoMap.get(url);
10743
+ if (!otherEntryBuildInfo) {
10744
+ return null;
10745
+ }
10746
+ return otherEntryBuildInfo;
10747
+ },
10748
+ });
10749
+ entryBuildInfo.buildFileContents = result.buildFileContents;
10750
+ entryBuildInfo.buildFileVersions = result.buildFileVersions;
10751
+ entryBuildInfo.buildInlineContents = result.buildInlineContents;
10752
+ entryBuildInfo.buildManifest = result.buildManifest;
10753
+ entryBuildInfo.duration = Date.now() - entryPointBuildStartMs;
10754
+ onEntryPointBuildEnd();
10755
+ })();
10756
+ entryBuildInfo.promise = promise;
10757
+ return promise;
10758
+ },
10759
+ };
10760
+ entryBuildInfoMap.set(entryReference.url, entryBuildInfo);
10761
+ entryPointIndex++;
10762
+ }
10763
+
10764
+ const promises = [];
10765
+ for (const [, entryBuildInfo] of entryBuildInfoMap) {
10766
+ const promise = entryBuildInfo.buildEntryPoint();
10767
+ promises.push(promise);
10768
+ }
10769
+ await Promise.all(promises);
10770
+
10771
+ const buildFileContents = {};
10772
+ const buildFileVersions = {};
10773
+ const buildInlineContents = {};
10774
+ const buildManifest = {};
10775
+ for (const [, entryBuildInfo] of entryBuildInfoMap) {
10776
+ Object.assign(buildFileContents, entryBuildInfo.buildFileContents);
10777
+ Object.assign(buildFileVersions, entryBuildInfo.buildFileVersions);
10778
+ Object.assign(buildInlineContents, entryBuildInfo.buildInlineContents);
10779
+ Object.assign(buildManifest, entryBuildInfo.buildManifest);
10658
10780
  }
10659
- const { buildFileContents, buildInlineContents, buildManifest } =
10660
- buildSpecifierManager.getBuildInfo();
10661
10781
  if (writeOnFileSystem) {
10662
- const writingFiles = createBuildTask("write files in build directory");
10663
10782
  clearDirectorySync(buildDirectoryUrl, buildDirectoryCleanPatterns);
10664
10783
  const buildRelativeUrls = Object.keys(buildFileContents);
10665
10784
  buildRelativeUrls.forEach((buildRelativeUrl) => {
@@ -10668,23 +10787,17 @@ build ${entryPointKeys.length} entry points`);
10668
10787
  buildFileContents[buildRelativeUrl],
10669
10788
  );
10670
10789
  });
10671
- if (versioning && assetManifest && Object.keys(buildManifest).length) {
10672
- writeFileSync(
10673
- new URL(assetManifestFileRelativeUrl, buildDirectoryUrl),
10674
- JSON.stringify(buildManifest, null, " "),
10675
- );
10676
- }
10677
- writingFiles.done();
10678
10790
  }
10679
- logger.info(
10680
- createUrlGraphSummary(finalKitchen.graph, {
10681
- title: "build files",
10682
- }),
10683
- );
10791
+ onBuildEnd({
10792
+ buildFileContents,
10793
+ buildInlineContents,
10794
+ buildManifest,
10795
+ duration: Date.now() - startDate,
10796
+ });
10684
10797
  return {
10685
10798
  ...(returnBuildInlineContents ? { buildInlineContents } : {}),
10686
10799
  ...(returnBuildManifest ? { buildManifest } : {}),
10687
- ...(subbuilds.length ? { subbuilds: subbuildResults } : {}),
10800
+ ...(returnBuildFileVersions ? { buildFileVersions } : {}),
10688
10801
  };
10689
10802
  };
10690
10803
 
@@ -10692,7 +10805,6 @@ build ${entryPointKeys.length} entry points`);
10692
10805
  try {
10693
10806
  const result = await runBuild({
10694
10807
  signal: operation.signal,
10695
- logLevel: logs.level,
10696
10808
  });
10697
10809
  return result;
10698
10810
  } finally {
@@ -10765,4 +10877,583 @@ build ${entryPointKeys.length} entry points`);
10765
10877
  return stopWatchingSourceFiles;
10766
10878
  };
10767
10879
 
10880
+ const entryPointDefaultParams = {
10881
+ buildRelativeUrl: undefined,
10882
+ runtimeCompat: defaultRuntimeCompat,
10883
+ plugins: [],
10884
+ mappings: undefined,
10885
+ assetsDirectory: undefined,
10886
+ base: undefined,
10887
+ ignore: undefined,
10888
+
10889
+ bundling: true,
10890
+ minification: true,
10891
+ versioning: true,
10892
+
10893
+ referenceAnalysis: {},
10894
+ nodeEsmResolution: undefined,
10895
+ packageConditions: undefined,
10896
+ magicExtensions: undefined,
10897
+ magicDirectoryIndex: undefined,
10898
+ directoryReferenceEffect: undefined,
10899
+ scenarioPlaceholders: undefined,
10900
+ injections: undefined,
10901
+ transpilation: {},
10902
+
10903
+ versioningMethod: "search_param", // "filename", "search_param"
10904
+ versioningViaImportmap: true,
10905
+ versionLength: 8,
10906
+ lineBreakNormalization: process.platform === "win32",
10907
+
10908
+ http: false,
10909
+
10910
+ sourcemaps: "none",
10911
+ sourcemapsSourcesContent: undefined,
10912
+ assetManifest: false,
10913
+ assetManifestFileRelativeUrl: "asset-manifest.json",
10914
+ };
10915
+
10916
+ const prepareEntryPointBuild = async (
10917
+ {
10918
+ signal,
10919
+ sourceDirectoryUrl,
10920
+ buildDirectoryUrl,
10921
+ sourceRelativeUrl,
10922
+ outDirectoryUrl,
10923
+ buildUrlsGenerator,
10924
+ someEntryPointUseNode,
10925
+ },
10926
+ entryPointParams,
10927
+ ) => {
10928
+ let {
10929
+ buildRelativeUrl,
10930
+ runtimeCompat,
10931
+ plugins,
10932
+ mappings,
10933
+ assetsDirectory,
10934
+ base,
10935
+ ignore,
10936
+
10937
+ bundling,
10938
+ minification,
10939
+ versioning,
10940
+
10941
+ referenceAnalysis,
10942
+ nodeEsmResolution,
10943
+ packageConditions,
10944
+ magicExtensions,
10945
+ magicDirectoryIndex,
10946
+ directoryReferenceEffect,
10947
+ scenarioPlaceholders,
10948
+ injections,
10949
+ transpilation,
10950
+
10951
+ versioningMethod,
10952
+ versioningViaImportmap,
10953
+ versionLength,
10954
+ lineBreakNormalization,
10955
+
10956
+ http,
10957
+
10958
+ sourcemaps,
10959
+ sourcemapsSourcesContent,
10960
+ assetManifest,
10961
+ assetManifestFileRelativeUrl,
10962
+ } = {
10963
+ ...entryPointDefaultParams,
10964
+ ...entryPointParams,
10965
+ };
10966
+
10967
+ // param defaults and normalization
10968
+ {
10969
+ if (entryPointParams.buildRelativeUrl === undefined) {
10970
+ buildRelativeUrl = entryPointParams.buildRelativeUrl = sourceRelativeUrl;
10971
+ }
10972
+ const buildUrl = new URL(buildRelativeUrl, buildDirectoryUrl);
10973
+ entryPointParams.buildRelativeUrl = buildRelativeUrl = urlToRelativeUrl(
10974
+ buildUrl,
10975
+ buildDirectoryUrl,
10976
+ );
10977
+ if (entryPointParams.assetsDirectory === undefined) {
10978
+ const entryBuildUrl = new URL(buildRelativeUrl, buildDirectoryUrl).href;
10979
+ const entryBuildRelativeUrl = urlToRelativeUrl(
10980
+ entryBuildUrl,
10981
+ buildDirectoryUrl,
10982
+ );
10983
+ if (entryBuildRelativeUrl.includes("/")) {
10984
+ const assetDirectoryUrl = new URL("./", entryBuildUrl);
10985
+ assetsDirectory = urlToRelativeUrl(
10986
+ assetDirectoryUrl,
10987
+ buildDirectoryUrl,
10988
+ );
10989
+ } else {
10990
+ assetsDirectory = "";
10991
+ }
10992
+ }
10993
+ if (
10994
+ assetsDirectory &&
10995
+ assetsDirectory[assetsDirectory.length - 1] !== "/"
10996
+ ) {
10997
+ assetsDirectory = `${assetsDirectory}/`;
10998
+ }
10999
+ if (entryPointParams.base === undefined) {
11000
+ base = someEntryPointUseNode ? "./" : "/";
11001
+ }
11002
+ if (entryPointParams.bundling === undefined) {
11003
+ bundling = true;
11004
+ }
11005
+ if (bundling === true) {
11006
+ bundling = {};
11007
+ }
11008
+ if (entryPointParams.minification === undefined) {
11009
+ minification = !someEntryPointUseNode;
11010
+ }
11011
+ if (minification === true) {
11012
+ minification = {};
11013
+ }
11014
+ if (entryPointParams.versioning === undefined) {
11015
+ versioning = !someEntryPointUseNode;
11016
+ }
11017
+ if (entryPointParams.versioningMethod === undefined) {
11018
+ versioningMethod = entryPointDefaultParams.versioningMethod;
11019
+ }
11020
+ if (entryPointParams.assetManifest === undefined) {
11021
+ assetManifest = versioningMethod === "filename";
11022
+ }
11023
+ }
11024
+
11025
+ const buildOperation = Abort.startOperation();
11026
+ buildOperation.addAbortSignal(signal);
11027
+
11028
+ const explicitJsModuleConversion =
11029
+ sourceRelativeUrl.includes("?js_module_fallback") ||
11030
+ sourceRelativeUrl.includes("?as_js_classic");
11031
+ const contextSharedDuringBuild = {
11032
+ buildStep: "craft",
11033
+ buildDirectoryUrl,
11034
+ assetsDirectory,
11035
+ versioning,
11036
+ versioningViaImportmap,
11037
+ };
11038
+ const rawKitchen = createKitchen({
11039
+ signal,
11040
+ logLevel: "warn",
11041
+ rootDirectoryUrl: sourceDirectoryUrl,
11042
+ ignore,
11043
+ // during first pass (craft) we keep "ignore:" when a reference is ignored
11044
+ // so that the second pass (shape) properly ignore those urls
11045
+ ignoreProtocol: "keep",
11046
+ build: true,
11047
+ runtimeCompat,
11048
+ initialContext: contextSharedDuringBuild,
11049
+ sourcemaps,
11050
+ sourcemapsSourcesContent,
11051
+ outDirectoryUrl: outDirectoryUrl
11052
+ ? new URL("craft/", outDirectoryUrl)
11053
+ : undefined,
11054
+ });
11055
+
11056
+ let _getOtherEntryBuildInfo;
11057
+ const rawPluginStore = createPluginStore([
11058
+ ...(mappings ? [jsenvPluginMappings(mappings)] : []),
11059
+ {
11060
+ name: "jsenv:other_entry_point_build_during_craft",
11061
+ fetchUrlContent: async (urlInfo) => {
11062
+ if (!_getOtherEntryBuildInfo) {
11063
+ return null;
11064
+ }
11065
+ const otherEntryBuildInfo = _getOtherEntryBuildInfo(urlInfo.url);
11066
+ if (!otherEntryBuildInfo) {
11067
+ return null;
11068
+ }
11069
+ urlInfo.otherEntryBuildInfo = otherEntryBuildInfo;
11070
+ return {
11071
+ type: "entry_build", // this ensure the rest of jsenv do not try to scan or modify the content
11072
+ content: "", // we don't know yet the content it will be known later
11073
+ filenameHint: otherEntryBuildInfo.entryUrlInfo.filenameHint,
11074
+ };
11075
+ },
11076
+ },
11077
+ ...plugins,
11078
+ ...(bundling ? [jsenvPluginBundling(bundling)] : []),
11079
+ ...(minification ? [jsenvPluginMinification(minification)] : []),
11080
+ ...getCorePlugins({
11081
+ rootDirectoryUrl: sourceDirectoryUrl,
11082
+ runtimeCompat,
11083
+ referenceAnalysis,
11084
+ nodeEsmResolution,
11085
+ packageConditions,
11086
+ magicExtensions,
11087
+ magicDirectoryIndex,
11088
+ directoryReferenceEffect,
11089
+ injections,
11090
+ transpilation: {
11091
+ babelHelpersAsImport: !explicitJsModuleConversion,
11092
+ ...transpilation,
11093
+ jsModuleFallback: false,
11094
+ },
11095
+ inlining: false,
11096
+ http,
11097
+ scenarioPlaceholders,
11098
+ }),
11099
+ ]);
11100
+ const rawPluginController = createPluginController(
11101
+ rawPluginStore,
11102
+ rawKitchen,
11103
+ );
11104
+ rawKitchen.setPluginController(rawPluginController);
11105
+
11106
+ const rawRootUrlInfo = rawKitchen.graph.rootUrlInfo;
11107
+ let entryReference;
11108
+ await rawRootUrlInfo.dependencies.startCollecting(() => {
11109
+ entryReference = rawRootUrlInfo.dependencies.found({
11110
+ trace: { message: `"${sourceRelativeUrl}" from "entryPoints"` },
11111
+ isEntryPoint: true,
11112
+ type: "entry_point",
11113
+ specifier: sourceRelativeUrl,
11114
+ filenameHint: buildRelativeUrl,
11115
+ });
11116
+ });
11117
+
11118
+ return {
11119
+ entryReference,
11120
+ buildEntryPoint: async ({ getOtherEntryBuildInfo }) => {
11121
+ {
11122
+ _getOtherEntryBuildInfo = getOtherEntryBuildInfo;
11123
+ if (outDirectoryUrl) {
11124
+ await ensureEmptyDirectory(new URL(`craft/`, outDirectoryUrl));
11125
+ }
11126
+ await rawRootUrlInfo.cookDependencies({ operation: buildOperation });
11127
+ }
11128
+
11129
+ const finalKitchen = createKitchen({
11130
+ name: "shape",
11131
+ logLevel: "warn",
11132
+ rootDirectoryUrl: sourceDirectoryUrl,
11133
+ // here most plugins are not there
11134
+ // - no external plugin
11135
+ // - no plugin putting reference.mustIgnore on https urls
11136
+ // At this stage it's only about redirecting urls to the build directory
11137
+ // consequently only a subset or urls are supported
11138
+ supportedProtocols: ["file:", "data:", "virtual:", "ignore:"],
11139
+ ignore,
11140
+ ignoreProtocol: "remove",
11141
+ build: true,
11142
+ runtimeCompat,
11143
+ initialContext: contextSharedDuringBuild,
11144
+ sourcemaps,
11145
+ sourcemapsComment: "relative",
11146
+ sourcemapsSourcesContent,
11147
+ outDirectoryUrl: outDirectoryUrl
11148
+ ? new URL("shape/", outDirectoryUrl)
11149
+ : undefined,
11150
+ });
11151
+ const buildSpecifierManager = createBuildSpecifierManager({
11152
+ rawKitchen,
11153
+ finalKitchen,
11154
+ logger: createLogger({ logLevel: "warn" }),
11155
+ sourceDirectoryUrl,
11156
+ buildDirectoryUrl,
11157
+ base,
11158
+ assetsDirectory,
11159
+ buildUrlsGenerator,
11160
+
11161
+ versioning,
11162
+ versioningMethod,
11163
+ versionLength,
11164
+ canUseImportmap:
11165
+ versioningViaImportmap &&
11166
+ rawKitchen.graph.getUrlInfo(entryReference.url).type === "html" &&
11167
+ rawKitchen.context.isSupportedOnCurrentClients("importmap"),
11168
+ });
11169
+ const finalPluginStore = createPluginStore([
11170
+ jsenvPluginReferenceAnalysis({
11171
+ ...referenceAnalysis,
11172
+ fetchInlineUrls: false,
11173
+ // inlineContent: false,
11174
+ }),
11175
+ jsenvPluginDirectoryReferenceEffect(directoryReferenceEffect, {
11176
+ rootDirectoryUrl: sourceDirectoryUrl,
11177
+ }),
11178
+ ...(lineBreakNormalization
11179
+ ? [jsenvPluginLineBreakNormalization()]
11180
+ : []),
11181
+ jsenvPluginJsModuleFallback({
11182
+ remapImportSpecifier: (specifier, parentUrl) => {
11183
+ return buildSpecifierManager.remapPlaceholder(specifier, parentUrl);
11184
+ },
11185
+ }),
11186
+ jsenvPluginInlining(),
11187
+ {
11188
+ name: "jsenv:optimize",
11189
+ appliesDuring: "build",
11190
+ transformUrlContent: async (urlInfo) => {
11191
+ await rawKitchen.pluginController.callAsyncHooks(
11192
+ "optimizeUrlContent",
11193
+ urlInfo,
11194
+ (optimizeReturnValue) => {
11195
+ urlInfo.mutateContent(optimizeReturnValue);
11196
+ },
11197
+ );
11198
+ },
11199
+ },
11200
+ buildSpecifierManager.jsenvPluginMoveToBuildDirectory,
11201
+ ]);
11202
+ const finalPluginController = createPluginController(
11203
+ finalPluginStore,
11204
+ finalKitchen,
11205
+ {
11206
+ initialPuginsMeta: rawKitchen.pluginController.pluginsMeta,
11207
+ },
11208
+ );
11209
+ finalKitchen.setPluginController(finalPluginController);
11210
+
11211
+ bundle: {
11212
+ const bundlerMap = new Map();
11213
+ for (const plugin of rawKitchen.pluginController.activePlugins) {
11214
+ const bundle = plugin.bundle;
11215
+ if (!bundle) {
11216
+ continue;
11217
+ }
11218
+ if (typeof bundle !== "object") {
11219
+ throw new Error(
11220
+ `bundle must be an object, found "${bundle}" on plugin named "${plugin.name}"`,
11221
+ );
11222
+ }
11223
+ for (const type of Object.keys(bundle)) {
11224
+ const bundleFunction = bundle[type];
11225
+ if (!bundleFunction) {
11226
+ continue;
11227
+ }
11228
+ if (bundlerMap.has(type)) {
11229
+ // first plugin to define a bundle hook wins
11230
+ continue;
11231
+ }
11232
+ bundlerMap.set(type, {
11233
+ plugin,
11234
+ bundleFunction: bundle[type],
11235
+ urlInfoMap: new Map(),
11236
+ });
11237
+ }
11238
+ }
11239
+ if (bundlerMap.size === 0) {
11240
+ break bundle;
11241
+ }
11242
+ const addToBundlerIfAny = (rawUrlInfo) => {
11243
+ const bundler = bundlerMap.get(rawUrlInfo.type);
11244
+ if (bundler) {
11245
+ bundler.urlInfoMap.set(rawUrlInfo.url, rawUrlInfo);
11246
+ }
11247
+ };
11248
+ // ignore unused urls thanks to "forEachUrlInfoStronglyReferenced"
11249
+ // it avoid bundling things that are not actually used
11250
+ // happens for:
11251
+ // - js import assertions
11252
+ // - conversion to js classic using ?as_js_classic or ?js_module_fallback
11253
+ GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
11254
+ rawKitchen.graph.rootUrlInfo,
11255
+ (rawUrlInfo) => {
11256
+ if (rawUrlInfo.isEntryPoint) {
11257
+ addToBundlerIfAny(rawUrlInfo);
11258
+ }
11259
+ if (rawUrlInfo.type === "html") {
11260
+ for (const referenceToOther of rawUrlInfo.referenceToOthersSet) {
11261
+ if (
11262
+ referenceToOther.isResourceHint &&
11263
+ referenceToOther.expectedType === "js_module"
11264
+ ) {
11265
+ const referencedUrlInfo = referenceToOther.urlInfo;
11266
+ if (
11267
+ referencedUrlInfo &&
11268
+ // something else than the resource hint is using this url
11269
+ referencedUrlInfo.referenceFromOthersSet.size > 0
11270
+ ) {
11271
+ addToBundlerIfAny(referencedUrlInfo);
11272
+ continue;
11273
+ }
11274
+ }
11275
+ if (referenceToOther.isWeak) {
11276
+ continue;
11277
+ }
11278
+ const referencedUrlInfo = referenceToOther.urlInfo;
11279
+ if (referencedUrlInfo.isInline) {
11280
+ if (referencedUrlInfo.type !== "js_module") {
11281
+ continue;
11282
+ }
11283
+ addToBundlerIfAny(referencedUrlInfo);
11284
+ continue;
11285
+ }
11286
+ addToBundlerIfAny(referencedUrlInfo);
11287
+ }
11288
+ return;
11289
+ }
11290
+ // File referenced with
11291
+ // - new URL("./file.js", import.meta.url)
11292
+ // - import.meta.resolve("./file.js")
11293
+ // are entry points that should be bundled
11294
+ // For instance we will bundle service worker/workers detected like this
11295
+ if (rawUrlInfo.type === "js_module") {
11296
+ for (const referenceToOther of rawUrlInfo.referenceToOthersSet) {
11297
+ if (
11298
+ referenceToOther.type === "js_url" ||
11299
+ referenceToOther.subtype === "import_meta_resolve"
11300
+ ) {
11301
+ const referencedUrlInfo = referenceToOther.urlInfo;
11302
+ let isAlreadyBundled = false;
11303
+ for (const referenceFromOther of referencedUrlInfo.referenceFromOthersSet) {
11304
+ if (referenceFromOther.url === referencedUrlInfo.url) {
11305
+ if (
11306
+ referenceFromOther.subtype === "import_dynamic" ||
11307
+ referenceFromOther.type === "script"
11308
+ ) {
11309
+ isAlreadyBundled = true;
11310
+ break;
11311
+ }
11312
+ }
11313
+ }
11314
+ if (!isAlreadyBundled) {
11315
+ addToBundlerIfAny(referencedUrlInfo);
11316
+ }
11317
+ continue;
11318
+ }
11319
+ if (referenceToOther.type === "js_inline_content") ;
11320
+ }
11321
+ }
11322
+ },
11323
+ );
11324
+ for (const [, bundler] of bundlerMap) {
11325
+ const urlInfosToBundle = Array.from(bundler.urlInfoMap.values());
11326
+ if (urlInfosToBundle.length === 0) {
11327
+ continue;
11328
+ }
11329
+ await buildSpecifierManager.applyBundling({
11330
+ bundler,
11331
+ urlInfosToBundle,
11332
+ });
11333
+ }
11334
+ }
11335
+
11336
+ {
11337
+ finalKitchen.context.buildStep = "shape";
11338
+ if (outDirectoryUrl) {
11339
+ await ensureEmptyDirectory(new URL(`shape/`, outDirectoryUrl));
11340
+ }
11341
+ const finalRootUrlInfo = finalKitchen.graph.rootUrlInfo;
11342
+ await finalRootUrlInfo.dependencies.startCollecting(() => {
11343
+ finalRootUrlInfo.dependencies.found({
11344
+ trace: { message: `entryPoint` },
11345
+ isEntryPoint: true,
11346
+ type: "entry_point",
11347
+ specifier: entryReference.url,
11348
+ });
11349
+ });
11350
+ await finalRootUrlInfo.cookDependencies({
11351
+ operation: buildOperation,
11352
+ });
11353
+ }
11354
+
11355
+ {
11356
+ finalKitchen.context.buildStep = "refine";
11357
+
11358
+ const htmlRefineSet = new Set();
11359
+ const registerHtmlRefine = (htmlRefine) => {
11360
+ htmlRefineSet.add(htmlRefine);
11361
+ };
11362
+
11363
+ {
11364
+ await buildSpecifierManager.replacePlaceholders();
11365
+ }
11366
+
11367
+ /*
11368
+ * Update <link rel="preload"> and friends after build (once we know everything)
11369
+ * - Used to remove resource hint targeting an url that is no longer used:
11370
+ * - because of bundlings
11371
+ * - because of import assertions transpilation (file is inlined into JS)
11372
+ */
11373
+ {
11374
+ buildSpecifierManager.prepareResyncResourceHints({
11375
+ registerHtmlRefine,
11376
+ });
11377
+ }
11378
+
11379
+ {
11380
+ GRAPH_VISITOR.forEach(finalKitchen.graph, (urlInfo) => {
11381
+ if (!urlInfo.url.startsWith("file:")) {
11382
+ return;
11383
+ }
11384
+ if (urlInfo.type !== "html") {
11385
+ return;
11386
+ }
11387
+ const htmlAst = parseHtml({
11388
+ html: urlInfo.content,
11389
+ url: urlInfo.url,
11390
+ storeOriginalPositions: false,
11391
+ });
11392
+ for (const htmlRefine of htmlRefineSet) {
11393
+ const htmlMutationCallbackSet = new Set();
11394
+ const registerHtmlMutation = (callback) => {
11395
+ htmlMutationCallbackSet.add(callback);
11396
+ };
11397
+ htmlRefine(htmlAst, { registerHtmlMutation });
11398
+ for (const htmlMutationCallback of htmlMutationCallbackSet) {
11399
+ htmlMutationCallback();
11400
+ }
11401
+ }
11402
+ // cleanup jsenv attributes from html as a last step
11403
+ urlInfo.content = stringifyHtmlAst(htmlAst, {
11404
+ cleanupJsenvAttributes: true,
11405
+ cleanupPositionAttributes: true,
11406
+ });
11407
+ });
11408
+ }
11409
+
11410
+ {
11411
+ const inject =
11412
+ buildSpecifierManager.prepareServiceWorkerUrlInjection();
11413
+ if (inject) {
11414
+ await inject();
11415
+ buildOperation.throwIfAborted();
11416
+ }
11417
+ }
11418
+ }
11419
+ const {
11420
+ buildFileContents,
11421
+ buildFileVersions,
11422
+ buildInlineContents,
11423
+ buildManifest,
11424
+ } = buildSpecifierManager.getBuildInfo();
11425
+ if (versioning && assetManifest && Object.keys(buildManifest).length) {
11426
+ buildFileContents[assetManifestFileRelativeUrl] = JSON.stringify(
11427
+ buildManifest,
11428
+ null,
11429
+ " ",
11430
+ );
11431
+ }
11432
+ return {
11433
+ buildFileContents,
11434
+ buildFileVersions,
11435
+ buildInlineContents,
11436
+ buildManifest,
11437
+ };
11438
+ },
11439
+ };
11440
+ };
11441
+
11442
+ const isBareSpecifier = (specifier) => {
11443
+ if (
11444
+ specifier[0] === "/" ||
11445
+ specifier.startsWith("./") ||
11446
+ specifier.startsWith("../")
11447
+ ) {
11448
+ return false;
11449
+ }
11450
+ try {
11451
+ // eslint-disable-next-line no-new
11452
+ new URL(specifier);
11453
+ return false;
11454
+ } catch {
11455
+ return true;
11456
+ }
11457
+ };
11458
+
10768
11459
  export { build };