@jsenv/core 40.6.2 → 40.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build/build.js +335 -135
- package/dist/build/jsenv_core_packages.js +100 -102
- package/dist/jsenv_core.js +4 -0
- package/dist/start_build_server/jsenv_core_packages.js +29 -29
- package/dist/start_dev_server/jsenv_core_packages.js +100 -102
- package/dist/start_dev_server/start_dev_server.js +338 -136
- package/package.json +4 -4
- package/src/dev/start_dev_server.js +3 -1
- package/src/kitchen/kitchen.js +2 -0
- package/src/kitchen/url_graph/url_graph.js +1 -0
- package/src/kitchen/url_graph/url_info_injections.js +172 -0
- package/src/kitchen/url_graph/url_info_transformations.js +28 -7
- package/src/main.js +1 -1
- package/src/plugins/global_scenarios/jsenv_plugin_global_scenarios.js +4 -9
- package/src/plugins/import_meta_scenarios/jsenv_plugin_import_meta_scenarios.js +2 -0
- package/src/plugins/injections/jsenv_plugin_injections.js +51 -85
- package/src/plugins/plugin_controller.js +3 -0
- package/src/plugins/plugins.js +3 -1
- package/src/plugins/protocol_file/jsenv_plugin_directory_listing.js +23 -22
- package/src/plugins/protocol_file/jsenv_plugin_fs_redirection.js +41 -3
- package/src/plugins/protocol_file/jsenv_plugin_protocol_file.js +2 -0
- package/src/plugins/injections/internal/inject_globals.js +0 -52
|
@@ -4,7 +4,7 @@ import { lookupPackageDirectory, registerDirectoryLifecycle, urlToRelativeUrl, m
|
|
|
4
4
|
import { readFileSync, existsSync, readdirSync, lstatSync, realpathSync } from "node:fs";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
import { generateSourcemapFileUrl, createMagicSource, composeTwoSourcemaps, generateSourcemapDataUrl, SOURCEMAP } from "@jsenv/sourcemap";
|
|
7
|
-
import { parseHtml, injectHtmlNodeAsEarlyAsPossible, createHtmlNode, stringifyHtmlAst, applyBabelPlugins, generateUrlForInlineContent, parseJsWithAcorn, parseCssUrls, getHtmlNodeAttribute, getHtmlNodePosition, getHtmlNodeAttributePosition, setHtmlNodeAttributes, parseSrcSet, getUrlForContentInsideHtml, removeHtmlNodeText, setHtmlNodeText, getHtmlNodeText, analyzeScriptNode, visitHtmlNodes, parseJsUrls, getUrlForContentInsideJs, analyzeLinkNode
|
|
7
|
+
import { parseHtml, injectHtmlNodeAsEarlyAsPossible, createHtmlNode, stringifyHtmlAst, applyBabelPlugins, generateUrlForInlineContent, injectJsenvScript, parseJsWithAcorn, parseCssUrls, getHtmlNodeAttribute, getHtmlNodePosition, getHtmlNodeAttributePosition, setHtmlNodeAttributes, parseSrcSet, getUrlForContentInsideHtml, removeHtmlNodeText, setHtmlNodeText, getHtmlNodeText, analyzeScriptNode, visitHtmlNodes, parseJsUrls, getUrlForContentInsideJs, analyzeLinkNode } from "@jsenv/ast";
|
|
8
8
|
import { performance } from "node:perf_hooks";
|
|
9
9
|
import { jsenvPluginSupervisor } from "@jsenv/plugin-supervisor";
|
|
10
10
|
import { jsenvPluginTranspilation } from "@jsenv/plugin-transpilation";
|
|
@@ -1898,6 +1898,7 @@ const createUrlInfo = (url, context) => {
|
|
|
1898
1898
|
contentLength: undefined,
|
|
1899
1899
|
contentFinalized: false,
|
|
1900
1900
|
contentSideEffects: [],
|
|
1901
|
+
contentInjections: {},
|
|
1901
1902
|
|
|
1902
1903
|
sourcemap: null,
|
|
1903
1904
|
sourcemapIsWrong: false,
|
|
@@ -2162,6 +2163,176 @@ ${urlInfo.url}`,
|
|
|
2162
2163
|
return urlInfo;
|
|
2163
2164
|
};
|
|
2164
2165
|
|
|
2166
|
+
const injectionSymbol = Symbol.for("jsenv_injection");
|
|
2167
|
+
const INJECTIONS = {
|
|
2168
|
+
global: (value) => {
|
|
2169
|
+
return { [injectionSymbol]: "global", value };
|
|
2170
|
+
},
|
|
2171
|
+
optional: (value) => {
|
|
2172
|
+
return { [injectionSymbol]: "optional", value };
|
|
2173
|
+
},
|
|
2174
|
+
};
|
|
2175
|
+
|
|
2176
|
+
const isPlaceholderInjection = (value) => {
|
|
2177
|
+
return (
|
|
2178
|
+
!value || !value[injectionSymbol] || value[injectionSymbol] !== "global"
|
|
2179
|
+
);
|
|
2180
|
+
};
|
|
2181
|
+
|
|
2182
|
+
const applyContentInjections = (content, contentInjections, urlInfo) => {
|
|
2183
|
+
const keys = Object.keys(contentInjections);
|
|
2184
|
+
const globals = {};
|
|
2185
|
+
const placeholderReplacements = [];
|
|
2186
|
+
for (const key of keys) {
|
|
2187
|
+
const contentInjection = contentInjections[key];
|
|
2188
|
+
if (contentInjection && contentInjection[injectionSymbol]) {
|
|
2189
|
+
const valueBehindSymbol = contentInjection[injectionSymbol];
|
|
2190
|
+
if (valueBehindSymbol === "global") {
|
|
2191
|
+
globals[key] = contentInjection.value;
|
|
2192
|
+
} else if (valueBehindSymbol === "optional") {
|
|
2193
|
+
placeholderReplacements.push({
|
|
2194
|
+
key,
|
|
2195
|
+
isOptional: true,
|
|
2196
|
+
value: contentInjection.value,
|
|
2197
|
+
});
|
|
2198
|
+
} else {
|
|
2199
|
+
throw new Error(`unknown injection type "${valueBehindSymbol}"`);
|
|
2200
|
+
}
|
|
2201
|
+
} else {
|
|
2202
|
+
placeholderReplacements.push({
|
|
2203
|
+
key,
|
|
2204
|
+
value: contentInjection,
|
|
2205
|
+
});
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
const needGlobalsInjection = Object.keys(globals).length > 0;
|
|
2210
|
+
const needPlaceholderReplacements = placeholderReplacements.length > 0;
|
|
2211
|
+
|
|
2212
|
+
if (needGlobalsInjection && needPlaceholderReplacements) {
|
|
2213
|
+
const globalInjectionResult = injectGlobals(content, globals, urlInfo);
|
|
2214
|
+
const replaceInjectionResult = injectPlaceholderReplacements(
|
|
2215
|
+
globalInjectionResult.content,
|
|
2216
|
+
placeholderReplacements,
|
|
2217
|
+
urlInfo,
|
|
2218
|
+
);
|
|
2219
|
+
return {
|
|
2220
|
+
content: replaceInjectionResult.content,
|
|
2221
|
+
sourcemap: composeTwoSourcemaps(
|
|
2222
|
+
globalInjectionResult.sourcemap,
|
|
2223
|
+
replaceInjectionResult.sourcemap,
|
|
2224
|
+
),
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
if (needGlobalsInjection) {
|
|
2228
|
+
return injectGlobals(content, globals, urlInfo);
|
|
2229
|
+
}
|
|
2230
|
+
if (needPlaceholderReplacements) {
|
|
2231
|
+
return injectPlaceholderReplacements(
|
|
2232
|
+
content,
|
|
2233
|
+
placeholderReplacements,
|
|
2234
|
+
urlInfo,
|
|
2235
|
+
);
|
|
2236
|
+
}
|
|
2237
|
+
return null;
|
|
2238
|
+
};
|
|
2239
|
+
|
|
2240
|
+
const injectPlaceholderReplacements = (
|
|
2241
|
+
content,
|
|
2242
|
+
placeholderReplacements,
|
|
2243
|
+
urlInfo,
|
|
2244
|
+
) => {
|
|
2245
|
+
const magicSource = createMagicSource(content);
|
|
2246
|
+
for (const { key, isOptional, value } of placeholderReplacements) {
|
|
2247
|
+
let index = content.indexOf(key);
|
|
2248
|
+
if (index === -1) {
|
|
2249
|
+
if (!isOptional) {
|
|
2250
|
+
urlInfo.context.logger.warn(
|
|
2251
|
+
`placeholder "${key}" not found in ${urlInfo.url}.
|
|
2252
|
+
--- suggestion a ---
|
|
2253
|
+
Add "${key}" in that file.
|
|
2254
|
+
--- suggestion b ---
|
|
2255
|
+
Fix eventual typo in "${key}"?
|
|
2256
|
+
--- suggestion c ---
|
|
2257
|
+
Mark injection as optional using INJECTIONS.optional():
|
|
2258
|
+
import { INJECTIONS } from "@jsenv/core";
|
|
2259
|
+
|
|
2260
|
+
return {
|
|
2261
|
+
"${key}": INJECTIONS.optional(${JSON.stringify(value)}),
|
|
2262
|
+
};`,
|
|
2263
|
+
);
|
|
2264
|
+
}
|
|
2265
|
+
continue;
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
while (index !== -1) {
|
|
2269
|
+
const start = index;
|
|
2270
|
+
const end = index + key.length;
|
|
2271
|
+
magicSource.replace({
|
|
2272
|
+
start,
|
|
2273
|
+
end,
|
|
2274
|
+
replacement:
|
|
2275
|
+
urlInfo.type === "js_classic" ||
|
|
2276
|
+
urlInfo.type === "js_module" ||
|
|
2277
|
+
urlInfo.type === "html"
|
|
2278
|
+
? JSON.stringify(value, null, " ")
|
|
2279
|
+
: value,
|
|
2280
|
+
});
|
|
2281
|
+
index = content.indexOf(key, end);
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
return magicSource.toContentAndSourcemap();
|
|
2285
|
+
};
|
|
2286
|
+
|
|
2287
|
+
const injectGlobals = (content, globals, urlInfo) => {
|
|
2288
|
+
if (urlInfo.type === "html") {
|
|
2289
|
+
return globalInjectorOnHtml(content, globals, urlInfo);
|
|
2290
|
+
}
|
|
2291
|
+
if (urlInfo.type === "js_classic" || urlInfo.type === "js_module") {
|
|
2292
|
+
return globalsInjectorOnJs(content, globals, urlInfo);
|
|
2293
|
+
}
|
|
2294
|
+
throw new Error(`cannot inject globals into "${urlInfo.type}"`);
|
|
2295
|
+
};
|
|
2296
|
+
const globalInjectorOnHtml = (content, globals, urlInfo) => {
|
|
2297
|
+
// ideally we would inject an importmap but browser support is too low
|
|
2298
|
+
// (even worse for worker/service worker)
|
|
2299
|
+
// so for now we inject code into entry points
|
|
2300
|
+
const htmlAst = parseHtml({
|
|
2301
|
+
html: content,
|
|
2302
|
+
url: urlInfo.url,
|
|
2303
|
+
storeOriginalPositions: false,
|
|
2304
|
+
});
|
|
2305
|
+
const clientCode = generateClientCodeForGlobals(globals, {
|
|
2306
|
+
isWebWorker: false,
|
|
2307
|
+
});
|
|
2308
|
+
injectJsenvScript(htmlAst, {
|
|
2309
|
+
content: clientCode,
|
|
2310
|
+
pluginName: "jsenv:inject_globals",
|
|
2311
|
+
});
|
|
2312
|
+
return {
|
|
2313
|
+
content: stringifyHtmlAst(htmlAst),
|
|
2314
|
+
};
|
|
2315
|
+
};
|
|
2316
|
+
const globalsInjectorOnJs = (content, globals, urlInfo) => {
|
|
2317
|
+
const clientCode = generateClientCodeForGlobals(globals, {
|
|
2318
|
+
isWebWorker:
|
|
2319
|
+
urlInfo.subtype === "worker" ||
|
|
2320
|
+
urlInfo.subtype === "service_worker" ||
|
|
2321
|
+
urlInfo.subtype === "shared_worker",
|
|
2322
|
+
});
|
|
2323
|
+
const magicSource = createMagicSource(content);
|
|
2324
|
+
magicSource.prepend(clientCode);
|
|
2325
|
+
return magicSource.toContentAndSourcemap();
|
|
2326
|
+
};
|
|
2327
|
+
const generateClientCodeForGlobals = (globals, { isWebWorker = false }) => {
|
|
2328
|
+
const globalName = isWebWorker ? "self" : "window";
|
|
2329
|
+
return `Object.assign(${globalName}, ${JSON.stringify(
|
|
2330
|
+
globals,
|
|
2331
|
+
null,
|
|
2332
|
+
" ",
|
|
2333
|
+
)});`;
|
|
2334
|
+
};
|
|
2335
|
+
|
|
2165
2336
|
const defineGettersOnPropertiesDerivedFromOriginalContent = (
|
|
2166
2337
|
urlInfo,
|
|
2167
2338
|
) => {
|
|
@@ -2442,6 +2613,7 @@ const createUrlInfoTransformer = ({
|
|
|
2442
2613
|
contentLength,
|
|
2443
2614
|
sourcemap,
|
|
2444
2615
|
sourcemapIsWrong,
|
|
2616
|
+
contentInjections,
|
|
2445
2617
|
} = transformations;
|
|
2446
2618
|
if (type) {
|
|
2447
2619
|
urlInfo.type = type;
|
|
@@ -2449,13 +2621,23 @@ const createUrlInfoTransformer = ({
|
|
|
2449
2621
|
if (contentType) {
|
|
2450
2622
|
urlInfo.contentType = contentType;
|
|
2451
2623
|
}
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2624
|
+
if (Object.hasOwn(transformations, "contentInjections")) {
|
|
2625
|
+
if (contentInjections) {
|
|
2626
|
+
Object.assign(urlInfo.contentInjections, contentInjections);
|
|
2627
|
+
}
|
|
2628
|
+
if (content === undefined) {
|
|
2629
|
+
return;
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
let contentModified;
|
|
2633
|
+
if (Object.hasOwn(transformations, "content")) {
|
|
2634
|
+
contentModified = setContentProperties(urlInfo, {
|
|
2635
|
+
content,
|
|
2636
|
+
contentAst,
|
|
2637
|
+
contentEtag,
|
|
2638
|
+
contentLength,
|
|
2639
|
+
});
|
|
2640
|
+
}
|
|
2459
2641
|
if (
|
|
2460
2642
|
sourcemap &&
|
|
2461
2643
|
mayHaveSourcemap(urlInfo) &&
|
|
@@ -2647,6 +2829,15 @@ const createUrlInfoTransformer = ({
|
|
|
2647
2829
|
if (transformations) {
|
|
2648
2830
|
applyTransformations(urlInfo, transformations);
|
|
2649
2831
|
}
|
|
2832
|
+
const { contentInjections } = urlInfo;
|
|
2833
|
+
if (contentInjections && Object.keys(contentInjections).length > 0) {
|
|
2834
|
+
const injectionTransformations = applyContentInjections(
|
|
2835
|
+
urlInfo.content,
|
|
2836
|
+
contentInjections,
|
|
2837
|
+
urlInfo,
|
|
2838
|
+
);
|
|
2839
|
+
applyTransformations(urlInfo, injectionTransformations);
|
|
2840
|
+
}
|
|
2650
2841
|
applyContentEffects(urlInfo);
|
|
2651
2842
|
urlInfo.contentFinalized = true;
|
|
2652
2843
|
};
|
|
@@ -2792,6 +2983,7 @@ const createKitchen = ({
|
|
|
2792
2983
|
inlineContentClientFileUrl,
|
|
2793
2984
|
isSupportedOnCurrentClients: memoizeIsSupported(clientRuntimeCompat),
|
|
2794
2985
|
isSupportedOnFutureClients: memoizeIsSupported(runtimeCompat),
|
|
2986
|
+
isPlaceholderInjection,
|
|
2795
2987
|
getPluginMeta: null,
|
|
2796
2988
|
sourcemaps,
|
|
2797
2989
|
outDirectoryUrl,
|
|
@@ -3632,10 +3824,10 @@ const generateHtmlForSyntaxError = (
|
|
|
3632
3824
|
errorLinkText: `${htmlRelativeUrl}:${line}:${column}`,
|
|
3633
3825
|
syntaxErrorHTML: errorToHTML(htmlErrorContentFrame),
|
|
3634
3826
|
};
|
|
3635
|
-
const html = replacePlaceholders
|
|
3827
|
+
const html = replacePlaceholders(htmlForSyntaxError, replacers);
|
|
3636
3828
|
return html;
|
|
3637
3829
|
};
|
|
3638
|
-
const replacePlaceholders
|
|
3830
|
+
const replacePlaceholders = (html, replacers) => {
|
|
3639
3831
|
return html.replace(/\$\{(\w+)\}/g, (match, name) => {
|
|
3640
3832
|
const replacer = replacers[name];
|
|
3641
3833
|
if (replacer === undefined) {
|
|
@@ -4070,6 +4262,9 @@ const returnValueAssertions = [
|
|
|
4070
4262
|
return undefined;
|
|
4071
4263
|
}
|
|
4072
4264
|
if (typeof content !== "string" && !Buffer.isBuffer(content) && !body) {
|
|
4265
|
+
if (Object.hasOwn(valueReturned, "contentInjections")) {
|
|
4266
|
+
return undefined;
|
|
4267
|
+
}
|
|
4073
4268
|
throw new Error(
|
|
4074
4269
|
`Unexpected "content" returned by "${hook.plugin.name}" ${hook.name} hook: it must be a string or a buffer; got ${content}`,
|
|
4075
4270
|
);
|
|
@@ -5877,103 +6072,6 @@ const FILE_AND_SERVER_URLS_CONVERTER = {
|
|
|
5877
6072
|
},
|
|
5878
6073
|
};
|
|
5879
6074
|
|
|
5880
|
-
const jsenvPluginInjections = (rawAssociations) => {
|
|
5881
|
-
let resolvedAssociations;
|
|
5882
|
-
|
|
5883
|
-
return {
|
|
5884
|
-
name: "jsenv:injections",
|
|
5885
|
-
appliesDuring: "*",
|
|
5886
|
-
init: (context) => {
|
|
5887
|
-
resolvedAssociations = URL_META.resolveAssociations(
|
|
5888
|
-
{ injectionsGetter: rawAssociations },
|
|
5889
|
-
context.rootDirectoryUrl,
|
|
5890
|
-
);
|
|
5891
|
-
},
|
|
5892
|
-
transformUrlContent: async (urlInfo) => {
|
|
5893
|
-
const { injectionsGetter } = URL_META.applyAssociations({
|
|
5894
|
-
url: asUrlWithoutSearch(urlInfo.url),
|
|
5895
|
-
associations: resolvedAssociations,
|
|
5896
|
-
});
|
|
5897
|
-
if (!injectionsGetter) {
|
|
5898
|
-
return null;
|
|
5899
|
-
}
|
|
5900
|
-
if (typeof injectionsGetter !== "function") {
|
|
5901
|
-
throw new TypeError("injectionsGetter must be a function");
|
|
5902
|
-
}
|
|
5903
|
-
const injections = await injectionsGetter(urlInfo);
|
|
5904
|
-
if (!injections) {
|
|
5905
|
-
return null;
|
|
5906
|
-
}
|
|
5907
|
-
const keys = Object.keys(injections);
|
|
5908
|
-
if (keys.length === 0) {
|
|
5909
|
-
return null;
|
|
5910
|
-
}
|
|
5911
|
-
return replacePlaceholders(urlInfo.content, injections, urlInfo);
|
|
5912
|
-
},
|
|
5913
|
-
};
|
|
5914
|
-
};
|
|
5915
|
-
|
|
5916
|
-
const injectionSymbol = Symbol.for("jsenv_injection");
|
|
5917
|
-
const INJECTIONS = {
|
|
5918
|
-
optional: (value) => {
|
|
5919
|
-
return { [injectionSymbol]: "optional", value };
|
|
5920
|
-
},
|
|
5921
|
-
};
|
|
5922
|
-
|
|
5923
|
-
// we export this because it is imported by jsenv_plugin_placeholder.js and unit test
|
|
5924
|
-
const replacePlaceholders = (content, replacements, urlInfo) => {
|
|
5925
|
-
const magicSource = createMagicSource(content);
|
|
5926
|
-
for (const key of Object.keys(replacements)) {
|
|
5927
|
-
let index = content.indexOf(key);
|
|
5928
|
-
const replacement = replacements[key];
|
|
5929
|
-
let isOptional;
|
|
5930
|
-
let value;
|
|
5931
|
-
if (replacement && replacement[injectionSymbol]) {
|
|
5932
|
-
const valueBehindSymbol = replacement[injectionSymbol];
|
|
5933
|
-
isOptional = valueBehindSymbol === "optional";
|
|
5934
|
-
value = replacement.value;
|
|
5935
|
-
} else {
|
|
5936
|
-
value = replacement;
|
|
5937
|
-
}
|
|
5938
|
-
if (index === -1) {
|
|
5939
|
-
if (!isOptional) {
|
|
5940
|
-
urlInfo.context.logger.warn(
|
|
5941
|
-
`placeholder "${key}" not found in ${urlInfo.url}.
|
|
5942
|
-
--- suggestion a ---
|
|
5943
|
-
Add "${key}" in that file.
|
|
5944
|
-
--- suggestion b ---
|
|
5945
|
-
Fix eventual typo in "${key}"?
|
|
5946
|
-
--- suggestion c ---
|
|
5947
|
-
Mark injection as optional using INJECTIONS.optional():
|
|
5948
|
-
import { INJECTIONS } from "@jsenv/core";
|
|
5949
|
-
|
|
5950
|
-
return {
|
|
5951
|
-
"${key}": INJECTIONS.optional(${JSON.stringify(value)}),
|
|
5952
|
-
};`,
|
|
5953
|
-
);
|
|
5954
|
-
}
|
|
5955
|
-
continue;
|
|
5956
|
-
}
|
|
5957
|
-
|
|
5958
|
-
while (index !== -1) {
|
|
5959
|
-
const start = index;
|
|
5960
|
-
const end = index + key.length;
|
|
5961
|
-
magicSource.replace({
|
|
5962
|
-
start,
|
|
5963
|
-
end,
|
|
5964
|
-
replacement:
|
|
5965
|
-
urlInfo.type === "js_classic" ||
|
|
5966
|
-
urlInfo.type === "js_module" ||
|
|
5967
|
-
urlInfo.type === "html"
|
|
5968
|
-
? JSON.stringify(value, null, " ")
|
|
5969
|
-
: value,
|
|
5970
|
-
});
|
|
5971
|
-
index = content.indexOf(key, end);
|
|
5972
|
-
}
|
|
5973
|
-
}
|
|
5974
|
-
return magicSource.toContentAndSourcemap();
|
|
5975
|
-
};
|
|
5976
|
-
|
|
5977
6075
|
/*
|
|
5978
6076
|
* NICE TO HAVE:
|
|
5979
6077
|
*
|
|
@@ -6083,22 +6181,22 @@ const jsenvPluginDirectoryListing = ({
|
|
|
6083
6181
|
}
|
|
6084
6182
|
const request = urlInfo.context.request;
|
|
6085
6183
|
const { rootDirectoryUrl, mainFilePath } = urlInfo.context;
|
|
6086
|
-
|
|
6087
|
-
|
|
6184
|
+
const directoryListingInjections = generateDirectoryListingInjection(
|
|
6185
|
+
requestedUrl,
|
|
6088
6186
|
{
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
enoent,
|
|
6098
|
-
}),
|
|
6187
|
+
autoreload,
|
|
6188
|
+
request,
|
|
6189
|
+
urlMocks,
|
|
6190
|
+
directoryContentMagicName,
|
|
6191
|
+
rootDirectoryUrl,
|
|
6192
|
+
mainFilePath,
|
|
6193
|
+
packageDirectory,
|
|
6194
|
+
enoent,
|
|
6099
6195
|
},
|
|
6100
|
-
urlInfo,
|
|
6101
6196
|
);
|
|
6197
|
+
return {
|
|
6198
|
+
contentInjections: directoryListingInjections,
|
|
6199
|
+
};
|
|
6102
6200
|
},
|
|
6103
6201
|
},
|
|
6104
6202
|
devServerRoutes: [
|
|
@@ -6117,8 +6215,10 @@ const jsenvPluginDirectoryListing = ({
|
|
|
6117
6215
|
directoryRelativeUrl,
|
|
6118
6216
|
rootDirectoryUrl,
|
|
6119
6217
|
);
|
|
6120
|
-
const closestDirectoryUrl =
|
|
6121
|
-
|
|
6218
|
+
const closestDirectoryUrl = getFirstExistingDirectoryUrl(
|
|
6219
|
+
requestedUrl,
|
|
6220
|
+
rootDirectoryUrl,
|
|
6221
|
+
);
|
|
6122
6222
|
const sendMessage = (message) => {
|
|
6123
6223
|
websocket.send(JSON.stringify(message));
|
|
6124
6224
|
};
|
|
@@ -6332,15 +6432,15 @@ const generateDirectoryListingInjection = (
|
|
|
6332
6432
|
};
|
|
6333
6433
|
};
|
|
6334
6434
|
const getFirstExistingDirectoryUrl = (requestedUrl, serverRootDirectoryUrl) => {
|
|
6335
|
-
let
|
|
6336
|
-
while (!existsSync(
|
|
6337
|
-
|
|
6338
|
-
if (!urlIsInsideOf(
|
|
6339
|
-
|
|
6435
|
+
let directoryUrlCandidate = new URL("./", requestedUrl);
|
|
6436
|
+
while (!existsSync(directoryUrlCandidate)) {
|
|
6437
|
+
directoryUrlCandidate = new URL("../", directoryUrlCandidate);
|
|
6438
|
+
if (!urlIsInsideOf(directoryUrlCandidate, serverRootDirectoryUrl)) {
|
|
6439
|
+
directoryUrlCandidate = new URL(serverRootDirectoryUrl);
|
|
6340
6440
|
break;
|
|
6341
6441
|
}
|
|
6342
6442
|
}
|
|
6343
|
-
return
|
|
6443
|
+
return directoryUrlCandidate;
|
|
6344
6444
|
};
|
|
6345
6445
|
const getDirectoryContentItems = ({
|
|
6346
6446
|
serverRootDirectoryUrl,
|
|
@@ -6384,6 +6484,7 @@ const getDirectoryContentItems = ({
|
|
|
6384
6484
|
};
|
|
6385
6485
|
|
|
6386
6486
|
const jsenvPluginFsRedirection = ({
|
|
6487
|
+
spa = true,
|
|
6387
6488
|
directoryContentMagicName,
|
|
6388
6489
|
magicExtensions = ["inherit", ".js"],
|
|
6389
6490
|
magicDirectoryIndex = true,
|
|
@@ -6473,11 +6574,19 @@ const jsenvPluginFsRedirection = ({
|
|
|
6473
6574
|
// 3. The url pathname does not ends with "/"
|
|
6474
6575
|
// In that case we assume client explicitely asks to load a directory
|
|
6475
6576
|
if (
|
|
6577
|
+
spa &&
|
|
6476
6578
|
!urlToExtension(urlObject) &&
|
|
6477
6579
|
!urlToPathname(urlObject).endsWith("/")
|
|
6478
6580
|
) {
|
|
6479
|
-
const {
|
|
6581
|
+
const { requestedUrl, rootDirectoryUrl, mainFilePath } =
|
|
6480
6582
|
reference.ownerUrlInfo.context;
|
|
6583
|
+
const closestHtmlRootFile = getClosestHtmlRootFile(
|
|
6584
|
+
requestedUrl,
|
|
6585
|
+
rootDirectoryUrl,
|
|
6586
|
+
);
|
|
6587
|
+
if (closestHtmlRootFile) {
|
|
6588
|
+
return closestHtmlRootFile;
|
|
6589
|
+
}
|
|
6481
6590
|
return new URL(mainFilePath, rootDirectoryUrl);
|
|
6482
6591
|
}
|
|
6483
6592
|
return null;
|
|
@@ -6527,9 +6636,34 @@ const resolveSymlink = (fileUrl) => {
|
|
|
6527
6636
|
return realUrlObject.href;
|
|
6528
6637
|
};
|
|
6529
6638
|
|
|
6639
|
+
const getClosestHtmlRootFile = (requestedUrl, serverRootDirectoryUrl) => {
|
|
6640
|
+
let directoryUrl = new URL("./", requestedUrl);
|
|
6641
|
+
while (true) {
|
|
6642
|
+
const indexHtmlFileUrl = new URL(`index.html`, directoryUrl);
|
|
6643
|
+
if (existsSync(indexHtmlFileUrl)) {
|
|
6644
|
+
return indexHtmlFileUrl.href;
|
|
6645
|
+
}
|
|
6646
|
+
const htmlFileUrlCandidate = new URL(
|
|
6647
|
+
`${urlToFilename(directoryUrl)}.html`,
|
|
6648
|
+
directoryUrl,
|
|
6649
|
+
);
|
|
6650
|
+
if (existsSync(htmlFileUrlCandidate)) {
|
|
6651
|
+
return htmlFileUrlCandidate.href;
|
|
6652
|
+
}
|
|
6653
|
+
if (
|
|
6654
|
+
!urlIsInsideOf(directoryUrl, serverRootDirectoryUrl) ||
|
|
6655
|
+
directoryUrl.href === serverRootDirectoryUrl
|
|
6656
|
+
) {
|
|
6657
|
+
return null;
|
|
6658
|
+
}
|
|
6659
|
+
directoryUrl = new URL("../", directoryUrl);
|
|
6660
|
+
}
|
|
6661
|
+
};
|
|
6662
|
+
|
|
6530
6663
|
const directoryContentMagicName = "...";
|
|
6531
6664
|
|
|
6532
6665
|
const jsenvPluginProtocolFile = ({
|
|
6666
|
+
spa,
|
|
6533
6667
|
magicExtensions,
|
|
6534
6668
|
magicDirectoryIndex,
|
|
6535
6669
|
preserveSymlinks,
|
|
@@ -6541,6 +6675,7 @@ const jsenvPluginProtocolFile = ({
|
|
|
6541
6675
|
}) => {
|
|
6542
6676
|
return [
|
|
6543
6677
|
jsenvPluginFsRedirection({
|
|
6678
|
+
spa,
|
|
6544
6679
|
directoryContentMagicName,
|
|
6545
6680
|
magicExtensions,
|
|
6546
6681
|
magicDirectoryIndex,
|
|
@@ -6872,6 +7007,69 @@ const jsenvPluginDirectoryReferenceEffect = (
|
|
|
6872
7007
|
};
|
|
6873
7008
|
};
|
|
6874
7009
|
|
|
7010
|
+
const jsenvPluginInjections = (rawAssociations) => {
|
|
7011
|
+
const getDefaultInjections = (urlInfo) => {
|
|
7012
|
+
if (urlInfo.context.dev && urlInfo.type === "html") {
|
|
7013
|
+
const relativeUrl = urlToRelativeUrl(
|
|
7014
|
+
urlInfo.url,
|
|
7015
|
+
urlInfo.context.rootDirectoryUrl,
|
|
7016
|
+
);
|
|
7017
|
+
return {
|
|
7018
|
+
HTML_ROOT_PATHNAME: INJECTIONS.global(`/${relativeUrl}`),
|
|
7019
|
+
};
|
|
7020
|
+
}
|
|
7021
|
+
return null;
|
|
7022
|
+
};
|
|
7023
|
+
let getInjections = null;
|
|
7024
|
+
|
|
7025
|
+
return {
|
|
7026
|
+
name: "jsenv:injections",
|
|
7027
|
+
appliesDuring: "*",
|
|
7028
|
+
init: (context) => {
|
|
7029
|
+
if (rawAssociations && Object.keys(rawAssociations).length > 0) {
|
|
7030
|
+
const resolvedAssociations = URL_META.resolveAssociations(
|
|
7031
|
+
{ injectionsGetter: rawAssociations },
|
|
7032
|
+
context.rootDirectoryUrl,
|
|
7033
|
+
);
|
|
7034
|
+
getInjections = (urlInfo) => {
|
|
7035
|
+
const { injectionsGetter } = URL_META.applyAssociations({
|
|
7036
|
+
url: asUrlWithoutSearch(urlInfo.url),
|
|
7037
|
+
associations: resolvedAssociations,
|
|
7038
|
+
});
|
|
7039
|
+
if (!injectionsGetter) {
|
|
7040
|
+
return null;
|
|
7041
|
+
}
|
|
7042
|
+
if (typeof injectionsGetter !== "function") {
|
|
7043
|
+
throw new TypeError("injectionsGetter must be a function");
|
|
7044
|
+
}
|
|
7045
|
+
return injectionsGetter(urlInfo);
|
|
7046
|
+
};
|
|
7047
|
+
}
|
|
7048
|
+
},
|
|
7049
|
+
transformUrlContent: async (urlInfo) => {
|
|
7050
|
+
const defaultInjections = getDefaultInjections(urlInfo);
|
|
7051
|
+
if (!getInjections) {
|
|
7052
|
+
return {
|
|
7053
|
+
contentInjections: defaultInjections,
|
|
7054
|
+
};
|
|
7055
|
+
}
|
|
7056
|
+
const injectionsResult = getInjections(urlInfo);
|
|
7057
|
+
if (!injectionsResult) {
|
|
7058
|
+
return {
|
|
7059
|
+
contentInjections: defaultInjections,
|
|
7060
|
+
};
|
|
7061
|
+
}
|
|
7062
|
+
const injections = await injectionsResult;
|
|
7063
|
+
return {
|
|
7064
|
+
contentInjections: {
|
|
7065
|
+
...defaultInjections,
|
|
7066
|
+
...injections,
|
|
7067
|
+
},
|
|
7068
|
+
};
|
|
7069
|
+
},
|
|
7070
|
+
};
|
|
7071
|
+
};
|
|
7072
|
+
|
|
6875
7073
|
const jsenvPluginInliningAsDataUrl = () => {
|
|
6876
7074
|
return {
|
|
6877
7075
|
name: "jsenv:inlining_as_data_url",
|
|
@@ -7317,6 +7515,8 @@ const babelPluginMetadataExpressionPaths = (
|
|
|
7317
7515
|
* - replaced by true: When scenario matches (import.meta.dev and it's the dev server)
|
|
7318
7516
|
* - left as is to be evaluated to undefined (import.meta.build but it's the dev server)
|
|
7319
7517
|
* - replaced by undefined (import.meta.dev but it's build; the goal is to ensure it's tree-shaked)
|
|
7518
|
+
*
|
|
7519
|
+
* TODO: ideally during dev we would keep import.meta.dev and ensure we set it to true rather than replacing it with true?
|
|
7320
7520
|
*/
|
|
7321
7521
|
|
|
7322
7522
|
|
|
@@ -7422,14 +7622,12 @@ const babelPluginMetadataImportMetaScenarios = () => {
|
|
|
7422
7622
|
|
|
7423
7623
|
const jsenvPluginGlobalScenarios = () => {
|
|
7424
7624
|
const transformIfNeeded = (urlInfo) => {
|
|
7425
|
-
return
|
|
7426
|
-
|
|
7427
|
-
{
|
|
7625
|
+
return {
|
|
7626
|
+
contentInjections: {
|
|
7428
7627
|
__DEV__: INJECTIONS.optional(urlInfo.context.dev),
|
|
7429
7628
|
__BUILD__: INJECTIONS.optional(urlInfo.context.build),
|
|
7430
7629
|
},
|
|
7431
|
-
|
|
7432
|
-
);
|
|
7630
|
+
};
|
|
7433
7631
|
};
|
|
7434
7632
|
|
|
7435
7633
|
return {
|
|
@@ -8672,6 +8870,7 @@ const getCorePlugins = ({
|
|
|
8672
8870
|
transpilation = true,
|
|
8673
8871
|
inlining = true,
|
|
8674
8872
|
http = false,
|
|
8873
|
+
spa,
|
|
8675
8874
|
|
|
8676
8875
|
clientAutoreload,
|
|
8677
8876
|
clientAutoreloadOnServerRestart,
|
|
@@ -8701,7 +8900,7 @@ const getCorePlugins = ({
|
|
|
8701
8900
|
|
|
8702
8901
|
return [
|
|
8703
8902
|
jsenvPluginReferenceAnalysis(referenceAnalysis),
|
|
8704
|
-
|
|
8903
|
+
jsenvPluginInjections(injections),
|
|
8705
8904
|
jsenvPluginTranspilation(transpilation),
|
|
8706
8905
|
// "jsenvPluginInlining" must be very soon because all other plugins will react differently once they see the file is inlined
|
|
8707
8906
|
...(inlining ? [jsenvPluginInlining()] : []),
|
|
@@ -8714,6 +8913,7 @@ const getCorePlugins = ({
|
|
|
8714
8913
|
*/
|
|
8715
8914
|
jsenvPluginProtocolHttp(http),
|
|
8716
8915
|
jsenvPluginProtocolFile({
|
|
8916
|
+
spa,
|
|
8717
8917
|
magicExtensions,
|
|
8718
8918
|
magicDirectoryIndex,
|
|
8719
8919
|
directoryListing,
|
|
@@ -8956,6 +9156,7 @@ const startDevServer = async ({
|
|
|
8956
9156
|
ribbon = true,
|
|
8957
9157
|
// toolbar = false,
|
|
8958
9158
|
onKitchenCreated = () => {},
|
|
9159
|
+
spa,
|
|
8959
9160
|
|
|
8960
9161
|
sourcemaps = "inline",
|
|
8961
9162
|
sourcemapsSourcesContent,
|
|
@@ -9124,6 +9325,7 @@ const startDevServer = async ({
|
|
|
9124
9325
|
supervisor,
|
|
9125
9326
|
injections,
|
|
9126
9327
|
transpilation,
|
|
9328
|
+
spa,
|
|
9127
9329
|
|
|
9128
9330
|
clientAutoreload,
|
|
9129
9331
|
clientAutoreloadOnServerRestart,
|
|
@@ -9501,7 +9703,7 @@ const startDevServer = async ({
|
|
|
9501
9703
|
body: response.body,
|
|
9502
9704
|
});
|
|
9503
9705
|
return {
|
|
9504
|
-
status:
|
|
9706
|
+
status: response.status,
|
|
9505
9707
|
headers: {
|
|
9506
9708
|
"content-type": "application/json",
|
|
9507
9709
|
"content-length": Buffer.byteLength(body),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/core",
|
|
3
|
-
"version": "40.
|
|
3
|
+
"version": "40.7.0",
|
|
4
4
|
"description": "Tool to develop, test and build js projects",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -82,11 +82,11 @@
|
|
|
82
82
|
"dependencies": {
|
|
83
83
|
"@financial-times/polyfill-useragent-normaliser": "1.10.2",
|
|
84
84
|
"@jsenv/ast": "6.7.3",
|
|
85
|
-
"@jsenv/js-module-fallback": "1.4.
|
|
85
|
+
"@jsenv/js-module-fallback": "1.4.14",
|
|
86
86
|
"@jsenv/plugin-bundling": "2.9.7",
|
|
87
87
|
"@jsenv/plugin-minification": "1.7.0",
|
|
88
|
-
"@jsenv/plugin-supervisor": "1.7.
|
|
89
|
-
"@jsenv/plugin-transpilation": "1.5.
|
|
88
|
+
"@jsenv/plugin-supervisor": "1.7.2",
|
|
89
|
+
"@jsenv/plugin-transpilation": "1.5.21",
|
|
90
90
|
"@jsenv/server": "16.1.2",
|
|
91
91
|
"@jsenv/sourcemap": "1.3.8"
|
|
92
92
|
},
|