@jsenv/core 40.0.7 → 40.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/js/build.js DELETED
@@ -1,2636 +0,0 @@
1
- import { parseHtml, findHtmlNode, getHtmlNodeAttribute, getHtmlNodeText, stringifyHtmlAst, removeHtmlNode, setHtmlNodeText, injectHtmlNodeAsEarlyAsPossible, createHtmlNode, visitHtmlNodes, setHtmlNodeAttributes, insertHtmlNodeAfter } from "@jsenv/ast";
2
- import { distributePercentages, ANSI, humanizeFileSize, urlIsInsideOf, urlToRelativeUrl, urlToFilename, comparePathnames, UNICODE, escapeRegexpSpecialChars, createDetailedMessage, ensurePathnameTrailingSlash, CONTENT_TYPE, injectQueryParamIntoSpecifierWithoutEncoding, renderUrlOrRelativeUrlFilename, assertAndNormalizeDirectoryUrl, lookupPackageDirectory, Abort, raceProcessTeardownEvents, createLogger, jsenvPluginBundling, jsenvPluginMinification, ensureEmptyDirectory, jsenvPluginJsModuleFallback, clearDirectorySync, writeFileSync, createTaskLog } from "../jsenv_core_packages.js";
3
- import { GRAPH_VISITOR, prependContent, isWebWorkerUrlInfo, getDefaultBase, defaultRuntimeCompat, logsDefault, watchSourceFiles, createKitchen, createPluginStore, getCorePlugins, createPluginController, jsenvPluginReferenceAnalysis, jsenvPluginDirectoryReferenceEffect, jsenvPluginInlining } from "../plugins.js";
4
- import { generateSourcemapFileUrl, createMagicSource } from "@jsenv/sourcemap";
5
- import { createHash } from "node:crypto";
6
- import "node:path";
7
- import "node:fs";
8
- import "node:url";
9
- import "node:module";
10
- import "@jsenv/js-module-fallback";
11
- import "node:process";
12
- import "node:os";
13
- import "node:tty";
14
- import "string-width";
15
- import "@jsenv/runtime-compat";
16
- import "node:perf_hooks";
17
- import "@jsenv/plugin-supervisor";
18
- import "@jsenv/server";
19
- import "../main.js";
20
-
21
- const createUrlGraphSummary = (
22
- urlGraph,
23
- { title = "graph summary" } = {},
24
- ) => {
25
- const graphReport = createUrlGraphReport(urlGraph);
26
- return `--- ${title} ---
27
- ${createRepartitionMessage(graphReport)}
28
- --------------------`;
29
- };
30
-
31
- const createUrlGraphReport = (urlGraph) => {
32
- const countGroups = {
33
- sourcemaps: 0,
34
- html: 0,
35
- css: 0,
36
- js: 0,
37
- json: 0,
38
- other: 0,
39
- total: 0,
40
- };
41
- const sizeGroups = {
42
- sourcemaps: 0,
43
- html: 0,
44
- css: 0,
45
- js: 0,
46
- json: 0,
47
- other: 0,
48
- total: 0,
49
- };
50
-
51
- GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
52
- urlGraph.rootUrlInfo,
53
- (urlInfo) => {
54
- // ignore:
55
- // - ignored files: we don't know their content
56
- // - inline files and data files: they are already taken into account in the file where they appear
57
- if (urlInfo.url.startsWith("ignore:")) {
58
- return;
59
- }
60
- if (urlInfo.isInline) {
61
- return;
62
- }
63
- if (urlInfo.url.startsWith("data:")) {
64
- return;
65
- }
66
-
67
- // file loaded via import assertion are already inside the graph
68
- // their js module equivalent are ignored to avoid counting it twice
69
- // in the build graph the file targeted by import assertion will likely be gone
70
- // and only the js module remain (likely bundled)
71
- if (
72
- urlInfo.searchParams.has("as_json_module") ||
73
- urlInfo.searchParams.has("as_css_module") ||
74
- urlInfo.searchParams.has("as_text_module")
75
- ) {
76
- return;
77
- }
78
-
79
- const urlContentSize = Buffer.byteLength(urlInfo.content);
80
- const category = determineCategory(urlInfo);
81
- if (category === "sourcemap") {
82
- countGroups.sourcemaps++;
83
- sizeGroups.sourcemaps += urlContentSize;
84
- return;
85
- }
86
- countGroups.total++;
87
- sizeGroups.total += urlContentSize;
88
- if (category === "html") {
89
- countGroups.html++;
90
- sizeGroups.html += urlContentSize;
91
- return;
92
- }
93
- if (category === "css") {
94
- countGroups.css++;
95
- sizeGroups.css += urlContentSize;
96
- return;
97
- }
98
- if (category === "js") {
99
- countGroups.js++;
100
- sizeGroups.js += urlContentSize;
101
- return;
102
- }
103
- if (category === "json") {
104
- countGroups.json++;
105
- sizeGroups.json += urlContentSize;
106
- return;
107
- }
108
- countGroups.other++;
109
- sizeGroups.other += urlContentSize;
110
- return;
111
- },
112
- );
113
-
114
- const sizesToDistribute = {};
115
- Object.keys(sizeGroups).forEach((groupName) => {
116
- if (groupName !== "sourcemaps" && groupName !== "total") {
117
- sizesToDistribute[groupName] = sizeGroups[groupName];
118
- }
119
- });
120
- const percentageGroups = distributePercentages(sizesToDistribute);
121
-
122
- return {
123
- // sourcemaps are special, there size are ignored
124
- // so there is no "percentage" associated
125
- sourcemaps: {
126
- count: countGroups.sourcemaps,
127
- size: sizeGroups.sourcemaps,
128
- percentage: undefined,
129
- },
130
-
131
- html: {
132
- count: countGroups.html,
133
- size: sizeGroups.html,
134
- percentage: percentageGroups.html,
135
- },
136
- css: {
137
- count: countGroups.css,
138
- size: sizeGroups.css,
139
- percentage: percentageGroups.css,
140
- },
141
- js: {
142
- count: countGroups.js,
143
- size: sizeGroups.js,
144
- percentage: percentageGroups.js,
145
- },
146
- json: {
147
- count: countGroups.json,
148
- size: sizeGroups.json,
149
- percentage: percentageGroups.json,
150
- },
151
- other: {
152
- count: countGroups.other,
153
- size: sizeGroups.other,
154
- percentage: percentageGroups.other,
155
- },
156
- total: {
157
- count: countGroups.total,
158
- size: sizeGroups.total,
159
- percentage: 100,
160
- },
161
- };
162
- };
163
-
164
- const determineCategory = (urlInfo) => {
165
- if (urlInfo.type === "sourcemap") {
166
- return "sourcemap";
167
- }
168
- if (urlInfo.type === "html") {
169
- return "html";
170
- }
171
- if (urlInfo.type === "css") {
172
- return "css";
173
- }
174
- if (urlInfo.type === "js_module" || urlInfo.type === "js_classic") {
175
- return "js";
176
- }
177
- if (urlInfo.type === "json") {
178
- return "json";
179
- }
180
- return "other";
181
- };
182
-
183
- const createRepartitionMessage = ({ html, css, js, json, other, total }) => {
184
- const addPart = (name, { count, size, percentage }) => {
185
- parts.push(
186
- `${ANSI.color(`${name}:`, ANSI.GREY)} ${count} (${humanizeFileSize(
187
- size,
188
- )} / ${percentage} %)`,
189
- );
190
- };
191
-
192
- const parts = [];
193
- // if (sourcemaps.count) {
194
- // parts.push(
195
- // `${ANSI.color(`sourcemaps:`, ANSI.GREY)} ${
196
- // sourcemaps.count
197
- // } (${humanizeFileSize(sourcemaps.size)})`,
198
- // )
199
- // }
200
- if (html.count) {
201
- addPart("html ", html);
202
- }
203
- if (css.count) {
204
- addPart("css ", css);
205
- }
206
- if (js.count) {
207
- addPart("js ", js);
208
- }
209
- if (json.count) {
210
- addPart("json ", json);
211
- }
212
- if (other.count) {
213
- addPart("other", other);
214
- }
215
- addPart("total", total);
216
- return `- ${parts.join(`
217
- - `)}`;
218
- };
219
-
220
- const createBuildUrlsGenerator = ({
221
- logger,
222
- sourceDirectoryUrl,
223
- buildDirectoryUrl,
224
- assetsDirectory,
225
- }) => {
226
- const cache = {};
227
- const getUrlName = (url, urlInfo) => {
228
- if (!urlInfo) {
229
- return urlToFilename(url);
230
- }
231
- if (urlInfo.filenameHint) {
232
- return urlInfo.filenameHint;
233
- }
234
- return urlToFilename(url);
235
- };
236
-
237
- const buildUrlCache = new Map();
238
-
239
- const associateBuildUrl = (url, buildUrl) => {
240
- buildUrlCache.set(url, buildUrl);
241
- logger.debug(`associate a build url
242
- ${ANSI.color(url, ANSI.GREY)} ->
243
- ${ANSI.color(buildUrl, ANSI.MAGENTA)}
244
- `);
245
- };
246
-
247
- const generate = (url, { urlInfo, ownerUrlInfo }) => {
248
- const buildUrlFromCache = buildUrlCache.get(url);
249
- if (buildUrlFromCache) {
250
- return buildUrlFromCache;
251
- }
252
- if (urlIsInsideOf(url, buildDirectoryUrl)) {
253
- buildUrlCache.set(url, url);
254
- return url;
255
- }
256
- if (
257
- urlInfo.type === "directory" ||
258
- (urlInfo.type === undefined && urlInfo.typeHint === "directory")
259
- ) {
260
- let directoryPath;
261
- if (url === sourceDirectoryUrl) {
262
- directoryPath = "";
263
- } else if (urlInfo.filenameHint) {
264
- directoryPath = urlInfo.filenameHint;
265
- } else {
266
- directoryPath = urlToRelativeUrl(url, sourceDirectoryUrl);
267
- }
268
- const { search } = new URL(url);
269
- const buildUrl = `${buildDirectoryUrl}${directoryPath}${search}`;
270
- associateBuildUrl(url, buildUrl);
271
- return buildUrl;
272
- }
273
-
274
- const directoryPath = determineDirectoryPath({
275
- sourceDirectoryUrl,
276
- assetsDirectory,
277
- urlInfo,
278
- ownerUrlInfo,
279
- });
280
- let names = cache[directoryPath];
281
- if (!names) {
282
- names = [];
283
- cache[directoryPath] = names;
284
- }
285
- const urlObject = new URL(url);
286
- let { search, hash } = urlObject;
287
- let name = getUrlName(url, urlInfo);
288
- let [basename, extension] = splitFileExtension(name);
289
- extension = extensionMappings[extension] || extension;
290
- let nameCandidate = `${basename}${extension}`; // reconstruct name in case extension was normalized
291
- let integer = 1;
292
- while (true) {
293
- if (!names.includes(nameCandidate)) {
294
- names.push(nameCandidate);
295
- break;
296
- }
297
- integer++;
298
- nameCandidate = `${basename}${integer}${extension}`;
299
- }
300
- const buildUrl = `${buildDirectoryUrl}${directoryPath}${nameCandidate}${search}${hash}`;
301
- associateBuildUrl(url, buildUrl);
302
- return buildUrl;
303
- };
304
-
305
- return {
306
- generate,
307
- };
308
- };
309
-
310
- // It's best to generate files with an extension representing what is inside the file
311
- // and after build js files contains solely js (js or typescript is gone).
312
- // This way a static file server is already configured to server the correct content-type
313
- // (otherwise one would have to configure that ".jsx" is "text/javascript")
314
- // To keep in mind: if you have "user.jsx" and "user.js" AND both file are not bundled
315
- // you end up with "dist/js/user.js" and "dist/js/user2.js"
316
- const extensionMappings = {
317
- ".jsx": ".js",
318
- ".ts": ".js",
319
- ".tsx": ".js",
320
- };
321
-
322
- const splitFileExtension = (filename) => {
323
- const dotLastIndex = filename.lastIndexOf(".");
324
- if (dotLastIndex === -1) {
325
- return [filename, ""];
326
- }
327
- return [filename.slice(0, dotLastIndex), filename.slice(dotLastIndex)];
328
- };
329
-
330
- const determineDirectoryPath = ({
331
- sourceDirectoryUrl,
332
- assetsDirectory,
333
- urlInfo,
334
- ownerUrlInfo,
335
- }) => {
336
- if (urlInfo.dirnameHint) {
337
- return urlInfo.dirnameHint;
338
- }
339
- if (urlInfo.type === "directory") {
340
- return "";
341
- }
342
- if (urlInfo.isInline) {
343
- const parentDirectoryPath = determineDirectoryPath({
344
- sourceDirectoryUrl,
345
- assetsDirectory,
346
- urlInfo: ownerUrlInfo || urlInfo.firstReference.ownerUrlInfo,
347
- });
348
- return parentDirectoryPath;
349
- }
350
- if (urlInfo.isEntryPoint && !urlInfo.isDynamicEntryPoint) {
351
- return "";
352
- }
353
- if (urlInfo.type === "importmap") {
354
- return "";
355
- }
356
- if (urlInfo.type === "html") {
357
- return `${assetsDirectory}html/`;
358
- }
359
- if (urlInfo.type === "css") {
360
- return `${assetsDirectory}css/`;
361
- }
362
- if (urlInfo.type === "js_module" || urlInfo.type === "js_classic") {
363
- return `${assetsDirectory}js/`;
364
- }
365
- if (urlInfo.type === "json") {
366
- return `${assetsDirectory}json/`;
367
- }
368
- return `${assetsDirectory}other/`;
369
- };
370
-
371
- // https://bundlers.tooling.report/hashing/avoid-cascade/
372
-
373
-
374
- const injectGlobalMappings = async (urlInfo, mappings) => {
375
- if (urlInfo.type === "html") {
376
- const minification = Boolean(
377
- urlInfo.context.getPluginMeta("willMinifyJsClassic"),
378
- );
379
- const content = generateClientCodeForMappings(mappings, {
380
- globalName: "window",
381
- minification,
382
- });
383
- await prependContent(urlInfo, { type: "js_classic", content });
384
- return;
385
- }
386
- if (urlInfo.type === "js_classic" || urlInfo.type === "js_module") {
387
- const minification = Boolean(
388
- urlInfo.context.getPluginMeta("willMinifyJsClassic"),
389
- );
390
- const content = generateClientCodeForMappings(mappings, {
391
- globalName: isWebWorkerUrlInfo(urlInfo) ? "self" : "window",
392
- minification,
393
- });
394
- await prependContent(urlInfo, { type: "js_classic", content });
395
- return;
396
- }
397
- };
398
-
399
- const generateClientCodeForMappings = (
400
- versionMappings,
401
- { globalName, minification },
402
- ) => {
403
- if (minification) {
404
- return `;(function(){var m = ${JSON.stringify(
405
- versionMappings,
406
- )}; ${globalName}.__v__ = function (s) { return m[s] || s }; })();`;
407
- }
408
- return `;(function() {
409
- var __versionMappings__ = {
410
- ${stringifyParams(versionMappings, " ")}
411
- };
412
- ${globalName}.__v__ = function (specifier) {
413
- return __versionMappings__[specifier] || specifier
414
- };
415
- })();`;
416
- };
417
-
418
- const injectImportmapMappings = (urlInfo, getMappings) => {
419
- const htmlAst = parseHtml({
420
- html: urlInfo.content,
421
- url: urlInfo.url,
422
- storeOriginalPositions: false,
423
- });
424
- // jsenv_plugin_importmap.js is removing importmap during build
425
- // it means at this point we know HTML has no importmap in it
426
- // we can safely inject one
427
- const importmapMinification = Boolean(
428
- urlInfo.context.getPluginMeta("willMinifyJson"),
429
- );
430
- const importmapNode = findHtmlNode(htmlAst, (node) => {
431
- return (
432
- node.tagName === "script" &&
433
- getHtmlNodeAttribute(node, "type") === "importmap"
434
- );
435
- });
436
- const generateMappingText = (mappings) => {
437
- if (importmapMinification) {
438
- return JSON.stringify({ imports: mappings });
439
- }
440
- return JSON.stringify({ imports: mappings }, null, " ");
441
- };
442
-
443
- const mutate = (mutation) => {
444
- mutation();
445
- urlInfo.mutateContent({
446
- content: stringifyHtmlAst(htmlAst),
447
- });
448
- };
449
-
450
- if (importmapNode) {
451
- // we want to remove some mappings, override others, add eventually add new
452
- const currentMappings = JSON.parse(getHtmlNodeText(importmapNode));
453
- const mappings = getMappings(currentMappings.imports);
454
- if (!mappings || Object.keys(mappings).length === 0) {
455
- mutate(() => {
456
- removeHtmlNode(importmapNode);
457
- });
458
- return;
459
- }
460
- mutate(() => {
461
- setHtmlNodeText(importmapNode, generateMappingText(mappings), {
462
- indentation: "auto",
463
- });
464
- });
465
- return;
466
- }
467
- const mappings = getMappings(null);
468
- if (!mappings || Object.keys(mappings).length === 0) {
469
- return;
470
- }
471
- mutate(() => {
472
- injectHtmlNodeAsEarlyAsPossible(
473
- htmlAst,
474
- createHtmlNode({
475
- tagName: "script",
476
- type: "importmap",
477
- children: generateMappingText(getMappings(null)),
478
- }),
479
- "jsenv:versioning",
480
- );
481
- });
482
- return;
483
- };
484
-
485
- const stringifyParams = (params, prefix = "") => {
486
- const source = JSON.stringify(params, null, prefix);
487
- if (prefix.length) {
488
- // remove leading "{\n"
489
- // remove leading prefix
490
- // remove trailing "\n}"
491
- return source.slice(2 + prefix.length, -2);
492
- }
493
- // remove leading "{"
494
- // remove trailing "}"
495
- return source.slice(1, -1);
496
- };
497
-
498
- const createBuildSpecifierManager = ({
499
- rawKitchen,
500
- finalKitchen,
501
- logger,
502
- sourceDirectoryUrl,
503
- buildDirectoryUrl,
504
- base,
505
- assetsDirectory,
506
- length = 8,
507
-
508
- versioning,
509
- versioningMethod,
510
- versionLength,
511
- canUseImportmap,
512
- }) => {
513
- const buildUrlsGenerator = createBuildUrlsGenerator({
514
- logger,
515
- sourceDirectoryUrl,
516
- buildDirectoryUrl,
517
- assetsDirectory,
518
- });
519
- const placeholderAPI = createPlaceholderAPI({
520
- length,
521
- });
522
- const placeholderToReferenceMap = new Map();
523
- const urlInfoToBuildUrlMap = new Map();
524
- const buildUrlToUrlInfoMap = new Map();
525
- const buildUrlToBuildSpecifierMap = new Map();
526
-
527
- const generateReplacement = (reference) => {
528
- let buildUrl;
529
- if (reference.type === "sourcemap_comment") {
530
- const parentBuildUrl = urlInfoToBuildUrlMap.get(reference.ownerUrlInfo);
531
- buildUrl = generateSourcemapFileUrl(parentBuildUrl);
532
- reference.generatedSpecifier = buildUrl;
533
- } else {
534
- const url = reference.generatedUrl;
535
- let urlInfo;
536
- const rawUrlInfo = rawKitchen.graph.getUrlInfo(reference.url);
537
- if (rawUrlInfo) {
538
- urlInfo = rawUrlInfo;
539
- } else {
540
- const buildUrlInfo = reference.urlInfo;
541
- buildUrlInfo.type = reference.expectedType || "asset";
542
- buildUrlInfo.subtype = reference.expectedSubtype;
543
- urlInfo = buildUrlInfo;
544
- }
545
- buildUrl = buildUrlsGenerator.generate(url, {
546
- urlInfo,
547
- ownerUrlInfo: reference.ownerUrlInfo,
548
- });
549
- }
550
-
551
- let buildSpecifier;
552
- if (base === "./") {
553
- const { ownerUrlInfo } = reference;
554
- const parentBuildUrl = ownerUrlInfo.isRoot
555
- ? buildDirectoryUrl
556
- : urlInfoToBuildUrlMap.get(
557
- ownerUrlInfo.isInline
558
- ? ownerUrlInfo.findParentIfInline()
559
- : ownerUrlInfo,
560
- );
561
- const urlRelativeToParent = urlToRelativeUrl(buildUrl, parentBuildUrl);
562
- if (urlRelativeToParent[0] === ".") {
563
- buildSpecifier = urlRelativeToParent;
564
- } else {
565
- // ensure "./" on relative url (otherwise it could be a "bare specifier")
566
- buildSpecifier = `./${urlRelativeToParent}`;
567
- }
568
- } else {
569
- const urlRelativeToBuildDirectory = urlToRelativeUrl(
570
- buildUrl,
571
- buildDirectoryUrl,
572
- );
573
- buildSpecifier = `${base}${urlRelativeToBuildDirectory}`;
574
- }
575
-
576
- urlInfoToBuildUrlMap.set(reference.urlInfo, buildUrl);
577
- buildUrlToUrlInfoMap.set(buildUrl, reference.urlInfo);
578
- buildUrlToBuildSpecifierMap.set(buildUrl, buildSpecifier);
579
- const buildGeneratedSpecifier = applyVersioningOnBuildSpecifier(
580
- buildSpecifier,
581
- reference,
582
- );
583
- return buildGeneratedSpecifier;
584
- };
585
- const internalRedirections = new Map();
586
- const bundleInfoMap = new Map();
587
-
588
- const applyBundling = async ({ bundler, urlInfosToBundle }) => {
589
- const urlInfosBundled = await rawKitchen.pluginController.callAsyncHook(
590
- {
591
- plugin: bundler.plugin,
592
- hookName: "bundle",
593
- value: bundler.bundleFunction,
594
- },
595
- urlInfosToBundle,
596
- );
597
- for (const url of Object.keys(urlInfosBundled)) {
598
- const urlInfoBundled = urlInfosBundled[url];
599
- if (urlInfoBundled.sourceUrls) {
600
- for (const sourceUrl of urlInfoBundled.sourceUrls) {
601
- const sourceRawUrlInfo = rawKitchen.graph.getUrlInfo(sourceUrl);
602
- if (sourceRawUrlInfo) {
603
- sourceRawUrlInfo.data.bundled = true;
604
- }
605
- }
606
- }
607
- bundleInfoMap.set(url, urlInfoBundled);
608
- }
609
- };
610
-
611
- const jsenvPluginMoveToBuildDirectory = {
612
- name: "jsenv:move_to_build_directory",
613
- appliesDuring: "build",
614
- // reference resolution is split in 2
615
- // the redirection to build directory is done in a second phase (redirectReference)
616
- // to let opportunity to others plugins (js_module_fallback)
617
- // to mutate reference (inject ?js_module_fallback)
618
- // before it gets redirected to build directory
619
- resolveReference: (reference) => {
620
- const { ownerUrlInfo } = reference;
621
- if (ownerUrlInfo.remapReference && !reference.isInline) {
622
- const newSpecifier = ownerUrlInfo.remapReference(reference);
623
- reference.specifier = newSpecifier;
624
- }
625
- const referenceFromPlaceholder = placeholderToReferenceMap.get(
626
- reference.specifier,
627
- );
628
- if (referenceFromPlaceholder) {
629
- return referenceFromPlaceholder.url;
630
- }
631
- if (reference.type === "filesystem") {
632
- const ownerRawUrl = ensurePathnameTrailingSlash(ownerUrlInfo.url);
633
- const url = new URL(reference.specifier, ownerRawUrl).href;
634
- return url;
635
- }
636
- if (reference.specifierPathname[0] === "/") {
637
- const url = new URL(reference.specifier.slice(1), sourceDirectoryUrl)
638
- .href;
639
- return url;
640
- }
641
- if (reference.injected) {
642
- // js_module_fallback
643
- const url = new URL(
644
- reference.specifier,
645
- reference.baseUrl || ownerUrlInfo.url,
646
- ).href;
647
- return url;
648
- }
649
- const parentUrl = reference.baseUrl || ownerUrlInfo.url;
650
- const url = new URL(reference.specifier, parentUrl).href;
651
- return url;
652
- },
653
- redirectReference: (reference) => {
654
- let referenceBeforeInlining = reference;
655
- if (
656
- referenceBeforeInlining.isInline &&
657
- referenceBeforeInlining.prev &&
658
- !referenceBeforeInlining.prev.isInline
659
- ) {
660
- referenceBeforeInlining = referenceBeforeInlining.prev;
661
- }
662
- const rawUrl = referenceBeforeInlining.url;
663
- const rawUrlInfo = rawKitchen.graph.getUrlInfo(rawUrl);
664
- if (rawUrlInfo) {
665
- reference.filenameHint = rawUrlInfo.filenameHint;
666
- return null;
667
- }
668
- if (referenceBeforeInlining.injected) {
669
- return null;
670
- }
671
- if (
672
- referenceBeforeInlining.isInline &&
673
- referenceBeforeInlining.ownerUrlInfo.url ===
674
- referenceBeforeInlining.ownerUrlInfo.originalUrl
675
- ) {
676
- const rawUrlInfo = findRawUrlInfoWhenInline(
677
- referenceBeforeInlining,
678
- rawKitchen,
679
- );
680
- if (rawUrlInfo) {
681
- reference.rawUrl = rawUrlInfo.url;
682
- reference.filenameHint = rawUrlInfo.filenameHint;
683
- return null;
684
- }
685
- }
686
- reference.filenameHint = referenceBeforeInlining.filenameHint;
687
- return null;
688
- },
689
- transformReferenceSearchParams: () => {
690
- // those search params are reflected into the build file name
691
- // moreover it create cleaner output
692
- // otherwise output is full of ?js_module_fallback search param
693
- return {
694
- js_module_fallback: undefined,
695
- as_json_module: undefined,
696
- as_css_module: undefined,
697
- as_text_module: undefined,
698
- as_js_module: undefined,
699
- as_js_classic: undefined,
700
- cjs_as_js_module: undefined,
701
- js_classic: undefined, // TODO: add comment to explain who is using this
702
- entry_point: undefined,
703
- dynamic_import: undefined,
704
- };
705
- },
706
- formatReference: (reference) => {
707
- const generatedUrl = reference.generatedUrl;
708
- if (!generatedUrl.startsWith("file:")) {
709
- return null;
710
- }
711
- if (reference.isWeak && reference.expectedType !== "directory") {
712
- return null;
713
- }
714
- if (reference.type === "sourcemap_comment") {
715
- return null;
716
- }
717
- const placeholder = placeholderAPI.generate();
718
- if (generatedUrl !== reference.url) {
719
- internalRedirections.set(generatedUrl, reference.url);
720
- }
721
- placeholderToReferenceMap.set(placeholder, reference);
722
- return placeholder;
723
- },
724
- fetchUrlContent: async (finalUrlInfo) => {
725
- // not need because it will be inherit from rawUrlInfo
726
- // if (urlIsInsideOf(finalUrlInfo.url, buildDirectoryUrl)) {
727
- // finalUrlInfo.type = "asset";
728
- // }
729
- let { firstReference } = finalUrlInfo;
730
- if (
731
- firstReference.isInline &&
732
- firstReference.prev &&
733
- !firstReference.prev.isInline
734
- ) {
735
- firstReference = firstReference.prev;
736
- }
737
- const rawUrl = firstReference.rawUrl || firstReference.url;
738
- const rawUrlInfo = rawKitchen.graph.getUrlInfo(rawUrl);
739
- const bundleInfo = bundleInfoMap.get(rawUrl);
740
- if (bundleInfo) {
741
- finalUrlInfo.remapReference = bundleInfo.remapReference;
742
- return {
743
- // url: bundleInfo.url,
744
- originalUrl: bundleInfo.originalUrl,
745
- type: bundleInfo.type,
746
- content: bundleInfo.content,
747
- contentType: bundleInfo.contentType,
748
- sourcemap: bundleInfo.sourcemap,
749
- data: bundleInfo.data,
750
- };
751
- }
752
- if (rawUrlInfo) {
753
- return rawUrlInfo;
754
- }
755
- // reference injected during "shape":
756
- // - "js_module_fallback" using getWithoutSearchParam to obtain source
757
- // url info that will be converted to systemjs/UMD
758
- // - "js_module_fallback" injecting "s.js"
759
- if (firstReference.injected) {
760
- const reference = firstReference.original || firstReference;
761
- const rawReference = rawKitchen.graph.rootUrlInfo.dependencies.inject({
762
- type: reference.type,
763
- expectedType: reference.expectedType,
764
- specifier: reference.specifier,
765
- specifierLine: reference.specifierLine,
766
- specifierColumn: reference.specifierColumn,
767
- specifierStart: reference.specifierStart,
768
- specifierEnd: reference.specifierEnd,
769
- isInline: reference.isInline,
770
- filenameHint: reference.filenameHint,
771
- content: reference.content,
772
- contentType: reference.contentType,
773
- });
774
- const rawUrlInfo = rawReference.urlInfo;
775
- await rawUrlInfo.cook();
776
- return {
777
- type: rawUrlInfo.type,
778
- content: rawUrlInfo.content,
779
- contentType: rawUrlInfo.contentType,
780
- originalContent: rawUrlInfo.originalContent,
781
- originalUrl: rawUrlInfo.originalUrl,
782
- sourcemap: rawUrlInfo.sourcemap,
783
- };
784
- }
785
- if (firstReference.isInline) {
786
- if (
787
- firstReference.ownerUrlInfo.url ===
788
- firstReference.ownerUrlInfo.originalUrl
789
- ) {
790
- if (rawUrlInfo) {
791
- return rawUrlInfo;
792
- }
793
- }
794
- return {
795
- originalContent: finalUrlInfo.originalContent,
796
- content: firstReference.content,
797
- contentType: firstReference.contentType,
798
- };
799
- }
800
- throw new Error(createDetailedMessage(`${rawUrl} not found in graph`));
801
- },
802
- };
803
-
804
- const buildSpecifierToBuildSpecifierVersionedMap = new Map();
805
-
806
- const versionMap = new Map();
807
-
808
- const referenceInSeparateContextSet = new Set();
809
- const referenceVersioningInfoMap = new Map();
810
- const _getReferenceVersioningInfo = (reference) => {
811
- if (!shouldApplyVersioningOnReference(reference)) {
812
- return {
813
- type: "not_versioned",
814
- };
815
- }
816
- const ownerUrlInfo = reference.ownerUrlInfo;
817
- if (ownerUrlInfo.jsQuote) {
818
- // here we use placeholder as specifier, so something like
819
- // "/other/file.png" becomes "!~{0001}~" and finally "__v__("/other/file.png")"
820
- // this is to support cases like CSS inlined in JS
821
- // CSS minifier must see valid CSS specifiers like background-image: url("!~{0001}~");
822
- // that is finally replaced by invalid css background-image: url("__v__("/other/file.png")")
823
- return {
824
- type: "global",
825
- render: (buildSpecifier) => {
826
- return placeholderAPI.markAsCode(
827
- `${ownerUrlInfo.jsQuote}+__v__(${JSON.stringify(buildSpecifier)})+${
828
- ownerUrlInfo.jsQuote
829
- }`,
830
- );
831
- },
832
- };
833
- }
834
- if (reference.type === "js_url") {
835
- return {
836
- type: "global",
837
- render: (buildSpecifier) => {
838
- return placeholderAPI.markAsCode(
839
- `__v__(${JSON.stringify(buildSpecifier)})`,
840
- );
841
- },
842
- };
843
- }
844
- if (reference.type === "js_import") {
845
- if (reference.subtype === "import_dynamic") {
846
- return {
847
- type: "global",
848
- render: (buildSpecifier) => {
849
- return placeholderAPI.markAsCode(
850
- `__v__(${JSON.stringify(buildSpecifier)})`,
851
- );
852
- },
853
- };
854
- }
855
- if (reference.subtype === "import_meta_resolve") {
856
- return {
857
- type: "global",
858
- render: (buildSpecifier) => {
859
- return placeholderAPI.markAsCode(
860
- `__v__(${JSON.stringify(buildSpecifier)})`,
861
- );
862
- },
863
- };
864
- }
865
- if (canUseImportmap && !isInsideSeparateContext(reference)) {
866
- return {
867
- type: "importmap",
868
- render: (buildSpecifier) => {
869
- return buildSpecifier;
870
- },
871
- };
872
- }
873
- }
874
- return {
875
- type: "inline",
876
- render: (buildSpecifier) => {
877
- const buildSpecifierVersioned =
878
- buildSpecifierToBuildSpecifierVersionedMap.get(buildSpecifier);
879
- return buildSpecifierVersioned;
880
- },
881
- };
882
- };
883
- const getReferenceVersioningInfo = (reference) => {
884
- const infoFromCache = referenceVersioningInfoMap.get(reference);
885
- if (infoFromCache) {
886
- return infoFromCache;
887
- }
888
- const info = _getReferenceVersioningInfo(reference);
889
- referenceVersioningInfoMap.set(reference, info);
890
- return info;
891
- };
892
- const isInsideSeparateContext = (reference) => {
893
- if (referenceInSeparateContextSet.has(reference)) {
894
- return true;
895
- }
896
- const referenceOwnerUrllInfo = reference.ownerUrlInfo;
897
- let is = false;
898
- if (isWebWorkerUrlInfo(referenceOwnerUrllInfo)) {
899
- is = true;
900
- } else {
901
- GRAPH_VISITOR.findDependent(
902
- referenceOwnerUrllInfo,
903
- (dependentUrlInfo) => {
904
- if (isWebWorkerUrlInfo(dependentUrlInfo)) {
905
- is = true;
906
- return true;
907
- }
908
- return false;
909
- },
910
- );
911
- }
912
- if (is) {
913
- referenceInSeparateContextSet.add(reference);
914
- return true;
915
- }
916
- return false;
917
- };
918
- const canUseVersionedUrl = (urlInfo) => {
919
- if (urlInfo.isRoot) {
920
- return false;
921
- }
922
- if (urlInfo.isEntryPoint) {
923
- // if (urlInfo.subtype === "worker") {
924
- // return true;
925
- // }
926
- return false;
927
- }
928
- return urlInfo.type !== "webmanifest";
929
- };
930
- const shouldApplyVersioningOnReference = (reference) => {
931
- if (reference.isInline) {
932
- return false;
933
- }
934
- if (reference.next && reference.next.isInline) {
935
- return false;
936
- }
937
- if (reference.type === "sourcemap_comment") {
938
- return false;
939
- }
940
- if (reference.expectedType === "directory") {
941
- return true;
942
- }
943
- // specifier comes from "normalize" hook done a bit earlier in this file
944
- // we want to get back their build url to access their infos
945
- const referencedUrlInfo = reference.urlInfo;
946
- if (!canUseVersionedUrl(referencedUrlInfo)) {
947
- return false;
948
- }
949
- return true;
950
- };
951
-
952
- const prepareVersioning = () => {
953
- const contentOnlyVersionMap = new Map();
954
- const urlInfoToContainedPlaceholderSetMap = new Map();
955
- const directoryUrlInfoSet = new Set();
956
- {
957
- GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
958
- finalKitchen.graph.rootUrlInfo,
959
- (urlInfo) => {
960
- // ignore:
961
- // - inline files and data files:
962
- // they are already taken into account in the file where they appear
963
- // - ignored files:
964
- // we don't know their content
965
- // - unused files without reference
966
- // File updated such as style.css -> style.css.js or file.js->file.nomodule.js
967
- // Are used at some point just to be discarded later because they need to be converted
968
- // There is no need to version them and we could not because the file have been ignored
969
- // so their content is unknown
970
- if (urlInfo.type === "sourcemap") {
971
- return;
972
- }
973
- if (urlInfo.isInline) {
974
- return;
975
- }
976
- if (urlInfo.url.startsWith("data:")) {
977
- // urlInfo became inline and is not referenced by something else
978
- return;
979
- }
980
- if (urlInfo.url.startsWith("ignore:")) {
981
- return;
982
- }
983
- let content = urlInfo.content;
984
- if (urlInfo.type === "html") {
985
- content = stringifyHtmlAst(
986
- parseHtml({
987
- html: urlInfo.content,
988
- url: urlInfo.url,
989
- storeOriginalPositions: false,
990
- }),
991
- {
992
- cleanupJsenvAttributes: true,
993
- cleanupPositionAttributes: true,
994
- },
995
- );
996
- }
997
- const containedPlaceholderSet = new Set();
998
- if (mayUsePlaceholder(urlInfo)) {
999
- const contentWithPredictibleVersionPlaceholders =
1000
- placeholderAPI.replaceWithDefault(content, (placeholder) => {
1001
- containedPlaceholderSet.add(placeholder);
1002
- });
1003
- content = contentWithPredictibleVersionPlaceholders;
1004
- }
1005
- urlInfoToContainedPlaceholderSetMap.set(
1006
- urlInfo,
1007
- containedPlaceholderSet,
1008
- );
1009
- const contentVersion = generateVersion([content], versionLength);
1010
- contentOnlyVersionMap.set(urlInfo, contentVersion);
1011
- },
1012
- {
1013
- directoryUrlInfoSet,
1014
- },
1015
- );
1016
- }
1017
-
1018
- {
1019
- const getSetOfUrlInfoInfluencingVersion = (urlInfo) => {
1020
- const placeholderInfluencingVersionSet = new Set();
1021
- const visitContainedPlaceholders = (urlInfo) => {
1022
- const referencedContentVersion = contentOnlyVersionMap.get(urlInfo);
1023
- if (!referencedContentVersion) {
1024
- // ignored while traversing graph (not used anymore, inline, ...)
1025
- return;
1026
- }
1027
- const containedPlaceholderSet =
1028
- urlInfoToContainedPlaceholderSetMap.get(urlInfo);
1029
- for (const containedPlaceholder of containedPlaceholderSet) {
1030
- if (placeholderInfluencingVersionSet.has(containedPlaceholder)) {
1031
- continue;
1032
- }
1033
- const reference =
1034
- placeholderToReferenceMap.get(containedPlaceholder);
1035
- const referenceVersioningInfo =
1036
- getReferenceVersioningInfo(reference);
1037
- if (
1038
- referenceVersioningInfo.type === "global" ||
1039
- referenceVersioningInfo.type === "importmap"
1040
- ) {
1041
- // when versioning is dynamic no need to take into account
1042
- continue;
1043
- }
1044
- placeholderInfluencingVersionSet.add(containedPlaceholder);
1045
- const referencedUrlInfo = reference.urlInfo;
1046
- visitContainedPlaceholders(referencedUrlInfo);
1047
- }
1048
- };
1049
- visitContainedPlaceholders(urlInfo);
1050
-
1051
- const setOfUrlInfluencingVersion = new Set();
1052
- for (const placeholderInfluencingVersion of placeholderInfluencingVersionSet) {
1053
- const reference = placeholderToReferenceMap.get(
1054
- placeholderInfluencingVersion,
1055
- );
1056
- const referencedUrlInfo = reference.urlInfo;
1057
- setOfUrlInfluencingVersion.add(referencedUrlInfo);
1058
- }
1059
- return setOfUrlInfluencingVersion;
1060
- };
1061
-
1062
- for (const [
1063
- contentOnlyUrlInfo,
1064
- contentOnlyVersion,
1065
- ] of contentOnlyVersionMap) {
1066
- const setOfUrlInfoInfluencingVersion =
1067
- getSetOfUrlInfoInfluencingVersion(contentOnlyUrlInfo);
1068
- const versionPartSet = new Set();
1069
- versionPartSet.add(contentOnlyVersion);
1070
- for (const urlInfoInfluencingVersion of setOfUrlInfoInfluencingVersion) {
1071
- const otherUrlInfoContentVersion = contentOnlyVersionMap.get(
1072
- urlInfoInfluencingVersion,
1073
- );
1074
- if (!otherUrlInfoContentVersion) {
1075
- throw new Error(
1076
- `cannot find content version for ${urlInfoInfluencingVersion.url} (used by ${contentOnlyUrlInfo.url})`,
1077
- );
1078
- }
1079
- versionPartSet.add(otherUrlInfoContentVersion);
1080
- }
1081
- const version = generateVersion(versionPartSet, versionLength);
1082
- versionMap.set(contentOnlyUrlInfo, version);
1083
- }
1084
- }
1085
-
1086
- {
1087
- // we should grab all the files inside this directory
1088
- // they will influence his versioning
1089
- for (const directoryUrlInfo of directoryUrlInfoSet) {
1090
- const directoryUrl = directoryUrlInfo.url;
1091
- // const urlInfoInsideThisDirectorySet = new Set();
1092
- const versionsInfluencingThisDirectorySet = new Set();
1093
- for (const [url, urlInfo] of finalKitchen.graph.urlInfoMap) {
1094
- if (!urlIsInsideOf(url, directoryUrl)) {
1095
- continue;
1096
- }
1097
- // ideally we should exclude eventual directories as the are redundant
1098
- // with the file they contains
1099
- const version = versionMap.get(urlInfo);
1100
- if (version !== undefined) {
1101
- versionsInfluencingThisDirectorySet.add(version);
1102
- }
1103
- }
1104
- const contentVersion =
1105
- versionsInfluencingThisDirectorySet.size === 0
1106
- ? "empty"
1107
- : generateVersion(
1108
- versionsInfluencingThisDirectorySet,
1109
- versionLength,
1110
- );
1111
- versionMap.set(directoryUrlInfo, contentVersion);
1112
- }
1113
- }
1114
- };
1115
-
1116
- const importMappings = {};
1117
- const globalMappings = {};
1118
-
1119
- const applyVersioningOnBuildSpecifier = (buildSpecifier, reference) => {
1120
- if (!versioning) {
1121
- return buildSpecifier;
1122
- }
1123
- const referenceVersioningInfo = getReferenceVersioningInfo(reference);
1124
- if (referenceVersioningInfo.type === "not_versioned") {
1125
- return buildSpecifier;
1126
- }
1127
- const version = versionMap.get(reference.urlInfo);
1128
- if (version === undefined) {
1129
- return buildSpecifier;
1130
- }
1131
- const buildSpecifierVersioned = injectVersionIntoBuildSpecifier({
1132
- buildSpecifier,
1133
- versioningMethod,
1134
- version,
1135
- });
1136
- buildSpecifierToBuildSpecifierVersionedMap.set(
1137
- buildSpecifier,
1138
- buildSpecifierVersioned,
1139
- );
1140
- return referenceVersioningInfo.render(buildSpecifier);
1141
- };
1142
- const finishVersioning = async () => {
1143
- {
1144
- for (const [reference, versioningInfo] of referenceVersioningInfoMap) {
1145
- if (versioningInfo.type === "global") {
1146
- const urlInfo = reference.urlInfo;
1147
- const buildUrl = urlInfoToBuildUrlMap.get(urlInfo);
1148
- const buildSpecifier = buildUrlToBuildSpecifierMap.get(buildUrl);
1149
- const buildSpecifierVersioned =
1150
- buildSpecifierToBuildSpecifierVersionedMap.get(buildSpecifier);
1151
- globalMappings[buildSpecifier] = buildSpecifierVersioned;
1152
- }
1153
- if (versioningInfo.type === "importmap") {
1154
- const urlInfo = reference.urlInfo;
1155
- const buildUrl = urlInfoToBuildUrlMap.get(urlInfo);
1156
- const buildSpecifier = buildUrlToBuildSpecifierMap.get(buildUrl);
1157
- const buildSpecifierVersioned =
1158
- buildSpecifierToBuildSpecifierVersionedMap.get(buildSpecifier);
1159
- importMappings[buildSpecifier] = buildSpecifierVersioned;
1160
- }
1161
- }
1162
- }
1163
- };
1164
-
1165
- const getBuildGeneratedSpecifier = (urlInfo) => {
1166
- const buildUrl = urlInfoToBuildUrlMap.get(urlInfo);
1167
- const buildSpecifier = buildUrlToBuildSpecifierMap.get(buildUrl);
1168
- const buildGeneratedSpecifier =
1169
- buildSpecifierToBuildSpecifierVersionedMap.get(buildSpecifier) ||
1170
- buildSpecifier;
1171
- return buildGeneratedSpecifier;
1172
- };
1173
-
1174
- return {
1175
- jsenvPluginMoveToBuildDirectory,
1176
- applyBundling,
1177
-
1178
- remapPlaceholder: (specifier) => {
1179
- const reference = placeholderToReferenceMap.get(specifier);
1180
- if (reference) {
1181
- return reference.specifier;
1182
- }
1183
- return specifier;
1184
- },
1185
-
1186
- replacePlaceholders: async () => {
1187
- if (versioning) {
1188
- prepareVersioning();
1189
- }
1190
- const urlInfoSet = new Set();
1191
- GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
1192
- finalKitchen.graph.rootUrlInfo,
1193
- (urlInfo) => {
1194
- urlInfoSet.add(urlInfo);
1195
- if (urlInfo.isEntryPoint) {
1196
- generateReplacement(urlInfo.firstReference);
1197
- }
1198
- if (urlInfo.type === "sourcemap") {
1199
- const { referenceFromOthersSet } = urlInfo;
1200
- let lastRef;
1201
- for (const ref of referenceFromOthersSet) {
1202
- lastRef = ref;
1203
- }
1204
- generateReplacement(lastRef);
1205
- }
1206
- if (urlInfo.isInline) {
1207
- generateReplacement(urlInfo.firstReference);
1208
- }
1209
- if (urlInfo.firstReference.type === "side_effect_file") {
1210
- // side effect stuff must be generated too
1211
- generateReplacement(urlInfo.firstReference);
1212
- }
1213
- if (mayUsePlaceholder(urlInfo)) {
1214
- const contentBeforeReplace = urlInfo.content;
1215
- const { content, sourcemap } = placeholderAPI.replaceAll(
1216
- contentBeforeReplace,
1217
- (placeholder) => {
1218
- const reference = placeholderToReferenceMap.get(placeholder);
1219
- const value = generateReplacement(reference);
1220
- return value;
1221
- },
1222
- );
1223
- urlInfo.mutateContent({ content, sourcemap });
1224
- }
1225
- },
1226
- );
1227
- referenceInSeparateContextSet.clear();
1228
- if (versioning) {
1229
- await finishVersioning();
1230
- }
1231
- const actions = [];
1232
- const visitors = [];
1233
- if (Object.keys(globalMappings).length > 0) {
1234
- visitors.push((urlInfo) => {
1235
- if (urlInfo.isRoot) {
1236
- return;
1237
- }
1238
- if (!urlInfo.isEntryPoint) {
1239
- return;
1240
- }
1241
- actions.push(async () => {
1242
- await injectGlobalMappings(urlInfo, globalMappings);
1243
- });
1244
- });
1245
- }
1246
- {
1247
- visitors.push((urlInfo) => {
1248
- if (urlInfo.isRoot) {
1249
- return;
1250
- }
1251
- if (!urlInfo.isEntryPoint) {
1252
- return;
1253
- }
1254
- if (urlInfo.type !== "html") {
1255
- return;
1256
- }
1257
-
1258
- actions.push(async () => {
1259
- await injectImportmapMappings(urlInfo, (topLevelMappings) => {
1260
- if (!topLevelMappings) {
1261
- return importMappings;
1262
- }
1263
- const topLevelMappingsToKeep = {};
1264
- for (const topLevelMappingKey of Object.keys(topLevelMappings)) {
1265
- const topLevelMappingValue =
1266
- topLevelMappings[topLevelMappingKey];
1267
- const urlInfo = finalKitchen.graph.getUrlInfo(
1268
- `ignore:${topLevelMappingKey}`,
1269
- );
1270
- if (urlInfo) {
1271
- topLevelMappingsToKeep[topLevelMappingKey] =
1272
- topLevelMappingValue;
1273
- }
1274
- }
1275
- return {
1276
- ...topLevelMappingsToKeep,
1277
- ...importMappings,
1278
- };
1279
- });
1280
- });
1281
- });
1282
- }
1283
-
1284
- if (visitors.length) {
1285
- GRAPH_VISITOR.forEach(finalKitchen.graph, (urlInfo) => {
1286
- for (const visitor of visitors) {
1287
- visitor(urlInfo);
1288
- }
1289
- });
1290
- if (actions.length) {
1291
- await Promise.all(actions.map((action) => action()));
1292
- }
1293
- }
1294
-
1295
- for (const urlInfo of urlInfoSet) {
1296
- urlInfo.kitchen.urlInfoTransformer.applySourcemapOnContent(
1297
- urlInfo,
1298
- (source) => {
1299
- const buildUrl = urlInfoToBuildUrlMap.get(urlInfo);
1300
- if (buildUrl) {
1301
- return urlToRelativeUrl(source, buildUrl);
1302
- }
1303
- return source;
1304
- },
1305
- );
1306
- }
1307
- urlInfoSet.clear();
1308
- },
1309
-
1310
- prepareResyncResourceHints: ({ registerHtmlRefine }) => {
1311
- const hintToInjectMap = new Map();
1312
- registerHtmlRefine((htmlAst, { registerHtmlMutation }) => {
1313
- visitHtmlNodes(htmlAst, {
1314
- link: (node) => {
1315
- const href = getHtmlNodeAttribute(node, "href");
1316
- if (href === undefined || href.startsWith("data:")) {
1317
- return;
1318
- }
1319
- const rel = getHtmlNodeAttribute(node, "rel");
1320
- const isResourceHint = [
1321
- "preconnect",
1322
- "dns-prefetch",
1323
- "prefetch",
1324
- "preload",
1325
- "modulepreload",
1326
- ].includes(rel);
1327
- if (!isResourceHint) {
1328
- return;
1329
- }
1330
- const rawUrl = href;
1331
- const finalUrl = internalRedirections.get(rawUrl) || rawUrl;
1332
- const urlInfo = finalKitchen.graph.getUrlInfo(finalUrl);
1333
- if (!urlInfo) {
1334
- logger.warn(
1335
- `${UNICODE.WARNING} remove resource hint because cannot find "${href}" in the graph`,
1336
- );
1337
- registerHtmlMutation(() => {
1338
- removeHtmlNode(node);
1339
- });
1340
- return;
1341
- }
1342
- if (!urlInfo.isUsed()) {
1343
- const rawUrlInfo = rawKitchen.graph.getUrlInfo(rawUrl);
1344
- if (rawUrlInfo && rawUrlInfo.data.bundled) {
1345
- logger.warn(
1346
- `${UNICODE.WARNING} remove resource hint on "${href}" because it was bundled`,
1347
- );
1348
- registerHtmlMutation(() => {
1349
- removeHtmlNode(node);
1350
- });
1351
- return;
1352
- }
1353
- logger.warn(
1354
- `${UNICODE.WARNING} remove resource hint on "${href}" because it is not used anymore`,
1355
- );
1356
- registerHtmlMutation(() => {
1357
- removeHtmlNode(node);
1358
- });
1359
- return;
1360
- }
1361
- const buildGeneratedSpecifier = getBuildGeneratedSpecifier(urlInfo);
1362
- registerHtmlMutation(() => {
1363
- setHtmlNodeAttributes(node, {
1364
- href: buildGeneratedSpecifier,
1365
- ...(urlInfo.type === "js_classic"
1366
- ? { crossorigin: undefined }
1367
- : {}),
1368
- });
1369
- });
1370
- for (const referenceToOther of urlInfo.referenceToOthersSet) {
1371
- if (referenceToOther.isWeak) {
1372
- continue;
1373
- }
1374
- const referencedUrlInfo = referenceToOther.urlInfo;
1375
- if (referencedUrlInfo.data.generatedToShareCode) {
1376
- hintToInjectMap.set(referencedUrlInfo, { node });
1377
- }
1378
- }
1379
- },
1380
- });
1381
- for (const [referencedUrlInfo, { node }] of hintToInjectMap) {
1382
- const buildGeneratedSpecifier =
1383
- getBuildGeneratedSpecifier(referencedUrlInfo);
1384
- const found = findHtmlNode(htmlAst, (htmlNode) => {
1385
- return (
1386
- htmlNode.nodeName === "link" &&
1387
- getHtmlNodeAttribute(htmlNode, "href") === buildGeneratedSpecifier
1388
- );
1389
- });
1390
- if (found) {
1391
- continue;
1392
- }
1393
- registerHtmlMutation(() => {
1394
- const nodeToInsert = createHtmlNode({
1395
- tagName: "link",
1396
- rel: getHtmlNodeAttribute(node, "rel"),
1397
- href: buildGeneratedSpecifier,
1398
- as: getHtmlNodeAttribute(node, "as"),
1399
- type: getHtmlNodeAttribute(node, "type"),
1400
- crossorigin: getHtmlNodeAttribute(node, "crossorigin"),
1401
- });
1402
- insertHtmlNodeAfter(nodeToInsert, node);
1403
- });
1404
- }
1405
- });
1406
- },
1407
-
1408
- prepareServiceWorkerUrlInjection: () => {
1409
- const serviceWorkerEntryUrlInfos = GRAPH_VISITOR.filter(
1410
- finalKitchen.graph,
1411
- (finalUrlInfo) => {
1412
- return (
1413
- finalUrlInfo.subtype === "service_worker" &&
1414
- finalUrlInfo.isEntryPoint &&
1415
- finalUrlInfo.isUsed()
1416
- );
1417
- },
1418
- );
1419
- if (serviceWorkerEntryUrlInfos.length === 0) {
1420
- return null;
1421
- }
1422
- return async () => {
1423
- const allResourcesFromJsenvBuild = {};
1424
- GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
1425
- finalKitchen.graph.rootUrlInfo,
1426
- (urlInfo) => {
1427
- if (!urlInfo.url.startsWith("file:")) {
1428
- return;
1429
- }
1430
- if (urlInfo.isInline) {
1431
- return;
1432
- }
1433
-
1434
- const buildUrl = urlInfoToBuildUrlMap.get(urlInfo);
1435
- const buildSpecifier = buildUrlToBuildSpecifierMap.get(buildUrl);
1436
- if (canUseVersionedUrl(urlInfo)) {
1437
- const buildSpecifierVersioned = versioning
1438
- ? buildSpecifierToBuildSpecifierVersionedMap.get(buildSpecifier)
1439
- : null;
1440
- allResourcesFromJsenvBuild[buildSpecifier] = {
1441
- version: versionMap.get(urlInfo),
1442
- versionedUrl: buildSpecifierVersioned,
1443
- };
1444
- } else {
1445
- // when url is not versioned we compute a "version" for that url anyway
1446
- // so that service worker source still changes and navigator
1447
- // detect there is a change
1448
- allResourcesFromJsenvBuild[buildSpecifier] = {
1449
- version: versionMap.get(urlInfo),
1450
- };
1451
- }
1452
- },
1453
- );
1454
- for (const serviceWorkerEntryUrlInfo of serviceWorkerEntryUrlInfos) {
1455
- const resourcesFromJsenvBuild = {
1456
- ...allResourcesFromJsenvBuild,
1457
- };
1458
- const serviceWorkerBuildUrl = urlInfoToBuildUrlMap.get(
1459
- serviceWorkerEntryUrlInfo,
1460
- );
1461
- const serviceWorkerBuildSpecifier = buildUrlToBuildSpecifierMap.get(
1462
- serviceWorkerBuildUrl,
1463
- );
1464
- delete resourcesFromJsenvBuild[serviceWorkerBuildSpecifier];
1465
- await prependContent(serviceWorkerEntryUrlInfo, {
1466
- type: "js_classic",
1467
- content: `self.resourcesFromJsenvBuild = ${JSON.stringify(
1468
- resourcesFromJsenvBuild,
1469
- null,
1470
- " ",
1471
- )};\n`,
1472
- });
1473
- }
1474
- };
1475
- },
1476
-
1477
- getBuildInfo: () => {
1478
- const buildManifest = {};
1479
- const buildContents = {};
1480
- const buildInlineRelativeUrlSet = new Set();
1481
- GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
1482
- finalKitchen.graph.rootUrlInfo,
1483
- (urlInfo) => {
1484
- const buildUrl = urlInfoToBuildUrlMap.get(urlInfo);
1485
- if (!buildUrl) {
1486
- return;
1487
- }
1488
- if (
1489
- urlInfo.type === "asset" &&
1490
- urlIsInsideOf(urlInfo.url, buildDirectoryUrl)
1491
- ) {
1492
- return;
1493
- }
1494
- const buildSpecifier = buildUrlToBuildSpecifierMap.get(buildUrl);
1495
- const buildSpecifierVersioned = versioning
1496
- ? buildSpecifierToBuildSpecifierVersionedMap.get(buildSpecifier)
1497
- : null;
1498
- const buildRelativeUrl = urlToRelativeUrl(
1499
- buildUrl,
1500
- buildDirectoryUrl,
1501
- );
1502
- let contentKey;
1503
- // if to guard for html where versioned build specifier is not generated
1504
- if (buildSpecifierVersioned) {
1505
- const buildUrlVersioned = asBuildUrlVersioned({
1506
- buildSpecifierVersioned,
1507
- buildDirectoryUrl,
1508
- });
1509
- const buildRelativeUrlVersioned = urlToRelativeUrl(
1510
- buildUrlVersioned,
1511
- buildDirectoryUrl,
1512
- );
1513
- buildManifest[buildRelativeUrl] = buildRelativeUrlVersioned;
1514
- contentKey = buildRelativeUrlVersioned;
1515
- } else {
1516
- contentKey = buildRelativeUrl;
1517
- }
1518
- if (urlInfo.type !== "directory") {
1519
- buildContents[contentKey] = urlInfo.content;
1520
- }
1521
- if (urlInfo.isInline) {
1522
- buildInlineRelativeUrlSet.add(buildRelativeUrl);
1523
- }
1524
- },
1525
- );
1526
- const buildFileContents = {};
1527
- const buildInlineContents = {};
1528
- Object.keys(buildContents)
1529
- .sort((a, b) => comparePathnames(a, b))
1530
- .forEach((buildRelativeUrl) => {
1531
- if (buildInlineRelativeUrlSet.has(buildRelativeUrl)) {
1532
- buildInlineContents[buildRelativeUrl] =
1533
- buildContents[buildRelativeUrl];
1534
- } else {
1535
- buildFileContents[buildRelativeUrl] =
1536
- buildContents[buildRelativeUrl];
1537
- }
1538
- });
1539
-
1540
- return { buildFileContents, buildInlineContents, buildManifest };
1541
- },
1542
- };
1543
- };
1544
-
1545
- const findRawUrlInfoWhenInline = (reference, rawKitchen) => {
1546
- const rawUrlInfo = GRAPH_VISITOR.find(
1547
- rawKitchen.graph,
1548
- (rawUrlInfoCandidate) => {
1549
- const { inlineUrlSite } = rawUrlInfoCandidate;
1550
- if (!inlineUrlSite) {
1551
- return false;
1552
- }
1553
- if (
1554
- inlineUrlSite.url === reference.ownerUrlInfo.url &&
1555
- inlineUrlSite.line === reference.specifierLine &&
1556
- inlineUrlSite.column === reference.specifierColumn &&
1557
- rawUrlInfoCandidate.contentType === reference.contentType
1558
- ) {
1559
- return true;
1560
- }
1561
- if (rawUrlInfoCandidate.content === reference.content) {
1562
- return true;
1563
- }
1564
- if (rawUrlInfoCandidate.originalContent === reference.content) {
1565
- return true;
1566
- }
1567
- return false;
1568
- },
1569
- );
1570
- return rawUrlInfo;
1571
- };
1572
-
1573
- // see https://github.com/rollup/rollup/blob/ce453507ab8457dd1ea3909d8dd7b117b2d14fab/src/utils/hashPlaceholders.ts#L1
1574
- // see also "New hashing algorithm that "fixes (nearly) everything"
1575
- // at https://github.com/rollup/rollup/pull/4543
1576
- const placeholderLeft = "!~{";
1577
- const placeholderRight = "}~";
1578
- const placeholderOverhead = placeholderLeft.length + placeholderRight.length;
1579
-
1580
- const createPlaceholderAPI = ({ length }) => {
1581
- const chars =
1582
- "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$";
1583
- const toBase64 = (value) => {
1584
- let outString = "";
1585
- do {
1586
- const currentDigit = value % 64;
1587
- value = (value / 64) | 0;
1588
- outString = chars[currentDigit] + outString;
1589
- } while (value !== 0);
1590
- return outString;
1591
- };
1592
-
1593
- let nextIndex = 0;
1594
- const generate = () => {
1595
- nextIndex++;
1596
- const id = toBase64(nextIndex);
1597
- let placeholder = placeholderLeft;
1598
- placeholder += id.padStart(length - placeholderOverhead, "0");
1599
- placeholder += placeholderRight;
1600
- return placeholder;
1601
- };
1602
-
1603
- const replaceFirst = (code, value) => {
1604
- let replaced = false;
1605
- return code.replace(PLACEHOLDER_REGEX, (match) => {
1606
- if (replaced) return match;
1607
- replaced = true;
1608
- return value;
1609
- });
1610
- };
1611
-
1612
- const extractFirst = (string) => {
1613
- const match = string.match(PLACEHOLDER_REGEX);
1614
- return match ? match[0] : null;
1615
- };
1616
-
1617
- const defaultPlaceholder = `${placeholderLeft}${"0".repeat(
1618
- length - placeholderOverhead,
1619
- )}${placeholderRight}`;
1620
- const replaceWithDefault = (code, onPlaceholder) => {
1621
- const transformedCode = code.replace(PLACEHOLDER_REGEX, (placeholder) => {
1622
- onPlaceholder(placeholder);
1623
- return defaultPlaceholder;
1624
- });
1625
- return transformedCode;
1626
- };
1627
-
1628
- const PLACEHOLDER_REGEX = new RegExp(
1629
- `${escapeRegexpSpecialChars(placeholderLeft)}[0-9a-zA-Z_$]{1,${
1630
- length - placeholderOverhead
1631
- }}${escapeRegexpSpecialChars(placeholderRight)}`,
1632
- "g",
1633
- );
1634
-
1635
- const markAsCode = (string) => {
1636
- return {
1637
- __isCode__: true,
1638
- toString: () => string,
1639
- value: string,
1640
- };
1641
- };
1642
-
1643
- const replaceAll = (string, replacer) => {
1644
- const magicSource = createMagicSource(string);
1645
-
1646
- string.replace(PLACEHOLDER_REGEX, (placeholder, index) => {
1647
- const replacement = replacer(placeholder, index);
1648
- if (!replacement) {
1649
- return;
1650
- }
1651
- let value;
1652
- let isCode = false;
1653
- if (replacement && replacement.__isCode__) {
1654
- value = replacement.value;
1655
- isCode = true;
1656
- } else {
1657
- value = replacement;
1658
- }
1659
-
1660
- let start = index;
1661
- let end = start + placeholder.length;
1662
- if (
1663
- isCode &&
1664
- // when specifier is wrapper by quotes
1665
- // we remove the quotes to transform the string
1666
- // into code that will be executed
1667
- isWrappedByQuote(string, start, end)
1668
- ) {
1669
- start = start - 1;
1670
- end = end + 1;
1671
- }
1672
- magicSource.replace({
1673
- start,
1674
- end,
1675
- replacement: value,
1676
- });
1677
- });
1678
- return magicSource.toContentAndSourcemap();
1679
- };
1680
-
1681
- return {
1682
- generate,
1683
- replaceFirst,
1684
- replaceAll,
1685
- extractFirst,
1686
- markAsCode,
1687
- replaceWithDefault,
1688
- };
1689
- };
1690
-
1691
- const mayUsePlaceholder = (urlInfo) => {
1692
- if (urlInfo.referenceToOthersSet.size === 0) {
1693
- return false;
1694
- }
1695
- if (!CONTENT_TYPE.isTextual(urlInfo.contentType)) {
1696
- return false;
1697
- }
1698
- return true;
1699
- };
1700
-
1701
- const isWrappedByQuote = (content, start, end) => {
1702
- const previousChar = content[start - 1];
1703
- const nextChar = content[end];
1704
- if (previousChar === `'` && nextChar === `'`) {
1705
- return true;
1706
- }
1707
- if (previousChar === `"` && nextChar === `"`) {
1708
- return true;
1709
- }
1710
- if (previousChar === "`" && nextChar === "`") {
1711
- return true;
1712
- }
1713
- return false;
1714
- };
1715
-
1716
- // https://github.com/rollup/rollup/blob/19e50af3099c2f627451a45a84e2fa90d20246d5/src/utils/FileEmitter.ts#L47
1717
- // https://github.com/rollup/rollup/blob/5a5391971d695c808eed0c5d7d2c6ccb594fc689/src/Chunk.ts#L870
1718
- const generateVersion = (parts, length) => {
1719
- const hash = createHash("sha256");
1720
- parts.forEach((part) => {
1721
- hash.update(part);
1722
- });
1723
- return hash.digest("hex").slice(0, length);
1724
- };
1725
-
1726
- const injectVersionIntoBuildSpecifier = ({
1727
- buildSpecifier,
1728
- version,
1729
- versioningMethod,
1730
- }) => {
1731
- if (versioningMethod === "search_param") {
1732
- return injectQueryParamIntoSpecifierWithoutEncoding(
1733
- buildSpecifier,
1734
- "v",
1735
- version,
1736
- );
1737
- }
1738
- return renderUrlOrRelativeUrlFilename(
1739
- buildSpecifier,
1740
- ({ basename, extension }) => {
1741
- return `${basename}-${version}${extension}`;
1742
- },
1743
- );
1744
- };
1745
-
1746
- const asBuildUrlVersioned = ({
1747
- buildSpecifierVersioned,
1748
- buildDirectoryUrl,
1749
- }) => {
1750
- if (buildSpecifierVersioned[0] === "/") {
1751
- return new URL(buildSpecifierVersioned.slice(1), buildDirectoryUrl).href;
1752
- }
1753
- const buildUrl = new URL(buildSpecifierVersioned, buildDirectoryUrl).href;
1754
- if (buildUrl.startsWith(buildDirectoryUrl)) {
1755
- return buildUrl;
1756
- }
1757
- // it's likely "base" parameter was set to an url origin like "https://cdn.example.com"
1758
- // let's move url to build directory
1759
- const { pathname, search, hash } = new URL(buildSpecifierVersioned);
1760
- return `${buildDirectoryUrl}${pathname}${search}${hash}`;
1761
- };
1762
-
1763
- const ensureUnixLineBreaks = (stringOrBuffer) => {
1764
- if (typeof stringOrBuffer === "string") {
1765
- const stringWithLinuxBreaks = stringOrBuffer.replace(/\r\n/g, "\n");
1766
- return stringWithLinuxBreaks;
1767
- }
1768
- return ensureUnixLineBreaksOnBuffer(stringOrBuffer);
1769
- };
1770
-
1771
- // https://github.com/nodejs/help/issues/1738#issuecomment-458460503
1772
- const ensureUnixLineBreaksOnBuffer = (buffer) => {
1773
- const int32Array = new Int32Array(buffer, 0, buffer.length);
1774
- const int32ArrayWithLineBreaksNormalized = int32Array.filter(
1775
- (element, index, typedArray) => {
1776
- if (element === 0x0d) {
1777
- if (typedArray[index + 1] === 0x0a) {
1778
- // Windows -> Unix
1779
- return false;
1780
- }
1781
- // Mac OS -> Unix
1782
- typedArray[index] = 0x0a;
1783
- }
1784
- return true;
1785
- },
1786
- );
1787
- return Buffer.from(int32ArrayWithLineBreaksNormalized);
1788
- };
1789
-
1790
- const jsenvPluginLineBreakNormalization = () => {
1791
- return {
1792
- name: "jsenv:line_break_normalizer",
1793
- appliesDuring: "build",
1794
- transformUrlContent: (urlInfo) => {
1795
- if (CONTENT_TYPE.isTextual(urlInfo.contentType)) {
1796
- return ensureUnixLineBreaks(urlInfo.content);
1797
- }
1798
- return null;
1799
- },
1800
- };
1801
- };
1802
-
1803
- const jsenvPluginSubbuilds = (
1804
- subBuildParamsArray,
1805
- { parentBuildParams, onCustomBuildDirectory, buildStart },
1806
- ) => {
1807
- if (subBuildParamsArray.length === 0) {
1808
- return [];
1809
- }
1810
- return subBuildParamsArray.map((subBuildParams, index) => {
1811
- const defaultChildBuildParams = {};
1812
- const childBuildParams = {
1813
- ...parentBuildParams,
1814
- logs: {
1815
- level: "warn",
1816
- disabled: true,
1817
- },
1818
- ...defaultChildBuildParams,
1819
- ...subBuildParams,
1820
- };
1821
- const subBuildDirectoryUrl = subBuildParams.buildDirectoryUrl;
1822
- if (subBuildDirectoryUrl) {
1823
- const subBuildRelativeUrl = urlToRelativeUrl(
1824
- subBuildDirectoryUrl,
1825
- parentBuildParams.buildDirectoryUrl,
1826
- );
1827
- const subbuildRuntimeCompat =
1828
- childBuildParams.runtimeCompat || defaultRuntimeCompat;
1829
- const subbuildBase =
1830
- subBuildParams.base || getDefaultBase(subbuildRuntimeCompat);
1831
- childBuildParams.base = `${subbuildBase}${subBuildRelativeUrl}`;
1832
- onCustomBuildDirectory(subBuildRelativeUrl);
1833
- }
1834
- const buildPromise = buildStart(childBuildParams, index);
1835
- const entryPointBuildUrlMap = new Map();
1836
- const entryPointSourceUrlSet = new Set();
1837
- const entryPointBuildUrlSet = new Set();
1838
- const childBuildEntryPoints = childBuildParams.entryPoints;
1839
- for (const key of Object.keys(childBuildEntryPoints)) {
1840
- const entryPointUrl = new URL(key, childBuildParams.sourceDirectoryUrl)
1841
- .href;
1842
- const entryPointBuildUrl = new URL(
1843
- childBuildEntryPoints[key],
1844
- childBuildParams.buildDirectoryUrl,
1845
- ).href;
1846
- entryPointBuildUrlMap.set(entryPointUrl, entryPointBuildUrl);
1847
- entryPointSourceUrlSet.add(entryPointUrl);
1848
- entryPointBuildUrlSet.add(entryPointBuildUrl);
1849
- }
1850
-
1851
- return {
1852
- name: `jsenv:subbuild_${index}`,
1853
- redirectReference: (reference) => {
1854
- const entryPointBuildUrl = entryPointBuildUrlMap.get(reference.url);
1855
- if (!entryPointBuildUrl) {
1856
- return null;
1857
- }
1858
- return entryPointBuildUrl;
1859
- },
1860
- fetchUrlContent: async (urlInfo) => {
1861
- if (!entryPointBuildUrlSet.has(urlInfo.url)) {
1862
- return;
1863
- }
1864
- await buildPromise;
1865
- urlInfo.typeHint = "asset"; // this ensure the rest of jsenv do not scan or modify the content of this file
1866
- },
1867
- };
1868
- });
1869
- };
1870
-
1871
- /*
1872
- * Build is split in 3 steps:
1873
- * 1. craft
1874
- * 2. shape
1875
- * 3. refine
1876
- *
1877
- * craft: prepare all the materials
1878
- * - resolve, fetch and transform all source files into "rawKitchen.graph"
1879
- * shape: this step can drastically change url content and their relationships
1880
- * - bundling
1881
- * - optimizations (minification)
1882
- * refine: perform minor changes on the url contents
1883
- * - cleaning html
1884
- * - url versioning
1885
- * - ressource hints
1886
- * - injecting urls into service workers
1887
- */
1888
-
1889
-
1890
- /**
1891
- * Generate an optimized version of source files into a directory.
1892
- *
1893
- * @param {Object} params
1894
- * @param {string|url} params.sourceDirectoryUrl
1895
- * Directory containing source files
1896
- * @param {string|url} params.buildDirectoryUrl
1897
- * Directory where optimized files will be written
1898
- * @param {object} params.entryPoints
1899
- * Object where keys are paths to source files and values are their future name in the build directory.
1900
- * Keys are relative to sourceDirectoryUrl
1901
- * @param {object} params.runtimeCompat
1902
- * Code generated will be compatible with these runtimes
1903
- * @param {string} [params.assetsDirectory=""]
1904
- * Directory where asset files will be written
1905
- * @param {string|url} [params.base=""]
1906
- * Urls in build file contents will be prefixed with this string
1907
- * @param {boolean|object} [params.bundling=true]
1908
- * Reduce number of files written in the build directory
1909
- * @param {boolean|object} [params.minification=true]
1910
- * Minify the content of files written into the build directory
1911
- * @param {boolean} [params.versioning=true]
1912
- * Use versioning on files written in the build directory
1913
- * @param {('search_param'|'filename')} [params.versioningMethod="search_param"]
1914
- * Controls how url are versioned in the build directory
1915
- * @param {('none'|'inline'|'file'|'programmatic')} [params.sourcemaps="none"]
1916
- * Generate sourcemaps in the build directory
1917
- * @param {('error'|'copy'|'preserve')|function} [params.directoryReferenceEffect="error"]
1918
- * What to do when a reference leads to a directory on the filesystem
1919
- * @return {Promise<Object>} buildReturnValue
1920
- * @return {Promise<Object>} buildReturnValue.buildInlineContents
1921
- * Contains content that is inline into build files
1922
- * @return {Promise<Object>} buildReturnValue.buildManifest
1923
- * Map build file paths without versioning to versioned file paths
1924
- */
1925
- const build = async ({
1926
- signal = new AbortController().signal,
1927
- handleSIGINT = true,
1928
- logs = logsDefault,
1929
- sourceDirectoryUrl,
1930
- buildDirectoryUrl,
1931
- entryPoints = {},
1932
- assetsDirectory = "",
1933
- runtimeCompat = defaultRuntimeCompat,
1934
- base = getDefaultBase(runtimeCompat),
1935
- ignore,
1936
-
1937
- subbuilds = [],
1938
- plugins = [],
1939
- referenceAnalysis = {},
1940
- nodeEsmResolution,
1941
- magicExtensions,
1942
- magicDirectoryIndex,
1943
- directoryReferenceEffect,
1944
- scenarioPlaceholders,
1945
- injections,
1946
- transpilation = {},
1947
- bundling = true,
1948
- minification = !runtimeCompat.node,
1949
- versioning = !runtimeCompat.node,
1950
- versioningMethod = "search_param", // "filename", "search_param"
1951
- versioningViaImportmap = true,
1952
- versionLength = 8,
1953
- lineBreakNormalization = process.platform === "win32",
1954
-
1955
- sourceFilesConfig = {},
1956
- cooldownBetweenFileEvents,
1957
- watch = false,
1958
- http = false,
1959
-
1960
- buildDirectoryCleanPatterns = {
1961
- "**/*": true,
1962
- },
1963
- sourcemaps = "none",
1964
- sourcemapsSourcesContent,
1965
- writeOnFileSystem = true,
1966
- outDirectoryUrl,
1967
- assetManifest = versioningMethod === "filename",
1968
- assetManifestFileRelativeUrl = "asset-manifest.json",
1969
- returnBuildInlineContents,
1970
- returnBuildManifest,
1971
- ...rest
1972
- }) => {
1973
- // param validation
1974
- {
1975
- const unexpectedParamNames = Object.keys(rest);
1976
- if (unexpectedParamNames.length > 0) {
1977
- throw new TypeError(
1978
- `${unexpectedParamNames.join(",")}: there is no such param`,
1979
- );
1980
- }
1981
- // logs
1982
- {
1983
- if (typeof logs !== "object") {
1984
- throw new TypeError(`logs must be an object, got ${logs}`);
1985
- }
1986
- const unexpectedLogsKeys = Object.keys(logs).filter(
1987
- (key) => !Object.hasOwn(logsDefault, key),
1988
- );
1989
- if (unexpectedLogsKeys.length > 0) {
1990
- throw new TypeError(
1991
- `${unexpectedLogsKeys.join(",")}: no such key on logs`,
1992
- );
1993
- }
1994
- logs = { ...logsDefault, ...logs };
1995
- }
1996
- sourceDirectoryUrl = assertAndNormalizeDirectoryUrl(
1997
- sourceDirectoryUrl,
1998
- "sourceDirectoryUrl",
1999
- );
2000
- buildDirectoryUrl = assertAndNormalizeDirectoryUrl(
2001
- buildDirectoryUrl,
2002
- "buildDirectoryUrl",
2003
- );
2004
- if (outDirectoryUrl === undefined) {
2005
- if (
2006
- process.env.CAPTURING_SIDE_EFFECTS ||
2007
- (false)
2008
- ) {
2009
- outDirectoryUrl = new URL("../.jsenv_b/", sourceDirectoryUrl);
2010
- } else {
2011
- const packageDirectoryUrl = lookupPackageDirectory(sourceDirectoryUrl);
2012
- if (packageDirectoryUrl) {
2013
- outDirectoryUrl = `${packageDirectoryUrl}.jsenv/`;
2014
- }
2015
- }
2016
- } else if (outDirectoryUrl !== null && outDirectoryUrl !== false) {
2017
- outDirectoryUrl = assertAndNormalizeDirectoryUrl(
2018
- outDirectoryUrl,
2019
- "outDirectoryUrl",
2020
- );
2021
- }
2022
-
2023
- if (typeof entryPoints !== "object" || entryPoints === null) {
2024
- throw new TypeError(`entryPoints must be an object, got ${entryPoints}`);
2025
- }
2026
- const keys = Object.keys(entryPoints);
2027
- keys.forEach((key) => {
2028
- if (!key.startsWith("./")) {
2029
- throw new TypeError(
2030
- `entryPoints keys must start with "./", found ${key}`,
2031
- );
2032
- }
2033
- const value = entryPoints[key];
2034
- if (typeof value !== "string") {
2035
- throw new TypeError(
2036
- `entryPoints values must be strings, found "${value}" on key "${key}"`,
2037
- );
2038
- }
2039
- if (value.includes("/")) {
2040
- throw new TypeError(
2041
- `entryPoints values must be plain strings (no "/"), found "${value}" on key "${key}"`,
2042
- );
2043
- }
2044
- });
2045
- if (!["filename", "search_param"].includes(versioningMethod)) {
2046
- throw new TypeError(
2047
- `versioningMethod must be "filename" or "search_param", got ${versioning}`,
2048
- );
2049
- }
2050
- if (bundling === true) {
2051
- bundling = {};
2052
- }
2053
- if (minification === true) {
2054
- minification = {};
2055
- }
2056
- }
2057
-
2058
- if (assetsDirectory && assetsDirectory[assetsDirectory.length - 1] !== "/") {
2059
- assetsDirectory = `${assetsDirectory}/`;
2060
- }
2061
-
2062
- const operation = Abort.startOperation();
2063
- operation.addAbortSignal(signal);
2064
- if (handleSIGINT) {
2065
- operation.addAbortSource((abort) => {
2066
- return raceProcessTeardownEvents(
2067
- {
2068
- SIGINT: true,
2069
- },
2070
- abort,
2071
- );
2072
- });
2073
- }
2074
-
2075
- const runBuild = async ({ signal, logLevel }) => {
2076
- const logger = createLogger({ logLevel });
2077
- const createBuildTask = (label) => {
2078
- return createTaskLog(label, {
2079
- disabled:
2080
- logs.disabled || (!logger.levels.debug && !logger.levels.info),
2081
- animated: logs.animation && !logger.levels.debug,
2082
- });
2083
- };
2084
-
2085
- const buildOperation = Abort.startOperation();
2086
- buildOperation.addAbortSignal(signal);
2087
- const entryPointKeys = Object.keys(entryPoints);
2088
- if (entryPointKeys.length === 1) {
2089
- logger.info(`
2090
- build "${entryPointKeys[0]}"`);
2091
- } else {
2092
- logger.info(`
2093
- build ${entryPointKeys.length} entry points`);
2094
- }
2095
- let explicitJsModuleConversion = false;
2096
- for (const entryPointKey of entryPointKeys) {
2097
- if (entryPointKey.includes("?js_module_fallback")) {
2098
- explicitJsModuleConversion = true;
2099
- break;
2100
- }
2101
- if (entryPointKey.includes("?as_js_classic")) {
2102
- explicitJsModuleConversion = true;
2103
- break;
2104
- }
2105
- }
2106
- const entryUrls = [];
2107
- const contextSharedDuringBuild = {
2108
- buildStep: "craft",
2109
- buildDirectoryUrl,
2110
- assetsDirectory,
2111
- versioning,
2112
- versioningViaImportmap,
2113
- };
2114
- const rawKitchen = createKitchen({
2115
- signal,
2116
- logLevel: logs.level,
2117
- rootDirectoryUrl: sourceDirectoryUrl,
2118
- ignore,
2119
- // during first pass (craft) we keep "ignore:" when a reference is ignored
2120
- // so that the second pass (shape) properly ignore those urls
2121
- ignoreProtocol: "keep",
2122
- build: true,
2123
- runtimeCompat,
2124
- initialContext: contextSharedDuringBuild,
2125
- sourcemaps,
2126
- sourcemapsSourcesContent,
2127
- outDirectoryUrl: outDirectoryUrl
2128
- ? new URL("craft/", outDirectoryUrl)
2129
- : undefined,
2130
- });
2131
-
2132
- let subbuildResults = [];
2133
-
2134
- const rawPluginStore = createPluginStore([
2135
- ...jsenvPluginSubbuilds(subbuilds, {
2136
- parentBuildParams: {
2137
- sourceDirectoryUrl,
2138
- buildDirectoryUrl,
2139
- runtimeCompat,
2140
- bundling,
2141
- minification,
2142
- versioning,
2143
- versioningMethod,
2144
- },
2145
- onCustomBuildDirectory: (subBuildRelativeUrl) => {
2146
- buildDirectoryCleanPatterns = {
2147
- ...buildDirectoryCleanPatterns,
2148
- [`${subBuildRelativeUrl}**/*`]: false,
2149
- };
2150
- },
2151
- buildStart: async (params, index) => {
2152
- const result = await build({
2153
- ...params,
2154
- signal,
2155
- handleSIGINT: false,
2156
- });
2157
- subbuildResults[index] = result;
2158
- return result;
2159
- },
2160
- }),
2161
- ...plugins,
2162
- ...(bundling ? [jsenvPluginBundling(bundling)] : []),
2163
- ...(minification ? [jsenvPluginMinification(minification)] : []),
2164
- ...getCorePlugins({
2165
- rootDirectoryUrl: sourceDirectoryUrl,
2166
- runtimeCompat,
2167
- referenceAnalysis,
2168
- nodeEsmResolution,
2169
- magicExtensions,
2170
- magicDirectoryIndex,
2171
- directoryReferenceEffect,
2172
- injections,
2173
- transpilation: {
2174
- babelHelpersAsImport: !explicitJsModuleConversion,
2175
- ...transpilation,
2176
- jsModuleFallback: false,
2177
- },
2178
- inlining: false,
2179
- http,
2180
- scenarioPlaceholders,
2181
- }),
2182
- ]);
2183
- const rawPluginController = createPluginController(
2184
- rawPluginStore,
2185
- rawKitchen,
2186
- );
2187
- rawKitchen.setPluginController(rawPluginController);
2188
-
2189
- {
2190
- const generateSourceGraph = createBuildTask("generate source graph");
2191
- try {
2192
- if (outDirectoryUrl) {
2193
- await ensureEmptyDirectory(new URL(`craft/`, outDirectoryUrl));
2194
- }
2195
- const rawRootUrlInfo = rawKitchen.graph.rootUrlInfo;
2196
- await rawRootUrlInfo.dependencies.startCollecting(() => {
2197
- Object.keys(entryPoints).forEach((key) => {
2198
- const entryReference = rawRootUrlInfo.dependencies.found({
2199
- trace: { message: `"${key}" in entryPoints parameter` },
2200
- isEntryPoint: true,
2201
- type: "entry_point",
2202
- specifier: key,
2203
- filenameHint: entryPoints[key],
2204
- });
2205
- entryUrls.push(entryReference.url);
2206
- });
2207
- });
2208
- await rawRootUrlInfo.cookDependencies({
2209
- operation: buildOperation,
2210
- });
2211
- } catch (e) {
2212
- generateSourceGraph.fail();
2213
- throw e;
2214
- }
2215
- generateSourceGraph.done();
2216
- }
2217
-
2218
- const finalKitchen = createKitchen({
2219
- name: "shape",
2220
- logLevel: logs.level,
2221
- rootDirectoryUrl: sourceDirectoryUrl,
2222
- // here most plugins are not there
2223
- // - no external plugin
2224
- // - no plugin putting reference.mustIgnore on https urls
2225
- // At this stage it's only about redirecting urls to the build directory
2226
- // consequently only a subset or urls are supported
2227
- supportedProtocols: ["file:", "data:", "virtual:", "ignore:"],
2228
- ignore,
2229
- ignoreProtocol: "remove",
2230
- build: true,
2231
- runtimeCompat,
2232
- initialContext: contextSharedDuringBuild,
2233
- sourcemaps,
2234
- sourcemapsComment: "relative",
2235
- sourcemapsSourcesContent,
2236
- outDirectoryUrl: outDirectoryUrl
2237
- ? new URL("shape/", outDirectoryUrl)
2238
- : undefined,
2239
- });
2240
- const buildSpecifierManager = createBuildSpecifierManager({
2241
- rawKitchen,
2242
- finalKitchen,
2243
- logger,
2244
- sourceDirectoryUrl,
2245
- buildDirectoryUrl,
2246
- base,
2247
- assetsDirectory,
2248
-
2249
- versioning,
2250
- versioningMethod,
2251
- versionLength,
2252
- canUseImportmap:
2253
- versioningViaImportmap &&
2254
- entryUrls.every((finalEntryUrl) => {
2255
- const entryUrlInfo = rawKitchen.graph.getUrlInfo(finalEntryUrl);
2256
- return entryUrlInfo.type === "html";
2257
- }) &&
2258
- rawKitchen.context.isSupportedOnCurrentClients("importmap"),
2259
- });
2260
- const finalPluginStore = createPluginStore([
2261
- jsenvPluginReferenceAnalysis({
2262
- ...referenceAnalysis,
2263
- fetchInlineUrls: false,
2264
- // inlineContent: false,
2265
- }),
2266
- jsenvPluginDirectoryReferenceEffect(directoryReferenceEffect),
2267
- ...(lineBreakNormalization ? [jsenvPluginLineBreakNormalization()] : []),
2268
- jsenvPluginJsModuleFallback({
2269
- remapImportSpecifier: (specifier, parentUrl) => {
2270
- return buildSpecifierManager.remapPlaceholder(specifier, parentUrl);
2271
- },
2272
- }),
2273
- jsenvPluginInlining(),
2274
- {
2275
- name: "jsenv:optimize",
2276
- appliesDuring: "build",
2277
- transformUrlContent: async (urlInfo) => {
2278
- await rawKitchen.pluginController.callAsyncHooks(
2279
- "optimizeUrlContent",
2280
- urlInfo,
2281
- (optimizeReturnValue) => {
2282
- urlInfo.mutateContent(optimizeReturnValue);
2283
- },
2284
- );
2285
- },
2286
- },
2287
- buildSpecifierManager.jsenvPluginMoveToBuildDirectory,
2288
- ]);
2289
- const finalPluginController = createPluginController(
2290
- finalPluginStore,
2291
- finalKitchen,
2292
- {
2293
- initialPuginsMeta: rawKitchen.pluginController.pluginsMeta,
2294
- },
2295
- );
2296
- finalKitchen.setPluginController(finalPluginController);
2297
-
2298
- const bundlers = {};
2299
- {
2300
- for (const plugin of rawKitchen.pluginController.activePlugins) {
2301
- const bundle = plugin.bundle;
2302
- if (!bundle) {
2303
- continue;
2304
- }
2305
- if (typeof bundle !== "object") {
2306
- throw new Error(
2307
- `bundle must be an object, found "${bundle}" on plugin named "${plugin.name}"`,
2308
- );
2309
- }
2310
- for (const type of Object.keys(bundle)) {
2311
- const bundleFunction = bundle[type];
2312
- if (!bundleFunction) {
2313
- continue;
2314
- }
2315
- const bundlerForThatType = bundlers[type];
2316
- if (bundlerForThatType) {
2317
- // first plugin to define a bundle hook wins
2318
- continue;
2319
- }
2320
- bundlers[type] = {
2321
- plugin,
2322
- bundleFunction: bundle[type],
2323
- urlInfoMap: new Map(),
2324
- };
2325
- }
2326
- }
2327
- const addToBundlerIfAny = (rawUrlInfo) => {
2328
- const bundler = bundlers[rawUrlInfo.type];
2329
- if (bundler) {
2330
- bundler.urlInfoMap.set(rawUrlInfo.url, rawUrlInfo);
2331
- }
2332
- };
2333
- // ignore unused urls thanks to "forEachUrlInfoStronglyReferenced"
2334
- // it avoid bundling things that are not actually used
2335
- // happens for:
2336
- // - js import assertions
2337
- // - conversion to js classic using ?as_js_classic or ?js_module_fallback
2338
- GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
2339
- rawKitchen.graph.rootUrlInfo,
2340
- (rawUrlInfo) => {
2341
- if (rawUrlInfo.isEntryPoint) {
2342
- addToBundlerIfAny(rawUrlInfo);
2343
- }
2344
- if (rawUrlInfo.type === "html") {
2345
- for (const referenceToOther of rawUrlInfo.referenceToOthersSet) {
2346
- if (
2347
- referenceToOther.isResourceHint &&
2348
- referenceToOther.expectedType === "js_module"
2349
- ) {
2350
- const referencedUrlInfo = referenceToOther.urlInfo;
2351
- if (
2352
- referencedUrlInfo &&
2353
- // something else than the resource hint is using this url
2354
- referencedUrlInfo.referenceFromOthersSet.size > 0
2355
- ) {
2356
- addToBundlerIfAny(referencedUrlInfo);
2357
- continue;
2358
- }
2359
- }
2360
- if (referenceToOther.isWeak) {
2361
- continue;
2362
- }
2363
- const referencedUrlInfo = referenceToOther.urlInfo;
2364
- if (referencedUrlInfo.isInline) {
2365
- if (referencedUrlInfo.type === "js_module") {
2366
- // bundle inline script type module deps
2367
- referencedUrlInfo.referenceToOthersSet.forEach(
2368
- (jsModuleReferenceToOther) => {
2369
- if (jsModuleReferenceToOther.type === "js_import") {
2370
- const inlineUrlInfo = jsModuleReferenceToOther.urlInfo;
2371
- addToBundlerIfAny(inlineUrlInfo);
2372
- }
2373
- },
2374
- );
2375
- }
2376
- // inline content cannot be bundled
2377
- continue;
2378
- }
2379
- addToBundlerIfAny(referencedUrlInfo);
2380
- }
2381
- return;
2382
- }
2383
- // File referenced with new URL('./file.js', import.meta.url)
2384
- // are entry points that should be bundled
2385
- // For instance we will bundle service worker/workers detected like this
2386
- if (rawUrlInfo.type === "js_module") {
2387
- for (const referenceToOther of rawUrlInfo.referenceToOthersSet) {
2388
- if (referenceToOther.type === "js_url") {
2389
- const referencedUrlInfo = referenceToOther.urlInfo;
2390
- let isAlreadyBundled = false;
2391
- for (const referenceFromOther of referencedUrlInfo.referenceFromOthersSet) {
2392
- if (referenceFromOther.url === referencedUrlInfo.url) {
2393
- if (
2394
- referenceFromOther.subtype === "import_dynamic" ||
2395
- referenceFromOther.type === "script"
2396
- ) {
2397
- isAlreadyBundled = true;
2398
- break;
2399
- }
2400
- }
2401
- }
2402
- if (!isAlreadyBundled) {
2403
- addToBundlerIfAny(referencedUrlInfo);
2404
- }
2405
- continue;
2406
- }
2407
- if (referenceToOther.type === "js_inline_content") ;
2408
- }
2409
- }
2410
- },
2411
- );
2412
- for (const type of Object.keys(bundlers)) {
2413
- const bundler = bundlers[type];
2414
- const urlInfosToBundle = Array.from(bundler.urlInfoMap.values());
2415
- if (urlInfosToBundle.length === 0) {
2416
- continue;
2417
- }
2418
- const bundleTask = createBuildTask(`bundle "${type}"`);
2419
- try {
2420
- await buildSpecifierManager.applyBundling({
2421
- bundler,
2422
- urlInfosToBundle,
2423
- });
2424
- } catch (e) {
2425
- bundleTask.fail();
2426
- throw e;
2427
- }
2428
- bundleTask.done();
2429
- }
2430
- }
2431
-
2432
- {
2433
- finalKitchen.context.buildStep = "shape";
2434
- const generateBuildGraph = createBuildTask("generate build graph");
2435
- try {
2436
- if (outDirectoryUrl) {
2437
- await ensureEmptyDirectory(new URL(`shape/`, outDirectoryUrl));
2438
- }
2439
- const finalRootUrlInfo = finalKitchen.graph.rootUrlInfo;
2440
- await finalRootUrlInfo.dependencies.startCollecting(() => {
2441
- entryUrls.forEach((entryUrl) => {
2442
- finalRootUrlInfo.dependencies.found({
2443
- trace: { message: `entryPoint` },
2444
- isEntryPoint: true,
2445
- type: "entry_point",
2446
- specifier: entryUrl,
2447
- });
2448
- });
2449
- });
2450
- await finalRootUrlInfo.cookDependencies({
2451
- operation: buildOperation,
2452
- });
2453
- } catch (e) {
2454
- generateBuildGraph.fail();
2455
- throw e;
2456
- }
2457
- generateBuildGraph.done();
2458
- }
2459
-
2460
- {
2461
- finalKitchen.context.buildStep = "refine";
2462
-
2463
- const htmlRefineSet = new Set();
2464
- const registerHtmlRefine = (htmlRefine) => {
2465
- htmlRefineSet.add(htmlRefine);
2466
- };
2467
-
2468
- {
2469
- await buildSpecifierManager.replacePlaceholders();
2470
- }
2471
-
2472
- /*
2473
- * Update <link rel="preload"> and friends after build (once we know everything)
2474
- * - Used to remove resource hint targeting an url that is no longer used:
2475
- * - because of bundlings
2476
- * - because of import assertions transpilation (file is inlined into JS)
2477
- */
2478
- {
2479
- buildSpecifierManager.prepareResyncResourceHints({
2480
- registerHtmlRefine,
2481
- });
2482
- }
2483
-
2484
- {
2485
- GRAPH_VISITOR.forEach(finalKitchen.graph, (urlInfo) => {
2486
- if (!urlInfo.url.startsWith("file:")) {
2487
- return;
2488
- }
2489
- if (urlInfo.type !== "html") {
2490
- return;
2491
- }
2492
- const htmlAst = parseHtml({
2493
- html: urlInfo.content,
2494
- url: urlInfo.url,
2495
- storeOriginalPositions: false,
2496
- });
2497
- for (const htmlRefine of htmlRefineSet) {
2498
- const htmlMutationCallbackSet = new Set();
2499
- const registerHtmlMutation = (callback) => {
2500
- htmlMutationCallbackSet.add(callback);
2501
- };
2502
- htmlRefine(htmlAst, { registerHtmlMutation });
2503
- for (const htmlMutationCallback of htmlMutationCallbackSet) {
2504
- htmlMutationCallback();
2505
- }
2506
- }
2507
- // cleanup jsenv attributes from html as a last step
2508
- urlInfo.content = stringifyHtmlAst(htmlAst, {
2509
- cleanupJsenvAttributes: true,
2510
- cleanupPositionAttributes: true,
2511
- });
2512
- });
2513
- }
2514
-
2515
- {
2516
- const inject = buildSpecifierManager.prepareServiceWorkerUrlInjection();
2517
- if (inject) {
2518
- const urlsInjectionInSw = createBuildTask(
2519
- "inject urls in service worker",
2520
- );
2521
- await inject();
2522
- urlsInjectionInSw.done();
2523
- buildOperation.throwIfAborted();
2524
- }
2525
- }
2526
- }
2527
- const { buildFileContents, buildInlineContents, buildManifest } =
2528
- buildSpecifierManager.getBuildInfo();
2529
- if (writeOnFileSystem) {
2530
- const writingFiles = createBuildTask("write files in build directory");
2531
- clearDirectorySync(buildDirectoryUrl, buildDirectoryCleanPatterns);
2532
- const buildRelativeUrls = Object.keys(buildFileContents);
2533
- buildRelativeUrls.forEach((buildRelativeUrl) => {
2534
- writeFileSync(
2535
- new URL(buildRelativeUrl, buildDirectoryUrl),
2536
- buildFileContents[buildRelativeUrl],
2537
- );
2538
- });
2539
- if (versioning && assetManifest && Object.keys(buildManifest).length) {
2540
- writeFileSync(
2541
- new URL(assetManifestFileRelativeUrl, buildDirectoryUrl),
2542
- JSON.stringify(buildManifest, null, " "),
2543
- );
2544
- }
2545
- writingFiles.done();
2546
- }
2547
- logger.info(
2548
- createUrlGraphSummary(finalKitchen.graph, {
2549
- title: "build files",
2550
- }),
2551
- );
2552
- return {
2553
- ...(returnBuildInlineContents ? { buildInlineContents } : {}),
2554
- ...(returnBuildManifest ? { buildManifest } : {}),
2555
- ...(subbuilds.length ? { subbuilds: subbuildResults } : {}),
2556
- };
2557
- };
2558
-
2559
- if (!watch) {
2560
- try {
2561
- const result = await runBuild({
2562
- signal: operation.signal,
2563
- logLevel: logs.level,
2564
- });
2565
- return result;
2566
- } finally {
2567
- await operation.end();
2568
- }
2569
- }
2570
-
2571
- let resolveFirstBuild;
2572
- let rejectFirstBuild;
2573
- const firstBuildPromise = new Promise((resolve, reject) => {
2574
- resolveFirstBuild = resolve;
2575
- rejectFirstBuild = reject;
2576
- });
2577
- let buildAbortController;
2578
- let watchFilesTask;
2579
- const startBuild = async () => {
2580
- const buildTask = createTaskLog("build");
2581
- buildAbortController = new AbortController();
2582
- try {
2583
- const result = await runBuild({
2584
- signal: buildAbortController.signal,
2585
- logLevel: "warn",
2586
- });
2587
- buildTask.done();
2588
- resolveFirstBuild(result);
2589
- watchFilesTask = createTaskLog("watch files");
2590
- } catch (e) {
2591
- if (Abort.isAbortError(e)) {
2592
- buildTask.fail(`build aborted`);
2593
- } else if (e.code === "PARSE_ERROR") {
2594
- buildTask.fail();
2595
- console.error(e.stack);
2596
- watchFilesTask = createTaskLog("watch files");
2597
- } else {
2598
- buildTask.fail();
2599
- rejectFirstBuild(e);
2600
- throw e;
2601
- }
2602
- }
2603
- };
2604
-
2605
- startBuild();
2606
- let startTimeout;
2607
- const stopWatchingSourceFiles = watchSourceFiles(
2608
- sourceDirectoryUrl,
2609
- ({ url, event }) => {
2610
- if (watchFilesTask) {
2611
- watchFilesTask.happen(
2612
- `${url.slice(sourceDirectoryUrl.length)} ${event}`,
2613
- );
2614
- watchFilesTask = null;
2615
- }
2616
- buildAbortController.abort();
2617
- // setTimeout is to ensure the abortController.abort() above
2618
- // is properly taken into account so that logs about abort comes first
2619
- // then logs about re-running the build happens
2620
- clearTimeout(startTimeout);
2621
- startTimeout = setTimeout(startBuild, 20);
2622
- },
2623
- {
2624
- sourceFilesConfig,
2625
- keepProcessAlive: true,
2626
- cooldownBetweenFileEvents,
2627
- },
2628
- );
2629
- operation.addAbortCallback(() => {
2630
- stopWatchingSourceFiles();
2631
- });
2632
- await firstBuildPromise;
2633
- return stopWatchingSourceFiles;
2634
- };
2635
-
2636
- export { build };