@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.
Files changed (29) hide show
  1. package/dist/js/autoreload.js +2 -2
  2. package/dist/jsenv_core.js +3221 -2483
  3. package/package.json +16 -15
  4. package/src/build/build.js +246 -1028
  5. package/src/build/build_specifier_manager.js +1200 -0
  6. package/src/build/build_urls_generator.js +40 -18
  7. package/src/build/version_mappings_injection.js +14 -16
  8. package/src/dev/file_service.js +0 -10
  9. package/src/dev/start_dev_server.js +0 -2
  10. package/src/kitchen/kitchen.js +54 -37
  11. package/src/kitchen/url_graph/references.js +84 -93
  12. package/src/kitchen/url_graph/url_graph.js +16 -6
  13. package/src/kitchen/url_graph/url_info_transformations.js +124 -55
  14. package/src/plugins/autoreload/client/autoreload.js +6 -2
  15. package/src/plugins/autoreload/jsenv_plugin_autoreload_server.js +20 -16
  16. package/src/plugins/autoreload/jsenv_plugin_hot_search_param.js +1 -1
  17. package/src/plugins/cache_control/jsenv_plugin_cache_control.js +2 -2
  18. package/src/plugins/clean_html/jsenv_plugin_clean_html.js +16 -0
  19. package/src/plugins/importmap/jsenv_plugin_importmap.js +11 -23
  20. package/src/plugins/inlining/jsenv_plugin_inlining_as_data_url.js +16 -1
  21. package/src/plugins/inlining/jsenv_plugin_inlining_into_html.js +14 -24
  22. package/src/plugins/plugin_controller.js +37 -25
  23. package/src/plugins/plugins.js +2 -0
  24. package/src/plugins/protocol_file/jsenv_plugin_protocol_file.js +31 -16
  25. package/src/plugins/reference_analysis/directory/jsenv_plugin_directory_reference_analysis.js +12 -6
  26. package/src/plugins/reference_analysis/html/jsenv_plugin_html_reference_analysis.js +33 -54
  27. package/src/plugins/reference_analysis/js/jsenv_plugin_js_reference_analysis.js +2 -9
  28. package/src/plugins/reference_analysis/jsenv_plugin_reference_analysis.js +15 -8
  29. package/src/build/build_versions_manager.js +0 -492
