@jsenv/core 41.4.4 → 41.4.6
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/build/build.js +202 -81
- package/dist/client/html_syntax_error/html_syntax_error.html +1 -1
- package/dist/start_dev_server/start_dev_server.js +152 -54
- package/package.json +6 -6
- package/src/build/build.js +5 -18
- package/src/build/build_specifier_manager.js +42 -4
- package/src/helpers/bare_specifier.js +18 -0
- package/src/kitchen/errors.js +29 -0
- package/src/kitchen/url_graph/url_graph.js +3 -0
- package/src/kitchen/url_graph/url_info_injections.js +20 -5
- package/src/plugins/injections/jsenv_plugin_injections.js +83 -14
- package/src/plugins/plugins.js +3 -1
- package/src/plugins/reference_analysis/js/jsenv_plugin_js_reference_analysis.js +1 -17
- package/src/plugins/resolution_node_esm/node_esm_resolver.js +1 -17
package/dist/build/build.js
CHANGED
|
@@ -333,6 +333,7 @@ ${reason}`,
|
|
|
333
333
|
}
|
|
334
334
|
return createFailedToResolveUrlError({
|
|
335
335
|
reason: `An error occured during specifier resolution`,
|
|
336
|
+
...detailsFromInjectionsOnOwner(reference),
|
|
336
337
|
...detailsFromValueThrown(error),
|
|
337
338
|
});
|
|
338
339
|
};
|
|
@@ -394,6 +395,7 @@ ${reason}`,
|
|
|
394
395
|
return createFailedToFetchUrlContentError({
|
|
395
396
|
code: "NOT_FOUND",
|
|
396
397
|
reason: "no entry on filesystem",
|
|
398
|
+
...detailsFromInjectionsOnOwner(urlInfo.firstReference),
|
|
397
399
|
});
|
|
398
400
|
}
|
|
399
401
|
}
|
|
@@ -626,6 +628,33 @@ const getFirstReferenceInProject = (reference) => {
|
|
|
626
628
|
return getFirstReferenceInProject(firstReference);
|
|
627
629
|
};
|
|
628
630
|
|
|
631
|
+
// Injections write urls in html attributes before references are analyzed, so an url
|
|
632
|
+
// that still cannot be resolved may be a placeholder no injection replaced. Rather than
|
|
633
|
+
// guessing what a placeholder looks like (the key is free-form), tell the file it comes
|
|
634
|
+
// from: injections are configured for it.
|
|
635
|
+
const detailsFromInjectionsOnOwner = (reference) => {
|
|
636
|
+
if (!reference) {
|
|
637
|
+
return {};
|
|
638
|
+
}
|
|
639
|
+
const ownerUrlInfo = reference.ownerUrlInfo;
|
|
640
|
+
if (ownerUrlInfo.type !== "html") {
|
|
641
|
+
// "jsenv-ignore" is an html attribute
|
|
642
|
+
return {};
|
|
643
|
+
}
|
|
644
|
+
const { hasInjections } = ownerUrlInfo.context;
|
|
645
|
+
if (!hasInjections || !hasInjections(ownerUrlInfo.url)) {
|
|
646
|
+
return {};
|
|
647
|
+
}
|
|
648
|
+
const { node, attributeName } = reference.astInfo || {};
|
|
649
|
+
if (!node || !attributeName) {
|
|
650
|
+
return {};
|
|
651
|
+
}
|
|
652
|
+
return {
|
|
653
|
+
suggestion: `injections are configured for this file; when "${reference.specifier}" is meant to be written by one of them, check the placeholder spelling, or add "jsenv-ignore" so jsenv leaves that url alone:
|
|
654
|
+
<${node.nodeName} jsenv-ignore ${attributeName}="${reference.specifier}" />`,
|
|
655
|
+
};
|
|
656
|
+
};
|
|
657
|
+
|
|
629
658
|
const detailsFromPluginController = (jsenvPluginsController) => {
|
|
630
659
|
const currentPlugin = jsenvPluginsController.getCurrentPlugin();
|
|
631
660
|
if (!currentPlugin) {
|
|
@@ -2073,6 +2102,9 @@ const createUrlInfo = (url, context) => {
|
|
|
2073
2102
|
contentFinalized: false,
|
|
2074
2103
|
contentSideEffects: [],
|
|
2075
2104
|
contentInjections: {},
|
|
2105
|
+
// placeholders already consumed somewhere else than the content (in a specifier),
|
|
2106
|
+
// so that not finding them in the content is not worth a warning
|
|
2107
|
+
contentInjectionUsedKeySet: new Set(),
|
|
2076
2108
|
|
|
2077
2109
|
sourcemap: null,
|
|
2078
2110
|
sourcemapIsWrong: false,
|
|
@@ -2385,6 +2417,13 @@ const INJECTIONS = {
|
|
|
2385
2417
|
},
|
|
2386
2418
|
};
|
|
2387
2419
|
|
|
2420
|
+
const readInjectionValue = (injection) => {
|
|
2421
|
+
if (injection && injection[injectionSymbol]) {
|
|
2422
|
+
return injection.value;
|
|
2423
|
+
}
|
|
2424
|
+
return injection;
|
|
2425
|
+
};
|
|
2426
|
+
|
|
2388
2427
|
const isPlaceholderInjection = (value) => {
|
|
2389
2428
|
return (
|
|
2390
2429
|
!value || !value[injectionSymbol] || value[injectionSymbol] !== "global"
|
|
@@ -2458,7 +2497,7 @@ const injectPlaceholderReplacements = (
|
|
|
2458
2497
|
for (const { key, isOptional, value } of placeholderReplacements) {
|
|
2459
2498
|
let index = content.indexOf(key);
|
|
2460
2499
|
if (index === -1) {
|
|
2461
|
-
if (!isOptional) {
|
|
2500
|
+
if (!isOptional && !urlInfo.contentInjectionUsedKeySet.has(key)) {
|
|
2462
2501
|
urlInfo.context.logger.warn(
|
|
2463
2502
|
`placeholder "${key}" not found in ${urlInfo.url}.
|
|
2464
2503
|
--- suggestion a ---
|
|
@@ -2483,7 +2522,7 @@ return {
|
|
|
2483
2522
|
magicSource.replace({
|
|
2484
2523
|
start,
|
|
2485
2524
|
end,
|
|
2486
|
-
replacement: asReplacement(value, urlInfo),
|
|
2525
|
+
replacement: asReplacement(value, urlInfo.type),
|
|
2487
2526
|
});
|
|
2488
2527
|
index = content.indexOf(key, end);
|
|
2489
2528
|
}
|
|
@@ -2494,8 +2533,8 @@ return {
|
|
|
2494
2533
|
// In JS the placeholder stands for a value, so it must be substituted by a literal.
|
|
2495
2534
|
// Everywhere else (html attributes and text, css, ...) it stands for a piece of text
|
|
2496
2535
|
// and is substituted as-is, so it can be concatenated: href="__BACKEND_URL__/users/me"
|
|
2497
|
-
const asReplacement = (value,
|
|
2498
|
-
if (
|
|
2536
|
+
const asReplacement = (value, type) => {
|
|
2537
|
+
if (type === "js_classic" || type === "js_module") {
|
|
2499
2538
|
return JSON.stringify(value, null, " ");
|
|
2500
2539
|
}
|
|
2501
2540
|
if (typeof value === "string") {
|
|
@@ -2511,7 +2550,14 @@ const injectGlobals = (content, globals, urlInfo) => {
|
|
|
2511
2550
|
if (urlInfo.type === "js_classic" || urlInfo.type === "js_module") {
|
|
2512
2551
|
return globalsInjectorOnJs(content, globals, urlInfo);
|
|
2513
2552
|
}
|
|
2514
|
-
throw new Error(
|
|
2553
|
+
throw new Error(
|
|
2554
|
+
createDetailedMessage(`cannot inject globals into "${urlInfo.type}"`, {
|
|
2555
|
+
file: urlInfo.url,
|
|
2556
|
+
...(urlInfo.isInline
|
|
2557
|
+
? { "inline content of": urlInfo.inlineUrlSite.url }
|
|
2558
|
+
: {}),
|
|
2559
|
+
}),
|
|
2560
|
+
);
|
|
2515
2561
|
};
|
|
2516
2562
|
const globalInjectorOnHtml = (content, globals, urlInfo) => {
|
|
2517
2563
|
// ideally we would inject an importmap but browser support is too low
|
|
@@ -4640,6 +4686,25 @@ const testAppliesDuring = (plugin, kitchen) => {
|
|
|
4640
4686
|
);
|
|
4641
4687
|
};
|
|
4642
4688
|
|
|
4689
|
+
// A bare specifier ("preact", "@jsenv/core/x.js") is resolved by node esm resolution,
|
|
4690
|
+
// everything else ("/a.js", "./a.js", "http://example.com/a.js") by url resolution
|
|
4691
|
+
const isBareSpecifier = (specifier) => {
|
|
4692
|
+
if (
|
|
4693
|
+
specifier[0] === "/" ||
|
|
4694
|
+
specifier.startsWith("./") ||
|
|
4695
|
+
specifier.startsWith("../")
|
|
4696
|
+
) {
|
|
4697
|
+
return false;
|
|
4698
|
+
}
|
|
4699
|
+
try {
|
|
4700
|
+
// eslint-disable-next-line no-new
|
|
4701
|
+
new URL(specifier);
|
|
4702
|
+
return false;
|
|
4703
|
+
} catch {
|
|
4704
|
+
return true;
|
|
4705
|
+
}
|
|
4706
|
+
};
|
|
4707
|
+
|
|
4643
4708
|
/*
|
|
4644
4709
|
* https://github.com/parcel-bundler/parcel/blob/v2/packages/transformers/css/src/CSSTransformer.js
|
|
4645
4710
|
*/
|
|
@@ -5581,7 +5646,7 @@ const parseAndTransformJsReferences = async (
|
|
|
5581
5646
|
let filenameHint;
|
|
5582
5647
|
if (
|
|
5583
5648
|
externalReferenceInfo.subtype === "import_dynamic" &&
|
|
5584
|
-
isBareSpecifier
|
|
5649
|
+
isBareSpecifier(externalReferenceInfo.specifier)
|
|
5585
5650
|
) {
|
|
5586
5651
|
filenameHint = `${externalReferenceInfo.specifier}.js`;
|
|
5587
5652
|
}
|
|
@@ -5671,23 +5736,6 @@ const parseAndTransformJsReferences = async (
|
|
|
5671
5736
|
return { content, sourcemap };
|
|
5672
5737
|
};
|
|
5673
5738
|
|
|
5674
|
-
const isBareSpecifier$2 = (specifier) => {
|
|
5675
|
-
if (
|
|
5676
|
-
specifier[0] === "/" ||
|
|
5677
|
-
specifier.startsWith("./") ||
|
|
5678
|
-
specifier.startsWith("../")
|
|
5679
|
-
) {
|
|
5680
|
-
return false;
|
|
5681
|
-
}
|
|
5682
|
-
try {
|
|
5683
|
-
// eslint-disable-next-line no-new
|
|
5684
|
-
new URL(specifier);
|
|
5685
|
-
return false;
|
|
5686
|
-
} catch {
|
|
5687
|
-
return true;
|
|
5688
|
-
}
|
|
5689
|
-
};
|
|
5690
|
-
|
|
5691
5739
|
const jsenvPluginReferenceExpectedTypes = () => {
|
|
5692
5740
|
const redirectJsReference = (reference) => {
|
|
5693
5741
|
const urlObject = new URL(reference.url);
|
|
@@ -6078,7 +6126,7 @@ const createBuildPackageConditions = (
|
|
|
6078
6126
|
for (const key of keys) {
|
|
6079
6127
|
const associatedValue = packageConditionsConfig[key];
|
|
6080
6128
|
|
|
6081
|
-
if (!isBareSpecifier
|
|
6129
|
+
if (!isBareSpecifier(key)) {
|
|
6082
6130
|
const url = new URL(key, rootDirectoryUrl);
|
|
6083
6131
|
associationsRaw[url] = associatedValue;
|
|
6084
6132
|
continue;
|
|
@@ -6113,7 +6161,7 @@ const createBuildPackageConditions = (
|
|
|
6113
6161
|
);
|
|
6114
6162
|
resolveConditionsFromSpecifier = (specifier, importer, { resolver }) => {
|
|
6115
6163
|
let associatedValue;
|
|
6116
|
-
if (isBareSpecifier
|
|
6164
|
+
if (isBareSpecifier(specifier)) {
|
|
6117
6165
|
const { url } = resolver({
|
|
6118
6166
|
specifier,
|
|
6119
6167
|
parentUrl: importer,
|
|
@@ -6138,7 +6186,7 @@ const createBuildPackageConditions = (
|
|
|
6138
6186
|
const nodeRuntimeEnabled = Object.keys(runtimeCompat).includes("node");
|
|
6139
6187
|
// https://nodejs.org/api/esm.html#resolver-algorithm-specification
|
|
6140
6188
|
const devResolver = (specifier, importer, { resolver }) => {
|
|
6141
|
-
if (isBareSpecifier
|
|
6189
|
+
if (isBareSpecifier(specifier)) {
|
|
6142
6190
|
const { url } = resolver({
|
|
6143
6191
|
specifier,
|
|
6144
6192
|
parentUrl: importer,
|
|
@@ -6202,7 +6250,7 @@ const createBuildPackageConditions = (
|
|
|
6202
6250
|
const associations = URL_META.resolveAssociations(
|
|
6203
6251
|
{ applies: value },
|
|
6204
6252
|
(pattern) => {
|
|
6205
|
-
if (isBareSpecifier
|
|
6253
|
+
if (isBareSpecifier(pattern)) {
|
|
6206
6254
|
try {
|
|
6207
6255
|
if (pattern.endsWith("/")) {
|
|
6208
6256
|
// avoid package path not exported
|
|
@@ -6225,7 +6273,7 @@ const createBuildPackageConditions = (
|
|
|
6225
6273
|
},
|
|
6226
6274
|
);
|
|
6227
6275
|
customResolver = (specifier, importer, { resolver }) => {
|
|
6228
|
-
if (isBareSpecifier
|
|
6276
|
+
if (isBareSpecifier(specifier)) {
|
|
6229
6277
|
const { url } = resolver({
|
|
6230
6278
|
specifier,
|
|
6231
6279
|
parentUrl: importer,
|
|
@@ -6357,23 +6405,6 @@ const createResolverWithFallbackOnError = (mainResolver, fallbackResolver) => {
|
|
|
6357
6405
|
};
|
|
6358
6406
|
};
|
|
6359
6407
|
|
|
6360
|
-
const isBareSpecifier$1 = (specifier) => {
|
|
6361
|
-
if (
|
|
6362
|
-
specifier[0] === "/" ||
|
|
6363
|
-
specifier.startsWith("./") ||
|
|
6364
|
-
specifier.startsWith("../")
|
|
6365
|
-
) {
|
|
6366
|
-
return false;
|
|
6367
|
-
}
|
|
6368
|
-
try {
|
|
6369
|
-
// eslint-disable-next-line no-new
|
|
6370
|
-
new URL(specifier);
|
|
6371
|
-
return false;
|
|
6372
|
-
} catch {
|
|
6373
|
-
return true;
|
|
6374
|
-
}
|
|
6375
|
-
};
|
|
6376
|
-
|
|
6377
6408
|
const jsenvPluginNodeEsmResolution = ({
|
|
6378
6409
|
packageDirectory,
|
|
6379
6410
|
resolutionConfig = {},
|
|
@@ -7641,6 +7672,7 @@ const jsenvPluginInjections = (rawAssociations) => {
|
|
|
7641
7672
|
}
|
|
7642
7673
|
return null;
|
|
7643
7674
|
};
|
|
7675
|
+
const injectionsForUrlInfoMap = new WeakMap();
|
|
7644
7676
|
let getInjections = null;
|
|
7645
7677
|
|
|
7646
7678
|
return {
|
|
@@ -7652,11 +7684,20 @@ const jsenvPluginInjections = (rawAssociations) => {
|
|
|
7652
7684
|
{ injectionsGetter: rawAssociations },
|
|
7653
7685
|
context.rootDirectoryUrl,
|
|
7654
7686
|
);
|
|
7655
|
-
const
|
|
7687
|
+
const findInjectionsGetterForUrl = (url) => {
|
|
7656
7688
|
const { injectionsGetter } = URL_META.applyAssociations({
|
|
7657
|
-
url: asUrlWithoutSearch(
|
|
7689
|
+
url: asUrlWithoutSearch(url),
|
|
7658
7690
|
associations: resolvedAssociations,
|
|
7659
7691
|
});
|
|
7692
|
+
return injectionsGetter;
|
|
7693
|
+
};
|
|
7694
|
+
// errors and the build read this to know an unresolved url may come from
|
|
7695
|
+
// an injection, and word what they report accordingly
|
|
7696
|
+
context.hasInjections = (url) => {
|
|
7697
|
+
return Boolean(findInjectionsGetterForUrl(url));
|
|
7698
|
+
};
|
|
7699
|
+
const findInjectionsGetter = (urlInfo) => {
|
|
7700
|
+
const injectionsGetter = findInjectionsGetterForUrl(urlInfo.url);
|
|
7660
7701
|
if (injectionsGetter) {
|
|
7661
7702
|
return { injectionsGetter, isInherited: false };
|
|
7662
7703
|
}
|
|
@@ -7688,9 +7729,7 @@ const jsenvPluginInjections = (rawAssociations) => {
|
|
|
7688
7729
|
if (!injections || !isInherited) {
|
|
7689
7730
|
return injections;
|
|
7690
7731
|
}
|
|
7691
|
-
|
|
7692
|
-
// is expected in one of them, not in each
|
|
7693
|
-
return asOptionalInjections(injections);
|
|
7732
|
+
return asInheritedInjections(injections);
|
|
7694
7733
|
};
|
|
7695
7734
|
}
|
|
7696
7735
|
},
|
|
@@ -7707,22 +7746,79 @@ const jsenvPluginInjections = (rawAssociations) => {
|
|
|
7707
7746
|
contentInjections: defaultInjections,
|
|
7708
7747
|
};
|
|
7709
7748
|
}
|
|
7749
|
+
injectionsForUrlInfoMap.set(urlInfo, injections);
|
|
7710
7750
|
return {
|
|
7711
|
-
contentInjections: {
|
|
7712
|
-
...defaultInjections,
|
|
7713
|
-
...injections,
|
|
7714
|
-
},
|
|
7751
|
+
contentInjections: { ...defaultInjections, ...injections },
|
|
7715
7752
|
};
|
|
7716
7753
|
},
|
|
7754
|
+
// The content still holds the placeholders when references are analyzed: they are
|
|
7755
|
+
// replaced at the very end, once the type of every inline content is known.
|
|
7756
|
+
// A specifier must not wait for that, it would resolve "__BACKEND_URL__/users/me"
|
|
7757
|
+
// as a file sitting next to the document. Only the placeholder is resolved here,
|
|
7758
|
+
// then the specifier goes to whoever resolves this kind of url (node esm, web, ...)
|
|
7759
|
+
resolveReference: (reference) => {
|
|
7760
|
+
const { ownerUrlInfo } = reference;
|
|
7761
|
+
const injections = injectionsForUrlInfoMap.get(ownerUrlInfo);
|
|
7762
|
+
if (!injections) {
|
|
7763
|
+
return null;
|
|
7764
|
+
}
|
|
7765
|
+
const { specifier, keySet } = injectIntoSpecifier(
|
|
7766
|
+
reference.specifier,
|
|
7767
|
+
injections,
|
|
7768
|
+
);
|
|
7769
|
+
if (keySet.size === 0) {
|
|
7770
|
+
return null;
|
|
7771
|
+
}
|
|
7772
|
+
reference.specifier = specifier;
|
|
7773
|
+
for (const key of keySet) {
|
|
7774
|
+
// rewriting the specifier takes the placeholder out of the content,
|
|
7775
|
+
// so the injection step must not expect to find it there
|
|
7776
|
+
ownerUrlInfo.contentInjectionUsedKeySet.add(key);
|
|
7777
|
+
}
|
|
7778
|
+
return null;
|
|
7779
|
+
},
|
|
7717
7780
|
};
|
|
7718
7781
|
};
|
|
7719
7782
|
|
|
7720
|
-
const
|
|
7721
|
-
|
|
7783
|
+
const injectIntoSpecifier = (specifier, injections) => {
|
|
7784
|
+
let specifierInjected = specifier;
|
|
7785
|
+
const keySet = new Set();
|
|
7722
7786
|
for (const key of Object.keys(injections)) {
|
|
7723
|
-
|
|
7787
|
+
if (!specifierInjected.includes(key)) {
|
|
7788
|
+
continue;
|
|
7789
|
+
}
|
|
7790
|
+
const injection = injections[key];
|
|
7791
|
+
if (!isPlaceholderInjection(injection)) {
|
|
7792
|
+
continue;
|
|
7793
|
+
}
|
|
7794
|
+
const value = readInjectionValue(injection);
|
|
7795
|
+
if (typeof value !== "string") {
|
|
7796
|
+
continue;
|
|
7797
|
+
}
|
|
7798
|
+
specifierInjected = specifierInjected.replaceAll(key, value);
|
|
7799
|
+
keySet.add(key);
|
|
7724
7800
|
}
|
|
7725
|
-
return
|
|
7801
|
+
return { specifier: specifierInjected, keySet };
|
|
7802
|
+
};
|
|
7803
|
+
|
|
7804
|
+
// What a file inlines (a <script> or a <style> inside html) is authored in that file
|
|
7805
|
+
// and inherits its injections, with two adjustments:
|
|
7806
|
+
// - a global belongs to the file itself, injecting it into each inline content would
|
|
7807
|
+
// repeat it and reach types that cannot receive globals (css)
|
|
7808
|
+
// - a placeholder configured for the file is expected in one of its inline contents,
|
|
7809
|
+
// not in each, so a missing one is not worth a warning
|
|
7810
|
+
const asInheritedInjections = (injections) => {
|
|
7811
|
+
const inheritedInjections = {};
|
|
7812
|
+
for (const key of Object.keys(injections)) {
|
|
7813
|
+
const value = injections[key];
|
|
7814
|
+
if (isPlaceholderInjection(value)) {
|
|
7815
|
+
inheritedInjections[key] = INJECTIONS.optional(value);
|
|
7816
|
+
}
|
|
7817
|
+
}
|
|
7818
|
+
if (Object.keys(inheritedInjections).length === 0) {
|
|
7819
|
+
return null;
|
|
7820
|
+
}
|
|
7821
|
+
return inheritedInjections;
|
|
7726
7822
|
};
|
|
7727
7823
|
|
|
7728
7824
|
/*
|
|
@@ -9779,8 +9875,10 @@ const getCorePlugins = ({
|
|
|
9779
9875
|
...(packageBundle
|
|
9780
9876
|
? [jsenvPluginWorkspaceBundle({ packageDirectory })]
|
|
9781
9877
|
: []),
|
|
9782
|
-
|
|
9878
|
+
// before reference analysis: an url written by an injection must hold its
|
|
9879
|
+
// final value when references are analyzed
|
|
9783
9880
|
jsenvPluginInjections(injections),
|
|
9881
|
+
jsenvPluginReferenceAnalysis(referenceAnalysis),
|
|
9784
9882
|
jsenvPluginTranspilation(transpilation),
|
|
9785
9883
|
// "jsenvPluginInlining" must be very soon because all other plugins will react differently once they see the file is inlined
|
|
9786
9884
|
...(inlining ? [jsenvPluginInlining()] : []),
|
|
@@ -11123,7 +11221,7 @@ const createBuildSpecifierManager = ({
|
|
|
11123
11221
|
|
|
11124
11222
|
prepareResyncResourceHints: ({ registerHtmlRefine }) => {
|
|
11125
11223
|
const hintToInjectMap = new Map();
|
|
11126
|
-
registerHtmlRefine((htmlAst, { registerHtmlMutation }) => {
|
|
11224
|
+
registerHtmlRefine((htmlAst, { registerHtmlMutation, htmlUrlInfo }) => {
|
|
11127
11225
|
visitHtmlNodes(htmlAst, {
|
|
11128
11226
|
link: (node) => {
|
|
11129
11227
|
if (getHtmlNodeAttribute(node, "jsenv-ignore") !== undefined) {
|
|
@@ -11147,12 +11245,39 @@ const createBuildSpecifierManager = ({
|
|
|
11147
11245
|
return;
|
|
11148
11246
|
}
|
|
11149
11247
|
const rawUrl = href;
|
|
11248
|
+
if (targetsAFileThatDoesNotExist(rawUrl)) {
|
|
11249
|
+
// this hint never designated anything, so nothing can explain its
|
|
11250
|
+
// removal; taking away markup written by hand on a guess is worse
|
|
11251
|
+
// than leaving it there
|
|
11252
|
+
logger.warn(
|
|
11253
|
+
createDetailedMessage(
|
|
11254
|
+
`${UNICODE.WARNING} resource hint kept as is: "${href}" cannot be resolved`,
|
|
11255
|
+
{
|
|
11256
|
+
"html file": htmlUrlInfo.url,
|
|
11257
|
+
...(rawKitchen.context.hasInjections
|
|
11258
|
+
? {
|
|
11259
|
+
suggestion: `when that url is written by an injection, jsenv resolves it in html attributes before analyzing references; check the placeholder spelling`,
|
|
11260
|
+
}
|
|
11261
|
+
: {}),
|
|
11262
|
+
},
|
|
11263
|
+
),
|
|
11264
|
+
);
|
|
11265
|
+
// the build url means nothing for something jsenv could not resolve,
|
|
11266
|
+
// and it would leak a local path into the build
|
|
11267
|
+
registerHtmlMutation(() => {
|
|
11268
|
+
setHtmlNodeAttributes(node, {
|
|
11269
|
+
href: urlToRelativeUrl(rawUrl, htmlUrlInfo.url),
|
|
11270
|
+
});
|
|
11271
|
+
});
|
|
11272
|
+
return;
|
|
11273
|
+
}
|
|
11150
11274
|
const finalUrl = internalRedirections.get(rawUrl) || rawUrl;
|
|
11151
11275
|
const urlInfo = finalKitchen.graph.getUrlInfo(finalUrl);
|
|
11152
11276
|
if (!urlInfo) {
|
|
11153
|
-
if (
|
|
11154
|
-
//
|
|
11155
|
-
//
|
|
11277
|
+
if (!href.startsWith("file:")) {
|
|
11278
|
+
// the hint designates a resource jsenv does not own: an origin for
|
|
11279
|
+
// preconnect/dns-prefetch, a remote file for the others. The author
|
|
11280
|
+
// knows what the page will request at runtime, keep it as-is.
|
|
11156
11281
|
return;
|
|
11157
11282
|
}
|
|
11158
11283
|
logger.warn(
|
|
@@ -11597,6 +11722,16 @@ const asBuildUrlVersioned = ({
|
|
|
11597
11722
|
return `${buildDirectoryUrl}${pathname}${search}${hash}`;
|
|
11598
11723
|
};
|
|
11599
11724
|
|
|
11725
|
+
const targetsAFileThatDoesNotExist = (url) => {
|
|
11726
|
+
if (!url.startsWith("file:")) {
|
|
11727
|
+
return false;
|
|
11728
|
+
}
|
|
11729
|
+
const urlObject = new URL(url);
|
|
11730
|
+
urlObject.search = "";
|
|
11731
|
+
urlObject.hash = "";
|
|
11732
|
+
return !existsSync(urlObject);
|
|
11733
|
+
};
|
|
11734
|
+
|
|
11600
11735
|
// import { ANSI } from "@jsenv/humanize";
|
|
11601
11736
|
|
|
11602
11737
|
const createBuildUrlsGenerator = ({
|
|
@@ -13320,7 +13455,10 @@ const prepareEntryPointBuild = async (
|
|
|
13320
13455
|
const registerHtmlMutation = (callback) => {
|
|
13321
13456
|
htmlMutationCallbackSet.add(callback);
|
|
13322
13457
|
};
|
|
13323
|
-
htmlRefine(htmlAst, {
|
|
13458
|
+
htmlRefine(htmlAst, {
|
|
13459
|
+
registerHtmlMutation,
|
|
13460
|
+
htmlUrlInfo: urlInfo,
|
|
13461
|
+
});
|
|
13324
13462
|
for (const htmlMutationCallback of htmlMutationCallbackSet) {
|
|
13325
13463
|
htmlMutationCallback();
|
|
13326
13464
|
}
|
|
@@ -13405,21 +13543,4 @@ const prepareEntryPointBuild = async (
|
|
|
13405
13543
|
};
|
|
13406
13544
|
};
|
|
13407
13545
|
|
|
13408
|
-
const isBareSpecifier = (specifier) => {
|
|
13409
|
-
if (
|
|
13410
|
-
specifier[0] === "/" ||
|
|
13411
|
-
specifier.startsWith("./") ||
|
|
13412
|
-
specifier.startsWith("../")
|
|
13413
|
-
) {
|
|
13414
|
-
return false;
|
|
13415
|
-
}
|
|
13416
|
-
try {
|
|
13417
|
-
// eslint-disable-next-line no-new
|
|
13418
|
-
new URL(specifier);
|
|
13419
|
-
return false;
|
|
13420
|
-
} catch {
|
|
13421
|
-
return true;
|
|
13422
|
-
}
|
|
13423
|
-
};
|
|
13424
|
-
|
|
13425
13546
|
export { build };
|
|
@@ -2208,6 +2208,25 @@ const isWebWorkerUrlInfo = (urlInfo) => {
|
|
|
2208
2208
|
// return false
|
|
2209
2209
|
// }
|
|
2210
2210
|
|
|
2211
|
+
// A bare specifier ("preact", "@jsenv/core/x.js") is resolved by node esm resolution,
|
|
2212
|
+
// everything else ("/a.js", "./a.js", "http://example.com/a.js") by url resolution
|
|
2213
|
+
const isBareSpecifier = (specifier) => {
|
|
2214
|
+
if (
|
|
2215
|
+
specifier[0] === "/" ||
|
|
2216
|
+
specifier.startsWith("./") ||
|
|
2217
|
+
specifier.startsWith("../")
|
|
2218
|
+
) {
|
|
2219
|
+
return false;
|
|
2220
|
+
}
|
|
2221
|
+
try {
|
|
2222
|
+
// eslint-disable-next-line no-new
|
|
2223
|
+
new URL(specifier);
|
|
2224
|
+
return false;
|
|
2225
|
+
} catch {
|
|
2226
|
+
return true;
|
|
2227
|
+
}
|
|
2228
|
+
};
|
|
2229
|
+
|
|
2211
2230
|
const jsenvPluginJsReferenceAnalysis = ({ inlineContent }) => {
|
|
2212
2231
|
return [
|
|
2213
2232
|
{
|
|
@@ -2304,7 +2323,7 @@ const parseAndTransformJsReferences = async (
|
|
|
2304
2323
|
let filenameHint;
|
|
2305
2324
|
if (
|
|
2306
2325
|
externalReferenceInfo.subtype === "import_dynamic" &&
|
|
2307
|
-
isBareSpecifier
|
|
2326
|
+
isBareSpecifier(externalReferenceInfo.specifier)
|
|
2308
2327
|
) {
|
|
2309
2328
|
filenameHint = `${externalReferenceInfo.specifier}.js`;
|
|
2310
2329
|
}
|
|
@@ -2394,23 +2413,6 @@ const parseAndTransformJsReferences = async (
|
|
|
2394
2413
|
return { content, sourcemap };
|
|
2395
2414
|
};
|
|
2396
2415
|
|
|
2397
|
-
const isBareSpecifier$1 = (specifier) => {
|
|
2398
|
-
if (
|
|
2399
|
-
specifier[0] === "/" ||
|
|
2400
|
-
specifier.startsWith("./") ||
|
|
2401
|
-
specifier.startsWith("../")
|
|
2402
|
-
) {
|
|
2403
|
-
return false;
|
|
2404
|
-
}
|
|
2405
|
-
try {
|
|
2406
|
-
// eslint-disable-next-line no-new
|
|
2407
|
-
new URL(specifier);
|
|
2408
|
-
return false;
|
|
2409
|
-
} catch {
|
|
2410
|
-
return true;
|
|
2411
|
-
}
|
|
2412
|
-
};
|
|
2413
|
-
|
|
2414
2416
|
const jsenvPluginReferenceExpectedTypes = () => {
|
|
2415
2417
|
const redirectJsReference = (reference) => {
|
|
2416
2418
|
const urlObject = new URL(reference.url);
|
|
@@ -3080,23 +3082,6 @@ const createResolverWithFallbackOnError = (mainResolver, fallbackResolver) => {
|
|
|
3080
3082
|
};
|
|
3081
3083
|
};
|
|
3082
3084
|
|
|
3083
|
-
const isBareSpecifier = (specifier) => {
|
|
3084
|
-
if (
|
|
3085
|
-
specifier[0] === "/" ||
|
|
3086
|
-
specifier.startsWith("./") ||
|
|
3087
|
-
specifier.startsWith("../")
|
|
3088
|
-
) {
|
|
3089
|
-
return false;
|
|
3090
|
-
}
|
|
3091
|
-
try {
|
|
3092
|
-
// eslint-disable-next-line no-new
|
|
3093
|
-
new URL(specifier);
|
|
3094
|
-
return false;
|
|
3095
|
-
} catch {
|
|
3096
|
-
return true;
|
|
3097
|
-
}
|
|
3098
|
-
};
|
|
3099
|
-
|
|
3100
3085
|
const jsenvPluginNodeEsmResolution = ({
|
|
3101
3086
|
packageDirectory,
|
|
3102
3087
|
resolutionConfig = {},
|
|
@@ -4563,6 +4548,7 @@ ${reason}`,
|
|
|
4563
4548
|
}
|
|
4564
4549
|
return createFailedToResolveUrlError({
|
|
4565
4550
|
reason: `An error occured during specifier resolution`,
|
|
4551
|
+
...detailsFromInjectionsOnOwner(reference),
|
|
4566
4552
|
...detailsFromValueThrown(error),
|
|
4567
4553
|
});
|
|
4568
4554
|
};
|
|
@@ -4624,6 +4610,7 @@ ${reason}`,
|
|
|
4624
4610
|
return createFailedToFetchUrlContentError({
|
|
4625
4611
|
code: "NOT_FOUND",
|
|
4626
4612
|
reason: "no entry on filesystem",
|
|
4613
|
+
...detailsFromInjectionsOnOwner(urlInfo.firstReference),
|
|
4627
4614
|
});
|
|
4628
4615
|
}
|
|
4629
4616
|
}
|
|
@@ -4856,6 +4843,33 @@ const getFirstReferenceInProject = (reference) => {
|
|
|
4856
4843
|
return getFirstReferenceInProject(firstReference);
|
|
4857
4844
|
};
|
|
4858
4845
|
|
|
4846
|
+
// Injections write urls in html attributes before references are analyzed, so an url
|
|
4847
|
+
// that still cannot be resolved may be a placeholder no injection replaced. Rather than
|
|
4848
|
+
// guessing what a placeholder looks like (the key is free-form), tell the file it comes
|
|
4849
|
+
// from: injections are configured for it.
|
|
4850
|
+
const detailsFromInjectionsOnOwner = (reference) => {
|
|
4851
|
+
if (!reference) {
|
|
4852
|
+
return {};
|
|
4853
|
+
}
|
|
4854
|
+
const ownerUrlInfo = reference.ownerUrlInfo;
|
|
4855
|
+
if (ownerUrlInfo.type !== "html") {
|
|
4856
|
+
// "jsenv-ignore" is an html attribute
|
|
4857
|
+
return {};
|
|
4858
|
+
}
|
|
4859
|
+
const { hasInjections } = ownerUrlInfo.context;
|
|
4860
|
+
if (!hasInjections || !hasInjections(ownerUrlInfo.url)) {
|
|
4861
|
+
return {};
|
|
4862
|
+
}
|
|
4863
|
+
const { node, attributeName } = reference.astInfo || {};
|
|
4864
|
+
if (!node || !attributeName) {
|
|
4865
|
+
return {};
|
|
4866
|
+
}
|
|
4867
|
+
return {
|
|
4868
|
+
suggestion: `injections are configured for this file; when "${reference.specifier}" is meant to be written by one of them, check the placeholder spelling, or add "jsenv-ignore" so jsenv leaves that url alone:
|
|
4869
|
+
<${node.nodeName} jsenv-ignore ${attributeName}="${reference.specifier}" />`,
|
|
4870
|
+
};
|
|
4871
|
+
};
|
|
4872
|
+
|
|
4859
4873
|
const detailsFromPluginController = (jsenvPluginsController) => {
|
|
4860
4874
|
const currentPlugin = jsenvPluginsController.getCurrentPlugin();
|
|
4861
4875
|
if (!currentPlugin) {
|
|
@@ -5019,6 +5033,13 @@ const INJECTIONS = {
|
|
|
5019
5033
|
},
|
|
5020
5034
|
};
|
|
5021
5035
|
|
|
5036
|
+
const readInjectionValue = (injection) => {
|
|
5037
|
+
if (injection && injection[injectionSymbol]) {
|
|
5038
|
+
return injection.value;
|
|
5039
|
+
}
|
|
5040
|
+
return injection;
|
|
5041
|
+
};
|
|
5042
|
+
|
|
5022
5043
|
const isPlaceholderInjection = (value) => {
|
|
5023
5044
|
return (
|
|
5024
5045
|
!value || !value[injectionSymbol] || value[injectionSymbol] !== "global"
|
|
@@ -5092,7 +5113,7 @@ const injectPlaceholderReplacements = (
|
|
|
5092
5113
|
for (const { key, isOptional, value } of placeholderReplacements) {
|
|
5093
5114
|
let index = content.indexOf(key);
|
|
5094
5115
|
if (index === -1) {
|
|
5095
|
-
if (!isOptional) {
|
|
5116
|
+
if (!isOptional && !urlInfo.contentInjectionUsedKeySet.has(key)) {
|
|
5096
5117
|
urlInfo.context.logger.warn(
|
|
5097
5118
|
`placeholder "${key}" not found in ${urlInfo.url}.
|
|
5098
5119
|
--- suggestion a ---
|
|
@@ -5117,7 +5138,7 @@ return {
|
|
|
5117
5138
|
magicSource.replace({
|
|
5118
5139
|
start,
|
|
5119
5140
|
end,
|
|
5120
|
-
replacement: asReplacement(value, urlInfo),
|
|
5141
|
+
replacement: asReplacement(value, urlInfo.type),
|
|
5121
5142
|
});
|
|
5122
5143
|
index = content.indexOf(key, end);
|
|
5123
5144
|
}
|
|
@@ -5128,8 +5149,8 @@ return {
|
|
|
5128
5149
|
// In JS the placeholder stands for a value, so it must be substituted by a literal.
|
|
5129
5150
|
// Everywhere else (html attributes and text, css, ...) it stands for a piece of text
|
|
5130
5151
|
// and is substituted as-is, so it can be concatenated: href="__BACKEND_URL__/users/me"
|
|
5131
|
-
const asReplacement = (value,
|
|
5132
|
-
if (
|
|
5152
|
+
const asReplacement = (value, type) => {
|
|
5153
|
+
if (type === "js_classic" || type === "js_module") {
|
|
5133
5154
|
return JSON.stringify(value, null, " ");
|
|
5134
5155
|
}
|
|
5135
5156
|
if (typeof value === "string") {
|
|
@@ -5145,7 +5166,14 @@ const injectGlobals = (content, globals, urlInfo) => {
|
|
|
5145
5166
|
if (urlInfo.type === "js_classic" || urlInfo.type === "js_module") {
|
|
5146
5167
|
return globalsInjectorOnJs(content, globals, urlInfo);
|
|
5147
5168
|
}
|
|
5148
|
-
throw new Error(
|
|
5169
|
+
throw new Error(
|
|
5170
|
+
createDetailedMessage(`cannot inject globals into "${urlInfo.type}"`, {
|
|
5171
|
+
file: urlInfo.url,
|
|
5172
|
+
...(urlInfo.isInline
|
|
5173
|
+
? { "inline content of": urlInfo.inlineUrlSite.url }
|
|
5174
|
+
: {}),
|
|
5175
|
+
}),
|
|
5176
|
+
);
|
|
5149
5177
|
};
|
|
5150
5178
|
const globalInjectorOnHtml = (content, globals, urlInfo) => {
|
|
5151
5179
|
// ideally we would inject an importmap but browser support is too low
|
|
@@ -5200,6 +5228,7 @@ const jsenvPluginInjections = (rawAssociations) => {
|
|
|
5200
5228
|
}
|
|
5201
5229
|
return null;
|
|
5202
5230
|
};
|
|
5231
|
+
const injectionsForUrlInfoMap = new WeakMap();
|
|
5203
5232
|
let getInjections = null;
|
|
5204
5233
|
|
|
5205
5234
|
return {
|
|
@@ -5211,11 +5240,20 @@ const jsenvPluginInjections = (rawAssociations) => {
|
|
|
5211
5240
|
{ injectionsGetter: rawAssociations },
|
|
5212
5241
|
context.rootDirectoryUrl,
|
|
5213
5242
|
);
|
|
5214
|
-
const
|
|
5243
|
+
const findInjectionsGetterForUrl = (url) => {
|
|
5215
5244
|
const { injectionsGetter } = URL_META.applyAssociations({
|
|
5216
|
-
url: asUrlWithoutSearch(
|
|
5245
|
+
url: asUrlWithoutSearch(url),
|
|
5217
5246
|
associations: resolvedAssociations,
|
|
5218
5247
|
});
|
|
5248
|
+
return injectionsGetter;
|
|
5249
|
+
};
|
|
5250
|
+
// errors and the build read this to know an unresolved url may come from
|
|
5251
|
+
// an injection, and word what they report accordingly
|
|
5252
|
+
context.hasInjections = (url) => {
|
|
5253
|
+
return Boolean(findInjectionsGetterForUrl(url));
|
|
5254
|
+
};
|
|
5255
|
+
const findInjectionsGetter = (urlInfo) => {
|
|
5256
|
+
const injectionsGetter = findInjectionsGetterForUrl(urlInfo.url);
|
|
5219
5257
|
if (injectionsGetter) {
|
|
5220
5258
|
return { injectionsGetter, isInherited: false };
|
|
5221
5259
|
}
|
|
@@ -5247,9 +5285,7 @@ const jsenvPluginInjections = (rawAssociations) => {
|
|
|
5247
5285
|
if (!injections || !isInherited) {
|
|
5248
5286
|
return injections;
|
|
5249
5287
|
}
|
|
5250
|
-
|
|
5251
|
-
// is expected in one of them, not in each
|
|
5252
|
-
return asOptionalInjections(injections);
|
|
5288
|
+
return asInheritedInjections(injections);
|
|
5253
5289
|
};
|
|
5254
5290
|
}
|
|
5255
5291
|
},
|
|
@@ -5266,22 +5302,79 @@ const jsenvPluginInjections = (rawAssociations) => {
|
|
|
5266
5302
|
contentInjections: defaultInjections,
|
|
5267
5303
|
};
|
|
5268
5304
|
}
|
|
5305
|
+
injectionsForUrlInfoMap.set(urlInfo, injections);
|
|
5269
5306
|
return {
|
|
5270
|
-
contentInjections: {
|
|
5271
|
-
...defaultInjections,
|
|
5272
|
-
...injections,
|
|
5273
|
-
},
|
|
5307
|
+
contentInjections: { ...defaultInjections, ...injections },
|
|
5274
5308
|
};
|
|
5275
5309
|
},
|
|
5310
|
+
// The content still holds the placeholders when references are analyzed: they are
|
|
5311
|
+
// replaced at the very end, once the type of every inline content is known.
|
|
5312
|
+
// A specifier must not wait for that, it would resolve "__BACKEND_URL__/users/me"
|
|
5313
|
+
// as a file sitting next to the document. Only the placeholder is resolved here,
|
|
5314
|
+
// then the specifier goes to whoever resolves this kind of url (node esm, web, ...)
|
|
5315
|
+
resolveReference: (reference) => {
|
|
5316
|
+
const { ownerUrlInfo } = reference;
|
|
5317
|
+
const injections = injectionsForUrlInfoMap.get(ownerUrlInfo);
|
|
5318
|
+
if (!injections) {
|
|
5319
|
+
return null;
|
|
5320
|
+
}
|
|
5321
|
+
const { specifier, keySet } = injectIntoSpecifier(
|
|
5322
|
+
reference.specifier,
|
|
5323
|
+
injections,
|
|
5324
|
+
);
|
|
5325
|
+
if (keySet.size === 0) {
|
|
5326
|
+
return null;
|
|
5327
|
+
}
|
|
5328
|
+
reference.specifier = specifier;
|
|
5329
|
+
for (const key of keySet) {
|
|
5330
|
+
// rewriting the specifier takes the placeholder out of the content,
|
|
5331
|
+
// so the injection step must not expect to find it there
|
|
5332
|
+
ownerUrlInfo.contentInjectionUsedKeySet.add(key);
|
|
5333
|
+
}
|
|
5334
|
+
return null;
|
|
5335
|
+
},
|
|
5276
5336
|
};
|
|
5277
5337
|
};
|
|
5278
5338
|
|
|
5279
|
-
const
|
|
5280
|
-
|
|
5339
|
+
const injectIntoSpecifier = (specifier, injections) => {
|
|
5340
|
+
let specifierInjected = specifier;
|
|
5341
|
+
const keySet = new Set();
|
|
5281
5342
|
for (const key of Object.keys(injections)) {
|
|
5282
|
-
|
|
5343
|
+
if (!specifierInjected.includes(key)) {
|
|
5344
|
+
continue;
|
|
5345
|
+
}
|
|
5346
|
+
const injection = injections[key];
|
|
5347
|
+
if (!isPlaceholderInjection(injection)) {
|
|
5348
|
+
continue;
|
|
5349
|
+
}
|
|
5350
|
+
const value = readInjectionValue(injection);
|
|
5351
|
+
if (typeof value !== "string") {
|
|
5352
|
+
continue;
|
|
5353
|
+
}
|
|
5354
|
+
specifierInjected = specifierInjected.replaceAll(key, value);
|
|
5355
|
+
keySet.add(key);
|
|
5356
|
+
}
|
|
5357
|
+
return { specifier: specifierInjected, keySet };
|
|
5358
|
+
};
|
|
5359
|
+
|
|
5360
|
+
// What a file inlines (a <script> or a <style> inside html) is authored in that file
|
|
5361
|
+
// and inherits its injections, with two adjustments:
|
|
5362
|
+
// - a global belongs to the file itself, injecting it into each inline content would
|
|
5363
|
+
// repeat it and reach types that cannot receive globals (css)
|
|
5364
|
+
// - a placeholder configured for the file is expected in one of its inline contents,
|
|
5365
|
+
// not in each, so a missing one is not worth a warning
|
|
5366
|
+
const asInheritedInjections = (injections) => {
|
|
5367
|
+
const inheritedInjections = {};
|
|
5368
|
+
for (const key of Object.keys(injections)) {
|
|
5369
|
+
const value = injections[key];
|
|
5370
|
+
if (isPlaceholderInjection(value)) {
|
|
5371
|
+
inheritedInjections[key] = INJECTIONS.optional(value);
|
|
5372
|
+
}
|
|
5373
|
+
}
|
|
5374
|
+
if (Object.keys(inheritedInjections).length === 0) {
|
|
5375
|
+
return null;
|
|
5283
5376
|
}
|
|
5284
|
-
return
|
|
5377
|
+
return inheritedInjections;
|
|
5285
5378
|
};
|
|
5286
5379
|
|
|
5287
5380
|
const jsenvPluginInliningAsDataUrl = () => {
|
|
@@ -7591,8 +7684,10 @@ const getCorePlugins = ({
|
|
|
7591
7684
|
...(packageBundle
|
|
7592
7685
|
? [jsenvPluginWorkspaceBundle({ packageDirectory })]
|
|
7593
7686
|
: []),
|
|
7594
|
-
|
|
7687
|
+
// before reference analysis: an url written by an injection must hold its
|
|
7688
|
+
// final value when references are analyzed
|
|
7595
7689
|
jsenvPluginInjections(injections),
|
|
7690
|
+
jsenvPluginReferenceAnalysis(referenceAnalysis),
|
|
7596
7691
|
jsenvPluginTranspilation(transpilation),
|
|
7597
7692
|
// "jsenvPluginInlining" must be very soon because all other plugins will react differently once they see the file is inlined
|
|
7598
7693
|
...(inlining ? [jsenvPluginInlining()] : []),
|
|
@@ -9255,6 +9350,9 @@ const createUrlInfo = (url, context) => {
|
|
|
9255
9350
|
contentFinalized: false,
|
|
9256
9351
|
contentSideEffects: [],
|
|
9257
9352
|
contentInjections: {},
|
|
9353
|
+
// placeholders already consumed somewhere else than the content (in a specifier),
|
|
9354
|
+
// so that not finding them in the content is not worth a warning
|
|
9355
|
+
contentInjectionUsedKeySet: new Set(),
|
|
9258
9356
|
|
|
9259
9357
|
sourcemap: null,
|
|
9260
9358
|
sourcemapIsWrong: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/core",
|
|
3
|
-
"version": "41.4.
|
|
3
|
+
"version": "41.4.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Tool to develop, test and build js projects",
|
|
6
6
|
"repository": {
|
|
@@ -72,12 +72,12 @@
|
|
|
72
72
|
"test:snapshot_clear": "npx @jsenv/filesystem clear **/tests/**/side_effects/"
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
|
-
"@jsenv/ast": "6.8.
|
|
76
|
-
"@jsenv/js-module-fallback": "1.4.
|
|
75
|
+
"@jsenv/ast": "6.8.5",
|
|
76
|
+
"@jsenv/js-module-fallback": "1.4.38",
|
|
77
77
|
"@jsenv/plugin-bundling": "2.10.16",
|
|
78
|
-
"@jsenv/plugin-minification": "1.7.
|
|
79
|
-
"@jsenv/plugin-supervisor": "1.8.
|
|
80
|
-
"@jsenv/plugin-transpilation": "1.5.
|
|
78
|
+
"@jsenv/plugin-minification": "1.7.6",
|
|
79
|
+
"@jsenv/plugin-supervisor": "1.8.9",
|
|
80
|
+
"@jsenv/plugin-transpilation": "1.5.79",
|
|
81
81
|
"@jsenv/server": "17.6.0",
|
|
82
82
|
"@jsenv/sourcemap": "1.4.2",
|
|
83
83
|
"react-table": "7.8.0"
|
package/src/build/build.js
CHANGED
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
createJsenvPluginsController,
|
|
70
70
|
createJsenvPluginStore,
|
|
71
71
|
} from "../plugins/jsenv_plugins_controller.js";
|
|
72
|
+
import { isBareSpecifier } from "../helpers/bare_specifier.js";
|
|
72
73
|
import { getCorePlugins } from "../plugins/plugins.js";
|
|
73
74
|
import { jsenvPluginReferenceAnalysis } from "../plugins/reference_analysis/jsenv_plugin_reference_analysis.js";
|
|
74
75
|
import { renderBuildDoneLog } from "./build_content_report.js";
|
|
@@ -1488,7 +1489,10 @@ const prepareEntryPointBuild = async (
|
|
|
1488
1489
|
const registerHtmlMutation = (callback) => {
|
|
1489
1490
|
htmlMutationCallbackSet.add(callback);
|
|
1490
1491
|
};
|
|
1491
|
-
htmlRefine(htmlAst, {
|
|
1492
|
+
htmlRefine(htmlAst, {
|
|
1493
|
+
registerHtmlMutation,
|
|
1494
|
+
htmlUrlInfo: urlInfo,
|
|
1495
|
+
});
|
|
1492
1496
|
for (const htmlMutationCallback of htmlMutationCallbackSet) {
|
|
1493
1497
|
htmlMutationCallback();
|
|
1494
1498
|
}
|
|
@@ -1572,20 +1576,3 @@ const prepareEntryPointBuild = async (
|
|
|
1572
1576
|
},
|
|
1573
1577
|
};
|
|
1574
1578
|
};
|
|
1575
|
-
|
|
1576
|
-
const isBareSpecifier = (specifier) => {
|
|
1577
|
-
if (
|
|
1578
|
-
specifier[0] === "/" ||
|
|
1579
|
-
specifier.startsWith("./") ||
|
|
1580
|
-
specifier.startsWith("../")
|
|
1581
|
-
) {
|
|
1582
|
-
return false;
|
|
1583
|
-
}
|
|
1584
|
-
try {
|
|
1585
|
-
// eslint-disable-next-line no-new
|
|
1586
|
-
new URL(specifier);
|
|
1587
|
-
return false;
|
|
1588
|
-
} catch {
|
|
1589
|
-
return true;
|
|
1590
|
-
}
|
|
1591
|
-
};
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js";
|
|
23
23
|
import { escapeRegexpSpecialChars } from "@jsenv/utils/src/string/escape_regexp_special_chars.js";
|
|
24
24
|
import { createHash } from "node:crypto";
|
|
25
|
+
import { existsSync } from "node:fs";
|
|
25
26
|
import { prependContent } from "../kitchen/prepend_content.js";
|
|
26
27
|
import { GRAPH_VISITOR } from "../kitchen/url_graph/url_graph_visitor.js";
|
|
27
28
|
import { isWebWorkerUrlInfo } from "../kitchen/web_workers.js";
|
|
@@ -895,7 +896,7 @@ export const createBuildSpecifierManager = ({
|
|
|
895
896
|
|
|
896
897
|
prepareResyncResourceHints: ({ registerHtmlRefine }) => {
|
|
897
898
|
const hintToInjectMap = new Map();
|
|
898
|
-
registerHtmlRefine((htmlAst, { registerHtmlMutation }) => {
|
|
899
|
+
registerHtmlRefine((htmlAst, { registerHtmlMutation, htmlUrlInfo }) => {
|
|
899
900
|
visitHtmlNodes(htmlAst, {
|
|
900
901
|
link: (node) => {
|
|
901
902
|
if (getHtmlNodeAttribute(node, "jsenv-ignore") !== undefined) {
|
|
@@ -919,12 +920,39 @@ export const createBuildSpecifierManager = ({
|
|
|
919
920
|
return;
|
|
920
921
|
}
|
|
921
922
|
const rawUrl = href;
|
|
923
|
+
if (targetsAFileThatDoesNotExist(rawUrl)) {
|
|
924
|
+
// this hint never designated anything, so nothing can explain its
|
|
925
|
+
// removal; taking away markup written by hand on a guess is worse
|
|
926
|
+
// than leaving it there
|
|
927
|
+
logger.warn(
|
|
928
|
+
createDetailedMessage(
|
|
929
|
+
`${UNICODE.WARNING} resource hint kept as is: "${href}" cannot be resolved`,
|
|
930
|
+
{
|
|
931
|
+
"html file": htmlUrlInfo.url,
|
|
932
|
+
...(rawKitchen.context.hasInjections
|
|
933
|
+
? {
|
|
934
|
+
suggestion: `when that url is written by an injection, jsenv resolves it in html attributes before analyzing references; check the placeholder spelling`,
|
|
935
|
+
}
|
|
936
|
+
: {}),
|
|
937
|
+
},
|
|
938
|
+
),
|
|
939
|
+
);
|
|
940
|
+
// the build url means nothing for something jsenv could not resolve,
|
|
941
|
+
// and it would leak a local path into the build
|
|
942
|
+
registerHtmlMutation(() => {
|
|
943
|
+
setHtmlNodeAttributes(node, {
|
|
944
|
+
href: urlToRelativeUrl(rawUrl, htmlUrlInfo.url),
|
|
945
|
+
});
|
|
946
|
+
});
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
922
949
|
const finalUrl = internalRedirections.get(rawUrl) || rawUrl;
|
|
923
950
|
const urlInfo = finalKitchen.graph.getUrlInfo(finalUrl);
|
|
924
951
|
if (!urlInfo) {
|
|
925
|
-
if (
|
|
926
|
-
//
|
|
927
|
-
//
|
|
952
|
+
if (!href.startsWith("file:")) {
|
|
953
|
+
// the hint designates a resource jsenv does not own: an origin for
|
|
954
|
+
// preconnect/dns-prefetch, a remote file for the others. The author
|
|
955
|
+
// knows what the page will request at runtime, keep it as-is.
|
|
928
956
|
return;
|
|
929
957
|
}
|
|
930
958
|
logger.warn(
|
|
@@ -1369,5 +1397,15 @@ const asBuildUrlVersioned = ({
|
|
|
1369
1397
|
return `${buildDirectoryUrl}${pathname}${search}${hash}`;
|
|
1370
1398
|
};
|
|
1371
1399
|
|
|
1400
|
+
const targetsAFileThatDoesNotExist = (url) => {
|
|
1401
|
+
if (!url.startsWith("file:")) {
|
|
1402
|
+
return false;
|
|
1403
|
+
}
|
|
1404
|
+
const urlObject = new URL(url);
|
|
1405
|
+
urlObject.search = "";
|
|
1406
|
+
urlObject.hash = "";
|
|
1407
|
+
return !existsSync(urlObject);
|
|
1408
|
+
};
|
|
1409
|
+
|
|
1372
1410
|
// export for unit tests
|
|
1373
1411
|
export { generateVersion };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// A bare specifier ("preact", "@jsenv/core/x.js") is resolved by node esm resolution,
|
|
2
|
+
// everything else ("/a.js", "./a.js", "http://example.com/a.js") by url resolution
|
|
3
|
+
export const isBareSpecifier = (specifier) => {
|
|
4
|
+
if (
|
|
5
|
+
specifier[0] === "/" ||
|
|
6
|
+
specifier.startsWith("./") ||
|
|
7
|
+
specifier.startsWith("../")
|
|
8
|
+
) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
// eslint-disable-next-line no-new
|
|
13
|
+
new URL(specifier);
|
|
14
|
+
return false;
|
|
15
|
+
} catch {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
};
|
package/src/kitchen/errors.js
CHANGED
|
@@ -78,6 +78,7 @@ ${reason}`,
|
|
|
78
78
|
}
|
|
79
79
|
return createFailedToResolveUrlError({
|
|
80
80
|
reason: `An error occured during specifier resolution`,
|
|
81
|
+
...detailsFromInjectionsOnOwner(reference),
|
|
81
82
|
...detailsFromValueThrown(error),
|
|
82
83
|
});
|
|
83
84
|
};
|
|
@@ -139,6 +140,7 @@ ${reason}`,
|
|
|
139
140
|
return createFailedToFetchUrlContentError({
|
|
140
141
|
code: "NOT_FOUND",
|
|
141
142
|
reason: "no entry on filesystem",
|
|
143
|
+
...detailsFromInjectionsOnOwner(urlInfo.firstReference),
|
|
142
144
|
});
|
|
143
145
|
}
|
|
144
146
|
}
|
|
@@ -371,6 +373,33 @@ const getFirstReferenceInProject = (reference) => {
|
|
|
371
373
|
return getFirstReferenceInProject(firstReference);
|
|
372
374
|
};
|
|
373
375
|
|
|
376
|
+
// Injections write urls in html attributes before references are analyzed, so an url
|
|
377
|
+
// that still cannot be resolved may be a placeholder no injection replaced. Rather than
|
|
378
|
+
// guessing what a placeholder looks like (the key is free-form), tell the file it comes
|
|
379
|
+
// from: injections are configured for it.
|
|
380
|
+
const detailsFromInjectionsOnOwner = (reference) => {
|
|
381
|
+
if (!reference) {
|
|
382
|
+
return {};
|
|
383
|
+
}
|
|
384
|
+
const ownerUrlInfo = reference.ownerUrlInfo;
|
|
385
|
+
if (ownerUrlInfo.type !== "html") {
|
|
386
|
+
// "jsenv-ignore" is an html attribute
|
|
387
|
+
return {};
|
|
388
|
+
}
|
|
389
|
+
const { hasInjections } = ownerUrlInfo.context;
|
|
390
|
+
if (!hasInjections || !hasInjections(ownerUrlInfo.url)) {
|
|
391
|
+
return {};
|
|
392
|
+
}
|
|
393
|
+
const { node, attributeName } = reference.astInfo || {};
|
|
394
|
+
if (!node || !attributeName) {
|
|
395
|
+
return {};
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
suggestion: `injections are configured for this file; when "${reference.specifier}" is meant to be written by one of them, check the placeholder spelling, or add "jsenv-ignore" so jsenv leaves that url alone:
|
|
399
|
+
<${node.nodeName} jsenv-ignore ${attributeName}="${reference.specifier}" />`,
|
|
400
|
+
};
|
|
401
|
+
};
|
|
402
|
+
|
|
374
403
|
const detailsFromPluginController = (jsenvPluginsController) => {
|
|
375
404
|
const currentPlugin = jsenvPluginsController.getCurrentPlugin();
|
|
376
405
|
if (!currentPlugin) {
|
|
@@ -214,6 +214,9 @@ const createUrlInfo = (url, context) => {
|
|
|
214
214
|
contentFinalized: false,
|
|
215
215
|
contentSideEffects: [],
|
|
216
216
|
contentInjections: {},
|
|
217
|
+
// placeholders already consumed somewhere else than the content (in a specifier),
|
|
218
|
+
// so that not finding them in the content is not worth a warning
|
|
219
|
+
contentInjectionUsedKeySet: new Set(),
|
|
217
220
|
|
|
218
221
|
sourcemap: null,
|
|
219
222
|
sourcemapIsWrong: false,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { injectJsenvScript, parseHtml, stringifyHtmlAst } from "@jsenv/ast";
|
|
2
|
+
import { createDetailedMessage } from "@jsenv/humanize";
|
|
2
3
|
import { composeTwoSourcemaps, createMagicSource } from "@jsenv/sourcemap";
|
|
3
4
|
|
|
4
5
|
const injectionSymbol = Symbol.for("jsenv_injection");
|
|
@@ -24,6 +25,13 @@ export const INJECTIONS = {
|
|
|
24
25
|
},
|
|
25
26
|
};
|
|
26
27
|
|
|
28
|
+
export const readInjectionValue = (injection) => {
|
|
29
|
+
if (injection && injection[injectionSymbol]) {
|
|
30
|
+
return injection.value;
|
|
31
|
+
}
|
|
32
|
+
return injection;
|
|
33
|
+
};
|
|
34
|
+
|
|
27
35
|
export const isPlaceholderInjection = (value) => {
|
|
28
36
|
return (
|
|
29
37
|
!value || !value[injectionSymbol] || value[injectionSymbol] !== "global"
|
|
@@ -97,7 +105,7 @@ export const injectPlaceholderReplacements = (
|
|
|
97
105
|
for (const { key, isOptional, value } of placeholderReplacements) {
|
|
98
106
|
let index = content.indexOf(key);
|
|
99
107
|
if (index === -1) {
|
|
100
|
-
if (!isOptional) {
|
|
108
|
+
if (!isOptional && !urlInfo.contentInjectionUsedKeySet.has(key)) {
|
|
101
109
|
urlInfo.context.logger.warn(
|
|
102
110
|
`placeholder "${key}" not found in ${urlInfo.url}.
|
|
103
111
|
--- suggestion a ---
|
|
@@ -122,7 +130,7 @@ return {
|
|
|
122
130
|
magicSource.replace({
|
|
123
131
|
start,
|
|
124
132
|
end,
|
|
125
|
-
replacement: asReplacement(value, urlInfo),
|
|
133
|
+
replacement: asReplacement(value, urlInfo.type),
|
|
126
134
|
});
|
|
127
135
|
index = content.indexOf(key, end);
|
|
128
136
|
}
|
|
@@ -133,8 +141,8 @@ return {
|
|
|
133
141
|
// In JS the placeholder stands for a value, so it must be substituted by a literal.
|
|
134
142
|
// Everywhere else (html attributes and text, css, ...) it stands for a piece of text
|
|
135
143
|
// and is substituted as-is, so it can be concatenated: href="__BACKEND_URL__/users/me"
|
|
136
|
-
const asReplacement = (value,
|
|
137
|
-
if (
|
|
144
|
+
export const asReplacement = (value, type) => {
|
|
145
|
+
if (type === "js_classic" || type === "js_module") {
|
|
138
146
|
return JSON.stringify(value, null, " ");
|
|
139
147
|
}
|
|
140
148
|
if (typeof value === "string") {
|
|
@@ -150,7 +158,14 @@ export const injectGlobals = (content, globals, urlInfo) => {
|
|
|
150
158
|
if (urlInfo.type === "js_classic" || urlInfo.type === "js_module") {
|
|
151
159
|
return globalsInjectorOnJs(content, globals, urlInfo);
|
|
152
160
|
}
|
|
153
|
-
throw new Error(
|
|
161
|
+
throw new Error(
|
|
162
|
+
createDetailedMessage(`cannot inject globals into "${urlInfo.type}"`, {
|
|
163
|
+
file: urlInfo.url,
|
|
164
|
+
...(urlInfo.isInline
|
|
165
|
+
? { "inline content of": urlInfo.inlineUrlSite.url }
|
|
166
|
+
: {}),
|
|
167
|
+
}),
|
|
168
|
+
);
|
|
154
169
|
};
|
|
155
170
|
const globalInjectorOnHtml = (content, globals, urlInfo) => {
|
|
156
171
|
// ideally we would inject an importmap but browser support is too low
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { URL_META } from "@jsenv/url-meta";
|
|
2
2
|
import { asUrlWithoutSearch, urlToRelativeUrl } from "@jsenv/urls";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
INJECTIONS,
|
|
5
|
+
isPlaceholderInjection,
|
|
6
|
+
readInjectionValue,
|
|
7
|
+
} from "../../kitchen/url_graph/url_info_injections.js";
|
|
4
8
|
|
|
5
9
|
export const jsenvPluginInjections = (rawAssociations) => {
|
|
6
10
|
const getDefaultInjections = (urlInfo) => {
|
|
@@ -15,6 +19,7 @@ export const jsenvPluginInjections = (rawAssociations) => {
|
|
|
15
19
|
}
|
|
16
20
|
return null;
|
|
17
21
|
};
|
|
22
|
+
const injectionsForUrlInfoMap = new WeakMap();
|
|
18
23
|
let getInjections = null;
|
|
19
24
|
|
|
20
25
|
return {
|
|
@@ -26,11 +31,20 @@ export const jsenvPluginInjections = (rawAssociations) => {
|
|
|
26
31
|
{ injectionsGetter: rawAssociations },
|
|
27
32
|
context.rootDirectoryUrl,
|
|
28
33
|
);
|
|
29
|
-
const
|
|
34
|
+
const findInjectionsGetterForUrl = (url) => {
|
|
30
35
|
const { injectionsGetter } = URL_META.applyAssociations({
|
|
31
|
-
url: asUrlWithoutSearch(
|
|
36
|
+
url: asUrlWithoutSearch(url),
|
|
32
37
|
associations: resolvedAssociations,
|
|
33
38
|
});
|
|
39
|
+
return injectionsGetter;
|
|
40
|
+
};
|
|
41
|
+
// errors and the build read this to know an unresolved url may come from
|
|
42
|
+
// an injection, and word what they report accordingly
|
|
43
|
+
context.hasInjections = (url) => {
|
|
44
|
+
return Boolean(findInjectionsGetterForUrl(url));
|
|
45
|
+
};
|
|
46
|
+
const findInjectionsGetter = (urlInfo) => {
|
|
47
|
+
const injectionsGetter = findInjectionsGetterForUrl(urlInfo.url);
|
|
34
48
|
if (injectionsGetter) {
|
|
35
49
|
return { injectionsGetter, isInherited: false };
|
|
36
50
|
}
|
|
@@ -62,9 +76,7 @@ export const jsenvPluginInjections = (rawAssociations) => {
|
|
|
62
76
|
if (!injections || !isInherited) {
|
|
63
77
|
return injections;
|
|
64
78
|
}
|
|
65
|
-
|
|
66
|
-
// is expected in one of them, not in each
|
|
67
|
-
return asOptionalInjections(injections);
|
|
79
|
+
return asInheritedInjections(injections);
|
|
68
80
|
};
|
|
69
81
|
}
|
|
70
82
|
},
|
|
@@ -81,20 +93,77 @@ export const jsenvPluginInjections = (rawAssociations) => {
|
|
|
81
93
|
contentInjections: defaultInjections,
|
|
82
94
|
};
|
|
83
95
|
}
|
|
96
|
+
injectionsForUrlInfoMap.set(urlInfo, injections);
|
|
84
97
|
return {
|
|
85
|
-
contentInjections: {
|
|
86
|
-
...defaultInjections,
|
|
87
|
-
...injections,
|
|
88
|
-
},
|
|
98
|
+
contentInjections: { ...defaultInjections, ...injections },
|
|
89
99
|
};
|
|
90
100
|
},
|
|
101
|
+
// The content still holds the placeholders when references are analyzed: they are
|
|
102
|
+
// replaced at the very end, once the type of every inline content is known.
|
|
103
|
+
// A specifier must not wait for that, it would resolve "__BACKEND_URL__/users/me"
|
|
104
|
+
// as a file sitting next to the document. Only the placeholder is resolved here,
|
|
105
|
+
// then the specifier goes to whoever resolves this kind of url (node esm, web, ...)
|
|
106
|
+
resolveReference: (reference) => {
|
|
107
|
+
const { ownerUrlInfo } = reference;
|
|
108
|
+
const injections = injectionsForUrlInfoMap.get(ownerUrlInfo);
|
|
109
|
+
if (!injections) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const { specifier, keySet } = injectIntoSpecifier(
|
|
113
|
+
reference.specifier,
|
|
114
|
+
injections,
|
|
115
|
+
);
|
|
116
|
+
if (keySet.size === 0) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
reference.specifier = specifier;
|
|
120
|
+
for (const key of keySet) {
|
|
121
|
+
// rewriting the specifier takes the placeholder out of the content,
|
|
122
|
+
// so the injection step must not expect to find it there
|
|
123
|
+
ownerUrlInfo.contentInjectionUsedKeySet.add(key);
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
},
|
|
91
127
|
};
|
|
92
128
|
};
|
|
93
129
|
|
|
94
|
-
const
|
|
95
|
-
|
|
130
|
+
const injectIntoSpecifier = (specifier, injections) => {
|
|
131
|
+
let specifierInjected = specifier;
|
|
132
|
+
const keySet = new Set();
|
|
96
133
|
for (const key of Object.keys(injections)) {
|
|
97
|
-
|
|
134
|
+
if (!specifierInjected.includes(key)) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const injection = injections[key];
|
|
138
|
+
if (!isPlaceholderInjection(injection)) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const value = readInjectionValue(injection);
|
|
142
|
+
if (typeof value !== "string") {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
specifierInjected = specifierInjected.replaceAll(key, value);
|
|
146
|
+
keySet.add(key);
|
|
147
|
+
}
|
|
148
|
+
return { specifier: specifierInjected, keySet };
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// What a file inlines (a <script> or a <style> inside html) is authored in that file
|
|
152
|
+
// and inherits its injections, with two adjustments:
|
|
153
|
+
// - a global belongs to the file itself, injecting it into each inline content would
|
|
154
|
+
// repeat it and reach types that cannot receive globals (css)
|
|
155
|
+
// - a placeholder configured for the file is expected in one of its inline contents,
|
|
156
|
+
// not in each, so a missing one is not worth a warning
|
|
157
|
+
const asInheritedInjections = (injections) => {
|
|
158
|
+
const inheritedInjections = {};
|
|
159
|
+
for (const key of Object.keys(injections)) {
|
|
160
|
+
const value = injections[key];
|
|
161
|
+
if (isPlaceholderInjection(value)) {
|
|
162
|
+
inheritedInjections[key] = INJECTIONS.optional(value);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (Object.keys(inheritedInjections).length === 0) {
|
|
166
|
+
return null;
|
|
98
167
|
}
|
|
99
|
-
return
|
|
168
|
+
return inheritedInjections;
|
|
100
169
|
};
|
package/src/plugins/plugins.js
CHANGED
|
@@ -87,8 +87,10 @@ export const getCorePlugins = ({
|
|
|
87
87
|
...(packageBundle
|
|
88
88
|
? [jsenvPluginWorkspaceBundle({ packageDirectory })]
|
|
89
89
|
: []),
|
|
90
|
-
|
|
90
|
+
// before reference analysis: an url written by an injection must hold its
|
|
91
|
+
// final value when references are analyzed
|
|
91
92
|
jsenvPluginInjections(injections),
|
|
93
|
+
jsenvPluginReferenceAnalysis(referenceAnalysis),
|
|
92
94
|
jsenvPluginTranspilation(transpilation),
|
|
93
95
|
// "jsenvPluginInlining" must be very soon because all other plugins will react differently once they see the file is inlined
|
|
94
96
|
...(inlining ? [jsenvPluginInlining()] : []),
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
import { createMagicSource } from "@jsenv/sourcemap";
|
|
7
7
|
import { urlToExtension } from "@jsenv/urls";
|
|
8
8
|
import { JS_QUOTES } from "@jsenv/utils/src/string/js_quotes.js";
|
|
9
|
+
import { isBareSpecifier } from "../../../helpers/bare_specifier.js";
|
|
9
10
|
|
|
10
11
|
export const jsenvPluginJsReferenceAnalysis = ({ inlineContent }) => {
|
|
11
12
|
return [
|
|
@@ -192,20 +193,3 @@ const parseAndTransformJsReferences = async (
|
|
|
192
193
|
const { content, sourcemap } = magicSource.toContentAndSourcemap();
|
|
193
194
|
return { content, sourcemap };
|
|
194
195
|
};
|
|
195
|
-
|
|
196
|
-
const isBareSpecifier = (specifier) => {
|
|
197
|
-
if (
|
|
198
|
-
specifier[0] === "/" ||
|
|
199
|
-
specifier.startsWith("./") ||
|
|
200
|
-
specifier.startsWith("../")
|
|
201
|
-
) {
|
|
202
|
-
return false;
|
|
203
|
-
}
|
|
204
|
-
try {
|
|
205
|
-
// eslint-disable-next-line no-new
|
|
206
|
-
new URL(specifier);
|
|
207
|
-
return false;
|
|
208
|
-
} catch {
|
|
209
|
-
return true;
|
|
210
|
-
}
|
|
211
|
-
};
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { URL_META } from "@jsenv/url-meta";
|
|
15
15
|
import { urlToBasename, urlToExtension } from "@jsenv/urls";
|
|
16
16
|
import { readFileSync } from "node:fs";
|
|
17
|
+
import { isBareSpecifier } from "../../helpers/bare_specifier.js";
|
|
17
18
|
|
|
18
19
|
export const createNodeEsmResolver = ({
|
|
19
20
|
packageDirectory,
|
|
@@ -487,20 +488,3 @@ const createResolverWithFallbackOnError = (mainResolver, fallbackResolver) => {
|
|
|
487
488
|
}
|
|
488
489
|
};
|
|
489
490
|
};
|
|
490
|
-
|
|
491
|
-
const isBareSpecifier = (specifier) => {
|
|
492
|
-
if (
|
|
493
|
-
specifier[0] === "/" ||
|
|
494
|
-
specifier.startsWith("./") ||
|
|
495
|
-
specifier.startsWith("../")
|
|
496
|
-
) {
|
|
497
|
-
return false;
|
|
498
|
-
}
|
|
499
|
-
try {
|
|
500
|
-
// eslint-disable-next-line no-new
|
|
501
|
-
new URL(specifier);
|
|
502
|
-
return false;
|
|
503
|
-
} catch {
|
|
504
|
-
return true;
|
|
505
|
-
}
|
|
506
|
-
};
|