@jsenv/core 40.0.2 → 40.0.4

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 (38) hide show
  1. package/dist/{js → client/autoreload}/autoreload.js +45 -2
  2. package/dist/{html → client/directory_listing}/directory_listing.html +2 -2
  3. package/dist/{js → client/directory_listing/js}/directory_listing.js +3 -3
  4. package/dist/js/build.js +319 -15321
  5. package/dist/js/start_build_server.js +8 -4
  6. package/dist/js/start_dev_server.js +29 -35
  7. package/dist/jsenv_core.js +13 -1
  8. package/dist/jsenv_core_packages.js +10061 -0
  9. package/dist/main.js +117 -0
  10. package/dist/plugins.js +7944 -0
  11. package/package.json +17 -17
  12. package/src/build/build.js +61 -60
  13. package/src/build/build_params.js +20 -0
  14. package/src/build/build_specifier_manager.js +10 -0
  15. package/src/build/build_urls_generator.js +5 -1
  16. package/src/build/jsenv_plugin_subbuilds.js +71 -0
  17. package/src/dev/start_dev_server.js +21 -7
  18. package/src/kitchen/fetched_content_compliance.js +7 -3
  19. package/src/kitchen/kitchen.js +3 -0
  20. package/src/plugins/autoreload/jsenv_plugin_autoreload_client.js +1 -4
  21. package/src/plugins/html_syntax_error_fallback/jsenv_plugin_html_syntax_error_fallback.js +4 -3
  22. package/src/plugins/import_meta_hot/jsenv_plugin_import_meta_hot.js +2 -3
  23. package/src/plugins/protocol_file/jsenv_plugin_directory_listing.js +1 -2
  24. package/src/plugins/ribbon/jsenv_plugin_ribbon.js +2 -2
  25. package/dist/js/main.js +0 -994
  26. package/dist/js/process_teardown_events.js +0 -1848
  27. /package/dist/babel_helpers/{OverloadYield → overloadYield}/OverloadYield.js +0 -0
  28. /package/dist/{css → client/directory_listing/css}/directory_listing.css +0 -0
  29. /package/dist/{other → client/directory_listing/other}/dir.png +0 -0
  30. /package/dist/{other → client/directory_listing/other}/file.png +0 -0
  31. /package/dist/{other → client/directory_listing/other}/home.svg +0 -0
  32. /package/dist/{html → client/html_syntax_error}/html_syntax_error.html +0 -0
  33. /package/dist/{js → client/import_meta_hot}/import_meta_hot.js +0 -0
  34. /package/dist/{js → client/inline_content}/inline_content.js +0 -0
  35. /package/dist/{js → client/new_stylesheet}/new_stylesheet.js +0 -0
  36. /package/dist/{js → client/regenerator_runtime}/regenerator_runtime.js +0 -0
  37. /package/dist/{js → client/ribbon}/ribbon.js +0 -0
  38. /package/dist/{js → client/server_events_client}/server_events_client.js +0 -0
