@jsenv/core 41.4.18 → 41.4.19

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.
@@ -4,7 +4,7 @@ import { jsenvPluginMinification } from "@jsenv/plugin-minification";
4
4
  import { jsenvPluginTranspilation, jsenvPluginJsModuleFallback } from "@jsenv/plugin-transpilation";
5
5
  import { memoryUsage } from "node:process";
6
6
  import { readFileSync, existsSync, realpathSync, readdirSync, lstatSync, statSync } from "node:fs";
7
- import { lookupPackageDirectory, urlIsOrIsInsideOf, registerDirectoryLifecycle, urlToRelativeUrl, createDetailedMessage, stringifyUrlSite, generateContentFrame, validateResponseIntegrity, ensureWindowsDriveLetter, setUrlFilename, moveUrl, getCallerPosition, urlToBasename, urlToExtension, asSpecifierWithoutSearch, asUrlWithoutSearch, injectQueryParamsIntoSpecifier, bufferToEtag, isFileSystemPath, urlToPathname, setUrlBasename, urlToFileSystemPath, writeFileSync, createLogger, URL_META, applyNodeEsmResolution, normalizeUrl, ANSI, RUNTIME_COMPAT, CONTENT_TYPE, readPackageAtOrNull, urlToFilename, DATA_URL, errorToHTML, normalizeImportMap, composeTwoImportMaps, resolveImport, JS_QUOTES, readCustomConditionsFromProcessArgs, collectFiles, readEntryStatSync, applyFileSystemMagicResolution, getExtensionsToTry, ensurePathnameTrailingSlash, compareFileUrls, setUrlExtension, isSpecifierForNodeBuiltin, injectQueryParams, renderDetails, humanizeDuration, humanizeFileSize, renderTable, renderBigSection, distributePercentages, humanizeMemory, comparePathnames, UNICODE, escapeRegexpSpecialChars, injectQueryParamIntoSpecifierWithoutEncoding, renderUrlOrRelativeUrlFilename, assertAndNormalizeDirectoryUrl, Abort, raceProcessTeardownEvents, startMonitoringCpuUsage, startMonitoringMemoryUsage, inferRuntimeCompatFromClosestPackage, browserDefaultRuntimeCompat, nodeDefaultRuntimeCompat, clearDirectorySync, createTaskLog, createLookupPackageDirectory, ensureEmptyDirectory, updateJsonFileSync, createDynamicLog } from "./jsenv_core_packages.js";
7
+ import { lookupPackageDirectory, urlIsOrIsInsideOf, registerDirectoryLifecycle, urlToRelativeUrl, createDetailedMessage, stringifyUrlSite, generateContentFrame, validateResponseIntegrity, ensureWindowsDriveLetter, setUrlFilename, moveUrl, getCallerPosition, urlToBasename, urlToExtension, asSpecifierWithoutSearch, asUrlWithoutSearch, injectQueryParamsIntoSpecifier, bufferToEtag, isFileSystemPath, urlToPathname, setUrlBasename, urlToFileSystemPath, writeFileSync, createLogger, URL_META, applyNodeEsmResolution, normalizeUrl, ANSI, RUNTIME_COMPAT, CONTENT_TYPE, readPackageAtOrNull, urlToFilename, DATA_URL, errorToHTML, normalizeImportMap, composeTwoImportMaps, resolveImport, JS_QUOTES, readCustomConditionsFromProcessArgs, collectFiles, readEntryStatSync, applyFileSystemMagicResolution, getExtensionsToTry, ensurePathnameTrailingSlash, compareFileUrls, setUrlExtension, isSpecifierForNodeBuiltin, injectQueryParams, renderDetails, humanizeDuration, humanizeFileSize, renderTable, renderBigSection, distributePercentages, humanizeMemory, comparePathnames, UNICODE, escapeRegexpSpecialChars, injectQueryParamIntoSpecifierWithoutEncoding, renderUrlOrRelativeUrlFilename, assertAndNormalizeDirectoryUrl, Abort, raceProcessTeardownEvents, startMonitoringMemoryUsage, inferRuntimeCompatFromClosestPackage, browserDefaultRuntimeCompat, nodeDefaultRuntimeCompat, clearDirectorySync, createTaskLog, createLookupPackageDirectory, ensureEmptyDirectory, updateJsonFileSync, createDynamicLog } from "./jsenv_core_packages.js";
8
8
  import { pathToFileURL } from "node:url";
9
9
  import { generateSourcemapFileUrl, createMagicSource, composeTwoSourcemaps, generateSourcemapDataUrl, SOURCEMAP } from "@jsenv/sourcemap";
10
10
  import { createPluginsController } from "@jsenv/server/src/plugins_controller.js";