@@ -1,492 +0,0 @@
1
- // see https://github.com/rollup/rollup/blob/ce453507ab8457dd1ea3909d8dd7b117b2d14fab/src/utils/hashPlaceholders.ts#L1
2
-
3
- import { createHash } from "node:crypto";
4
- import {
5
- injectQueryParamIntoSpecifierWithoutEncoding,
6
- renderUrlOrRelativeUrlFilename,
7
- } from "@jsenv/urls";
8
- import { parseHtmlString, stringifyHtmlAst } from "@jsenv/ast";
9
- import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js";
10
- import { escapeRegexpSpecialChars } from "@jsenv/utils/src/string/escape_regexp_special_chars.js";
11
- import { GRAPH_VISITOR } from "../kitchen/url_graph/url_graph_visitor.js";
12
- import { isWebWorkerUrlInfo } from "../kitchen/web_workers.js";
13
- import {
14
- injectVersionMappingsAsGlobal,
15
- injectVersionMappingsAsImportmap,
16
- } from "./version_mappings_injection.js";
17
-
18
- const placeholderLeft = "!~{";
19
- const placeholderRight = "}~";
20
- const placeholderOverhead = placeholderLeft.length + placeholderRight.length;
21
-
22
- export const createBuildVersionsManager = ({
23
- finalKitchen,
24
- versioningMethod,
25
- versionLength = 8,
26
- canUseImportmap,
27
- getBuildUrlFromBuildSpecifier,
28
- }) => {
29
- const workerReferenceSet = new Set();
30
- const isReferencedByWorker = (reference) => {
31
- if (workerReferenceSet.has(reference)) {
32
- return true;
33
- }
34
- const referencedUrlInfo = reference.urlInfo;
35
- const dependentWorker = GRAPH_VISITOR.findDependent(
36
- referencedUrlInfo,
37
- (dependentUrlInfo) => {
38
- return isWebWorkerUrlInfo(dependentUrlInfo);
39
- },
40
- );
41
- if (dependentWorker) {
42
- workerReferenceSet.add(reference);
43
- return true;
44
- }
45
- return Boolean(dependentWorker);
46
- };
47
-
48
- const chars =
49
- "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$";
50
- const base = 64;
51
- const toBase64 = (value) => {
52
- let outString = "";
53
- do {
54
- const currentDigit = value % base;
55
- value = (value / base) | 0;
56
- outString = chars[currentDigit] + outString;
57
- } while (value !== 0);
58
- return outString;
59
- };
60
-
61
- let nextIndex = 0;
62
- const generatePlaceholder = () => {
63
- nextIndex++;
64
- const id = toBase64(nextIndex);
65
- let placeholder = placeholderLeft;
66
- placeholder += id.padStart(versionLength - placeholderOverhead, "0");
67
- placeholder += placeholderRight;
68
- return placeholder;
69
- };
70
-
71
- const replaceFirstPlaceholder = (code, value) => {
72
- let replaced = false;
73
- return code.replace(REPLACER_REGEX, (match) => {
74
- if (replaced) return match;
75
- replaced = true;
76
- return value;
77
- });
78
- };
79
-
80
- const defaultPlaceholder = `${placeholderLeft}${"0".repeat(
81
- versionLength - placeholderOverhead,
82
- )}${placeholderRight}`;
83
- const replaceWithDefaultAndPopulateContainedPlaceholders = (
84
- code,
85
- containedPlaceholders,
86
- ) => {
87
- const transformedCode = code.replace(REPLACER_REGEX, (placeholder) => {
88
- containedPlaceholders.add(placeholder);
89
- return defaultPlaceholder;
90
- });
91
- return transformedCode;
92
- };
93
-
94
- const REPLACER_REGEX = new RegExp(
95
- `${escapeRegexpSpecialChars(placeholderLeft)}[0-9a-zA-Z_$]{1,${
96
- versionLength - placeholderOverhead
97
- }}${escapeRegexpSpecialChars(placeholderRight)}`,
98
- "g",
99
- );
100
-
101
- const placeholderToReferenceMap = new Map();
102
- const buildSpecifierToPlaceholderMap = new Map();
103
-
104
- const referenceVersionedByCodeMap = new Map();
105
- const referenceVersionedByImportmapMap = new Map();
106
- const specifierVersionedInlineSet = new Set();
107
- const specifierVersionedByCodeSet = new Set();
108
- const specifierVersionedByImportmapSet = new Set();
109
- const versionMap = new Map();
110
-
111
- const buildSpecifierToBuildSpecifierVersionedMap = new Map();
112
- // - will be used by global and importmap registry
113
- // - will be used by build during "inject_urls_in_service_workers" and
114
- // "resync_resource_hints"
115
- const getBuildSpecifierVersioned = (buildSpecifier) => {
116
- const fromCache =
117
- buildSpecifierToBuildSpecifierVersionedMap.get(buildSpecifier);
118
- if (fromCache) {
119
- return fromCache;
120
- }
121
- const buildSpecifierPlaceholder =
122
- buildSpecifierToPlaceholderMap.get(buildSpecifier);
123
- if (!buildSpecifierPlaceholder) {
124
- return null;
125
- }
126
- const buildUrl = getBuildUrlFromBuildSpecifier(buildSpecifier);
127
- const urlInfo = finalKitchen.graph.getUrlInfo(buildUrl);
128
- const version = versionMap.get(urlInfo);
129
- const buildSpecifierVersioned = replaceFirstPlaceholder(
130
- buildSpecifierPlaceholder,
131
- version,
132
- );
133
- buildSpecifierToBuildSpecifierVersionedMap.set(
134
- buildSpecifier,
135
- buildSpecifierVersioned,
136
- );
137
- return buildSpecifierVersioned;
138
- };
139
-
140
- return {
141
- generateBuildSpecifierPlaceholder: (reference, buildSpecifier) => {
142
- const placeholder = generatePlaceholder();
143
- const buildSpecifierWithVersionPlaceholder =
144
- injectVersionPlaceholderIntoBuildSpecifier({
145
- buildSpecifier,
146
- versionPlaceholder: placeholder,
147
- versioningMethod,
148
- });
149
- buildSpecifierToPlaceholderMap.set(
150
- buildSpecifier,
151
- buildSpecifierWithVersionPlaceholder,
152
- );
153
- placeholderToReferenceMap.set(placeholder, reference);
154
-
155
- const asPlaceholderForVersionedSpecifier = () => {
156
- specifierVersionedInlineSet.add(buildSpecifier);
157
- return buildSpecifierWithVersionPlaceholder;
158
- };
159
-
160
- const asPlaceholderForCodeVersionedByGlobalRegistry = (codeToInject) => {
161
- // here we use placeholder as specifier, so something like
162
- // "/other/file.png" becomes "!~{0001}~" and finally "__v__("/other/file.png")"
163
- // this is to support cases like CSS inlined in JS
164
- // CSS minifier must see valid CSS specifiers like background-image: url("!~{0001}~");
165
- // that is finally replaced by invalid css background-image: url("__v__("/other/file.png")")
166
- specifierVersionedByCodeSet.add(buildSpecifier);
167
- referenceVersionedByCodeMap.set(reference, codeToInject);
168
- return placeholder;
169
- };
170
-
171
- const asPlaceholderForSpecifierVersionedByImportmap = (
172
- specifierToUse,
173
- ) => {
174
- specifierVersionedByImportmapSet.add(buildSpecifier);
175
- referenceVersionedByImportmapMap.set(reference, specifierToUse);
176
- return buildSpecifier;
177
- };
178
-
179
- const ownerUrlInfo = reference.ownerUrlInfo;
180
- if (ownerUrlInfo.jsQuote) {
181
- return asPlaceholderForCodeVersionedByGlobalRegistry(
182
- `${ownerUrlInfo.jsQuote}+__v__(${JSON.stringify(buildSpecifier)})+${
183
- ownerUrlInfo.jsQuote
184
- }`,
185
- );
186
- }
187
- if (reference.type === "js_url") {
188
- return asPlaceholderForCodeVersionedByGlobalRegistry(
189
- `__v__(${JSON.stringify(buildSpecifier)})`,
190
- );
191
- }
192
- if (reference.type === "js_import") {
193
- if (reference.subtype === "import_dynamic") {
194
- return asPlaceholderForCodeVersionedByGlobalRegistry(
195
- `__v__(${JSON.stringify(buildSpecifier)})`,
196
- );
197
- }
198
- if (reference.subtype === "import_meta_resolve") {
199
- return asPlaceholderForCodeVersionedByGlobalRegistry(
200
- `__v__(${JSON.stringify(buildSpecifier)})`,
201
- );
202
- }
203
- if (canUseImportmap && !isReferencedByWorker(reference)) {
204
- return asPlaceholderForSpecifierVersionedByImportmap(
205
- JSON.stringify(buildSpecifier),
206
- );
207
- }
208
- }
209
- return asPlaceholderForVersionedSpecifier();
210
- },
211
- applyVersioning: async (finalKitchen) => {
212
- workerReferenceSet.clear();
213
-
214
- const contentOnlyVersionMap = new Map();
215
- generate_content_only_versions: {
216
- GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
217
- finalKitchen.graph.rootUrlInfo,
218
- (urlInfo) => {
219
- // ignore:
220
- // - inline files and data files:
221
- // they are already taken into account in the file where they appear
222
- // - ignored files:
223
- // we don't know their content
224
- // - unused files without reference
225
- // File updated such as style.css -> style.css.js or file.js->file.nomodule.js
226
- // Are used at some point just to be discarded later because they need to be converted
227
- // There is no need to version them and we could not because the file have been ignored
228
- // so their content is unknown
229
- if (urlInfo.type === "sourcemap") {
230
- return;
231
- }
232
- if (urlInfo.isInline) {
233
- return;
234
- }
235
- if (urlInfo.url.startsWith("data:")) {
236
- // urlInfo became inline and is not referenced by something else
237
- return;
238
- }
239
- if (urlInfo.url.startsWith("ignore:")) {
240
- return;
241
- }
242
- let content = urlInfo.content;
243
- if (urlInfo.type === "html") {
244
- content = stringifyHtmlAst(
245
- parseHtmlString(urlInfo.content, {
246
- storeOriginalPositions: false,
247
- }),
248
- {
249
- cleanupJsenvAttributes: true,
250
- cleanupPositionAttributes: true,
251
- },
252
- );
253
- }
254
- if (
255
- CONTENT_TYPE.isTextual(urlInfo.contentType) &&
256
- urlInfo.referenceToOthersSet.size > 0
257
- ) {
258
- const containedPlaceholders = new Set();
259
- const contentWithPredictibleVersionPlaceholders =
260
- replaceWithDefaultAndPopulateContainedPlaceholders(
261
- content,
262
- containedPlaceholders,
263
- );
264
- content = contentWithPredictibleVersionPlaceholders;
265
- }
266
- const contentVersion = generateVersion([content], versionLength);
267
- contentOnlyVersionMap.set(urlInfo, contentVersion);
268
- },
269
- );
270
- }
271
-
272
- generate_versions: {
273
- const getSetOfUrlInfoInfluencingVersion = (urlInfo) => {
274
- // there is no need to take into account dependencies
275
- // when the reference can reference them without version
276
- // (importmap or window.__v__)
277
- const setOfUrlInfluencingVersion = new Set();
278
- const visitDependencies = (urlInfo) => {
279
- urlInfo.referenceToOthersSet.forEach((referenceToOther) => {
280
- if (
281
- referenceVersionedByCodeMap.has(referenceToOther) ||
282
- referenceVersionedByImportmapMap.has(referenceToOther)
283
- ) {
284
- // when versioning is dynamic no need to take into account
285
- // happens for:
286
- // - specifier mapped by window.__v__()
287
- // - specifier mapped by importmap
288
- return;
289
- }
290
- const referencedUrlInfo = referenceToOther.urlInfo;
291
- const referencedContentVersion =
292
- contentOnlyVersionMap.get(referencedUrlInfo);
293
- if (!referencedContentVersion) {
294
- // ignored while traversing graph (not used anymore, inline, ...)
295
- return;
296
- }
297
- if (setOfUrlInfluencingVersion.has(referencedUrlInfo)) {
298
- // handle circular deps
299
- return;
300
- }
301
- setOfUrlInfluencingVersion.add(referencedUrlInfo);
302
- visitDependencies(referencedUrlInfo);
303
- });
304
- };
305
- visitDependencies(urlInfo);
306
- return setOfUrlInfluencingVersion;
307
- };
308
-
309
- contentOnlyVersionMap.forEach((contentOnlyVersion, urlInfo) => {
310
- const setOfUrlInfoInfluencingVersion =
311
- getSetOfUrlInfoInfluencingVersion(urlInfo);
312
- const versionPartSet = new Set();
313
- versionPartSet.add(contentOnlyVersion);
314
- setOfUrlInfoInfluencingVersion.forEach(
315
- (urlInfoInfluencingVersion) => {
316
- const otherUrlInfoContentVersion = contentOnlyVersionMap.get(
317
- urlInfoInfluencingVersion,
318
- );
319
- if (!otherUrlInfoContentVersion) {
320
- throw new Error(
321
- `cannot find content version for ${urlInfoInfluencingVersion.url} (used by ${urlInfo.url})`,
322
- );
323
- }
324
- versionPartSet.add(otherUrlInfoContentVersion);
325
- },
326
- );
327
- const version = generateVersion(versionPartSet, versionLength);
328
- versionMap.set(urlInfo, version);
329
- });
330
- }
331
-
332
- replace_version_placeholders: {
333
- // now replace all placeholders in urlInfos with the real versions
334
- versionMap.forEach((version, urlInfo) => {
335
- if (!CONTENT_TYPE.isTextual(urlInfo.contentType)) return;
336
- if (urlInfo.referenceToOthersSet.size === 0) return;
337
-
338
- let replacements = [];
339
- let content = urlInfo.content;
340
- content.replace(REPLACER_REGEX, (placeholder, index) => {
341
- const replacement = {
342
- start: index,
343
- placeholder,
344
- value: null,
345
- valueType: "",
346
- };
347
- replacements.push(replacement);
348
- const reference = placeholderToReferenceMap.get(placeholder);
349
-
350
- const codeToInject = referenceVersionedByCodeMap.get(reference);
351
- if (codeToInject) {
352
- replacement.value = codeToInject;
353
- replacement.valueType = "code";
354
- return;
355
- }
356
- const specifierToUse =
357
- referenceVersionedByImportmapMap.get(reference);
358
- if (specifierToUse) {
359
- replacement.value = specifierToUse;
360
- replacement.valueType = "specifier";
361
- return;
362
- }
363
- const version = versionMap.get(reference.urlInfo);
364
- replacement.value = version;
365
- replacement.valueType = "specifier";
366
- });
367
-
368
- let diff = 0;
369
- replacements.forEach((replacement) => {
370
- const placeholder = replacement.placeholder;
371
- const value = replacement.value;
372
- let start = replacement.start + diff;
373
- let end = start + placeholder.length;
374
- if (
375
- replacement.valueType === "code" &&
376
- // when specifier is wrapper by quotes
377
- // we remove the quotes to transform the string
378
- // into code that will be executed
379
- isWrappedByQuote(content, start, end)
380
- ) {
381
- start = start - 1;
382
- end = end + 1;
383
- diff = diff - 2;
384
- }
385
- const before = content.slice(0, start);
386
- const after = content.slice(end);
387
- content = before + value + after;
388
- const charAdded = value.length - placeholder.length;
389
- diff += charAdded;
390
- });
391
- urlInfo.content = content;
392
- });
393
- }
394
-
395
- inject_global_registry_and_importmap: {
396
- const actions = [];
397
- const visitors = [];
398
- if (specifierVersionedByCodeSet.size) {
399
- const versionMappingsNeeded = {};
400
- specifierVersionedByCodeSet.forEach((buildSpecifier) => {
401
- versionMappingsNeeded[buildSpecifier] =
402
- getBuildSpecifierVersioned(buildSpecifier);
403
- });
404
- visitors.push((urlInfo) => {
405
- if (urlInfo.isEntryPoint) {
406
- actions.push(async () => {
407
- await injectVersionMappingsAsGlobal(
408
- urlInfo,
409
- versionMappingsNeeded,
410
- );
411
- });
412
- }
413
- });
414
- }
415
- if (specifierVersionedByImportmapSet.size) {
416
- const versionMappingsNeeded = {};
417
- specifierVersionedByImportmapSet.forEach((buildSpecifier) => {
418
- versionMappingsNeeded[buildSpecifier] =
419
- getBuildSpecifierVersioned(buildSpecifier);
420
- });
421
- visitors.push((urlInfo) => {
422
- if (urlInfo.type === "html" && urlInfo.isEntryPoint) {
423
- actions.push(async () => {
424
- await injectVersionMappingsAsImportmap(
425
- urlInfo,
426
- versionMappingsNeeded,
427
- );
428
- });
429
- }
430
- });
431
- }
432
-
433
- if (visitors.length) {
434
- GRAPH_VISITOR.forEach(finalKitchen.graph, (urlInfo) => {
435
- if (urlInfo.isRoot) return;
436
- visitors.forEach((visitor) => visitor(urlInfo));
437
- });
438
- if (actions.length) {
439
- await Promise.all(actions.map((action) => action()));
440
- }
441
- }
442
- }
443
- },
444
- getVersion: (urlInfo) => versionMap.get(urlInfo),
445
- getBuildSpecifierVersioned,
446
- };
447
- };
448
-
449
- const isWrappedByQuote = (content, start, end) => {
450
- const previousChar = content[start - 1];
451
- const nextChar = content[end];
452
- if (previousChar === `'` && nextChar === `'`) {
453
- return true;
454
- }
455
- if (previousChar === `"` && nextChar === `"`) {
456
- return true;
457
- }
458
- if (previousChar === "`" && nextChar === "`") {
459
- return true;
460
- }
461
- return false;
462
- };
463
-
464
- // https://github.com/rollup/rollup/blob/19e50af3099c2f627451a45a84e2fa90d20246d5/src/utils/FileEmitter.ts#L47
465
- // https://github.com/rollup/rollup/blob/5a5391971d695c808eed0c5d7d2c6ccb594fc689/src/Chunk.ts#L870
466
- export const generateVersion = (parts, length) => {
467
- const hash = createHash("sha256");
468
- parts.forEach((part) => {
469
- hash.update(part);
470
- });
471
- return hash.digest("hex").slice(0, length);
472
- };
473
-
474
- const injectVersionPlaceholderIntoBuildSpecifier = ({
475
- buildSpecifier,
476
- versionPlaceholder,
477
- versioningMethod,
478
- }) => {
479
- if (versioningMethod === "search_param") {
480
- return injectQueryParamIntoSpecifierWithoutEncoding(
481
- buildSpecifier,
482
- "v",
483
- versionPlaceholder,
484
- );
485
- }
486
- return renderUrlOrRelativeUrlFilename(
487
- buildSpecifier,
488
- ({ basename, extension }) => {
489
- return `${basename}-${versionPlaceholder}${extension}`;
490
- },
491
- );
492
- };