package/dist/js/main.js DELETED
@@ -1,994 +0,0 @@
1
- import { createMagicSource } from "@jsenv/sourcemap";
2
-
3
- /*
4
- * Link to things doing pattern matching:
5
- * https://git-scm.com/docs/gitignore
6
- * https://github.com/kaelzhang/node-ignore
7
- */
8
-
9
- /** @module jsenv_url_meta **/
10
- /**
11
- * An object representing the result of applying a pattern to an url
12
- * @typedef {Object} MatchResult
13
- * @property {boolean} matched Indicates if url matched pattern
14
- * @property {number} patternIndex Index where pattern stopped matching url, otherwise pattern.length
15
- * @property {number} urlIndex Index where url stopped matching pattern, otherwise url.length
16
- * @property {Array} matchGroups Array of strings captured during pattern matching
17
- */
18
-
19
- /**
20
- * Apply a pattern to an url
21
- * @param {Object} applyPatternMatchingParams
22
- * @param {string} applyPatternMatchingParams.pattern "*", "**" and trailing slash have special meaning
23
- * @param {string} applyPatternMatchingParams.url a string representing an url
24
- * @return {MatchResult}
25
- */
26
- const applyPattern = ({ url, pattern }) => {
27
- const { matched, patternIndex, index, groups } = applyMatching(pattern, url);
28
- const matchGroups = [];
29
- let groupIndex = 0;
30
- for (const group of groups) {
31
- if (group.name) {
32
- matchGroups[group.name] = group.string;
33
- } else {
34
- matchGroups[groupIndex] = group.string;
35
- groupIndex++;
36
- }
37
- }
38
- return {
39
- matched,
40
- patternIndex,
41
- urlIndex: index,
42
- matchGroups,
43
- };
44
- };
45
-
46
- const applyMatching = (pattern, string) => {
47
- const groups = [];
48
- let patternIndex = 0;
49
- let index = 0;
50
- let remainingPattern = pattern;
51
- let remainingString = string;
52
- let restoreIndexes = true;
53
-
54
- const consumePattern = (count) => {
55
- const subpattern = remainingPattern.slice(0, count);
56
- remainingPattern = remainingPattern.slice(count);
57
- patternIndex += count;
58
- return subpattern;
59
- };
60
- const consumeString = (count) => {
61
- const substring = remainingString.slice(0, count);
62
- remainingString = remainingString.slice(count);
63
- index += count;
64
- return substring;
65
- };
66
- const consumeRemainingString = () => {
67
- return consumeString(remainingString.length);
68
- };
69
-
70
- let matched;
71
- const iterate = () => {
72
- const patternIndexBefore = patternIndex;
73
- const indexBefore = index;
74
- matched = matchOne();
75
- if (matched === undefined) {
76
- consumePattern(1);
77
- consumeString(1);
78
- iterate();
79
- return;
80
- }
81
- if (matched === false && restoreIndexes) {
82
- patternIndex = patternIndexBefore;
83
- index = indexBefore;
84
- }
85
- };
86
- const matchOne = () => {
87
- // pattern consumed
88
- if (remainingPattern === "") {
89
- if (remainingString === "") {
90
- return true; // string fully matched pattern
91
- }
92
- if (remainingString[0] === "?") {
93
- // match search params
94
- consumeRemainingString();
95
-
96
- return true;
97
- }
98
- // if remainingString
99
- return false; // fails because string longer than expect
100
- }
101
- // -- from this point pattern is not consumed --
102
- // string consumed, pattern not consumed
103
- if (remainingString === "") {
104
- if (remainingPattern === "**") {
105
- // trailing "**" is optional
106
- consumePattern(2);
107
- return true;
108
- }
109
- if (remainingPattern === "*") {
110
- groups.push({ string: "" });
111
- }
112
- return false; // fail because string shorter than expect
113
- }
114
- // -- from this point pattern and string are not consumed --
115
- // fast path trailing slash
116
- if (remainingPattern === "/") {
117
- if (remainingString[0] === "/") {
118
- // trailing slash match remaining
119
- consumePattern(1);
120
- groups.push({ string: consumeRemainingString() });
121
- return true;
122
- }
123
- return false;
124
- }
125
- // fast path trailing '**'
126
- if (remainingPattern === "**") {
127
- consumePattern(2);
128
- consumeRemainingString();
129
- return true;
130
- }
131
- if (remainingPattern.slice(0, 4) === "/**/") {
132
- consumePattern(3); // consumes "/**/"
133
- const skipResult = skipUntilMatch({
134
- pattern: remainingPattern,
135
- string: remainingString,
136
- canSkipSlash: true,
137
- });
138
- groups.push(...skipResult.groups);
139
- consumePattern(skipResult.patternIndex);
140
- consumeRemainingString();
141
- restoreIndexes = false;
142
- return skipResult.matched;
143
- }
144
- // pattern leading **
145
- if (remainingPattern.slice(0, 2) === "**") {
146
- consumePattern(2); // consumes "**"
147
- let skipAllowed = true;
148
- if (remainingPattern[0] === "/") {
149
- consumePattern(1); // consumes "/"
150
- // when remainingPattern was preceeded by "**/"
151
- // and remainingString have no "/"
152
- // then skip is not allowed, a regular match will be performed
153
- if (!remainingString.includes("/")) {
154
- skipAllowed = false;
155
- }
156
- }
157
- // pattern ending with "**" or "**/" match remaining string
158
- if (remainingPattern === "") {
159
- consumeRemainingString();
160
- return true;
161
- }
162
- if (skipAllowed) {
163
- const skipResult = skipUntilMatch({
164
- pattern: remainingPattern,
165
- string: remainingString,
166
- canSkipSlash: true,
167
- });
168
- groups.push(...skipResult.groups);
169
- consumePattern(skipResult.patternIndex);
170
- consumeRemainingString();
171
- restoreIndexes = false;
172
- return skipResult.matched;
173
- }
174
- }
175
- if (remainingPattern[0] === "*") {
176
- consumePattern(1); // consumes "*"
177
- if (remainingPattern === "") {
178
- // matches everything except "/"
179
- const slashIndex = remainingString.indexOf("/");
180
- if (slashIndex === -1) {
181
- groups.push({ string: consumeRemainingString() });
182
- return true;
183
- }
184
- groups.push({ string: consumeString(slashIndex) });
185
- return false;
186
- }
187
- // the next char must not the one expect by remainingPattern[0]
188
- // because * is greedy and expect to skip at least one char
189
- if (remainingPattern[0] === remainingString[0]) {
190
- groups.push({ string: "" });
191
- patternIndex = patternIndex - 1;
192
- return false;
193
- }
194
- const skipResult = skipUntilMatch({
195
- pattern: remainingPattern,
196
- string: remainingString,
197
- canSkipSlash: false,
198
- });
199
- groups.push(skipResult.group, ...skipResult.groups);
200
- consumePattern(skipResult.patternIndex);
201
- consumeString(skipResult.index);
202
- restoreIndexes = false;
203
- return skipResult.matched;
204
- }
205
- if (remainingPattern[0] !== remainingString[0]) {
206
- return false;
207
- }
208
- return undefined;
209
- };
210
- iterate();
211
-
212
- return {
213
- matched,
214
- patternIndex,
215
- index,
216
- groups,
217
- };
218
- };
219
-
220
- const skipUntilMatch = ({ pattern, string, canSkipSlash }) => {
221
- let index = 0;
222
- let remainingString = string;
223
- let longestAttemptRange = null;
224
- let isLastAttempt = false;
225
-
226
- const failure = () => {
227
- return {
228
- matched: false,
229
- patternIndex: longestAttemptRange.patternIndex,
230
- index: longestAttemptRange.index + longestAttemptRange.length,
231
- groups: longestAttemptRange.groups,
232
- group: {
233
- string: string.slice(0, longestAttemptRange.index),
234
- },
235
- };
236
- };
237
-
238
- const tryToMatch = () => {
239
- const matchAttempt = applyMatching(pattern, remainingString);
240
- if (matchAttempt.matched) {
241
- return {
242
- matched: true,
243
- patternIndex: matchAttempt.patternIndex,
244
- index: index + matchAttempt.index,
245
- groups: matchAttempt.groups,
246
- group: {
247
- string:
248
- remainingString === ""
249
- ? string
250
- : string.slice(0, -remainingString.length),
251
- },
252
- };
253
- }
254
- const attemptIndex = matchAttempt.index;
255
- const attemptRange = {
256
- patternIndex: matchAttempt.patternIndex,
257
- index,
258
- length: attemptIndex,
259
- groups: matchAttempt.groups,
260
- };
261
- if (
262
- !longestAttemptRange ||
263
- longestAttemptRange.length < attemptRange.length
264
- ) {
265
- longestAttemptRange = attemptRange;
266
- }
267
- if (isLastAttempt) {
268
- return failure();
269
- }
270
- const nextIndex = attemptIndex + 1;
271
- if (nextIndex >= remainingString.length) {
272
- return failure();
273
- }
274
- if (remainingString[0] === "/") {
275
- if (!canSkipSlash) {
276
- return failure();
277
- }
278
- // when it's the last slash, the next attempt is the last
279
- if (remainingString.indexOf("/", 1) === -1) {
280
- isLastAttempt = true;
281
- }
282
- }
283
- // search against the next unattempted string
284
- index += nextIndex;
285
- remainingString = remainingString.slice(nextIndex);
286
- return tryToMatch();
287
- };
288
- return tryToMatch();
289
- };
290
-
291
- const applyPatternMatching = ({ url, pattern }) => {
292
- assertUrlLike(pattern, "pattern");
293
- if (url && typeof url.href === "string") url = url.href;
294
- assertUrlLike(url, "url");
295
- return applyPattern({ url, pattern });
296
- };
297
-
298
- const resolveAssociations = (associations, baseUrl) => {
299
- if (baseUrl && typeof baseUrl.href === "string") baseUrl = baseUrl.href;
300
- assertUrlLike(baseUrl, "baseUrl");
301
-
302
- const associationsResolved = {};
303
- for (const key of Object.keys(associations)) {
304
- const value = associations[key];
305
- if (typeof value === "object" && value !== null) {
306
- const valueMapResolved = {};
307
- for (const pattern of Object.keys(value)) {
308
- const valueAssociated = value[pattern];
309
- let patternResolved;
310
- try {
311
- patternResolved = String(new URL(pattern, baseUrl));
312
- } catch {
313
- // it's not really an url, no need to perform url resolution nor encoding
314
- patternResolved = pattern;
315
- }
316
-
317
- valueMapResolved[patternResolved] = valueAssociated;
318
- }
319
- associationsResolved[key] = valueMapResolved;
320
- } else {
321
- associationsResolved[key] = value;
322
- }
323
- }
324
- return associationsResolved;
325
- };
326
-
327
- const asFlatAssociations = (associations) => {
328
- if (!isPlainObject(associations)) {
329
- throw new TypeError(
330
- `associations must be a plain object, got ${associations}`,
331
- );
332
- }
333
- const flatAssociations = {};
334
- for (const associationName of Object.keys(associations)) {
335
- const associationValue = associations[associationName];
336
- if (!isPlainObject(associationValue)) {
337
- continue;
338
- }
339
- for (const pattern of Object.keys(associationValue)) {
340
- const patternValue = associationValue[pattern];
341
- const previousValue = flatAssociations[pattern];
342
- if (isPlainObject(previousValue)) {
343
- flatAssociations[pattern] = {
344
- ...previousValue,
345
- [associationName]: patternValue,
346
- };
347
- } else {
348
- flatAssociations[pattern] = {
349
- [associationName]: patternValue,
350
- };
351
- }
352
- }
353
- }
354
- return flatAssociations;
355
- };
356
-
357
- const applyAssociations = ({ url, associations }) => {
358
- if (url && typeof url.href === "string") url = url.href;
359
- assertUrlLike(url);
360
- const flatAssociations = asFlatAssociations(associations);
361
- let associatedValue = {};
362
- for (const pattern of Object.keys(flatAssociations)) {
363
- const { matched } = applyPatternMatching({
364
- pattern,
365
- url,
366
- });
367
- if (matched) {
368
- const value = flatAssociations[pattern];
369
- associatedValue = deepAssign(associatedValue, value);
370
- }
371
- }
372
- return associatedValue;
373
- };
374
-
375
- const deepAssign = (firstValue, secondValue) => {
376
- if (!isPlainObject(firstValue)) {
377
- if (isPlainObject(secondValue)) {
378
- return deepAssign({}, secondValue);
379
- }
380
- return secondValue;
381
- }
382
- if (!isPlainObject(secondValue)) {
383
- return secondValue;
384
- }
385
- for (const key of Object.keys(secondValue)) {
386
- const leftPopertyValue = firstValue[key];
387
- const rightPropertyValue = secondValue[key];
388
- firstValue[key] = deepAssign(leftPopertyValue, rightPropertyValue);
389
- }
390
- return firstValue;
391
- };
392
-
393
- const urlChildMayMatch = ({ url, associations, predicate }) => {
394
- if (url && typeof url.href === "string") url = url.href;
395
- assertUrlLike(url, "url");
396
- // the function was meants to be used on url ending with '/'
397
- if (!url.endsWith("/")) {
398
- throw new Error(`url should end with /, got ${url}`);
399
- }
400
- if (typeof predicate !== "function") {
401
- throw new TypeError(`predicate must be a function, got ${predicate}`);
402
- }
403
- const flatAssociations = asFlatAssociations(associations);
404
- // for full match we must create an object to allow pattern to override previous ones
405
- let fullMatchMeta = {};
406
- let someFullMatch = false;
407
- // for partial match, any meta satisfying predicate will be valid because
408
- // we don't know for sure if pattern will still match for a file inside pathname
409
- const partialMatchMetaArray = [];
410
- for (const pattern of Object.keys(flatAssociations)) {
411
- const value = flatAssociations[pattern];
412
- const matchResult = applyPatternMatching({
413
- pattern,
414
- url,
415
- });
416
- if (matchResult.matched) {
417
- someFullMatch = true;
418
- if (isPlainObject(fullMatchMeta) && isPlainObject(value)) {
419
- fullMatchMeta = {
420
- ...fullMatchMeta,
421
- ...value,
422
- };
423
- } else {
424
- fullMatchMeta = value;
425
- }
426
- } else if (someFullMatch === false && matchResult.urlIndex >= url.length) {
427
- partialMatchMetaArray.push(value);
428
- }
429
- }
430
- if (someFullMatch) {
431
- return Boolean(predicate(fullMatchMeta));
432
- }
433
- return partialMatchMetaArray.some((partialMatchMeta) =>
434
- predicate(partialMatchMeta),
435
- );
436
- };
437
-
438
- const applyAliases = ({ url, aliases }) => {
439
- let aliasFullMatchResult;
440
- const aliasMatchingKey = Object.keys(aliases).find((key) => {
441
- const aliasMatchResult = applyPatternMatching({
442
- pattern: key,
443
- url,
444
- });
445
- if (aliasMatchResult.matched) {
446
- aliasFullMatchResult = aliasMatchResult;
447
- return true;
448
- }
449
- return false;
450
- });
451
- if (!aliasMatchingKey) {
452
- return url;
453
- }
454
- const { matchGroups } = aliasFullMatchResult;
455
- const alias = aliases[aliasMatchingKey];
456
- const parts = alias.split("*");
457
- let newUrl = "";
458
- let index = 0;
459
- for (const part of parts) {
460
- newUrl += `${part}`;
461
- if (index < parts.length - 1) {
462
- newUrl += matchGroups[index];
463
- }
464
- index++;
465
- }
466
- return newUrl;
467
- };
468
-
469
- const matches = (url, patterns) => {
470
- return Boolean(
471
- applyAssociations({
472
- url,
473
- associations: {
474
- yes: patterns,
475
- },
476
- }).yes,
477
- );
478
- };
479
-
480
- // const assertSpecifierMetaMap = (value, checkComposition = true) => {
481
- // if (!isPlainObject(value)) {
482
- // throw new TypeError(
483
- // `specifierMetaMap must be a plain object, got ${value}`,
484
- // );
485
- // }
486
- // if (checkComposition) {
487
- // const plainObject = value;
488
- // Object.keys(plainObject).forEach((key) => {
489
- // assertUrlLike(key, "specifierMetaMap key");
490
- // const value = plainObject[key];
491
- // if (value !== null && !isPlainObject(value)) {
492
- // throw new TypeError(
493
- // `specifierMetaMap value must be a plain object or null, got ${value} under key ${key}`,
494
- // );
495
- // }
496
- // });
497
- // }
498
- // };
499
- const assertUrlLike = (value, name = "url") => {
500
- if (typeof value !== "string") {
501
- throw new TypeError(`${name} must be a url string, got ${value}`);
502
- }
503
- if (isWindowsPathnameSpecifier(value)) {
504
- throw new TypeError(
505
- `${name} must be a url but looks like a windows pathname, got ${value}`,
506
- );
507
- }
508
- if (!hasScheme(value)) {
509
- throw new TypeError(
510
- `${name} must be a url and no scheme found, got ${value}`,
511
- );
512
- }
513
- };
514
- const isPlainObject = (value) => {
515
- if (value === null) {
516
- return false;
517
- }
518
- if (typeof value === "object") {
519
- if (Array.isArray(value)) {
520
- return false;
521
- }
522
- return true;
523
- }
524
- return false;
525
- };
526
- const isWindowsPathnameSpecifier = (specifier) => {
527
- const firstChar = specifier[0];
528
- if (!/[a-zA-Z]/.test(firstChar)) return false;
529
- const secondChar = specifier[1];
530
- if (secondChar !== ":") return false;
531
- const thirdChar = specifier[2];
532
- return thirdChar === "/" || thirdChar === "\\";
533
- };
534
- const hasScheme = (specifier) => /^[a-zA-Z]+:/.test(specifier);
535
-
536
- const createFilter = (patterns, url, map = (v) => v) => {
537
- const associations = resolveAssociations(
538
- {
539
- yes: patterns,
540
- },
541
- url,
542
- );
543
- return (url) => {
544
- const meta = applyAssociations({ url, associations });
545
- return Boolean(map(meta.yes));
546
- };
547
- };
548
-
549
- const URL_META = {
550
- resolveAssociations,
551
- applyAssociations,
552
- applyAliases,
553
- applyPatternMatching,
554
- urlChildMayMatch,
555
- matches,
556
- createFilter,
557
- };
558
-
559
- const pathnameToExtension = (pathname) => {
560
- const slashLastIndex = pathname.lastIndexOf("/");
561
- const filename =
562
- slashLastIndex === -1 ? pathname : pathname.slice(slashLastIndex + 1);
563
- if (filename.match(/@([0-9])+(\.[0-9]+)?(\.[0-9]+)?$/)) {
564
- return "";
565
- }
566
- const dotLastIndex = filename.lastIndexOf(".");
567
- if (dotLastIndex === -1) {
568
- return "";
569
- }
570
- // if (dotLastIndex === pathname.length - 1) return ""
571
- const extension = filename.slice(dotLastIndex);
572
- return extension;
573
- };
574
-
575
- const resourceToPathname = (resource) => {
576
- const searchSeparatorIndex = resource.indexOf("?");
577
- if (searchSeparatorIndex > -1) {
578
- return resource.slice(0, searchSeparatorIndex);
579
- }
580
- const hashIndex = resource.indexOf("#");
581
- if (hashIndex > -1) {
582
- return resource.slice(0, hashIndex);
583
- }
584
- return resource;
585
- };
586
-
587
- const urlToScheme = (url) => {
588
- const urlString = String(url);
589
- const colonIndex = urlString.indexOf(":");
590
- if (colonIndex === -1) {
591
- return "";
592
- }
593
-
594
- const scheme = urlString.slice(0, colonIndex);
595
- return scheme;
596
- };
597
-
598
- const urlToResource = (url) => {
599
- const scheme = urlToScheme(url);
600
-
601
- if (scheme === "file") {
602
- const urlAsStringWithoutFileProtocol = String(url).slice("file://".length);
603
- return urlAsStringWithoutFileProtocol;
604
- }
605
-
606
- if (scheme === "https" || scheme === "http") {
607
- // remove origin
608
- const afterProtocol = String(url).slice(scheme.length + "://".length);
609
- const pathnameSlashIndex = afterProtocol.indexOf("/", "://".length);
610
- const urlAsStringWithoutOrigin = afterProtocol.slice(pathnameSlashIndex);
611
- return urlAsStringWithoutOrigin;
612
- }
613
-
614
- const urlAsStringWithoutProtocol = String(url).slice(scheme.length + 1);
615
- return urlAsStringWithoutProtocol;
616
- };
617
-
618
- const urlToPathname = (url) => {
619
- const resource = urlToResource(url);
620
- const pathname = resourceToPathname(resource);
621
- return pathname;
622
- };
623
-
624
- const urlToFilename = (url) => {
625
- const pathname = urlToPathname(url);
626
- return pathnameToFilename(pathname);
627
- };
628
-
629
- const pathnameToFilename = (pathname) => {
630
- const pathnameBeforeLastSlash = pathname.endsWith("/")
631
- ? pathname.slice(0, -1)
632
- : pathname;
633
- const slashLastIndex = pathnameBeforeLastSlash.lastIndexOf("/");
634
- const filename =
635
- slashLastIndex === -1
636
- ? pathnameBeforeLastSlash
637
- : pathnameBeforeLastSlash.slice(slashLastIndex + 1);
638
- return filename;
639
- };
640
-
641
- const urlToBasename = (url, removeAllExtensions) => {
642
- const filename = urlToFilename(url);
643
- const basename = filenameToBasename(filename);
644
- {
645
- return basename;
646
- }
647
- };
648
-
649
- const filenameToBasename = (filename) => {
650
- const dotLastIndex = filename.lastIndexOf(".");
651
- const basename =
652
- dotLastIndex === -1 ? filename : filename.slice(0, dotLastIndex);
653
- return basename;
654
- };
655
-
656
- const urlToExtension = (url) => {
657
- const pathname = urlToPathname(url);
658
- return pathnameToExtension(pathname);
659
- };
660
-
661
- const urlToOrigin = (url) => {
662
- const urlString = String(url);
663
- if (urlString.startsWith("file://")) {
664
- return `file://`;
665
- }
666
- return new URL(urlString).origin;
667
- };
668
-
669
- const asUrlWithoutSearch = (url) => {
670
- url = String(url);
671
- if (url.includes("?")) {
672
- const urlObject = new URL(url);
673
- urlObject.search = "";
674
- return urlObject.href;
675
- }
676
- return url;
677
- };
678
-
679
- const isValidUrl = (url) => {
680
- try {
681
- // eslint-disable-next-line no-new
682
- new URL(url);
683
- return true;
684
- } catch {
685
- return false;
686
- }
687
- };
688
-
689
- const asSpecifierWithoutSearch = (specifier) => {
690
- if (isValidUrl(specifier)) {
691
- return asUrlWithoutSearch(specifier);
692
- }
693
- const [beforeQuestion] = specifier.split("?");
694
- return beforeQuestion;
695
- };
696
-
697
- // normalize url search params:
698
- // Using URLSearchParams to alter the url search params
699
- // can result into "file:///file.css?css_module"
700
- // becoming "file:///file.css?css_module="
701
- // we want to get rid of the "=" and consider it's the same url
702
- const normalizeUrl = (url) => {
703
- if (url.includes("?")) {
704
- // disable on data urls (would mess up base64 encoding)
705
- if (url.startsWith("data:")) {
706
- return url;
707
- }
708
- return url.replace(/[=](?=&|$)/g, "");
709
- }
710
- return url;
711
- };
712
-
713
- const injectQueryParamsIntoSpecifier = (specifier, params) => {
714
- if (isValidUrl(specifier)) {
715
- return injectQueryParams(specifier, params);
716
- }
717
- const [beforeQuestion, afterQuestion = ""] = specifier.split("?");
718
- const searchParams = new URLSearchParams(afterQuestion);
719
- Object.keys(params).forEach((key) => {
720
- const value = params[key];
721
- if (value === undefined) {
722
- searchParams.delete(key);
723
- } else {
724
- searchParams.set(key, value);
725
- }
726
- });
727
- let paramsString = searchParams.toString();
728
- if (paramsString) {
729
- paramsString = paramsString.replace(/[=](?=&|$)/g, "");
730
- return `${beforeQuestion}?${paramsString}`;
731
- }
732
- return beforeQuestion;
733
- };
734
-
735
- const injectQueryParams = (url, params) => {
736
- const urlObject = new URL(url);
737
- const { searchParams } = urlObject;
738
- Object.keys(params).forEach((key) => {
739
- const value = params[key];
740
- if (value === undefined) {
741
- searchParams.delete(key);
742
- } else {
743
- searchParams.set(key, value);
744
- }
745
- });
746
- const urlWithParams = urlObject.href;
747
- return normalizeUrl(urlWithParams);
748
- };
749
-
750
- const injectQueryParamWithoutEncoding = (url, key, value) => {
751
- const urlObject = new URL(url);
752
- let { origin, pathname, search, hash } = urlObject;
753
- // origin is "null" for "file://" urls with Node.js
754
- if (origin === "null" && urlObject.href.startsWith("file:")) {
755
- origin = "file://";
756
- }
757
- if (search === "") {
758
- search = `?${key}=${value}`;
759
- } else {
760
- search += `${key}=${value}`;
761
- }
762
- return `${origin}${pathname}${search}${hash}`;
763
- };
764
- const injectQueryParamIntoSpecifierWithoutEncoding = (
765
- specifier,
766
- key,
767
- value,
768
- ) => {
769
- if (isValidUrl(specifier)) {
770
- return injectQueryParamWithoutEncoding(specifier, key, value);
771
- }
772
- const [beforeQuestion, afterQuestion = ""] = specifier.split("?");
773
- const searchParams = new URLSearchParams(afterQuestion);
774
- let search = searchParams.toString();
775
- if (search === "") {
776
- search = `?${key}=${value}`;
777
- } else {
778
- search = `?${search}&${key}=${value}`;
779
- }
780
- return `${beforeQuestion}${search}`;
781
- };
782
-
783
- const renderUrlOrRelativeUrlFilename = (urlOrRelativeUrl, renderer) => {
784
- const questionIndex = urlOrRelativeUrl.indexOf("?");
785
- const beforeQuestion =
786
- questionIndex === -1
787
- ? urlOrRelativeUrl
788
- : urlOrRelativeUrl.slice(0, questionIndex);
789
- const afterQuestion =
790
- questionIndex === -1 ? "" : urlOrRelativeUrl.slice(questionIndex);
791
- const beforeLastSlash = beforeQuestion.endsWith("/")
792
- ? beforeQuestion.slice(0, -1)
793
- : beforeQuestion;
794
- const slashLastIndex = beforeLastSlash.lastIndexOf("/");
795
- const beforeFilename =
796
- slashLastIndex === -1 ? "" : beforeQuestion.slice(0, slashLastIndex + 1);
797
- const filename =
798
- slashLastIndex === -1
799
- ? beforeQuestion
800
- : beforeQuestion.slice(slashLastIndex + 1);
801
- const dotLastIndex = filename.lastIndexOf(".");
802
- const basename =
803
- dotLastIndex === -1 ? filename : filename.slice(0, dotLastIndex);
804
- const extension = dotLastIndex === -1 ? "" : filename.slice(dotLastIndex);
805
- const newFilename = renderer({
806
- basename,
807
- extension,
808
- });
809
- return `${beforeFilename}${newFilename}${afterQuestion}`;
810
- };
811
-
812
- const setUrlExtension = (url, extension) => {
813
- const origin = urlToOrigin(url);
814
- const currentExtension = urlToExtension(url);
815
- if (typeof extension === "function") {
816
- extension = extension(currentExtension);
817
- }
818
- const resource = urlToResource(url);
819
- const [pathname, search] = resource.split("?");
820
- const pathnameWithoutExtension = currentExtension
821
- ? pathname.slice(0, -currentExtension.length)
822
- : pathname;
823
- let newPathname;
824
- if (pathnameWithoutExtension.endsWith("/")) {
825
- newPathname = pathnameWithoutExtension.slice(0, -1);
826
- newPathname += extension;
827
- newPathname += "/";
828
- } else {
829
- newPathname = pathnameWithoutExtension;
830
- newPathname += extension;
831
- }
832
- return `${origin}${newPathname}${search ? `?${search}` : ""}`;
833
- };
834
-
835
- const setUrlFilename = (url, filename) => {
836
- const parentPathname = new URL("./", url).pathname;
837
- return transformUrlPathname(url, (pathname) => {
838
- if (typeof filename === "function") {
839
- filename = filename(pathnameToFilename(pathname));
840
- }
841
- return `${parentPathname}${filename}`;
842
- });
843
- };
844
-
845
- const setUrlBasename = (url, basename) => {
846
- return setUrlFilename(url, (filename) => {
847
- if (typeof basename === "function") {
848
- basename = basename(filenameToBasename(filename));
849
- }
850
- return `${basename}${urlToExtension(url)}`;
851
- });
852
- };
853
-
854
- const transformUrlPathname = (url, transformer) => {
855
- if (typeof url === "string") {
856
- const urlObject = new URL(url);
857
- const { pathname } = urlObject;
858
- const pathnameTransformed = transformer(pathname);
859
- if (pathnameTransformed === pathname) {
860
- return url;
861
- }
862
- let { origin } = urlObject;
863
- // origin is "null" for "file://" urls with Node.js
864
- if (origin === "null" && urlObject.href.startsWith("file:")) {
865
- origin = "file://";
866
- }
867
- const { search, hash } = urlObject;
868
- const urlWithPathnameTransformed = `${origin}${pathnameTransformed}${search}${hash}`;
869
- return urlWithPathnameTransformed;
870
- }
871
- const pathnameTransformed = transformer(url.pathname);
872
- url.pathname = pathnameTransformed;
873
- return url;
874
- };
875
- const ensurePathnameTrailingSlash = (url) => {
876
- return transformUrlPathname(url, (pathname) => {
877
- return pathname.endsWith("/") ? pathname : `${pathname}/`;
878
- });
879
- };
880
-
881
- const jsenvPluginInjections = (rawAssociations) => {
882
- let resolvedAssociations;
883
-
884
- return {
885
- name: "jsenv:injections",
886
- appliesDuring: "*",
887
- init: (context) => {
888
- resolvedAssociations = URL_META.resolveAssociations(
889
- { injectionsGetter: rawAssociations },
890
- context.rootDirectoryUrl,
891
- );
892
- },
893
- transformUrlContent: async (urlInfo) => {
894
- const { injectionsGetter } = URL_META.applyAssociations({
895
- url: asUrlWithoutSearch(urlInfo.url),
896
- associations: resolvedAssociations,
897
- });
898
- if (!injectionsGetter) {
899
- return null;
900
- }
901
- if (typeof injectionsGetter !== "function") {
902
- throw new TypeError("injectionsGetter must be a function");
903
- }
904
- const injections = await injectionsGetter(urlInfo);
905
- if (!injections) {
906
- return null;
907
- }
908
- const keys = Object.keys(injections);
909
- if (keys.length === 0) {
910
- return null;
911
- }
912
- return replacePlaceholders(urlInfo.content, injections, urlInfo);
913
- },
914
- };
915
- };
916
-
917
- const injectionSymbol = Symbol.for("jsenv_injection");
918
- const INJECTIONS = {
919
- optional: (value) => {
920
- return { [injectionSymbol]: "optional", value };
921
- },
922
- };
923
-
924
- // we export this because it is imported by jsenv_plugin_placeholder.js and unit test
925
- const replacePlaceholders = (content, replacements, urlInfo) => {
926
- const magicSource = createMagicSource(content);
927
- for (const key of Object.keys(replacements)) {
928
- let index = content.indexOf(key);
929
- const replacement = replacements[key];
930
- let isOptional;
931
- let value;
932
- if (replacement && replacement[injectionSymbol]) {
933
- const valueBehindSymbol = replacement[injectionSymbol];
934
- isOptional = valueBehindSymbol === "optional";
935
- value = replacement.value;
936
- } else {
937
- value = replacement;
938
- }
939
- if (index === -1) {
940
- if (!isOptional) {
941
- urlInfo.context.logger.warn(
942
- `placeholder "${key}" not found in ${urlInfo.url}.
943
- --- suggestion a ---
944
- Add "${key}" in that file.
945
- --- suggestion b ---
946
- Fix eventual typo in "${key}"?
947
- --- suggestion c ---
948
- Mark injection as optional using INJECTIONS.optional():
949
- import { INJECTIONS } from "@jsenv/core";
950
-
951
- return {
952
- "${key}": INJECTIONS.optional(${JSON.stringify(value)}),
953
- };`,
954
- );
955
- }
956
- continue;
957
- }
958
-
959
- while (index !== -1) {
960
- const start = index;
961
- const end = index + key.length;
962
- magicSource.replace({
963
- start,
964
- end,
965
- replacement:
966
- urlInfo.type === "js_classic" ||
967
- urlInfo.type === "js_module" ||
968
- urlInfo.type === "html"
969
- ? JSON.stringify(value, null, " ")
970
- : value,
971
- });
972
- index = content.indexOf(key, end);
973
- }
974
- }
975
- return magicSource.toContentAndSourcemap();
976
- };
977
-
978
- // dev
979
- const startDevServer = async (...args) => {
980
- const namespace = await import("./start_dev_server.js");
981
- return namespace.startDevServer(...args);
982
- };
983
-
984
- // build
985
- const build = async (...args) => {
986
- const namespace = await import("./build.js").then(n => n.build);
987
- return namespace.build(...args);
988
- };
989
- const startBuildServer = async (...args) => {
990
- const namespace = await import("./start_build_server.js");
991
- return namespace.startBuildServer(...args);
992
- };
993
-
994
- export { INJECTIONS, URL_META, asSpecifierWithoutSearch, asUrlWithoutSearch, build, ensurePathnameTrailingSlash, injectQueryParamIntoSpecifierWithoutEncoding, injectQueryParams, injectQueryParamsIntoSpecifier, jsenvPluginInjections, normalizeUrl, renderUrlOrRelativeUrlFilename, replacePlaceholders, setUrlBasename, setUrlExtension, setUrlFilename, startBuildServer, startDevServer, urlToBasename, urlToExtension, urlToFilename, urlToPathname };