@jsenv/core 37.1.4 → 38.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/js/autoreload.js +2 -2
- package/dist/jsenv_core.js +3221 -2483
- package/package.json +16 -15
- package/src/build/build.js +246 -1028
- package/src/build/build_specifier_manager.js +1200 -0
- package/src/build/build_urls_generator.js +40 -18
- package/src/build/version_mappings_injection.js +14 -16
- package/src/dev/file_service.js +0 -10
- package/src/dev/start_dev_server.js +0 -2
- package/src/kitchen/kitchen.js +54 -37
- package/src/kitchen/url_graph/references.js +84 -93
- package/src/kitchen/url_graph/url_graph.js +16 -6
- package/src/kitchen/url_graph/url_info_transformations.js +124 -55
- package/src/plugins/autoreload/client/autoreload.js +6 -2
- package/src/plugins/autoreload/jsenv_plugin_autoreload_server.js +20 -16
- package/src/plugins/autoreload/jsenv_plugin_hot_search_param.js +1 -1
- package/src/plugins/cache_control/jsenv_plugin_cache_control.js +2 -2
- package/src/plugins/clean_html/jsenv_plugin_clean_html.js +16 -0
- package/src/plugins/importmap/jsenv_plugin_importmap.js +11 -23
- package/src/plugins/inlining/jsenv_plugin_inlining_as_data_url.js +16 -1
- package/src/plugins/inlining/jsenv_plugin_inlining_into_html.js +14 -24
- package/src/plugins/plugin_controller.js +37 -25
- package/src/plugins/plugins.js +2 -0
- package/src/plugins/protocol_file/jsenv_plugin_protocol_file.js +31 -16
- package/src/plugins/reference_analysis/directory/jsenv_plugin_directory_reference_analysis.js +12 -6
- package/src/plugins/reference_analysis/html/jsenv_plugin_html_reference_analysis.js +33 -54
- package/src/plugins/reference_analysis/js/jsenv_plugin_js_reference_analysis.js +2 -9
- package/src/plugins/reference_analysis/jsenv_plugin_reference_analysis.js +15 -8
- package/src/build/build_versions_manager.js +0 -492
|
@@ -0,0 +1,1200 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { createDetailedMessage } from "@jsenv/log";
|
|
3
|
+
import { comparePathnames } from "@jsenv/filesystem";
|
|
4
|
+
import { createMagicSource, generateSourcemapFileUrl } from "@jsenv/sourcemap";
|
|
5
|
+
import {
|
|
6
|
+
ensurePathnameTrailingSlash,
|
|
7
|
+
urlToRelativeUrl,
|
|
8
|
+
injectQueryParamIntoSpecifierWithoutEncoding,
|
|
9
|
+
renderUrlOrRelativeUrlFilename,
|
|
10
|
+
} from "@jsenv/urls";
|
|
11
|
+
import {
|
|
12
|
+
parseHtmlString,
|
|
13
|
+
stringifyHtmlAst,
|
|
14
|
+
visitHtmlNodes,
|
|
15
|
+
getHtmlNodeAttribute,
|
|
16
|
+
setHtmlNodeAttributes,
|
|
17
|
+
removeHtmlNode,
|
|
18
|
+
createHtmlNode,
|
|
19
|
+
insertHtmlNodeAfter,
|
|
20
|
+
findHtmlNode,
|
|
21
|
+
} from "@jsenv/ast";
|
|
22
|
+
import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js";
|
|
23
|
+
|
|
24
|
+
import { escapeRegexpSpecialChars } from "@jsenv/utils/src/string/escape_regexp_special_chars.js";
|
|
25
|
+
import { GRAPH_VISITOR } from "../kitchen/url_graph/url_graph_visitor.js";
|
|
26
|
+
import { isWebWorkerUrlInfo } from "../kitchen/web_workers.js";
|
|
27
|
+
import { prependContent } from "../kitchen/prepend_content.js";
|
|
28
|
+
import { createBuildUrlsGenerator } from "./build_urls_generator.js";
|
|
29
|
+
import {
|
|
30
|
+
injectVersionMappingsAsGlobal,
|
|
31
|
+
injectVersionMappingsAsImportmap,
|
|
32
|
+
} from "./version_mappings_injection.js";
|
|
33
|
+
|
|
34
|
+
export const createBuildSpecifierManager = ({
|
|
35
|
+
rawKitchen,
|
|
36
|
+
finalKitchen,
|
|
37
|
+
logger,
|
|
38
|
+
sourceDirectoryUrl,
|
|
39
|
+
buildDirectoryUrl,
|
|
40
|
+
base,
|
|
41
|
+
assetsDirectory,
|
|
42
|
+
length = 8,
|
|
43
|
+
|
|
44
|
+
versioning,
|
|
45
|
+
versioningMethod,
|
|
46
|
+
versionLength,
|
|
47
|
+
canUseImportmap,
|
|
48
|
+
}) => {
|
|
49
|
+
const buildUrlsGenerator = createBuildUrlsGenerator({
|
|
50
|
+
logger,
|
|
51
|
+
sourceDirectoryUrl,
|
|
52
|
+
buildDirectoryUrl,
|
|
53
|
+
assetsDirectory,
|
|
54
|
+
});
|
|
55
|
+
const placeholderAPI = createPlaceholderAPI({
|
|
56
|
+
length,
|
|
57
|
+
});
|
|
58
|
+
const placeholderToReferenceMap = new Map();
|
|
59
|
+
const urlInfoToBuildUrlMap = new Map();
|
|
60
|
+
const buildUrlToUrlInfoMap = new Map();
|
|
61
|
+
const buildUrlToBuildSpecifierMap = new Map();
|
|
62
|
+
|
|
63
|
+
const generateReplacement = (reference) => {
|
|
64
|
+
let buildUrl;
|
|
65
|
+
if (reference.type === "sourcemap_comment") {
|
|
66
|
+
const parentBuildUrl = urlInfoToBuildUrlMap.get(reference.ownerUrlInfo);
|
|
67
|
+
buildUrl = generateSourcemapFileUrl(parentBuildUrl);
|
|
68
|
+
reference.generatedSpecifier = buildUrl;
|
|
69
|
+
} else {
|
|
70
|
+
const url = reference.generatedUrl;
|
|
71
|
+
let urlInfo;
|
|
72
|
+
const rawUrlInfo = rawKitchen.graph.getUrlInfo(reference.url);
|
|
73
|
+
if (rawUrlInfo) {
|
|
74
|
+
urlInfo = rawUrlInfo;
|
|
75
|
+
} else {
|
|
76
|
+
const buildUrlInfo = reference.urlInfo;
|
|
77
|
+
buildUrlInfo.type = reference.expectedType || "asset";
|
|
78
|
+
buildUrlInfo.subtype = reference.expectedSubtype;
|
|
79
|
+
urlInfo = buildUrlInfo;
|
|
80
|
+
}
|
|
81
|
+
buildUrl = buildUrlsGenerator.generate(url, {
|
|
82
|
+
urlInfo,
|
|
83
|
+
ownerUrlInfo: reference.ownerUrlInfo,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let buildSpecifier;
|
|
88
|
+
if (base === "./") {
|
|
89
|
+
const parentBuildUrl = reference.ownerUrlInfo.isRoot
|
|
90
|
+
? buildDirectoryUrl
|
|
91
|
+
: urlInfoToBuildUrlMap.get(reference.ownerUrlInfo);
|
|
92
|
+
const urlRelativeToParent = urlToRelativeUrl(buildUrl, parentBuildUrl);
|
|
93
|
+
if (urlRelativeToParent[0] === ".") {
|
|
94
|
+
buildSpecifier = urlRelativeToParent;
|
|
95
|
+
} else {
|
|
96
|
+
// ensure "./" on relative url (otherwise it could be a "bare specifier")
|
|
97
|
+
buildSpecifier = `./${urlRelativeToParent}`;
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
const urlRelativeToBuildDirectory = urlToRelativeUrl(
|
|
101
|
+
buildUrl,
|
|
102
|
+
buildDirectoryUrl,
|
|
103
|
+
);
|
|
104
|
+
buildSpecifier = `${base}${urlRelativeToBuildDirectory}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
urlInfoToBuildUrlMap.set(reference.urlInfo, buildUrl);
|
|
108
|
+
buildUrlToUrlInfoMap.set(buildUrl, reference.urlInfo);
|
|
109
|
+
buildUrlToBuildSpecifierMap.set(buildUrl, buildSpecifier);
|
|
110
|
+
const buildGeneratedSpecifier = applyVersioningOnBuildSpecifier(
|
|
111
|
+
buildSpecifier,
|
|
112
|
+
reference,
|
|
113
|
+
);
|
|
114
|
+
return buildGeneratedSpecifier;
|
|
115
|
+
};
|
|
116
|
+
const internalRedirections = new Map();
|
|
117
|
+
const bundleInfoMap = new Map();
|
|
118
|
+
|
|
119
|
+
const applyBundling = async ({ bundler, urlInfosToBundle }) => {
|
|
120
|
+
const urlInfosBundled = await rawKitchen.pluginController.callAsyncHook(
|
|
121
|
+
{
|
|
122
|
+
plugin: bundler.plugin,
|
|
123
|
+
hookName: "bundle",
|
|
124
|
+
value: bundler.bundleFunction,
|
|
125
|
+
},
|
|
126
|
+
urlInfosToBundle,
|
|
127
|
+
);
|
|
128
|
+
Object.keys(urlInfosBundled).forEach((url) => {
|
|
129
|
+
const urlInfoBundled = urlInfosBundled[url];
|
|
130
|
+
if (urlInfoBundled.sourceUrls) {
|
|
131
|
+
urlInfoBundled.sourceUrls.forEach((sourceUrl) => {
|
|
132
|
+
const sourceRawUrlInfo = rawKitchen.graph.getUrlInfo(sourceUrl);
|
|
133
|
+
if (sourceRawUrlInfo) {
|
|
134
|
+
sourceRawUrlInfo.data.bundled = true;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
bundleInfoMap.set(url, urlInfoBundled);
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const jsenvPluginMoveToBuildDirectory = {
|
|
143
|
+
name: "jsenv:move_to_build_directory",
|
|
144
|
+
appliesDuring: "build",
|
|
145
|
+
// reference resolution is split in 2
|
|
146
|
+
// the redirection to build directory is done in a second phase (redirectReference)
|
|
147
|
+
// to let opportunity to others plugins (js_module_fallback)
|
|
148
|
+
// to mutate reference (inject ?js_module_fallback)
|
|
149
|
+
// before it gets redirected to build directory
|
|
150
|
+
resolveReference: (reference) => {
|
|
151
|
+
const { ownerUrlInfo } = reference;
|
|
152
|
+
if (ownerUrlInfo.remapReference && !reference.isInline) {
|
|
153
|
+
const newSpecifier = ownerUrlInfo.remapReference(reference);
|
|
154
|
+
reference.specifier = newSpecifier;
|
|
155
|
+
}
|
|
156
|
+
const referenceFromPlaceholder = placeholderToReferenceMap.get(
|
|
157
|
+
reference.specifier,
|
|
158
|
+
);
|
|
159
|
+
if (referenceFromPlaceholder) {
|
|
160
|
+
return referenceFromPlaceholder.url;
|
|
161
|
+
}
|
|
162
|
+
if (reference.type === "filesystem") {
|
|
163
|
+
const ownerRawUrl = ensurePathnameTrailingSlash(ownerUrlInfo.url);
|
|
164
|
+
const url = new URL(reference.specifier, ownerRawUrl).href;
|
|
165
|
+
return url;
|
|
166
|
+
}
|
|
167
|
+
if (reference.specifier[0] === "/") {
|
|
168
|
+
const url = new URL(reference.specifier.slice(1), sourceDirectoryUrl)
|
|
169
|
+
.href;
|
|
170
|
+
return url;
|
|
171
|
+
}
|
|
172
|
+
if (reference.injected) {
|
|
173
|
+
// js_module_fallback
|
|
174
|
+
const url = new URL(
|
|
175
|
+
reference.specifier,
|
|
176
|
+
reference.baseUrl || ownerUrlInfo.url,
|
|
177
|
+
).href;
|
|
178
|
+
return url;
|
|
179
|
+
}
|
|
180
|
+
const parentUrl = reference.baseUrl || ownerUrlInfo.url;
|
|
181
|
+
const url = new URL(reference.specifier, parentUrl).href;
|
|
182
|
+
return url;
|
|
183
|
+
},
|
|
184
|
+
transformReferenceSearchParams: () => {
|
|
185
|
+
// those search params are reflected into the build file name
|
|
186
|
+
// moreover it create cleaner output
|
|
187
|
+
// otherwise output is full of ?js_module_fallback search param
|
|
188
|
+
return {
|
|
189
|
+
js_module_fallback: undefined,
|
|
190
|
+
as_json_module: undefined,
|
|
191
|
+
as_css_module: undefined,
|
|
192
|
+
as_text_module: undefined,
|
|
193
|
+
as_js_module: undefined,
|
|
194
|
+
as_js_classic: undefined,
|
|
195
|
+
cjs_as_js_module: undefined,
|
|
196
|
+
js_classic: undefined, // TODO: add comment to explain who is using this
|
|
197
|
+
entry_point: undefined,
|
|
198
|
+
dynamic_import: undefined,
|
|
199
|
+
};
|
|
200
|
+
},
|
|
201
|
+
formatReference: (reference) => {
|
|
202
|
+
const generatedUrl = reference.generatedUrl;
|
|
203
|
+
if (!generatedUrl.startsWith("file:")) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
if (reference.isWeak) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
if (reference.type === "sourcemap_comment") {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
const placeholder = placeholderAPI.generate();
|
|
213
|
+
if (generatedUrl !== reference.url) {
|
|
214
|
+
internalRedirections.set(generatedUrl, reference.url);
|
|
215
|
+
}
|
|
216
|
+
placeholderToReferenceMap.set(placeholder, reference);
|
|
217
|
+
return placeholder;
|
|
218
|
+
},
|
|
219
|
+
fetchUrlContent: async (finalUrlInfo) => {
|
|
220
|
+
let { firstReference } = finalUrlInfo;
|
|
221
|
+
if (
|
|
222
|
+
firstReference.isInline &&
|
|
223
|
+
firstReference.prev &&
|
|
224
|
+
!firstReference.prev.isInline
|
|
225
|
+
) {
|
|
226
|
+
firstReference = firstReference.prev;
|
|
227
|
+
}
|
|
228
|
+
const rawUrl = firstReference.url;
|
|
229
|
+
const rawUrlInfo = rawKitchen.graph.getUrlInfo(rawUrl);
|
|
230
|
+
const bundleInfo = bundleInfoMap.get(rawUrl);
|
|
231
|
+
if (bundleInfo) {
|
|
232
|
+
if (rawUrlInfo && !finalUrlInfo.filenameHint) {
|
|
233
|
+
finalUrlInfo.filenameHint = rawUrlInfo.filenameHint;
|
|
234
|
+
}
|
|
235
|
+
finalUrlInfo.remapReference = bundleInfo.remapReference;
|
|
236
|
+
return {
|
|
237
|
+
// url: bundleInfo.url,
|
|
238
|
+
originalUrl: bundleInfo.originalUrl,
|
|
239
|
+
type: bundleInfo.type,
|
|
240
|
+
content: bundleInfo.content,
|
|
241
|
+
contentType: bundleInfo.contentType,
|
|
242
|
+
sourcemap: bundleInfo.sourcemap,
|
|
243
|
+
data: bundleInfo.data,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
if (rawUrlInfo) {
|
|
247
|
+
if (rawUrlInfo && !finalUrlInfo.filenameHint) {
|
|
248
|
+
finalUrlInfo.filenameHint = rawUrlInfo.filenameHint;
|
|
249
|
+
}
|
|
250
|
+
return rawUrlInfo;
|
|
251
|
+
}
|
|
252
|
+
// reference injected during "shape":
|
|
253
|
+
// - "js_module_fallback" using getWithoutSearchParam to obtain source
|
|
254
|
+
// url info that will be converted to systemjs/UMD
|
|
255
|
+
// - "js_module_fallback" injecting "s.js"
|
|
256
|
+
if (firstReference.injected) {
|
|
257
|
+
const reference = firstReference.original || firstReference;
|
|
258
|
+
const rawReference = rawKitchen.graph.rootUrlInfo.dependencies.inject({
|
|
259
|
+
type: reference.type,
|
|
260
|
+
expectedType: reference.expectedType,
|
|
261
|
+
specifier: reference.specifier,
|
|
262
|
+
specifierLine: reference.specifierLine,
|
|
263
|
+
specifierColumn: reference.specifierColumn,
|
|
264
|
+
specifierStart: reference.specifierStart,
|
|
265
|
+
specifierEnd: reference.specifierEnd,
|
|
266
|
+
isInline: reference.isInline,
|
|
267
|
+
filenameHint: reference.filenameHint,
|
|
268
|
+
content: reference.content,
|
|
269
|
+
contentType: reference.contentType,
|
|
270
|
+
});
|
|
271
|
+
if (!finalUrlInfo.filenameHint) {
|
|
272
|
+
finalUrlInfo.filenameHint = reference.filenameHint;
|
|
273
|
+
}
|
|
274
|
+
const rawUrlInfo = rawReference.urlInfo;
|
|
275
|
+
await rawUrlInfo.cook();
|
|
276
|
+
return {
|
|
277
|
+
type: rawUrlInfo.type,
|
|
278
|
+
content: rawUrlInfo.content,
|
|
279
|
+
contentType: rawUrlInfo.contentType,
|
|
280
|
+
originalContent: rawUrlInfo.originalContent,
|
|
281
|
+
originalUrl: rawUrlInfo.originalUrl,
|
|
282
|
+
sourcemap: rawUrlInfo.sourcemap,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
if (firstReference.isInline) {
|
|
286
|
+
if (
|
|
287
|
+
firstReference.ownerUrlInfo.url ===
|
|
288
|
+
firstReference.ownerUrlInfo.originalUrl
|
|
289
|
+
) {
|
|
290
|
+
const rawUrlInfo = GRAPH_VISITOR.find(
|
|
291
|
+
rawKitchen.graph,
|
|
292
|
+
(rawUrlInfoCandidate) => {
|
|
293
|
+
const { inlineUrlSite } = rawUrlInfoCandidate;
|
|
294
|
+
if (!inlineUrlSite) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
if (
|
|
298
|
+
inlineUrlSite.url === firstReference.ownerUrlInfo.url &&
|
|
299
|
+
inlineUrlSite.line === firstReference.specifierLine &&
|
|
300
|
+
inlineUrlSite.column === firstReference.specifierColumn
|
|
301
|
+
) {
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
if (rawUrlInfoCandidate.content === firstReference.content) {
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
if (
|
|
308
|
+
rawUrlInfoCandidate.originalContent === firstReference.content
|
|
309
|
+
) {
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
return false;
|
|
313
|
+
},
|
|
314
|
+
);
|
|
315
|
+
if (rawUrlInfo) {
|
|
316
|
+
if (!finalUrlInfo.filenameHint) {
|
|
317
|
+
finalUrlInfo.filenameHint = rawUrlInfo.filenameHint;
|
|
318
|
+
}
|
|
319
|
+
return rawUrlInfo;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (!finalUrlInfo.filenameHint) {
|
|
323
|
+
finalUrlInfo.filenameHint = firstReference.filenameHint;
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
originalContent: finalUrlInfo.originalContent,
|
|
327
|
+
content: firstReference.content,
|
|
328
|
+
contentType: firstReference.contentType,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
throw new Error(createDetailedMessage(`Cannot fetch ${rawUrl}`));
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const buildSpecifierToBuildSpecifierVersionedMap = new Map();
|
|
336
|
+
|
|
337
|
+
const versionMap = new Map();
|
|
338
|
+
|
|
339
|
+
const workerReferenceSet = new Set();
|
|
340
|
+
const referenceVersioningInfoMap = new Map();
|
|
341
|
+
const _getReferenceVersioningInfo = (reference) => {
|
|
342
|
+
if (!shouldApplyVersioningOnReference(reference)) {
|
|
343
|
+
return {
|
|
344
|
+
type: "not_versioned",
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
const ownerUrlInfo = reference.ownerUrlInfo;
|
|
348
|
+
if (ownerUrlInfo.jsQuote) {
|
|
349
|
+
// here we use placeholder as specifier, so something like
|
|
350
|
+
// "/other/file.png" becomes "!~{0001}~" and finally "__v__("/other/file.png")"
|
|
351
|
+
// this is to support cases like CSS inlined in JS
|
|
352
|
+
// CSS minifier must see valid CSS specifiers like background-image: url("!~{0001}~");
|
|
353
|
+
// that is finally replaced by invalid css background-image: url("__v__("/other/file.png")")
|
|
354
|
+
return {
|
|
355
|
+
type: "global",
|
|
356
|
+
render: (buildSpecifier) => {
|
|
357
|
+
return placeholderAPI.markAsCode(
|
|
358
|
+
`${ownerUrlInfo.jsQuote}+__v__(${JSON.stringify(buildSpecifier)})+${
|
|
359
|
+
ownerUrlInfo.jsQuote
|
|
360
|
+
}`,
|
|
361
|
+
);
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
if (reference.type === "js_url") {
|
|
366
|
+
return {
|
|
367
|
+
type: "global",
|
|
368
|
+
render: (buildSpecifier) => {
|
|
369
|
+
return placeholderAPI.markAsCode(
|
|
370
|
+
`__v__(${JSON.stringify(buildSpecifier)})`,
|
|
371
|
+
);
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
if (reference.type === "js_import") {
|
|
376
|
+
if (reference.subtype === "import_dynamic") {
|
|
377
|
+
return {
|
|
378
|
+
type: "global",
|
|
379
|
+
render: (buildSpecifier) => {
|
|
380
|
+
return placeholderAPI.markAsCode(
|
|
381
|
+
`__v__(${JSON.stringify(buildSpecifier)})`,
|
|
382
|
+
);
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
if (reference.subtype === "import_meta_resolve") {
|
|
387
|
+
return {
|
|
388
|
+
type: "global",
|
|
389
|
+
render: (buildSpecifier) => {
|
|
390
|
+
return placeholderAPI.markAsCode(
|
|
391
|
+
`__v__(${JSON.stringify(buildSpecifier)})`,
|
|
392
|
+
);
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
if (canUseImportmap && !isInsideWorker(reference)) {
|
|
397
|
+
return {
|
|
398
|
+
type: "importmap",
|
|
399
|
+
render: (buildSpecifier) => {
|
|
400
|
+
return buildSpecifier;
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
type: "inline",
|
|
407
|
+
render: (buildSpecifier) => {
|
|
408
|
+
const buildSpecifierVersioned =
|
|
409
|
+
buildSpecifierToBuildSpecifierVersionedMap.get(buildSpecifier);
|
|
410
|
+
return buildSpecifierVersioned;
|
|
411
|
+
},
|
|
412
|
+
};
|
|
413
|
+
};
|
|
414
|
+
const getReferenceVersioningInfo = (reference) => {
|
|
415
|
+
const infoFromCache = referenceVersioningInfoMap.get(reference);
|
|
416
|
+
if (infoFromCache) {
|
|
417
|
+
return infoFromCache;
|
|
418
|
+
}
|
|
419
|
+
const info = _getReferenceVersioningInfo(reference);
|
|
420
|
+
referenceVersioningInfoMap.set(reference, info);
|
|
421
|
+
return info;
|
|
422
|
+
};
|
|
423
|
+
const isInsideWorker = (reference) => {
|
|
424
|
+
if (workerReferenceSet.has(reference)) {
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
const referenceOwnerUrllInfo = reference.ownerUrlInfo;
|
|
428
|
+
let is = false;
|
|
429
|
+
if (isWebWorkerUrlInfo(referenceOwnerUrllInfo)) {
|
|
430
|
+
is = true;
|
|
431
|
+
} else {
|
|
432
|
+
GRAPH_VISITOR.findDependent(
|
|
433
|
+
referenceOwnerUrllInfo,
|
|
434
|
+
(dependentUrlInfo) => {
|
|
435
|
+
if (isWebWorkerUrlInfo(dependentUrlInfo)) {
|
|
436
|
+
is = true;
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
return false;
|
|
440
|
+
},
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
if (is) {
|
|
444
|
+
workerReferenceSet.add(reference);
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
return false;
|
|
448
|
+
};
|
|
449
|
+
const canUseVersionedUrl = (urlInfo) => {
|
|
450
|
+
if (urlInfo.isRoot) {
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
if (urlInfo.isEntryPoint) {
|
|
454
|
+
// if (urlInfo.subtype === "worker") {
|
|
455
|
+
// return true;
|
|
456
|
+
// }
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
return urlInfo.type !== "webmanifest";
|
|
460
|
+
};
|
|
461
|
+
const shouldApplyVersioningOnReference = (reference) => {
|
|
462
|
+
if (reference.isInline) {
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
if (reference.next && reference.next.isInline) {
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
if (reference.type === "sourcemap_comment") {
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
// specifier comes from "normalize" hook done a bit earlier in this file
|
|
472
|
+
// we want to get back their build url to access their infos
|
|
473
|
+
const referencedUrlInfo = reference.urlInfo;
|
|
474
|
+
if (!canUseVersionedUrl(referencedUrlInfo)) {
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
return true;
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
const prepareVersioning = () => {
|
|
481
|
+
const contentOnlyVersionMap = new Map();
|
|
482
|
+
const urlInfoToContainedPlaceholderSetMap = new Map();
|
|
483
|
+
generate_content_only_versions: {
|
|
484
|
+
GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
|
|
485
|
+
finalKitchen.graph.rootUrlInfo,
|
|
486
|
+
(urlInfo) => {
|
|
487
|
+
// ignore:
|
|
488
|
+
// - inline files and data files:
|
|
489
|
+
// they are already taken into account in the file where they appear
|
|
490
|
+
// - ignored files:
|
|
491
|
+
// we don't know their content
|
|
492
|
+
// - unused files without reference
|
|
493
|
+
// File updated such as style.css -> style.css.js or file.js->file.nomodule.js
|
|
494
|
+
// Are used at some point just to be discarded later because they need to be converted
|
|
495
|
+
// There is no need to version them and we could not because the file have been ignored
|
|
496
|
+
// so their content is unknown
|
|
497
|
+
if (urlInfo.type === "sourcemap") {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (urlInfo.isInline) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (urlInfo.url.startsWith("data:")) {
|
|
504
|
+
// urlInfo became inline and is not referenced by something else
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (urlInfo.url.startsWith("ignore:")) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
let content = urlInfo.content;
|
|
511
|
+
if (urlInfo.type === "html") {
|
|
512
|
+
content = stringifyHtmlAst(
|
|
513
|
+
parseHtmlString(urlInfo.content, {
|
|
514
|
+
storeOriginalPositions: false,
|
|
515
|
+
}),
|
|
516
|
+
{
|
|
517
|
+
cleanupJsenvAttributes: true,
|
|
518
|
+
cleanupPositionAttributes: true,
|
|
519
|
+
},
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
const containedPlaceholderSet = new Set();
|
|
523
|
+
if (mayUsePlaceholder(urlInfo)) {
|
|
524
|
+
const contentWithPredictibleVersionPlaceholders =
|
|
525
|
+
placeholderAPI.replaceWithDefault(content, (placeholder) => {
|
|
526
|
+
containedPlaceholderSet.add(placeholder);
|
|
527
|
+
});
|
|
528
|
+
content = contentWithPredictibleVersionPlaceholders;
|
|
529
|
+
}
|
|
530
|
+
urlInfoToContainedPlaceholderSetMap.set(
|
|
531
|
+
urlInfo,
|
|
532
|
+
containedPlaceholderSet,
|
|
533
|
+
);
|
|
534
|
+
const contentVersion = generateVersion([content], versionLength);
|
|
535
|
+
contentOnlyVersionMap.set(urlInfo, contentVersion);
|
|
536
|
+
},
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
generate_versions: {
|
|
541
|
+
const getSetOfUrlInfoInfluencingVersion = (urlInfo) => {
|
|
542
|
+
const placeholderInfluencingVersionSet = new Set();
|
|
543
|
+
const visitContainedPlaceholders = (urlInfo) => {
|
|
544
|
+
const referencedContentVersion = contentOnlyVersionMap.get(urlInfo);
|
|
545
|
+
if (!referencedContentVersion) {
|
|
546
|
+
// ignored while traversing graph (not used anymore, inline, ...)
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
const containedPlaceholderSet =
|
|
550
|
+
urlInfoToContainedPlaceholderSetMap.get(urlInfo);
|
|
551
|
+
for (const containedPlaceholder of containedPlaceholderSet) {
|
|
552
|
+
if (placeholderInfluencingVersionSet.has(containedPlaceholder)) {
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
const reference =
|
|
556
|
+
placeholderToReferenceMap.get(containedPlaceholder);
|
|
557
|
+
const referenceVersioningInfo =
|
|
558
|
+
getReferenceVersioningInfo(reference);
|
|
559
|
+
if (
|
|
560
|
+
referenceVersioningInfo.type === "global" ||
|
|
561
|
+
referenceVersioningInfo.type === "importmap"
|
|
562
|
+
) {
|
|
563
|
+
// when versioning is dynamic no need to take into account
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
placeholderInfluencingVersionSet.add(containedPlaceholder);
|
|
567
|
+
const referencedUrlInfo = reference.urlInfo;
|
|
568
|
+
visitContainedPlaceholders(referencedUrlInfo);
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
visitContainedPlaceholders(urlInfo);
|
|
572
|
+
|
|
573
|
+
const setOfUrlInfluencingVersion = new Set();
|
|
574
|
+
for (const placeholderInfluencingVersion of placeholderInfluencingVersionSet) {
|
|
575
|
+
const reference = placeholderToReferenceMap.get(
|
|
576
|
+
placeholderInfluencingVersion,
|
|
577
|
+
);
|
|
578
|
+
const referencedUrlInfo = reference.urlInfo;
|
|
579
|
+
setOfUrlInfluencingVersion.add(referencedUrlInfo);
|
|
580
|
+
}
|
|
581
|
+
return setOfUrlInfluencingVersion;
|
|
582
|
+
};
|
|
583
|
+
for (const [urlInfo, contentOnlyVersion] of contentOnlyVersionMap) {
|
|
584
|
+
const setOfUrlInfoInfluencingVersion =
|
|
585
|
+
getSetOfUrlInfoInfluencingVersion(urlInfo);
|
|
586
|
+
const versionPartSet = new Set();
|
|
587
|
+
versionPartSet.add(contentOnlyVersion);
|
|
588
|
+
for (const urlInfoInfluencingVersion of setOfUrlInfoInfluencingVersion) {
|
|
589
|
+
const otherUrlInfoContentVersion = contentOnlyVersionMap.get(
|
|
590
|
+
urlInfoInfluencingVersion,
|
|
591
|
+
);
|
|
592
|
+
if (!otherUrlInfoContentVersion) {
|
|
593
|
+
throw new Error(
|
|
594
|
+
`cannot find content version for ${urlInfoInfluencingVersion.url} (used by ${urlInfo.url})`,
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
versionPartSet.add(otherUrlInfoContentVersion);
|
|
598
|
+
}
|
|
599
|
+
const version = generateVersion(versionPartSet, versionLength);
|
|
600
|
+
versionMap.set(urlInfo, version);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
const applyVersioningOnBuildSpecifier = (buildSpecifier, reference) => {
|
|
606
|
+
if (!versioning) {
|
|
607
|
+
return buildSpecifier;
|
|
608
|
+
}
|
|
609
|
+
const referenceVersioningInfo = getReferenceVersioningInfo(reference);
|
|
610
|
+
if (referenceVersioningInfo.type === "not_versioned") {
|
|
611
|
+
return buildSpecifier;
|
|
612
|
+
}
|
|
613
|
+
const version = versionMap.get(reference.urlInfo);
|
|
614
|
+
const buildSpecifierVersioned = injectVersionIntoBuildSpecifier({
|
|
615
|
+
buildSpecifier,
|
|
616
|
+
versioningMethod,
|
|
617
|
+
version,
|
|
618
|
+
});
|
|
619
|
+
buildSpecifierToBuildSpecifierVersionedMap.set(
|
|
620
|
+
buildSpecifier,
|
|
621
|
+
buildSpecifierVersioned,
|
|
622
|
+
);
|
|
623
|
+
return referenceVersioningInfo.render(buildSpecifier);
|
|
624
|
+
};
|
|
625
|
+
const finishVersioning = async () => {
|
|
626
|
+
inject_global_registry_and_importmap: {
|
|
627
|
+
const actions = [];
|
|
628
|
+
const visitors = [];
|
|
629
|
+
const globalMappings = {};
|
|
630
|
+
const importmapMappings = {};
|
|
631
|
+
for (const [reference, versioningInfo] of referenceVersioningInfoMap) {
|
|
632
|
+
if (versioningInfo.type === "global") {
|
|
633
|
+
const urlInfo = reference.urlInfo;
|
|
634
|
+
const buildUrl = urlInfoToBuildUrlMap.get(urlInfo);
|
|
635
|
+
const buildSpecifier = buildUrlToBuildSpecifierMap.get(buildUrl);
|
|
636
|
+
const buildSpecifierVersioned =
|
|
637
|
+
buildSpecifierToBuildSpecifierVersionedMap.get(buildSpecifier);
|
|
638
|
+
globalMappings[buildSpecifier] = buildSpecifierVersioned;
|
|
639
|
+
}
|
|
640
|
+
if (versioningInfo.type === "importmap") {
|
|
641
|
+
const urlInfo = reference.urlInfo;
|
|
642
|
+
const buildUrl = urlInfoToBuildUrlMap.get(urlInfo);
|
|
643
|
+
const buildSpecifier = buildUrlToBuildSpecifierMap.get(buildUrl);
|
|
644
|
+
const buildSpecifierVersioned =
|
|
645
|
+
buildSpecifierToBuildSpecifierVersionedMap.get(buildSpecifier);
|
|
646
|
+
importmapMappings[buildSpecifier] = buildSpecifierVersioned;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (Object.keys(globalMappings).length > 0) {
|
|
650
|
+
visitors.push((urlInfo) => {
|
|
651
|
+
if (urlInfo.isEntryPoint) {
|
|
652
|
+
actions.push(async () => {
|
|
653
|
+
await injectVersionMappingsAsGlobal(urlInfo, globalMappings);
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
if (Object.keys(importmapMappings).length > 0) {
|
|
659
|
+
visitors.push((urlInfo) => {
|
|
660
|
+
if (urlInfo.type === "html" && urlInfo.isEntryPoint) {
|
|
661
|
+
actions.push(async () => {
|
|
662
|
+
await injectVersionMappingsAsImportmap(
|
|
663
|
+
urlInfo,
|
|
664
|
+
importmapMappings,
|
|
665
|
+
);
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
if (visitors.length) {
|
|
671
|
+
GRAPH_VISITOR.forEach(finalKitchen.graph, (urlInfo) => {
|
|
672
|
+
if (urlInfo.isRoot) return;
|
|
673
|
+
visitors.forEach((visitor) => visitor(urlInfo));
|
|
674
|
+
});
|
|
675
|
+
if (actions.length) {
|
|
676
|
+
await Promise.all(actions.map((action) => action()));
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
const getBuildGeneratedSpecifier = (urlInfo) => {
|
|
683
|
+
const buildUrl = urlInfoToBuildUrlMap.get(urlInfo);
|
|
684
|
+
const buildSpecifier = buildUrlToBuildSpecifierMap.get(buildUrl);
|
|
685
|
+
const buildGeneratedSpecifier =
|
|
686
|
+
buildSpecifierToBuildSpecifierVersionedMap.get(buildSpecifier) ||
|
|
687
|
+
buildSpecifier;
|
|
688
|
+
return buildGeneratedSpecifier;
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
return {
|
|
692
|
+
jsenvPluginMoveToBuildDirectory,
|
|
693
|
+
applyBundling,
|
|
694
|
+
|
|
695
|
+
remapPlaceholder: (specifier) => {
|
|
696
|
+
const reference = placeholderToReferenceMap.get(specifier);
|
|
697
|
+
if (reference) {
|
|
698
|
+
return reference.specifier;
|
|
699
|
+
}
|
|
700
|
+
return specifier;
|
|
701
|
+
},
|
|
702
|
+
|
|
703
|
+
replacePlaceholders: async () => {
|
|
704
|
+
if (versioning) {
|
|
705
|
+
prepareVersioning();
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const urlInfoSet = new Set();
|
|
709
|
+
GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
|
|
710
|
+
finalKitchen.graph.rootUrlInfo,
|
|
711
|
+
(urlInfo) => {
|
|
712
|
+
urlInfoSet.add(urlInfo);
|
|
713
|
+
if (urlInfo.isEntryPoint) {
|
|
714
|
+
generateReplacement(urlInfo.firstReference);
|
|
715
|
+
}
|
|
716
|
+
if (urlInfo.isInline) {
|
|
717
|
+
generateReplacement(urlInfo.firstReference);
|
|
718
|
+
}
|
|
719
|
+
if (urlInfo.type === "sourcemap") {
|
|
720
|
+
generateReplacement(urlInfo.firstReference);
|
|
721
|
+
}
|
|
722
|
+
if (urlInfo.firstReference.type === "side_effect_file") {
|
|
723
|
+
generateReplacement(urlInfo.firstReference);
|
|
724
|
+
}
|
|
725
|
+
// side effect stuff must be generated too
|
|
726
|
+
if (mayUsePlaceholder(urlInfo)) {
|
|
727
|
+
const contentBeforeReplace = urlInfo.content;
|
|
728
|
+
const { content, sourcemap } = placeholderAPI.replaceAll(
|
|
729
|
+
contentBeforeReplace,
|
|
730
|
+
(placeholder) => {
|
|
731
|
+
const reference = placeholderToReferenceMap.get(placeholder);
|
|
732
|
+
return generateReplacement(reference);
|
|
733
|
+
},
|
|
734
|
+
);
|
|
735
|
+
urlInfo.mutateContent({ content, sourcemap });
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
);
|
|
739
|
+
|
|
740
|
+
workerReferenceSet.clear();
|
|
741
|
+
if (versioning) {
|
|
742
|
+
await finishVersioning();
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
for (const urlInfo of urlInfoSet) {
|
|
746
|
+
urlInfo.kitchen.urlInfoTransformer.applySourcemapOnContent(
|
|
747
|
+
urlInfo,
|
|
748
|
+
(source) => {
|
|
749
|
+
const buildUrl = urlInfoToBuildUrlMap.get(urlInfo);
|
|
750
|
+
if (buildUrl) {
|
|
751
|
+
return urlToRelativeUrl(source, buildUrl);
|
|
752
|
+
}
|
|
753
|
+
return source;
|
|
754
|
+
},
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
urlInfoSet.clear();
|
|
758
|
+
},
|
|
759
|
+
|
|
760
|
+
prepareResyncResourceHints: () => {
|
|
761
|
+
const actions = [];
|
|
762
|
+
GRAPH_VISITOR.forEach(finalKitchen.graph, (urlInfo) => {
|
|
763
|
+
if (urlInfo.type !== "html") {
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
const htmlAst = parseHtmlString(urlInfo.content, {
|
|
767
|
+
storeOriginalPositions: false,
|
|
768
|
+
});
|
|
769
|
+
const mutations = [];
|
|
770
|
+
const hintsToInject = [];
|
|
771
|
+
visitHtmlNodes(htmlAst, {
|
|
772
|
+
link: (node) => {
|
|
773
|
+
const href = getHtmlNodeAttribute(node, "href");
|
|
774
|
+
if (href === undefined || href.startsWith("data:")) {
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
const rel = getHtmlNodeAttribute(node, "rel");
|
|
778
|
+
const isResourceHint = [
|
|
779
|
+
"preconnect",
|
|
780
|
+
"dns-prefetch",
|
|
781
|
+
"prefetch",
|
|
782
|
+
"preload",
|
|
783
|
+
"modulepreload",
|
|
784
|
+
].includes(rel);
|
|
785
|
+
if (!isResourceHint) {
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
const rawUrl = href;
|
|
789
|
+
const finalUrl = internalRedirections.get(rawUrl) || rawUrl;
|
|
790
|
+
const urlInfo = finalKitchen.graph.getUrlInfo(finalUrl);
|
|
791
|
+
if (!urlInfo) {
|
|
792
|
+
logger.warn(
|
|
793
|
+
`remove resource hint because cannot find "${href}" in the graph`,
|
|
794
|
+
);
|
|
795
|
+
mutations.push(() => {
|
|
796
|
+
removeHtmlNode(node);
|
|
797
|
+
});
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
if (!urlInfo.isUsed()) {
|
|
801
|
+
const rawUrlInfo = rawKitchen.graph.getUrlInfo(rawUrl);
|
|
802
|
+
if (rawUrlInfo && rawUrlInfo.data.bundled) {
|
|
803
|
+
logger.warn(
|
|
804
|
+
`remove resource hint on "${href}" because it was bundled`,
|
|
805
|
+
);
|
|
806
|
+
mutations.push(() => {
|
|
807
|
+
removeHtmlNode(node);
|
|
808
|
+
});
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
logger.warn(
|
|
812
|
+
`remove resource hint on "${href}" because it is not used anymore`,
|
|
813
|
+
);
|
|
814
|
+
mutations.push(() => {
|
|
815
|
+
removeHtmlNode(node);
|
|
816
|
+
});
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
const buildGeneratedSpecifier = getBuildGeneratedSpecifier(urlInfo);
|
|
820
|
+
mutations.push(() => {
|
|
821
|
+
setHtmlNodeAttributes(node, {
|
|
822
|
+
href: buildGeneratedSpecifier,
|
|
823
|
+
...(urlInfo.type === "js_classic"
|
|
824
|
+
? { crossorigin: undefined }
|
|
825
|
+
: {}),
|
|
826
|
+
});
|
|
827
|
+
});
|
|
828
|
+
for (const referenceToOther of urlInfo.referenceToOthersSet) {
|
|
829
|
+
if (referenceToOther.isWeak) {
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
const referencedUrlInfo = referenceToOther.urlInfo;
|
|
833
|
+
if (referencedUrlInfo.data.generatedToShareCode) {
|
|
834
|
+
hintsToInject.push({ urlInfo, node });
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
},
|
|
838
|
+
});
|
|
839
|
+
hintsToInject.forEach(({ urlInfo, node }) => {
|
|
840
|
+
const buildGeneratedSpecifier = getBuildGeneratedSpecifier(urlInfo);
|
|
841
|
+
const found = findHtmlNode(htmlAst, (htmlNode) => {
|
|
842
|
+
return (
|
|
843
|
+
htmlNode.nodeName === "link" &&
|
|
844
|
+
getHtmlNodeAttribute(htmlNode, "href") === buildGeneratedSpecifier
|
|
845
|
+
);
|
|
846
|
+
});
|
|
847
|
+
if (!found) {
|
|
848
|
+
mutations.push(() => {
|
|
849
|
+
const nodeToInsert = createHtmlNode({
|
|
850
|
+
tagName: "link",
|
|
851
|
+
href: buildGeneratedSpecifier,
|
|
852
|
+
rel: getHtmlNodeAttribute(node, "rel"),
|
|
853
|
+
as: getHtmlNodeAttribute(node, "as"),
|
|
854
|
+
type: getHtmlNodeAttribute(node, "type"),
|
|
855
|
+
crossorigin: getHtmlNodeAttribute(node, "crossorigin"),
|
|
856
|
+
});
|
|
857
|
+
insertHtmlNodeAfter(nodeToInsert, node);
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
if (mutations.length > 0) {
|
|
862
|
+
actions.push(() => {
|
|
863
|
+
mutations.forEach((mutation) => mutation());
|
|
864
|
+
urlInfo.mutateContent({
|
|
865
|
+
content: stringifyHtmlAst(htmlAst),
|
|
866
|
+
});
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
if (actions.length === 0) {
|
|
871
|
+
return null;
|
|
872
|
+
}
|
|
873
|
+
return () => {
|
|
874
|
+
actions.map((resourceHintAction) => resourceHintAction());
|
|
875
|
+
};
|
|
876
|
+
},
|
|
877
|
+
|
|
878
|
+
prepareServiceWorkerUrlInjection: () => {
|
|
879
|
+
const serviceWorkerEntryUrlInfos = GRAPH_VISITOR.filter(
|
|
880
|
+
finalKitchen.graph,
|
|
881
|
+
(finalUrlInfo) => {
|
|
882
|
+
return (
|
|
883
|
+
finalUrlInfo.subtype === "service_worker" &&
|
|
884
|
+
finalUrlInfo.isEntryPoint &&
|
|
885
|
+
finalUrlInfo.isUsed()
|
|
886
|
+
);
|
|
887
|
+
},
|
|
888
|
+
);
|
|
889
|
+
if (serviceWorkerEntryUrlInfos.length === 0) {
|
|
890
|
+
return null;
|
|
891
|
+
}
|
|
892
|
+
return async () => {
|
|
893
|
+
const allResourcesFromJsenvBuild = {};
|
|
894
|
+
GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
|
|
895
|
+
finalKitchen.graph.rootUrlInfo,
|
|
896
|
+
(urlInfo) => {
|
|
897
|
+
if (!urlInfo.url.startsWith("file:")) {
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
if (urlInfo.isInline) {
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
const buildUrl = urlInfoToBuildUrlMap.get(urlInfo);
|
|
905
|
+
const buildSpecifier = buildUrlToBuildSpecifierMap.get(buildUrl);
|
|
906
|
+
if (canUseVersionedUrl(urlInfo)) {
|
|
907
|
+
const buildSpecifierVersioned = versioning
|
|
908
|
+
? buildSpecifierToBuildSpecifierVersionedMap.get(buildSpecifier)
|
|
909
|
+
: null;
|
|
910
|
+
allResourcesFromJsenvBuild[buildSpecifier] = {
|
|
911
|
+
version: versionMap.get(urlInfo),
|
|
912
|
+
versionedUrl: buildSpecifierVersioned,
|
|
913
|
+
};
|
|
914
|
+
} else {
|
|
915
|
+
// when url is not versioned we compute a "version" for that url anyway
|
|
916
|
+
// so that service worker source still changes and navigator
|
|
917
|
+
// detect there is a change
|
|
918
|
+
allResourcesFromJsenvBuild[buildSpecifier] = {
|
|
919
|
+
version: versionMap.get(urlInfo),
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
},
|
|
923
|
+
);
|
|
924
|
+
for (const serviceWorkerEntryUrlInfo of serviceWorkerEntryUrlInfos) {
|
|
925
|
+
const resourcesFromJsenvBuild = {
|
|
926
|
+
...allResourcesFromJsenvBuild,
|
|
927
|
+
};
|
|
928
|
+
const serviceWorkerBuildUrl = urlInfoToBuildUrlMap.get(
|
|
929
|
+
serviceWorkerEntryUrlInfo,
|
|
930
|
+
);
|
|
931
|
+
const serviceWorkerBuildSpecifier = buildUrlToBuildSpecifierMap.get(
|
|
932
|
+
serviceWorkerBuildUrl,
|
|
933
|
+
);
|
|
934
|
+
delete resourcesFromJsenvBuild[serviceWorkerBuildSpecifier];
|
|
935
|
+
await prependContent(serviceWorkerEntryUrlInfo, {
|
|
936
|
+
type: "js_classic",
|
|
937
|
+
content: `self.resourcesFromJsenvBuild = ${JSON.stringify(
|
|
938
|
+
resourcesFromJsenvBuild,
|
|
939
|
+
null,
|
|
940
|
+
" ",
|
|
941
|
+
)};\n`,
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
},
|
|
946
|
+
|
|
947
|
+
getBuildInfo: () => {
|
|
948
|
+
const buildManifest = {};
|
|
949
|
+
const buildContents = {};
|
|
950
|
+
const buildInlineRelativeUrlSet = new Set();
|
|
951
|
+
GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
|
|
952
|
+
finalKitchen.graph.rootUrlInfo,
|
|
953
|
+
(urlInfo) => {
|
|
954
|
+
if (!urlInfo.url.startsWith("file:")) {
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
const buildUrl = urlInfoToBuildUrlMap.get(urlInfo);
|
|
958
|
+
const buildSpecifier = buildUrlToBuildSpecifierMap.get(buildUrl);
|
|
959
|
+
const buildSpecifierVersioned = versioning
|
|
960
|
+
? buildSpecifierToBuildSpecifierVersionedMap.get(buildSpecifier)
|
|
961
|
+
: null;
|
|
962
|
+
const buildRelativeUrl = urlToRelativeUrl(
|
|
963
|
+
buildUrl,
|
|
964
|
+
buildDirectoryUrl,
|
|
965
|
+
);
|
|
966
|
+
let contentKey;
|
|
967
|
+
// if to guard for html where versioned build specifier is not generated
|
|
968
|
+
if (buildSpecifierVersioned) {
|
|
969
|
+
const buildUrlVersioned = asBuildUrlVersioned({
|
|
970
|
+
buildSpecifierVersioned,
|
|
971
|
+
buildDirectoryUrl,
|
|
972
|
+
});
|
|
973
|
+
const buildRelativeUrlVersioned = urlToRelativeUrl(
|
|
974
|
+
buildUrlVersioned,
|
|
975
|
+
buildDirectoryUrl,
|
|
976
|
+
);
|
|
977
|
+
buildManifest[buildRelativeUrl] = buildRelativeUrlVersioned;
|
|
978
|
+
contentKey = buildRelativeUrlVersioned;
|
|
979
|
+
} else {
|
|
980
|
+
contentKey = buildRelativeUrl;
|
|
981
|
+
}
|
|
982
|
+
if (urlInfo.type !== "directory") {
|
|
983
|
+
buildContents[contentKey] = urlInfo.content;
|
|
984
|
+
}
|
|
985
|
+
if (urlInfo.isInline) {
|
|
986
|
+
buildInlineRelativeUrlSet.add(buildRelativeUrl);
|
|
987
|
+
}
|
|
988
|
+
},
|
|
989
|
+
);
|
|
990
|
+
const buildFileContents = {};
|
|
991
|
+
const buildInlineContents = {};
|
|
992
|
+
Object.keys(buildContents)
|
|
993
|
+
.sort((a, b) => comparePathnames(a, b))
|
|
994
|
+
.forEach((buildRelativeUrl) => {
|
|
995
|
+
if (buildInlineRelativeUrlSet.has(buildRelativeUrl)) {
|
|
996
|
+
buildInlineContents[buildRelativeUrl] =
|
|
997
|
+
buildContents[buildRelativeUrl];
|
|
998
|
+
} else {
|
|
999
|
+
buildFileContents[buildRelativeUrl] =
|
|
1000
|
+
buildContents[buildRelativeUrl];
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
return { buildFileContents, buildInlineContents, buildManifest };
|
|
1005
|
+
},
|
|
1006
|
+
};
|
|
1007
|
+
};
|
|
1008
|
+
|
|
1009
|
+
// see https://github.com/rollup/rollup/blob/ce453507ab8457dd1ea3909d8dd7b117b2d14fab/src/utils/hashPlaceholders.ts#L1
|
|
1010
|
+
// see also "New hashing algorithm that "fixes (nearly) everything"
|
|
1011
|
+
// at https://github.com/rollup/rollup/pull/4543
|
|
1012
|
+
const placeholderLeft = "!~{";
|
|
1013
|
+
const placeholderRight = "}~";
|
|
1014
|
+
const placeholderOverhead = placeholderLeft.length + placeholderRight.length;
|
|
1015
|
+
|
|
1016
|
+
const createPlaceholderAPI = ({ length }) => {
|
|
1017
|
+
const chars =
|
|
1018
|
+
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$";
|
|
1019
|
+
const toBase64 = (value) => {
|
|
1020
|
+
let outString = "";
|
|
1021
|
+
do {
|
|
1022
|
+
const currentDigit = value % 64;
|
|
1023
|
+
value = (value / 64) | 0;
|
|
1024
|
+
outString = chars[currentDigit] + outString;
|
|
1025
|
+
} while (value !== 0);
|
|
1026
|
+
return outString;
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
let nextIndex = 0;
|
|
1030
|
+
const generate = () => {
|
|
1031
|
+
nextIndex++;
|
|
1032
|
+
const id = toBase64(nextIndex);
|
|
1033
|
+
let placeholder = placeholderLeft;
|
|
1034
|
+
placeholder += id.padStart(length - placeholderOverhead, "0");
|
|
1035
|
+
placeholder += placeholderRight;
|
|
1036
|
+
return placeholder;
|
|
1037
|
+
};
|
|
1038
|
+
|
|
1039
|
+
const replaceFirst = (code, value) => {
|
|
1040
|
+
let replaced = false;
|
|
1041
|
+
return code.replace(PLACEHOLDER_REGEX, (match) => {
|
|
1042
|
+
if (replaced) return match;
|
|
1043
|
+
replaced = true;
|
|
1044
|
+
return value;
|
|
1045
|
+
});
|
|
1046
|
+
};
|
|
1047
|
+
|
|
1048
|
+
const extractFirst = (string) => {
|
|
1049
|
+
const match = string.match(PLACEHOLDER_REGEX);
|
|
1050
|
+
return match ? match[0] : null;
|
|
1051
|
+
};
|
|
1052
|
+
|
|
1053
|
+
const defaultPlaceholder = `${placeholderLeft}${"0".repeat(
|
|
1054
|
+
length - placeholderOverhead,
|
|
1055
|
+
)}${placeholderRight}`;
|
|
1056
|
+
const replaceWithDefault = (code, onPlaceholder) => {
|
|
1057
|
+
const transformedCode = code.replace(PLACEHOLDER_REGEX, (placeholder) => {
|
|
1058
|
+
onPlaceholder(placeholder);
|
|
1059
|
+
return defaultPlaceholder;
|
|
1060
|
+
});
|
|
1061
|
+
return transformedCode;
|
|
1062
|
+
};
|
|
1063
|
+
|
|
1064
|
+
const PLACEHOLDER_REGEX = new RegExp(
|
|
1065
|
+
`${escapeRegexpSpecialChars(placeholderLeft)}[0-9a-zA-Z_$]{1,${
|
|
1066
|
+
length - placeholderOverhead
|
|
1067
|
+
}}${escapeRegexpSpecialChars(placeholderRight)}`,
|
|
1068
|
+
"g",
|
|
1069
|
+
);
|
|
1070
|
+
|
|
1071
|
+
const markAsCode = (string) => {
|
|
1072
|
+
return {
|
|
1073
|
+
__isCode__: true,
|
|
1074
|
+
toString: () => string,
|
|
1075
|
+
value: string,
|
|
1076
|
+
};
|
|
1077
|
+
};
|
|
1078
|
+
|
|
1079
|
+
const replaceAll = (string, replacer) => {
|
|
1080
|
+
const magicSource = createMagicSource(string);
|
|
1081
|
+
|
|
1082
|
+
string.replace(PLACEHOLDER_REGEX, (placeholder, index) => {
|
|
1083
|
+
const replacement = replacer(placeholder, index);
|
|
1084
|
+
if (!replacement) {
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
let value;
|
|
1088
|
+
let isCode = false;
|
|
1089
|
+
if (replacement && replacement.__isCode__) {
|
|
1090
|
+
value = replacement.value;
|
|
1091
|
+
isCode = true;
|
|
1092
|
+
} else {
|
|
1093
|
+
value = replacement;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
let start = index;
|
|
1097
|
+
let end = start + placeholder.length;
|
|
1098
|
+
if (
|
|
1099
|
+
isCode &&
|
|
1100
|
+
// when specifier is wrapper by quotes
|
|
1101
|
+
// we remove the quotes to transform the string
|
|
1102
|
+
// into code that will be executed
|
|
1103
|
+
isWrappedByQuote(string, start, end)
|
|
1104
|
+
) {
|
|
1105
|
+
start = start - 1;
|
|
1106
|
+
end = end + 1;
|
|
1107
|
+
}
|
|
1108
|
+
magicSource.replace({
|
|
1109
|
+
start,
|
|
1110
|
+
end,
|
|
1111
|
+
replacement: value,
|
|
1112
|
+
});
|
|
1113
|
+
});
|
|
1114
|
+
return magicSource.toContentAndSourcemap();
|
|
1115
|
+
};
|
|
1116
|
+
|
|
1117
|
+
return {
|
|
1118
|
+
generate,
|
|
1119
|
+
replaceFirst,
|
|
1120
|
+
replaceAll,
|
|
1121
|
+
extractFirst,
|
|
1122
|
+
markAsCode,
|
|
1123
|
+
replaceWithDefault,
|
|
1124
|
+
};
|
|
1125
|
+
};
|
|
1126
|
+
|
|
1127
|
+
const mayUsePlaceholder = (urlInfo) => {
|
|
1128
|
+
if (urlInfo.referenceToOthersSet.size === 0) {
|
|
1129
|
+
return false;
|
|
1130
|
+
}
|
|
1131
|
+
if (!CONTENT_TYPE.isTextual(urlInfo.contentType)) {
|
|
1132
|
+
return false;
|
|
1133
|
+
}
|
|
1134
|
+
return true;
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
const isWrappedByQuote = (content, start, end) => {
|
|
1138
|
+
const previousChar = content[start - 1];
|
|
1139
|
+
const nextChar = content[end];
|
|
1140
|
+
if (previousChar === `'` && nextChar === `'`) {
|
|
1141
|
+
return true;
|
|
1142
|
+
}
|
|
1143
|
+
if (previousChar === `"` && nextChar === `"`) {
|
|
1144
|
+
return true;
|
|
1145
|
+
}
|
|
1146
|
+
if (previousChar === "`" && nextChar === "`") {
|
|
1147
|
+
return true;
|
|
1148
|
+
}
|
|
1149
|
+
return false;
|
|
1150
|
+
};
|
|
1151
|
+
|
|
1152
|
+
// https://github.com/rollup/rollup/blob/19e50af3099c2f627451a45a84e2fa90d20246d5/src/utils/FileEmitter.ts#L47
|
|
1153
|
+
// https://github.com/rollup/rollup/blob/5a5391971d695c808eed0c5d7d2c6ccb594fc689/src/Chunk.ts#L870
|
|
1154
|
+
const generateVersion = (parts, length) => {
|
|
1155
|
+
const hash = createHash("sha256");
|
|
1156
|
+
parts.forEach((part) => {
|
|
1157
|
+
hash.update(part);
|
|
1158
|
+
});
|
|
1159
|
+
return hash.digest("hex").slice(0, length);
|
|
1160
|
+
};
|
|
1161
|
+
|
|
1162
|
+
const injectVersionIntoBuildSpecifier = ({
|
|
1163
|
+
buildSpecifier,
|
|
1164
|
+
version,
|
|
1165
|
+
versioningMethod,
|
|
1166
|
+
}) => {
|
|
1167
|
+
if (versioningMethod === "search_param") {
|
|
1168
|
+
return injectQueryParamIntoSpecifierWithoutEncoding(
|
|
1169
|
+
buildSpecifier,
|
|
1170
|
+
"v",
|
|
1171
|
+
version,
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
return renderUrlOrRelativeUrlFilename(
|
|
1175
|
+
buildSpecifier,
|
|
1176
|
+
({ basename, extension }) => {
|
|
1177
|
+
return `${basename}-${version}${extension}`;
|
|
1178
|
+
},
|
|
1179
|
+
);
|
|
1180
|
+
};
|
|
1181
|
+
|
|
1182
|
+
const asBuildUrlVersioned = ({
|
|
1183
|
+
buildSpecifierVersioned,
|
|
1184
|
+
buildDirectoryUrl,
|
|
1185
|
+
}) => {
|
|
1186
|
+
if (buildSpecifierVersioned[0] === "/") {
|
|
1187
|
+
return new URL(buildSpecifierVersioned.slice(1), buildDirectoryUrl).href;
|
|
1188
|
+
}
|
|
1189
|
+
const buildUrl = new URL(buildSpecifierVersioned, buildDirectoryUrl).href;
|
|
1190
|
+
if (buildUrl.startsWith(buildDirectoryUrl)) {
|
|
1191
|
+
return buildUrl;
|
|
1192
|
+
}
|
|
1193
|
+
// it's likely "base" parameter was set to an url origin like "https://cdn.example.com"
|
|
1194
|
+
// let's move url to build directory
|
|
1195
|
+
const { pathname, search, hash } = new URL(buildSpecifierVersioned);
|
|
1196
|
+
return `${buildDirectoryUrl}${pathname}${search}${hash}`;
|
|
1197
|
+
};
|
|
1198
|
+
|
|
1199
|
+
// export for unit tests
|
|
1200
|
+
export { generateVersion };
|