@@ -910,10 +910,9 @@ const prependJsClassicInJsClassic = (jsUrlInfo, urlInfoToPrepend) => {
910
910
  const magicSource = createMagicSource(jsUrlInfo.content);
911
911
  magicSource.prepend(`${urlInfoToPrepend.content}\n\n`);
912
912
  const magicResult = magicSource.toContentAndSourcemap();
913
- const sourcemap = composeTwoSourcemaps(
914
- jsUrlInfo.sourcemap,
915
- magicResult.sourcemap,
916
- );
913
+ const sourcemap = jsUrlInfo.context.sourcemapsEnabled
914
+ ? composeTwoSourcemaps(jsUrlInfo.sourcemap, magicResult.sourcemap)
915
+ : null;
917
916
  jsUrlInfo.mutateContent({
918
917
  content: magicResult.content,
919
918
  sourcemap,
@@ -931,6 +930,7 @@ const prependJsClassicInJsModule = async (jsUrlInfo, urlInfoToPrepend) => {
931
930
  input: jsUrlInfo.content,
932
931
  inputIsJsModule: true,
933
932
  inputUrl: jsUrlInfo.originalUrl,
933
+ options: { sourceMaps: jsUrlInfo.context.sourcemapsEnabled },
934
934
  });
935
935
  jsUrlInfo.mutateContent({
936
936
  content: code,
@@ -2920,7 +2920,6 @@ const createUrlInfoTransformer = ({
2920
2920
  contentAst, // undefined most of the time
2921
2921
  contentEtag, // in practice always undefined
2922
2922
  contentLength,
2923
- sourcemap,
2924
2923
  sourcemapIsWrong,
2925
2924
  contentInjections,
2926
2925
  } = transformations;
@@ -2947,11 +2946,14 @@ const createUrlInfoTransformer = ({
2947
2946
  contentLength,
2948
2947
  });
2949
2948
  }
2950
- if (
2951
- sourcemap &&
2952
- mayHaveSourcemap(urlInfo) &&
2953
- shouldHandleSourcemap(urlInfo)
2954
- ) {
2949
+ // "sourcemap" is read last, and only when it will be used: a plugin can
2950
+ // hand it back as a getter that generates the map on first read, so that
2951
+ // nothing is generated for a kitchen that throws sourcemaps away
2952
+ const sourcemap =
2953
+ mayHaveSourcemap(urlInfo) && shouldHandleSourcemap(urlInfo)
2954
+ ? transformations.sourcemap
2955
+ : null;
2956
+ if (sourcemap) {
2955
2957
  const sourcemapNormalized = normalizeSourcemap(urlInfo, sourcemap);
2956
2958
  let currentSourcemap = urlInfo.sourcemap;
2957
2959
  const finalSourcemap = composeTwoSourcemaps(
@@ -2984,6 +2986,14 @@ const createUrlInfoTransformer = ({
2984
2986
  writeInsideOutDirectory(urlInfo);
2985
2987
  };
2986
2988
 
2989
+ // Written synchronously on purpose, and measured: async is the tempting
2990
+ // choice, but here it loses. A cold load cooks hundreds of files; their
2991
+ // synchronous writes block the event loop ~160ms in total, while
2992
+ // asynchronous writes queue in the threadpool behind tens of MB of content
2993
+ // and sourcemaps, and a response that waits for its own write then waits
2994
+ // ~36ms on average (18s summed over 500 responses). Not waiting is not an
2995
+ // option either: the last write lands after the test that cooked it has
2996
+ // ended, and the side-effect snapshots lose it.
2987
2997
  const writeInsideOutDirectory = (urlInfo) => {
2988
2998
  // writing result inside ".jsenv" directory (debug purposes)
2989
2999
  if (!outDirectoryUrl) {
@@ -3303,6 +3313,12 @@ const createKitchen = ({
3303
3313
  INJECTIONS,
3304
3314
  getPluginMeta: null,
3305
3315
  sourcemaps,
3316
+ // a plugin producing a sourcemap can skip the work when it would be
3317
+ // thrown away (see shouldHandleSourcemap in url_info_transformations.js)
3318
+ sourcemapsEnabled:
3319
+ sourcemaps === "inline" ||
3320
+ sourcemaps === "file" ||
3321
+ sourcemaps === "programmatic",
3306
3322
  outDirectoryUrl,
3307
3323
  },
3308
3324
  resolve: (specifier, importer = rootDirectoryUrl) => {
@@ -5772,8 +5788,7 @@ const parseAndTransformJsReferences = async (
5772
5788
  for (const sequentialAction of sequentialActions) {
5773
5789
  await sequentialAction();
5774
5790
  }
5775
- const { content, sourcemap } = magicSource.toContentAndSourcemap();
5776
- return { content, sourcemap };
5791
+ return magicSource.toContentAndSourcemap();
5777
5792
  };
5778
5793
 
5779
5794
  const jsenvPluginReferenceExpectedTypes = () => {
@@ -8327,6 +8342,8 @@ const jsenvPluginImportMetaCss = () => {
8327
8342
  inputIsJsModule: true,
8328
8343
  inputUrl: urlInfo.originalUrl,
8329
8344
  outputUrl: urlInfo.generatedUrl,
8345
+ // the map would be dropped: the content sent back carries none
8346
+ options: { sourceMaps: false },
8330
8347
  });
8331
8348
  if (code === urlInfo.content) {
8332
8349
  // all assignments were already in array form (pre-built file) — nothing to do
@@ -10165,11 +10182,12 @@ const getCorePlugins = ({
10165
10182
  ];
10166
10183
  };
10167
10184
 
10168
- const humanizeProcessCpuUsage = (ratio) => {
10169
- const percentageAsNumber = ratio * 100;
10170
- const percentageAsNumberRounded = Math.round(percentageAsNumber);
10171
- const percentage = `${percentageAsNumberRounded}%`;
10172
- return percentage;
10185
+ // a phase can be a few milliseconds long (refine, mostly): shown as such
10186
+ const humanizePhaseDuration = (ms) => {
10187
+ if (ms < 1000) {
10188
+ return `${Math.round(ms)}ms`;
10189
+ }
10190
+ return humanizeDuration(ms, { short: true });
10173
10191
  };
10174
10192
  const humanizeProcessMemoryUsage = (value) => {
10175
10193
  return humanizeMemory(value, { short: true, decimals: 0 });
@@ -10178,7 +10196,8 @@ const renderBuildDoneLog = ({
10178
10196
  entryPointArray,
10179
10197
  duration,
10180
10198
  buildFileContents,
10181
- processCpuUsage,
10199
+ cpuUsage,
10200
+ phaseTimings,
10182
10201
  processMemoryUsage,
10183
10202
  }) => {
10184
10203
  const buildContentReport = createBuildContentReport(buildFileContents);
@@ -10193,15 +10212,10 @@ const renderBuildDoneLog = ({
10193
10212
  title = `build done (${entryPointCount} entry points)`;
10194
10213
  }
10195
10214
 
10196
- // cpu usage
10197
- let cpuUsageLine = "cpu: ";
10198
- cpuUsageLine += `${humanizeProcessCpuUsage(processCpuUsage.end)}`;
10199
- cpuUsageLine += renderDetails({
10200
- med: humanizeProcessCpuUsage(processCpuUsage.median),
10201
- min: humanizeProcessCpuUsage(processCpuUsage.min),
10202
- max: humanizeProcessCpuUsage(processCpuUsage.max),
10203
- });
10204
- lines.push(cpuUsageLine);
10215
+ // cpu: the process cpu time over the build duration, 100% being one core
10216
+ // busy the whole time (more when a native module works on other threads)
10217
+ const cpuMs = (cpuUsage.user + cpuUsage.system) / 1000;
10218
+ lines.push(`cpu: ${Math.round((cpuMs / duration) * 100)}%`);
10205
10219
 
10206
10220
  // memory usage
10207
10221
  let memoryUsageLine = "memory: ";
@@ -10216,6 +10230,16 @@ const renderBuildDoneLog = ({
10216
10230
  // duration
10217
10231
  let durationLine = `duration: `;
10218
10232
  durationLine += humanizeDuration(duration, { short: true });
10233
+ if (phaseTimings) {
10234
+ durationLine += renderDetails(
10235
+ Object.fromEntries(
10236
+ Object.keys(phaseTimings).map((phaseName) => [
10237
+ phaseName,
10238
+ humanizePhaseDuration(phaseTimings[phaseName]),
10239
+ ]),
10240
+ ),
10241
+ );
10242
+ }
10219
10243
  lines.push(durationLine);
10220
10244
 
10221
10245
  // content
@@ -10406,6 +10430,8 @@ const createBuildContentReport = (buildFileContents) => {
10406
10430
  };
10407
10431
 
10408
10432
  const determineCategory = (buildRelativeUrl) => {
10433
+ // a versioned url ("main.js?v=abc") is categorized by its file
10434
+ buildRelativeUrl = buildRelativeUrl.replace(/[?#].*$/, "");
10409
10435
  if (buildRelativeUrl.endsWith(".map")) {
10410
10436
  return "sourcemap";
10411
10437
  }
@@ -11332,7 +11358,7 @@ const createBuildSpecifierManager = ({
11332
11358
  }
11333
11359
  if (mayUsePlaceholder(urlInfo)) {
11334
11360
  const contentBeforeReplace = urlInfo.content;
11335
- const { content, sourcemap } = placeholderAPI.replaceAll(
11361
+ const replaceResult = placeholderAPI.replaceAll(
11336
11362
  contentBeforeReplace,
11337
11363
  (placeholder) => {
11338
11364
  const reference = placeholderToReferenceMap.get(placeholder);
@@ -11340,7 +11366,7 @@ const createBuildSpecifierManager = ({
11340
11366
  return value;
11341
11367
  },
11342
11368
  );
11343
- urlInfo.mutateContent({ content, sourcemap });
11369
+ urlInfo.mutateContent(replaceResult);
11344
11370
  }
11345
11371
  },
11346
11372
  );
@@ -12581,13 +12607,12 @@ entryPoints: {
12581
12607
  });
12582
12608
  }
12583
12609
 
12584
- const cpuMonitoring = startMonitoringCpuUsage();
12585
- operation.addEndCallback(cpuMonitoring.stop);
12586
- const [processCpuUsageMonitoring] = cpuMonitoring;
12610
+ // cpu is not sampled: cooking, rollup and terser hold the event loop for
12611
+ // seconds at a time, a sampler only runs in the gaps and would report the
12612
+ // idle time. What the build cost is process.cpuUsage() over its duration.
12587
12613
  const memoryMonitoring = startMonitoringMemoryUsage();
12588
12614
  const [processMemoryUsageMonitoring] = memoryMonitoring;
12589
12615
  const interval = setInterval(() => {
12590
- processCpuUsageMonitoring.measure();
12591
12616
  processMemoryUsageMonitoring.measure();
12592
12617
  }, 500).unref();
12593
12618
  operation.addEndCallback(() => {
@@ -12657,19 +12682,24 @@ entryPoints: {
12657
12682
  content += "\n";
12658
12683
  return content;
12659
12684
  };
12660
- const renderBuildEndLog = ({ duration, buildFileContents }) => {
12685
+ const renderBuildEndLog = ({
12686
+ duration,
12687
+ buildFileContents,
12688
+ cpuUsage,
12689
+ phaseTimings,
12690
+ }) => {
12661
12691
  // tell how many files are generated in build directory
12662
12692
  // tell the repartition?
12663
12693
  // this is not really useful for single build right?
12664
12694
 
12665
- processCpuUsageMonitoring.end();
12666
12695
  processMemoryUsageMonitoring.end();
12667
12696
 
12668
12697
  return renderBuildDoneLog({
12669
12698
  entryPointArray,
12670
12699
  duration,
12671
12700
  buildFileContents,
12672
- processCpuUsage: processCpuUsageMonitoring.info,
12701
+ cpuUsage,
12702
+ phaseTimings,
12673
12703
  processMemoryUsage: processMemoryUsageMonitoring.info,
12674
12704
  });
12675
12705
  };
@@ -12744,13 +12774,25 @@ entryPoints: {
12744
12774
  }, renderDynamicLog());
12745
12775
  };
12746
12776
  },
12747
- onBuildEnd: ({ buildFileContents, duration }) => {
12777
+ onBuildEnd: ({
12778
+ buildFileContents,
12779
+ duration,
12780
+ cpuUsage,
12781
+ phaseTimings,
12782
+ }) => {
12748
12783
  clearInterval(interval);
12749
12784
  dynamicLog.update("");
12750
12785
  dynamicLog.destroy();
12751
12786
  dynamicLog = null;
12752
12787
  logger.info("");
12753
- logger.info(renderBuildEndLog({ duration, buildFileContents }));
12788
+ logger.info(
12789
+ renderBuildEndLog({
12790
+ duration,
12791
+ buildFileContents,
12792
+ cpuUsage,
12793
+ phaseTimings,
12794
+ }),
12795
+ );
12754
12796
  },
12755
12797
  };
12756
12798
  };
@@ -12777,8 +12819,20 @@ entryPoints: {
12777
12819
  );
12778
12820
  };
12779
12821
  },
12780
- onBuildEnd: ({ buildFileContents, duration }) => {
12781
- logger.info(renderBuildEndLog({ duration, buildFileContents }));
12822
+ onBuildEnd: ({
12823
+ buildFileContents,
12824
+ duration,
12825
+ cpuUsage,
12826
+ phaseTimings,
12827
+ }) => {
12828
+ logger.info(
12829
+ renderBuildEndLog({
12830
+ duration,
12831
+ buildFileContents,
12832
+ cpuUsage,
12833
+ phaseTimings,
12834
+ }),
12835
+ );
12782
12836
  },
12783
12837
  };
12784
12838
  };
@@ -12827,6 +12881,7 @@ entryPoints: {
12827
12881
 
12828
12882
  const runBuild = async ({ signal }) => {
12829
12883
  const startDate = Date.now();
12884
+ const cpuUsageAtStart = process.cpuUsage();
12830
12885
  const { onBuildEnd, onEntryPointBuildStart } = startBuildLogs();
12831
12886
 
12832
12887
  const buildUrlsGenerator = createBuildUrlsGenerator({
@@ -12935,6 +12990,7 @@ entryPoints: {
12935
12990
  entryBuildInfo.buildInlineContents = result.buildInlineContents;
12936
12991
  entryBuildInfo.buildManifest = result.buildManifest;
12937
12992
  entryBuildInfo.buildSideEffectFiles = result.buildSideEffectFiles;
12993
+ entryBuildInfo.phaseTimings = result.phaseTimings;
12938
12994
  entryBuildInfo.duration = Date.now() - entryPointBuildStartMs;
12939
12995
  onEntryPointBuildEnd();
12940
12996
  })();
@@ -13039,11 +13095,19 @@ entryPoints: {
13039
13095
  }
13040
13096
  }
13041
13097
  }
13098
+ // entry points are built concurrently, interleaved on the same thread:
13099
+ // their phases overlap and only a single one can be told apart
13100
+ const phaseTimings =
13101
+ entryBuildInfoMap.size === 1
13102
+ ? entryBuildInfoMap.values().next().value.phaseTimings
13103
+ : null;
13042
13104
  onBuildEnd({
13043
13105
  buildFileContents,
13044
13106
  buildInlineContents,
13045
13107
  buildManifest,
13046
13108
  duration: Date.now() - startDate,
13109
+ cpuUsage: process.cpuUsage(cpuUsageAtStart),
13110
+ phaseTimings,
13047
13111
  });
13048
13112
  return {
13049
13113
  ...(returnBuildInlineContents ? { buildInlineContents } : {}),
@@ -13407,6 +13471,14 @@ const prepareEntryPointBuild = async (
13407
13471
  return {
13408
13472
  entryReference,
13409
13473
  buildEntryPoint: async ({ getOtherEntryBuildInfo }) => {
13474
+ const phaseTimings = {};
13475
+ let phaseStartMs = Date.now();
13476
+ const endPhase = (name) => {
13477
+ const nowMs = Date.now();
13478
+ phaseTimings[name] = nowMs - phaseStartMs;
13479
+ phaseStartMs = nowMs;
13480
+ };
13481
+
13410
13482
  {
13411
13483
  _getOtherEntryBuildInfo = getOtherEntryBuildInfo;
13412
13484
  if (outDirectoryUrl) {
@@ -13414,6 +13486,7 @@ const prepareEntryPointBuild = async (
13414
13486
  }
13415
13487
  await rawRootUrlInfo.cookDependencies({ operation: buildOperation });
13416
13488
  }
13489
+ endPhase("craft");
13417
13490
 
13418
13491
  const finalKitchen = createKitchen({
13419
13492
  name: "shape",
@@ -13626,6 +13699,8 @@ const prepareEntryPointBuild = async (
13626
13699
  }
13627
13700
  }
13628
13701
 
13702
+ endPhase("bundle");
13703
+
13629
13704
  {
13630
13705
  finalKitchen.context.buildStep = "shape";
13631
13706
  if (outDirectoryUrl) {
@@ -13645,6 +13720,8 @@ const prepareEntryPointBuild = async (
13645
13720
  });
13646
13721
  }
13647
13722
 
13723
+ endPhase("shape");
13724
+
13648
13725
  const buildSideEffectFiles = [];
13649
13726
  {
13650
13727
  finalKitchen.context.buildStep = "refine";
@@ -13752,6 +13829,8 @@ const prepareEntryPointBuild = async (
13752
13829
  }
13753
13830
  }
13754
13831
  }
13832
+ endPhase("refine");
13833
+
13755
13834
  const {
13756
13835
  buildFileContents,
13757
13836
  buildFileVersions,
@@ -13771,6 +13850,7 @@ const prepareEntryPointBuild = async (
13771
13850
  buildInlineContents,
13772
13851
  buildManifest,
13773
13852
  buildSideEffectFiles,
13853
+ phaseTimings,
13774
13854
  };
13775
13855
  },
13776
13856
  };
@@ -3,8 +3,8 @@ import { extname } from "node:path";
3
3
  import { readFileSync as readFileSync$1, existsSync, readdir, chmod, stat, lstat, chmodSync, statSync, lstatSync, promises, readdirSync, openSync, closeSync, unlinkSync, rmdirSync, mkdirSync, writeFileSync as writeFileSync$1, unlink, rmdir, watch, realpathSync } from "node:fs";
4
4
  import crypto, { createHash } from "node:crypto";
5
5
  import { pathToFileURL, fileURLToPath } from "node:url";
6
- import { cpus, totalmem, freemem } from "node:os";
7
- import { cpuUsage, memoryUsage } from "node:process";
6
+ import { totalmem, freemem } from "node:os";
7
+ import { memoryUsage } from "node:process";
8
8
  import { stripVTControlCharacters } from "node:util";
9
9
 
10
10
  const createCallbackListNotifiedOnce = () => {
@@ -6548,174 +6548,6 @@ const getExtensionsToTry = (magicExtensions, importer) => {
6548
6548
  return Array.from(extensionsSet.values());
6549
6549
  };
6550
6550
 
6551
- const startMeasuringTotalCpuUsage = () => {
6552
- let previousCpuArray = cpus();
6553
- let previousMs = Date.now();
6554
- let previousCpuUsage = cpuUsage();
6555
-
6556
- const overall = {
6557
- inactive: 100,
6558
- active: 0,
6559
- system: 0,
6560
- user: 0,
6561
- };
6562
- const thisProcess = {
6563
- active: 0,
6564
- system: 0,
6565
- user: 0,
6566
- };
6567
- const details = previousCpuArray.map(() => {
6568
- return {
6569
- inactive: 100,
6570
- active: 0,
6571
- system: 0,
6572
- user: 0,
6573
- };
6574
- });
6575
-
6576
- const samples = [];
6577
- const interval = setInterval(() => {
6578
- let cpuArray = cpus();
6579
- const ms = Date.now();
6580
- const ellapsedMs = ms - previousMs;
6581
- const cpuUsageSampleArray = [];
6582
- let overallSystemMs = 0;
6583
- let overallUserMs = 0;
6584
- let overallInactiveMs = 0;
6585
- let overallActiveMs = 0;
6586
- let overallMsEllapsed = 0;
6587
- let index = 0;
6588
- for (const cpu of cpuArray) {
6589
- const previousCpuTimes = previousCpuArray[index].times;
6590
- const cpuTimes = cpu.times;
6591
- const systemMs = cpuTimes.sys - previousCpuTimes.sys;
6592
- const userMs = cpuTimes.user - previousCpuTimes.user;
6593
- const activeMs = systemMs + userMs;
6594
- const inactiveMs = ellapsedMs - activeMs;
6595
- const cpuUsageSample = {
6596
- inactive: inactiveMs / ellapsedMs,
6597
- active: activeMs / ellapsedMs,
6598
- system: systemMs / ellapsedMs,
6599
- user: userMs / ellapsedMs,
6600
- };
6601
- cpuUsageSampleArray.push(cpuUsageSample);
6602
-
6603
- overallSystemMs += systemMs;
6604
- overallUserMs += userMs;
6605
- overallInactiveMs += inactiveMs;
6606
- overallActiveMs += activeMs;
6607
- overallMsEllapsed += ellapsedMs;
6608
- index++;
6609
- }
6610
- const overallUsageSample = {
6611
- inactive: overallInactiveMs / overallMsEllapsed,
6612
- active: overallActiveMs / overallMsEllapsed,
6613
- system: overallSystemMs / overallMsEllapsed,
6614
- user: overallUserMs / overallMsEllapsed,
6615
- };
6616
- previousCpuArray = cpuArray;
6617
- previousMs = ms;
6618
-
6619
- const processCpuUsage = cpuUsage();
6620
- const thisProcessSystemMs = Math.round(
6621
- (processCpuUsage.system - previousCpuUsage.system) / 1000,
6622
- );
6623
- const thisProcessUserMs = Math.round(
6624
- (processCpuUsage.user - previousCpuUsage.user) / 1000,
6625
- );
6626
- previousCpuUsage = processCpuUsage;
6627
-
6628
- const thisProcessActiveMs = thisProcessSystemMs + thisProcessUserMs;
6629
- const thisProcessInactiveMs = overallMsEllapsed - thisProcessActiveMs;
6630
- const thisProcessSample = {
6631
- inactive: thisProcessInactiveMs / overallMsEllapsed,
6632
- active: thisProcessActiveMs / overallMsEllapsed,
6633
- system: thisProcessSystemMs / overallMsEllapsed,
6634
- user: thisProcessUserMs / overallMsEllapsed,
6635
- };
6636
- samples.push({
6637
- cpuUsageSampleArray,
6638
- overallUsageSample,
6639
- thisProcessSample,
6640
- });
6641
- if (samples.length === 10) {
6642
- {
6643
- let index = 0;
6644
- for (const detail of details) {
6645
- let systemSum = 0;
6646
- let userSum = 0;
6647
- let inactiveSum = 0;
6648
- let activeSum = 0;
6649
- for (const sample of samples) {
6650
- const { cpuUsageSampleArray } = sample;
6651
- const cpuUsageSample = cpuUsageSampleArray[index];
6652
- inactiveSum += cpuUsageSample.inactive;
6653
- activeSum += cpuUsageSample.active;
6654
- systemSum += cpuUsageSample.system;
6655
- userSum += cpuUsageSample.user;
6656
- }
6657
- Object.assign(detail, {
6658
- inactive: inactiveSum / samples.length,
6659
- active: activeSum / samples.length,
6660
- system: systemSum / samples.length,
6661
- user: userSum / samples.length,
6662
- });
6663
- index++;
6664
- }
6665
- }
6666
- {
6667
- let overallSystemSum = 0;
6668
- let overallUserSum = 0;
6669
- let overallInactiveSum = 0;
6670
- let overallActiveSum = 0;
6671
- for (const sample of samples) {
6672
- const { overallUsageSample } = sample;
6673
- overallSystemSum += overallUsageSample.system;
6674
- overallUserSum += overallUsageSample.user;
6675
- overallInactiveSum += overallUsageSample.inactive;
6676
- overallActiveSum += overallUsageSample.active;
6677
- }
6678
- Object.assign(overall, {
6679
- inactive: overallInactiveSum / samples.length,
6680
- active: overallActiveSum / samples.length,
6681
- system: overallSystemSum / samples.length,
6682
- user: overallUserSum / samples.length,
6683
- });
6684
- }
6685
- {
6686
- let thisProcessSystemSum = 0;
6687
- let thisProcessUserSum = 0;
6688
- let thisProcessInactiveSum = 0;
6689
- let thisProcessActiveSum = 0;
6690
- for (const sample of samples) {
6691
- const { thisProcessSample } = sample;
6692
- thisProcessSystemSum += thisProcessSample.system;
6693
- thisProcessUserSum += thisProcessSample.user;
6694
- thisProcessInactiveSum += thisProcessSample.inactive;
6695
- thisProcessActiveSum += thisProcessSample.active;
6696
- }
6697
- Object.assign(thisProcess, {
6698
- inactive: thisProcessInactiveSum / samples.length,
6699
- active: thisProcessActiveSum / samples.length,
6700
- system: thisProcessSystemSum / samples.length,
6701
- user: thisProcessUserSum / samples.length,
6702
- });
6703
- }
6704
- samples.length = 0;
6705
- }
6706
- }, 15);
6707
- interval.unref();
6708
-
6709
- return {
6710
- overall,
6711
- thisProcess,
6712
- details,
6713
- stop: () => {
6714
- clearInterval(interval);
6715
- },
6716
- };
6717
- };
6718
-
6719
6551
  const startMonitoringMetric = (measure) => {
6720
6552
  const metrics = [];
6721
6553
  const takeMeasure = () => {
@@ -6761,22 +6593,6 @@ const medianFromSortedArray = (array) => {
6761
6593
  return medianNumber;
6762
6594
  };
6763
6595
 
6764
- // https://gist.github.com/GaetanoPiazzolla/c40e1ebb9f709d091208e89baf9f4e00
6765
-
6766
-
6767
- const startMonitoringCpuUsage = () => {
6768
- const cpuUsage = startMeasuringTotalCpuUsage();
6769
- const processCpuUsageMonitoring = startMonitoringMetric(() => {
6770
- return cpuUsage.thisProcess.active;
6771
- });
6772
- const osCpuUsageMonitoring = startMonitoringMetric(() => {
6773
- return cpuUsage.overall.active;
6774
- });
6775
- const result = [processCpuUsageMonitoring, osCpuUsageMonitoring];
6776
- result.stop = cpuUsage.stop;
6777
- return result;
6778
- };
6779
-
6780
6596
  const startMonitoringMemoryUsage = () => {
6781
6597
  const processMemoryUsageMonitoring = startMonitoringMetric(() => {
6782
6598
  return memoryUsage().rss;
@@ -11231,4 +11047,4 @@ const escapeRegexpSpecialChars = (string) => {
11231
11047
  });
11232
11048
  };
11233
11049
 
11234
- export { ANSI, Abort, CONTENT_TYPE, DATA_URL, JS_QUOTES, RUNTIME_COMPAT, UNICODE, URL_META, applyFileSystemMagicResolution, applyNodeEsmResolution, asSpecifierWithoutSearch, asUrlWithoutSearch, assertAndNormalizeDirectoryUrl, browserDefaultRuntimeCompat, bufferToEtag, clearDirectorySync, collectFiles, compareFileUrls, comparePathnames, composeTwoImportMaps, createDetailedMessage$1 as createDetailedMessage, createDynamicLog, createLogger, createLookupPackageDirectory, createTaskLog, distributePercentages, ensureEmptyDirectory, ensurePathnameTrailingSlash, ensureWindowsDriveLetter, errorToHTML, escapeRegexpSpecialChars, generateContentFrame, getCallerPosition, getExtensionsToTry, humanizeDuration, humanizeFileSize, humanizeMemory, inferRuntimeCompatFromClosestPackage, injectQueryParamIntoSpecifierWithoutEncoding, injectQueryParams, injectQueryParamsIntoSpecifier, isFileSystemPath, isSpecifierForNodeBuiltin, lookupPackageDirectory, moveUrl, nodeDefaultRuntimeCompat, normalizeImportMap, normalizeUrl, raceProcessTeardownEvents, readCustomConditionsFromProcessArgs, readEntryStatSync, readPackageAtOrNull, registerDirectoryLifecycle, renderBigSection, renderDetails, renderTable, renderUrlOrRelativeUrlFilename, resolveImport, setUrlBasename, setUrlExtension, setUrlFilename, startMonitoringCpuUsage, startMonitoringMemoryUsage, stringifyUrlSite, updateJsonFileSync, urlIsOrIsInsideOf, urlToBasename, urlToExtension$1 as urlToExtension, urlToFileSystemPath, urlToFilename$1 as urlToFilename, urlToPathname$1 as urlToPathname, urlToRelativeUrl, validateResponseIntegrity, writeFileSync };
11050
+ export { ANSI, Abort, CONTENT_TYPE, DATA_URL, JS_QUOTES, RUNTIME_COMPAT, UNICODE, URL_META, applyFileSystemMagicResolution, applyNodeEsmResolution, asSpecifierWithoutSearch, asUrlWithoutSearch, assertAndNormalizeDirectoryUrl, browserDefaultRuntimeCompat, bufferToEtag, clearDirectorySync, collectFiles, compareFileUrls, comparePathnames, composeTwoImportMaps, createDetailedMessage$1 as createDetailedMessage, createDynamicLog, createLogger, createLookupPackageDirectory, createTaskLog, distributePercentages, ensureEmptyDirectory, ensurePathnameTrailingSlash, ensureWindowsDriveLetter, errorToHTML, escapeRegexpSpecialChars, generateContentFrame, getCallerPosition, getExtensionsToTry, humanizeDuration, humanizeFileSize, humanizeMemory, inferRuntimeCompatFromClosestPackage, injectQueryParamIntoSpecifierWithoutEncoding, injectQueryParams, injectQueryParamsIntoSpecifier, isFileSystemPath, isSpecifierForNodeBuiltin, lookupPackageDirectory, moveUrl, nodeDefaultRuntimeCompat, normalizeImportMap, normalizeUrl, raceProcessTeardownEvents, readCustomConditionsFromProcessArgs, readEntryStatSync, readPackageAtOrNull, registerDirectoryLifecycle, renderBigSection, renderDetails, renderTable, renderUrlOrRelativeUrlFilename, resolveImport, setUrlBasename, setUrlExtension, setUrlFilename, startMonitoringMemoryUsage, stringifyUrlSite, updateJsonFileSync, urlIsOrIsInsideOf, urlToBasename, urlToExtension$1 as urlToExtension, urlToFileSystemPath, urlToFilename$1 as urlToFilename, urlToPathname$1 as urlToPathname, urlToRelativeUrl, validateResponseIntegrity, writeFileSync };
@@ -2467,8 +2467,7 @@ const parseAndTransformJsReferences = async (
2467
2467
  for (const sequentialAction of sequentialActions) {
2468
2468
  await sequentialAction();
2469
2469
  }
2470
- const { content, sourcemap } = magicSource.toContentAndSourcemap();
2471
- return { content, sourcemap };
2470
+ return magicSource.toContentAndSourcemap();
2472
2471
  };
2473
2472
 
2474
2473
  const jsenvPluginReferenceExpectedTypes = () => {
@@ -6161,6 +6160,8 @@ const jsenvPluginImportMetaCss = () => {
6161
6160
  inputIsJsModule: true,
6162
6161
  inputUrl: urlInfo.originalUrl,
6163
6162
  outputUrl: urlInfo.generatedUrl,
6163
+ // the map would be dropped: the content sent back carries none
6164
+ options: { sourceMaps: false },
6164
6165
  });
6165
6166
  if (code === urlInfo.content) {
6166
6167
  // all assignments were already in array form (pre-built file) — nothing to do
@@ -8241,7 +8242,10 @@ const devServerPluginOmegaErrorHandler = () => {
8241
8242
  status: 403,
8242
8243
  };
8243
8244
  }
8244
- return convertFileSystemErrorToResponseProperties(error);
8245
+ // the dev server runs with canExposeSensitiveData: file paths are welcome
8246
+ return convertFileSystemErrorToResponseProperties(error, {
8247
+ canExposeSensitiveData: true,
8248
+ });
8245
8249
  };
8246
8250
  const response = getResponseForError();
8247
8251
  if (!response) {
@@ -8427,10 +8431,9 @@ const prependJsClassicInJsClassic = (jsUrlInfo, urlInfoToPrepend) => {
8427
8431
  const magicSource = createMagicSource(jsUrlInfo.content);
8428
8432
  magicSource.prepend(`${urlInfoToPrepend.content}\n\n`);
8429
8433
  const magicResult = magicSource.toContentAndSourcemap();
8430
- const sourcemap = composeTwoSourcemaps(
8431
- jsUrlInfo.sourcemap,
8432
- magicResult.sourcemap,
8433
- );
8434
+ const sourcemap = jsUrlInfo.context.sourcemapsEnabled
8435
+ ? composeTwoSourcemaps(jsUrlInfo.sourcemap, magicResult.sourcemap)
8436
+ : null;
8434
8437
  jsUrlInfo.mutateContent({
8435
8438
  content: magicResult.content,
8436
8439
  sourcemap,
@@ -8448,6 +8451,7 @@ const prependJsClassicInJsModule = async (jsUrlInfo, urlInfoToPrepend) => {
8448
8451
  input: jsUrlInfo.content,
8449
8452
  inputIsJsModule: true,
8450
8453
  inputUrl: jsUrlInfo.originalUrl,
8454
+ options: { sourceMaps: jsUrlInfo.context.sourcemapsEnabled },
8451
8455
  });
8452
8456
  jsUrlInfo.mutateContent({
8453
8457
  content: code,
@@ -10172,7 +10176,6 @@ const createUrlInfoTransformer = ({
10172
10176
  contentAst, // undefined most of the time
10173
10177
  contentEtag, // in practice always undefined
10174
10178
  contentLength,
10175
- sourcemap,
10176
10179
  sourcemapIsWrong,
10177
10180
  contentInjections,
10178
10181
  } = transformations;
@@ -10199,11 +10202,14 @@ const createUrlInfoTransformer = ({
10199
10202
  contentLength,
10200
10203
  });
10201
10204
  }
10202
- if (
10203
- sourcemap &&
10204
- mayHaveSourcemap(urlInfo) &&
10205
- shouldHandleSourcemap(urlInfo)
10206
- ) {
10205
+ // "sourcemap" is read last, and only when it will be used: a plugin can
10206
+ // hand it back as a getter that generates the map on first read, so that
10207
+ // nothing is generated for a kitchen that throws sourcemaps away
10208
+ const sourcemap =
10209
+ mayHaveSourcemap(urlInfo) && shouldHandleSourcemap(urlInfo)
10210
+ ? transformations.sourcemap
10211
+ : null;
10212
+ if (sourcemap) {
10207
10213
  const sourcemapNormalized = normalizeSourcemap(urlInfo, sourcemap);
10208
10214
  let currentSourcemap = urlInfo.sourcemap;
10209
10215
  const finalSourcemap = composeTwoSourcemaps(
@@ -10236,6 +10242,14 @@ const createUrlInfoTransformer = ({
10236
10242
  writeInsideOutDirectory(urlInfo);
10237
10243
  };
10238
10244
 
10245
+ // Written synchronously on purpose, and measured: async is the tempting
10246
+ // choice, but here it loses. A cold load cooks hundreds of files; their
10247
+ // synchronous writes block the event loop ~160ms in total, while
10248
+ // asynchronous writes queue in the threadpool behind tens of MB of content
10249
+ // and sourcemaps, and a response that waits for its own write then waits
10250
+ // ~36ms on average (18s summed over 500 responses). Not waiting is not an
10251
+ // option either: the last write lands after the test that cooked it has
10252
+ // ended, and the side-effect snapshots lose it.
10239
10253
  const writeInsideOutDirectory = (urlInfo) => {
10240
10254
  // writing result inside ".jsenv" directory (debug purposes)
10241
10255
  if (!outDirectoryUrl) {
@@ -10555,6 +10569,12 @@ const createKitchen = ({
10555
10569
  INJECTIONS,
10556
10570
  getPluginMeta: null,
10557
10571
  sourcemaps,
10572
+ // a plugin producing a sourcemap can skip the work when it would be
10573
+ // thrown away (see shouldHandleSourcemap in url_info_transformations.js)
10574
+ sourcemapsEnabled:
10575
+ sourcemaps === "inline" ||
10576
+ sourcemaps === "file" ||
10577
+ sourcemaps === "programmatic",
10558
10578
  outDirectoryUrl,
10559
10579
  },
10560
10580
  resolve: (specifier, importer = rootDirectoryUrl) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "41.4.18",
3
+ "version": "41.4.19",
4
4
  "type": "module",
5
5
  "description": "Tool to develop, test and build js projects",
6
6
  "repository": {
@@ -74,11 +74,11 @@
74
74
  "dependencies": {
75
75
  "@jsenv/ast": "6.9.2",
76
76
  "@jsenv/js-module-fallback": "1.5.1",
77
- "@jsenv/plugin-bundling": "2.10.17",
78
- "@jsenv/plugin-minification": "1.7.9",
77
+ "@jsenv/plugin-bundling": "2.10.18",
78
+ "@jsenv/plugin-minification": "1.7.10",
79
79
  "@jsenv/plugin-supervisor": "1.8.13",
80
80
  "@jsenv/plugin-transpilation": "1.6.1",
81
- "@jsenv/server": "17.6.1",
81
+ "@jsenv/server": "17.6.2",
82
82
  "@jsenv/sourcemap": "1.4.2",
83
83
  "react-table": "7.8.0"
84
84
  },
@@ -38,10 +38,7 @@ import {
38
38
  UNICODE,
39
39
  } from "@jsenv/humanize";
40
40
  import { applyNodeEsmResolution } from "@jsenv/node-esm-resolution";
41
- import {
42
- startMonitoringCpuUsage,
43
- startMonitoringMemoryUsage,
44
- } from "@jsenv/os-metrics";
41
+ import { startMonitoringMemoryUsage } from "@jsenv/os-metrics";
45
42
  import { jsenvPluginBundling } from "@jsenv/plugin-bundling";
46
43
  import { jsenvPluginMinification } from "@jsenv/plugin-minification";
47
44
  import { jsenvPluginJsModuleFallback } from "@jsenv/plugin-transpilation";
@@ -395,13 +392,12 @@ entryPoints: {
395
392
  });
396
393
  }
397
394
 
398
- const cpuMonitoring = startMonitoringCpuUsage();
399
- operation.addEndCallback(cpuMonitoring.stop);
400
- const [processCpuUsageMonitoring] = cpuMonitoring;
395
+ // cpu is not sampled: cooking, rollup and terser hold the event loop for
396
+ // seconds at a time, a sampler only runs in the gaps and would report the
397
+ // idle time. What the build cost is process.cpuUsage() over its duration.
401
398
  const memoryMonitoring = startMonitoringMemoryUsage();
402
399
  const [processMemoryUsageMonitoring] = memoryMonitoring;
403
400
  const interval = setInterval(() => {
404
- processCpuUsageMonitoring.measure();
405
401
  processMemoryUsageMonitoring.measure();
406
402
  }, 500).unref();
407
403
  operation.addEndCallback(() => {
@@ -471,19 +467,24 @@ entryPoints: {
471
467
  content += "\n";
472
468
  return content;
473
469
  };
474
- const renderBuildEndLog = ({ duration, buildFileContents }) => {
470
+ const renderBuildEndLog = ({
471
+ duration,
472
+ buildFileContents,
473
+ cpuUsage,
474
+ phaseTimings,
475
+ }) => {
475
476
  // tell how many files are generated in build directory
476
477
  // tell the repartition?
477
478
  // this is not really useful for single build right?
478
479
 
479
- processCpuUsageMonitoring.end();
480
480
  processMemoryUsageMonitoring.end();
481
481
 
482
482
  return renderBuildDoneLog({
483
483
  entryPointArray,
484
484
  duration,
485
485
  buildFileContents,
486
- processCpuUsage: processCpuUsageMonitoring.info,
486
+ cpuUsage,
487
+ phaseTimings,
487
488
  processMemoryUsage: processMemoryUsageMonitoring.info,
488
489
  });
489
490
  };
@@ -558,13 +559,25 @@ entryPoints: {
558
559
  }, renderDynamicLog());
559
560
  };
560
561
  },
561
- onBuildEnd: ({ buildFileContents, duration }) => {
562
+ onBuildEnd: ({
563
+ buildFileContents,
564
+ duration,
565
+ cpuUsage,
566
+ phaseTimings,
567
+ }) => {
562
568
  clearInterval(interval);
563
569
  dynamicLog.update("");
564
570
  dynamicLog.destroy();
565
571
  dynamicLog = null;
566
572
  logger.info("");
567
- logger.info(renderBuildEndLog({ duration, buildFileContents }));
573
+ logger.info(
574
+ renderBuildEndLog({
575
+ duration,
576
+ buildFileContents,
577
+ cpuUsage,
578
+ phaseTimings,
579
+ }),
580
+ );
568
581
  },
569
582
  };
570
583
  };
@@ -591,8 +604,20 @@ entryPoints: {
591
604
  );
592
605
  };
593
606
  },
594
- onBuildEnd: ({ buildFileContents, duration }) => {
595
- logger.info(renderBuildEndLog({ duration, buildFileContents }));
607
+ onBuildEnd: ({
608
+ buildFileContents,
609
+ duration,
610
+ cpuUsage,
611
+ phaseTimings,
612
+ }) => {
613
+ logger.info(
614
+ renderBuildEndLog({
615
+ duration,
616
+ buildFileContents,
617
+ cpuUsage,
618
+ phaseTimings,
619
+ }),
620
+ );
596
621
  },
597
622
  };
598
623
  };
@@ -642,6 +667,7 @@ entryPoints: {
642
667
 
643
668
  const runBuild = async ({ signal }) => {
644
669
  const startDate = Date.now();
670
+ const cpuUsageAtStart = process.cpuUsage();
645
671
  const { onBuildEnd, onEntryPointBuildStart } = startBuildLogs();
646
672
 
647
673
  const buildUrlsGenerator = createBuildUrlsGenerator({
@@ -750,6 +776,7 @@ entryPoints: {
750
776
  entryBuildInfo.buildInlineContents = result.buildInlineContents;
751
777
  entryBuildInfo.buildManifest = result.buildManifest;
752
778
  entryBuildInfo.buildSideEffectFiles = result.buildSideEffectFiles;
779
+ entryBuildInfo.phaseTimings = result.phaseTimings;
753
780
  entryBuildInfo.duration = Date.now() - entryPointBuildStartMs;
754
781
  onEntryPointBuildEnd();
755
782
  })();
@@ -854,11 +881,19 @@ entryPoints: {
854
881
  }
855
882
  }
856
883
  }
884
+ // entry points are built concurrently, interleaved on the same thread:
885
+ // their phases overlap and only a single one can be told apart
886
+ const phaseTimings =
887
+ entryBuildInfoMap.size === 1
888
+ ? entryBuildInfoMap.values().next().value.phaseTimings
889
+ : null;
857
890
  onBuildEnd({
858
891
  buildFileContents,
859
892
  buildInlineContents,
860
893
  buildManifest,
861
894
  duration: Date.now() - startDate,
895
+ cpuUsage: process.cpuUsage(cpuUsageAtStart),
896
+ phaseTimings,
862
897
  });
863
898
  return {
864
899
  ...(returnBuildInlineContents ? { buildInlineContents } : {}),
@@ -1222,6 +1257,14 @@ const prepareEntryPointBuild = async (
1222
1257
  return {
1223
1258
  entryReference,
1224
1259
  buildEntryPoint: async ({ getOtherEntryBuildInfo }) => {
1260
+ const phaseTimings = {};
1261
+ let phaseStartMs = Date.now();
1262
+ const endPhase = (name) => {
1263
+ const nowMs = Date.now();
1264
+ phaseTimings[name] = nowMs - phaseStartMs;
1265
+ phaseStartMs = nowMs;
1266
+ };
1267
+
1225
1268
  craft: {
1226
1269
  _getOtherEntryBuildInfo = getOtherEntryBuildInfo;
1227
1270
  if (outDirectoryUrl) {
@@ -1229,6 +1272,7 @@ const prepareEntryPointBuild = async (
1229
1272
  }
1230
1273
  await rawRootUrlInfo.cookDependencies({ operation: buildOperation });
1231
1274
  }
1275
+ endPhase("craft");
1232
1276
 
1233
1277
  const finalKitchen = createKitchen({
1234
1278
  name: "shape",
@@ -1443,6 +1487,8 @@ const prepareEntryPointBuild = async (
1443
1487
  }
1444
1488
  }
1445
1489
 
1490
+ endPhase("bundle");
1491
+
1446
1492
  shape: {
1447
1493
  finalKitchen.context.buildStep = "shape";
1448
1494
  if (outDirectoryUrl) {
@@ -1462,6 +1508,8 @@ const prepareEntryPointBuild = async (
1462
1508
  });
1463
1509
  }
1464
1510
 
1511
+ endPhase("shape");
1512
+
1465
1513
  const buildSideEffectFiles = [];
1466
1514
  refine: {
1467
1515
  finalKitchen.context.buildStep = "refine";
@@ -1569,6 +1617,8 @@ const prepareEntryPointBuild = async (
1569
1617
  }
1570
1618
  }
1571
1619
  }
1620
+ endPhase("refine");
1621
+
1572
1622
  const {
1573
1623
  buildFileContents,
1574
1624
  buildFileVersions,
@@ -1588,6 +1638,7 @@ const prepareEntryPointBuild = async (
1588
1638
  buildInlineContents,
1589
1639
  buildManifest,
1590
1640
  buildSideEffectFiles,
1641
+ phaseTimings,
1591
1642
  };
1592
1643
  },
1593
1644
  };
@@ -19,11 +19,12 @@ ${createRepartitionMessage(buildContentReport, { indent })}
19
19
  --------------------`;
20
20
  };
21
21
 
22
- const humanizeProcessCpuUsage = (ratio) => {
23
- const percentageAsNumber = ratio * 100;
24
- const percentageAsNumberRounded = Math.round(percentageAsNumber);
25
- const percentage = `${percentageAsNumberRounded}%`;
26
- return percentage;
22
+ // a phase can be a few milliseconds long (refine, mostly): shown as such
23
+ const humanizePhaseDuration = (ms) => {
24
+ if (ms < 1000) {
25
+ return `${Math.round(ms)}ms`;
26
+ }
27
+ return humanizeDuration(ms, { short: true });
27
28
  };
28
29
  const humanizeProcessMemoryUsage = (value) => {
29
30
  return humanizeMemory(value, { short: true, decimals: 0 });
@@ -32,7 +33,8 @@ export const renderBuildDoneLog = ({
32
33
  entryPointArray,
33
34
  duration,
34
35
  buildFileContents,
35
- processCpuUsage,
36
+ cpuUsage,
37
+ phaseTimings,
36
38
  processMemoryUsage,
37
39
  }) => {
38
40
  const buildContentReport = createBuildContentReport(buildFileContents);
@@ -47,15 +49,10 @@ export const renderBuildDoneLog = ({
47
49
  title = `build done (${entryPointCount} entry points)`;
48
50
  }
49
51
 
50
- // cpu usage
51
- let cpuUsageLine = "cpu: ";
52
- cpuUsageLine += `${humanizeProcessCpuUsage(processCpuUsage.end)}`;
53
- cpuUsageLine += renderDetails({
54
- med: humanizeProcessCpuUsage(processCpuUsage.median),
55
- min: humanizeProcessCpuUsage(processCpuUsage.min),
56
- max: humanizeProcessCpuUsage(processCpuUsage.max),
57
- });
58
- lines.push(cpuUsageLine);
52
+ // cpu: the process cpu time over the build duration, 100% being one core
53
+ // busy the whole time (more when a native module works on other threads)
54
+ const cpuMs = (cpuUsage.user + cpuUsage.system) / 1000;
55
+ lines.push(`cpu: ${Math.round((cpuMs / duration) * 100)}%`);
59
56
 
60
57
  // memory usage
61
58
  let memoryUsageLine = "memory: ";
@@ -70,6 +67,16 @@ export const renderBuildDoneLog = ({
70
67
  // duration
71
68
  let durationLine = `duration: `;
72
69
  durationLine += humanizeDuration(duration, { short: true });
70
+ if (phaseTimings) {
71
+ durationLine += renderDetails(
72
+ Object.fromEntries(
73
+ Object.keys(phaseTimings).map((phaseName) => [
74
+ phaseName,
75
+ humanizePhaseDuration(phaseTimings[phaseName]),
76
+ ]),
77
+ ),
78
+ );
79
+ }
73
80
  lines.push(durationLine);
74
81
 
75
82
  // content
@@ -269,6 +276,8 @@ const createBuildContentReport = (buildFileContents) => {
269
276
  };
270
277
 
271
278
  const determineCategory = (buildRelativeUrl) => {
279
+ // a versioned url ("main.js?v=abc") is categorized by its file
280
+ buildRelativeUrl = buildRelativeUrl.replace(/[?#].*$/, "");
272
281
  if (buildRelativeUrl.endsWith(".map")) {
273
282
  return "sourcemap";
274
283
  }
@@ -801,7 +801,7 @@ export const createBuildSpecifierManager = ({
801
801
  }
802
802
  if (mayUsePlaceholder(urlInfo)) {
803
803
  const contentBeforeReplace = urlInfo.content;
804
- const { content, sourcemap } = placeholderAPI.replaceAll(
804
+ const replaceResult = placeholderAPI.replaceAll(
805
805
  contentBeforeReplace,
806
806
  (placeholder) => {
807
807
  const reference = placeholderToReferenceMap.get(placeholder);
@@ -809,7 +809,7 @@ export const createBuildSpecifierManager = ({
809
809
  return value;
810
810
  },
811
811
  );
812
- urlInfo.mutateContent({ content, sourcemap });
812
+ urlInfo.mutateContent(replaceResult);
813
813
  }
814
814
  },
815
815
  );
@@ -15,7 +15,10 @@ export const devServerPluginOmegaErrorHandler = () => {
15
15
  status: 403,
16
16
  };
17
17
  }
18
- return convertFileSystemErrorToResponseProperties(error);
18
+ // the dev server runs with canExposeSensitiveData: file paths are welcome
19
+ return convertFileSystemErrorToResponseProperties(error, {
20
+ canExposeSensitiveData: true,
21
+ });
19
22
  };
20
23
  const response = getResponseForError();
21
24
  if (!response) {
@@ -118,6 +118,12 @@ export const createKitchen = ({
118
118
  INJECTIONS,
119
119
  getPluginMeta: null,
120
120
  sourcemaps,
121
+ // a plugin producing a sourcemap can skip the work when it would be
122
+ // thrown away (see shouldHandleSourcemap in url_info_transformations.js)
123
+ sourcemapsEnabled:
124
+ sourcemaps === "inline" ||
125
+ sourcemaps === "file" ||
126
+ sourcemaps === "programmatic",
121
127
  outDirectoryUrl,
122
128
  },
123
129
  resolve: (specifier, importer = rootDirectoryUrl) => {
@@ -67,10 +67,9 @@ const prependJsClassicInJsClassic = (jsUrlInfo, urlInfoToPrepend) => {
67
67
  const magicSource = createMagicSource(jsUrlInfo.content);
68
68
  magicSource.prepend(`${urlInfoToPrepend.content}\n\n`);
69
69
  const magicResult = magicSource.toContentAndSourcemap();
70
- const sourcemap = composeTwoSourcemaps(
71
- jsUrlInfo.sourcemap,
72
- magicResult.sourcemap,
73
- );
70
+ const sourcemap = jsUrlInfo.context.sourcemapsEnabled
71
+ ? composeTwoSourcemaps(jsUrlInfo.sourcemap, magicResult.sourcemap)
72
+ : null;
74
73
  jsUrlInfo.mutateContent({
75
74
  content: magicResult.content,
76
75
  sourcemap,
@@ -88,6 +87,7 @@ const prependJsClassicInJsModule = async (jsUrlInfo, urlInfoToPrepend) => {
88
87
  input: jsUrlInfo.content,
89
88
  inputIsJsModule: true,
90
89
  inputUrl: jsUrlInfo.originalUrl,
90
+ options: { sourceMaps: jsUrlInfo.context.sourcemapsEnabled },
91
91
  });
92
92
  jsUrlInfo.mutateContent({
93
93
  content: code,
@@ -199,7 +199,6 @@ export const createUrlInfoTransformer = ({
199
199
  contentAst, // undefined most of the time
200
200
  contentEtag, // in practice always undefined
201
201
  contentLength,
202
- sourcemap,
203
202
  sourcemapIsWrong,
204
203
  contentInjections,
205
204
  } = transformations;
@@ -226,11 +225,14 @@ export const createUrlInfoTransformer = ({
226
225
  contentLength,
227
226
  });
228
227
  }
229
- if (
230
- sourcemap &&
231
- mayHaveSourcemap(urlInfo) &&
232
- shouldHandleSourcemap(urlInfo)
233
- ) {
228
+ // "sourcemap" is read last, and only when it will be used: a plugin can
229
+ // hand it back as a getter that generates the map on first read, so that
230
+ // nothing is generated for a kitchen that throws sourcemaps away
231
+ const sourcemap =
232
+ mayHaveSourcemap(urlInfo) && shouldHandleSourcemap(urlInfo)
233
+ ? transformations.sourcemap
234
+ : null;
235
+ if (sourcemap) {
234
236
  const sourcemapNormalized = normalizeSourcemap(urlInfo, sourcemap);
235
237
  let currentSourcemap = urlInfo.sourcemap;
236
238
  const finalSourcemap = composeTwoSourcemaps(
@@ -263,6 +265,14 @@ export const createUrlInfoTransformer = ({
263
265
  writeInsideOutDirectory(urlInfo);
264
266
  };
265
267
 
268
+ // Written synchronously on purpose, and measured: async is the tempting
269
+ // choice, but here it loses. A cold load cooks hundreds of files; their
270
+ // synchronous writes block the event loop ~160ms in total, while
271
+ // asynchronous writes queue in the threadpool behind tens of MB of content
272
+ // and sourcemaps, and a response that waits for its own write then waits
273
+ // ~36ms on average (18s summed over 500 responses). Not waiting is not an
274
+ // option either: the last write lands after the test that cooked it has
275
+ // ended, and the side-effect snapshots lose it.
266
276
  const writeInsideOutDirectory = (urlInfo) => {
267
277
  // writing result inside ".jsenv" directory (debug purposes)
268
278
  if (!outDirectoryUrl) {
@@ -75,6 +75,8 @@ export const jsenvPluginImportMetaCss = () => {
75
75
  inputIsJsModule: true,
76
76
  inputUrl: urlInfo.originalUrl,
77
77
  outputUrl: urlInfo.generatedUrl,
78
+ // the map would be dropped: the content sent back carries none
79
+ options: { sourceMaps: false },
78
80
  });
79
81
  if (code === urlInfo.content) {
80
82
  // all assignments were already in array form (pre-built file) — nothing to do
@@ -190,6 +190,5 @@ const parseAndTransformJsReferences = async (
190
190
  for (const sequentialAction of sequentialActions) {
191
191
  await sequentialAction();
192
192
  }
193
- const { content, sourcemap } = magicSource.toContentAndSourcemap();
194
- return { content, sourcemap };
193
+ return magicSource.toContentAndSourcemap();
195
194
  };