@jsenv/core 40.6.2 → 40.7.1
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/browserslist_index/browserslist_index.js +62 -48
- package/dist/build/build.js +412 -185
- package/dist/build/jsenv_core_packages.js +103 -105
- package/dist/client/directory_listing/js/directory_listing.js +41 -26
- package/dist/client/ribbon/ribbon.js +40 -37
- 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 +103 -105
- package/dist/start_dev_server/start_dev_server.js +412 -182
- package/package.json +21 -12
- package/src/build/build.js +9 -9
- package/src/build/build_specifier_manager.js +3 -3
- package/src/build/build_urls_generator.js +2 -2
- package/src/dev/start_dev_server.js +11 -8
- package/src/helpers/web_url_converter.js +2 -2
- package/src/kitchen/errors.js +1 -1
- package/src/kitchen/kitchen.js +2 -0
- package/src/kitchen/out_directory_url.js +2 -2
- 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/autoreload/jsenv_plugin_autoreload_server.js +2 -2
- package/src/plugins/chrome_devtools_json/jsenv_plugin_chrome_devtools_json.js +1 -0
- 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 +28 -7
- package/src/plugins/plugins.js +3 -1
- package/src/plugins/protocol_file/client/directory_listing.jsx +42 -23
- package/src/plugins/protocol_file/file_and_server_urls_converter.js +2 -5
- package/src/plugins/protocol_file/jsenv_plugin_directory_listing.js +65 -49
- package/src/plugins/protocol_file/jsenv_plugin_fs_redirection.js +36 -3
- package/src/plugins/protocol_file/jsenv_plugin_protocol_file.js +3 -0
- package/src/plugins/ribbon/client/ribbon.js +40 -37
- package/src/plugins/injections/internal/inject_globals.js +0 -52
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { WebSocketResponse, pickContentType, ServerEvents, jsenvServiceCORS, jsenvAccessControlAllowedHeaders, composeTwoResponses, serveDirectory, jsenvServiceErrorHandler, startServer } from "@jsenv/server";
|
|
2
2
|
import { convertFileSystemErrorToResponseProperties } from "@jsenv/server/src/internal/convertFileSystemErrorToResponseProperties.js";
|
|
3
|
-
import { lookupPackageDirectory, registerDirectoryLifecycle, urlToRelativeUrl, moveUrl,
|
|
3
|
+
import { lookupPackageDirectory, registerDirectoryLifecycle, urlToRelativeUrl, moveUrl, urlIsOrIsInsideOf, ensureWindowsDriveLetter, createDetailedMessage, stringifyUrlSite, generateContentFrame, validateResponseIntegrity, setUrlFilename, getCallerPosition, urlToBasename, urlToExtension, asSpecifierWithoutSearch, asUrlWithoutSearch, injectQueryParamsIntoSpecifier, bufferToEtag, isFileSystemPath, urlToPathname, setUrlBasename, urlToFileSystemPath, writeFileSync, createLogger, URL_META, applyNodeEsmResolution, RUNTIME_COMPAT, normalizeUrl, ANSI, CONTENT_TYPE, errorToHTML, DATA_URL, normalizeImportMap, composeTwoImportMaps, resolveImport, JS_QUOTES, defaultLookupPackageScope, defaultReadPackageJson, readCustomConditionsFromProcessArgs, readEntryStatSync, ensurePathnameTrailingSlash, compareFileUrls, urlToFilename, applyFileSystemMagicResolution, getExtensionsToTry, setUrlExtension, isSpecifierForNodeBuiltin, memoizeByFirstArgument, assertAndNormalizeDirectoryUrl, createTaskLog, formatError, readPackageAtOrNull } from "./jsenv_core_packages.js";
|
|
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";
|
|
@@ -165,7 +165,7 @@ const watchSourceFiles = (
|
|
|
165
165
|
|
|
166
166
|
const WEB_URL_CONVERTER = {
|
|
167
167
|
asWebUrl: (fileUrl, webServer) => {
|
|
168
|
-
if (
|
|
168
|
+
if (urlIsOrIsInsideOf(fileUrl, webServer.rootDirectoryUrl)) {
|
|
169
169
|
return moveUrl({
|
|
170
170
|
url: fileUrl,
|
|
171
171
|
from: webServer.rootDirectoryUrl,
|
|
@@ -365,7 +365,7 @@ ${error.message}`,
|
|
|
365
365
|
name: "TRANSFORM_URL_CONTENT_ERROR",
|
|
366
366
|
code: "PARSE_ERROR",
|
|
367
367
|
reason: error.message,
|
|
368
|
-
stack:
|
|
368
|
+
stack: transformError.stack,
|
|
369
369
|
trace,
|
|
370
370
|
asResponse: error.asResponse,
|
|
371
371
|
});
|
|
@@ -590,7 +590,7 @@ const determineFileUrlForOutDirectory = (urlInfo) => {
|
|
|
590
590
|
if (!url.startsWith("file:")) {
|
|
591
591
|
return url;
|
|
592
592
|
}
|
|
593
|
-
if (!
|
|
593
|
+
if (!urlIsOrIsInsideOf(url, rootDirectoryUrl)) {
|
|
594
594
|
const fsRootUrl = ensureWindowsDriveLetter("file:///", url);
|
|
595
595
|
url = `${rootDirectoryUrl}@fs/${url.slice(fsRootUrl.length)}`;
|
|
596
596
|
}
|
|
@@ -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) {
|
|
@@ -3648,10 +3840,19 @@ const replacePlaceholders$1 = (html, replacers) => {
|
|
|
3648
3840
|
});
|
|
3649
3841
|
};
|
|
3650
3842
|
|
|
3651
|
-
const createPluginStore = (plugins) => {
|
|
3843
|
+
const createPluginStore = async (plugins) => {
|
|
3652
3844
|
const allDevServerRoutes = [];
|
|
3845
|
+
const allDevServerServices = [];
|
|
3653
3846
|
const pluginArray = [];
|
|
3654
|
-
|
|
3847
|
+
|
|
3848
|
+
const pluginPromises = [];
|
|
3849
|
+
const addPlugin = async (plugin) => {
|
|
3850
|
+
if (plugin && typeof plugin.then === "function") {
|
|
3851
|
+
pluginPromises.push(plugin);
|
|
3852
|
+
const value = await plugin;
|
|
3853
|
+
addPlugin(value);
|
|
3854
|
+
return;
|
|
3855
|
+
}
|
|
3655
3856
|
if (Array.isArray(plugin)) {
|
|
3656
3857
|
for (const subplugin of plugin) {
|
|
3657
3858
|
addPlugin(subplugin);
|
|
@@ -3670,21 +3871,28 @@ const createPluginStore = (plugins) => {
|
|
|
3670
3871
|
allDevServerRoutes.push(devServerRoute);
|
|
3671
3872
|
}
|
|
3672
3873
|
}
|
|
3874
|
+
if (plugin.devServerServices) {
|
|
3875
|
+
const devServerServices = plugin.devServerServices;
|
|
3876
|
+
for (const devServerService of devServerServices) {
|
|
3877
|
+
allDevServerServices.push(devServerService);
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3673
3880
|
pluginArray.push(plugin);
|
|
3674
3881
|
};
|
|
3675
3882
|
addPlugin(jsenvPluginHtmlSyntaxErrorFallback());
|
|
3676
3883
|
for (const plugin of plugins) {
|
|
3677
3884
|
addPlugin(plugin);
|
|
3678
3885
|
}
|
|
3886
|
+
await Promise.all(pluginPromises);
|
|
3679
3887
|
|
|
3680
3888
|
return {
|
|
3681
3889
|
pluginArray,
|
|
3682
|
-
|
|
3683
3890
|
allDevServerRoutes,
|
|
3891
|
+
allDevServerServices,
|
|
3684
3892
|
};
|
|
3685
3893
|
};
|
|
3686
3894
|
|
|
3687
|
-
const createPluginController = (
|
|
3895
|
+
const createPluginController = async (
|
|
3688
3896
|
pluginStore,
|
|
3689
3897
|
kitchen,
|
|
3690
3898
|
{ initialPuginsMeta = {} } = {},
|
|
@@ -3707,7 +3915,7 @@ const createPluginController = (
|
|
|
3707
3915
|
pluginCandidate.destroy?.();
|
|
3708
3916
|
continue;
|
|
3709
3917
|
}
|
|
3710
|
-
const initPluginResult = initPlugin(pluginCandidate, kitchen);
|
|
3918
|
+
const initPluginResult = await initPlugin(pluginCandidate, kitchen);
|
|
3711
3919
|
if (!initPluginResult) {
|
|
3712
3920
|
pluginCandidate.destroy?.();
|
|
3713
3921
|
continue;
|
|
@@ -3759,6 +3967,7 @@ const createPluginController = (
|
|
|
3759
3967
|
key === "serverEvents" ||
|
|
3760
3968
|
key === "mustStayFirst" ||
|
|
3761
3969
|
key === "devServerRoutes" ||
|
|
3970
|
+
key === "devServerServices" ||
|
|
3762
3971
|
key === "effect"
|
|
3763
3972
|
) {
|
|
3764
3973
|
continue;
|
|
@@ -3932,6 +4141,7 @@ const createPluginController = (
|
|
|
3932
4141
|
const HOOK_NAMES = [
|
|
3933
4142
|
"init",
|
|
3934
4143
|
"devServerRoutes", // is called only during dev/tests
|
|
4144
|
+
"devServerServices", // is called only during dev/tests
|
|
3935
4145
|
"resolveReference",
|
|
3936
4146
|
"redirectReference",
|
|
3937
4147
|
"transformReferenceSearchParams",
|
|
@@ -3986,12 +4196,12 @@ const testAppliesDuring = (plugin, kitchen) => {
|
|
|
3986
4196
|
`"appliesDuring" must be an object or a string, got ${appliesDuring}`,
|
|
3987
4197
|
);
|
|
3988
4198
|
};
|
|
3989
|
-
const initPlugin = (plugin, kitchen) => {
|
|
4199
|
+
const initPlugin = async (plugin, kitchen) => {
|
|
3990
4200
|
const { init } = plugin;
|
|
3991
4201
|
if (!init) {
|
|
3992
4202
|
return true;
|
|
3993
4203
|
}
|
|
3994
|
-
const initReturnValue = init(kitchen.context, { plugin });
|
|
4204
|
+
const initReturnValue = await init(kitchen.context, { plugin });
|
|
3995
4205
|
if (initReturnValue === false) {
|
|
3996
4206
|
return false;
|
|
3997
4207
|
}
|
|
@@ -4070,6 +4280,9 @@ const returnValueAssertions = [
|
|
|
4070
4280
|
return undefined;
|
|
4071
4281
|
}
|
|
4072
4282
|
if (typeof content !== "string" && !Buffer.isBuffer(content) && !body) {
|
|
4283
|
+
if (Object.hasOwn(valueReturned, "contentInjections")) {
|
|
4284
|
+
return undefined;
|
|
4285
|
+
}
|
|
4073
4286
|
throw new Error(
|
|
4074
4287
|
`Unexpected "content" returned by "${hook.plugin.name}" ${hook.name} hook: it must be a string or a buffer; got ${content}`,
|
|
4075
4288
|
);
|
|
@@ -5848,10 +6061,7 @@ const jsenvPluginVersionSearchParam = () => {
|
|
|
5848
6061
|
|
|
5849
6062
|
const FILE_AND_SERVER_URLS_CONVERTER = {
|
|
5850
6063
|
asServerUrl: (fileUrl, serverRootDirectoryUrl) => {
|
|
5851
|
-
if (fileUrl
|
|
5852
|
-
return "/";
|
|
5853
|
-
}
|
|
5854
|
-
if (urlIsInsideOf(fileUrl, serverRootDirectoryUrl)) {
|
|
6064
|
+
if (urlIsOrIsInsideOf(fileUrl, serverRootDirectoryUrl)) {
|
|
5855
6065
|
const urlRelativeToServer = urlToRelativeUrl(
|
|
5856
6066
|
fileUrl,
|
|
5857
6067
|
serverRootDirectoryUrl,
|
|
@@ -5877,103 +6087,6 @@ const FILE_AND_SERVER_URLS_CONVERTER = {
|
|
|
5877
6087
|
},
|
|
5878
6088
|
};
|
|
5879
6089
|
|
|
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
6090
|
/*
|
|
5978
6091
|
* NICE TO HAVE:
|
|
5979
6092
|
*
|
|
@@ -6004,6 +6117,7 @@ const htmlFileUrlForDirectory = import.meta.resolve(
|
|
|
6004
6117
|
);
|
|
6005
6118
|
|
|
6006
6119
|
const jsenvPluginDirectoryListing = ({
|
|
6120
|
+
spa,
|
|
6007
6121
|
urlMocks = false,
|
|
6008
6122
|
autoreload = true,
|
|
6009
6123
|
directoryContentMagicName,
|
|
@@ -6045,7 +6159,7 @@ const jsenvPluginDirectoryListing = ({
|
|
|
6045
6159
|
return null;
|
|
6046
6160
|
}
|
|
6047
6161
|
}
|
|
6048
|
-
return `${htmlFileUrlForDirectory}?url=${encodeURIComponent(
|
|
6162
|
+
return `${htmlFileUrlForDirectory}?url=${encodeURIComponent(requestedUrl)}&enoent`;
|
|
6049
6163
|
}
|
|
6050
6164
|
const isDirectory = fsStat?.isDirectory();
|
|
6051
6165
|
if (!isDirectory) {
|
|
@@ -6071,34 +6185,35 @@ const jsenvPluginDirectoryListing = ({
|
|
|
6071
6185
|
if (urlWithoutSearch !== String(htmlFileUrlForDirectory)) {
|
|
6072
6186
|
return null;
|
|
6073
6187
|
}
|
|
6074
|
-
const
|
|
6075
|
-
if (!
|
|
6188
|
+
const urlNotFound = urlInfo.searchParams.get("url");
|
|
6189
|
+
if (!urlNotFound) {
|
|
6076
6190
|
return null;
|
|
6077
6191
|
}
|
|
6192
|
+
|
|
6078
6193
|
urlInfo.headers["cache-control"] = "no-cache";
|
|
6079
6194
|
const enoent = urlInfo.searchParams.has("enoent");
|
|
6080
6195
|
if (enoent) {
|
|
6081
6196
|
urlInfo.status = 404;
|
|
6082
|
-
urlInfo.headers["cache-control"] = "no-cache";
|
|
6083
6197
|
}
|
|
6084
6198
|
const request = urlInfo.context.request;
|
|
6085
6199
|
const { rootDirectoryUrl, mainFilePath } = urlInfo.context;
|
|
6086
|
-
|
|
6087
|
-
|
|
6200
|
+
const directoryListingInjections = generateDirectoryListingInjection(
|
|
6201
|
+
urlNotFound,
|
|
6088
6202
|
{
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
}),
|
|
6203
|
+
spa,
|
|
6204
|
+
autoreload,
|
|
6205
|
+
request,
|
|
6206
|
+
urlMocks,
|
|
6207
|
+
directoryContentMagicName,
|
|
6208
|
+
rootDirectoryUrl,
|
|
6209
|
+
mainFilePath,
|
|
6210
|
+
packageDirectory,
|
|
6211
|
+
enoent,
|
|
6099
6212
|
},
|
|
6100
|
-
urlInfo,
|
|
6101
6213
|
);
|
|
6214
|
+
return {
|
|
6215
|
+
contentInjections: directoryListingInjections,
|
|
6216
|
+
};
|
|
6102
6217
|
},
|
|
6103
6218
|
},
|
|
6104
6219
|
devServerRoutes: [
|
|
@@ -6117,8 +6232,10 @@ const jsenvPluginDirectoryListing = ({
|
|
|
6117
6232
|
directoryRelativeUrl,
|
|
6118
6233
|
rootDirectoryUrl,
|
|
6119
6234
|
);
|
|
6120
|
-
const closestDirectoryUrl =
|
|
6121
|
-
|
|
6235
|
+
const closestDirectoryUrl = getFirstExistingDirectoryUrl(
|
|
6236
|
+
requestedUrl,
|
|
6237
|
+
rootDirectoryUrl,
|
|
6238
|
+
);
|
|
6122
6239
|
const sendMessage = (message) => {
|
|
6123
6240
|
websocket.send(JSON.stringify(message));
|
|
6124
6241
|
};
|
|
@@ -6176,8 +6293,9 @@ const jsenvPluginDirectoryListing = ({
|
|
|
6176
6293
|
};
|
|
6177
6294
|
|
|
6178
6295
|
const generateDirectoryListingInjection = (
|
|
6179
|
-
|
|
6296
|
+
urlNotFound,
|
|
6180
6297
|
{
|
|
6298
|
+
spa,
|
|
6181
6299
|
rootDirectoryUrl,
|
|
6182
6300
|
mainFilePath,
|
|
6183
6301
|
packageDirectory,
|
|
@@ -6190,7 +6308,7 @@ const generateDirectoryListingInjection = (
|
|
|
6190
6308
|
) => {
|
|
6191
6309
|
let serverRootDirectoryUrl = rootDirectoryUrl;
|
|
6192
6310
|
const firstExistingDirectoryUrl = getFirstExistingDirectoryUrl(
|
|
6193
|
-
|
|
6311
|
+
urlNotFound,
|
|
6194
6312
|
serverRootDirectoryUrl,
|
|
6195
6313
|
);
|
|
6196
6314
|
const directoryContentItems = getDirectoryContentItems({
|
|
@@ -6241,8 +6359,8 @@ const generateDirectoryListingInjection = (
|
|
|
6241
6359
|
const { host } = new URL(request.url);
|
|
6242
6360
|
const websocketUrl = `${websocketScheme}://${host}/.internal/directory_content.websocket?directory=${encodeURIComponent(directoryUrlRelativeToServer)}`;
|
|
6243
6361
|
|
|
6244
|
-
const
|
|
6245
|
-
|
|
6362
|
+
const generateBreadcrumb = () => {
|
|
6363
|
+
const breadcrumb = [];
|
|
6246
6364
|
const lastItemUrl = firstExistingDirectoryUrl;
|
|
6247
6365
|
const lastItemRelativeUrl = urlToRelativeUrl(lastItemUrl, rootDirectoryUrl);
|
|
6248
6366
|
const rootDirectoryUrlName = urlToFilename(rootDirectoryUrl);
|
|
@@ -6252,7 +6370,6 @@ const generateDirectoryListingInjection = (
|
|
|
6252
6370
|
} else {
|
|
6253
6371
|
parts = [rootDirectoryUrlName];
|
|
6254
6372
|
}
|
|
6255
|
-
|
|
6256
6373
|
let i = 0;
|
|
6257
6374
|
while (i < parts.length) {
|
|
6258
6375
|
const part = parts[i];
|
|
@@ -6273,7 +6390,7 @@ const generateDirectoryListingInjection = (
|
|
|
6273
6390
|
navItemUrl,
|
|
6274
6391
|
serverRootDirectoryUrl,
|
|
6275
6392
|
);
|
|
6276
|
-
let urlRelativeToDocument = urlToRelativeUrl(navItemUrl,
|
|
6393
|
+
let urlRelativeToDocument = urlToRelativeUrl(navItemUrl, urlNotFound);
|
|
6277
6394
|
const isServerRootDirectory = navItemUrl === serverRootDirectoryUrl;
|
|
6278
6395
|
if (isServerRootDirectory) {
|
|
6279
6396
|
urlRelativeToServer = `/${directoryContentMagicName}`;
|
|
@@ -6281,7 +6398,7 @@ const generateDirectoryListingInjection = (
|
|
|
6281
6398
|
}
|
|
6282
6399
|
const name = part;
|
|
6283
6400
|
const isCurrent = navItemUrl === String(firstExistingDirectoryUrl);
|
|
6284
|
-
|
|
6401
|
+
breadcrumb.push({
|
|
6285
6402
|
url: navItemUrl,
|
|
6286
6403
|
urlRelativeToServer,
|
|
6287
6404
|
urlRelativeToDocument,
|
|
@@ -6291,34 +6408,47 @@ const generateDirectoryListingInjection = (
|
|
|
6291
6408
|
});
|
|
6292
6409
|
i++;
|
|
6293
6410
|
}
|
|
6294
|
-
|
|
6411
|
+
return breadcrumb;
|
|
6412
|
+
};
|
|
6413
|
+
const breadcrumb = generateBreadcrumb();
|
|
6295
6414
|
|
|
6296
6415
|
let enoentDetails = null;
|
|
6297
6416
|
if (enoent) {
|
|
6417
|
+
const buildEnoentPathInfo = (urlBase, closestExistingUrl) => {
|
|
6418
|
+
let filePathExisting;
|
|
6419
|
+
let filePathNotFound;
|
|
6420
|
+
const existingIndex = String(closestExistingUrl).length;
|
|
6421
|
+
filePathExisting = urlToRelativeUrl(
|
|
6422
|
+
closestExistingUrl,
|
|
6423
|
+
serverRootDirectoryUrl,
|
|
6424
|
+
);
|
|
6425
|
+
filePathNotFound = urlBase.slice(existingIndex);
|
|
6426
|
+
return [filePathExisting, filePathNotFound];
|
|
6427
|
+
};
|
|
6298
6428
|
const fileRelativeUrl = urlToRelativeUrl(
|
|
6299
|
-
|
|
6429
|
+
urlNotFound,
|
|
6300
6430
|
serverRootDirectoryUrl,
|
|
6301
6431
|
);
|
|
6302
|
-
let filePathExisting;
|
|
6303
|
-
let filePathNotFound;
|
|
6304
|
-
const existingIndex = String(firstExistingDirectoryUrl).length;
|
|
6305
|
-
filePathExisting = urlToRelativeUrl(
|
|
6306
|
-
firstExistingDirectoryUrl,
|
|
6307
|
-
serverRootDirectoryUrl,
|
|
6308
|
-
);
|
|
6309
|
-
filePathNotFound = requestedUrl.slice(existingIndex);
|
|
6310
6432
|
enoentDetails = {
|
|
6311
|
-
fileUrl:
|
|
6433
|
+
fileUrl: urlNotFound,
|
|
6312
6434
|
fileRelativeUrl,
|
|
6435
|
+
};
|
|
6436
|
+
|
|
6437
|
+
const [filePathExisting, filePathNotFound] = buildEnoentPathInfo(
|
|
6438
|
+
urlNotFound,
|
|
6439
|
+
firstExistingDirectoryUrl,
|
|
6440
|
+
);
|
|
6441
|
+
Object.assign(enoentDetails, {
|
|
6313
6442
|
filePathExisting: `/${filePathExisting}`,
|
|
6314
6443
|
filePathNotFound,
|
|
6315
|
-
};
|
|
6444
|
+
});
|
|
6316
6445
|
}
|
|
6317
6446
|
|
|
6318
6447
|
return {
|
|
6319
6448
|
__DIRECTORY_LISTING__: {
|
|
6449
|
+
spa,
|
|
6320
6450
|
enoentDetails,
|
|
6321
|
-
|
|
6451
|
+
breadcrumb,
|
|
6322
6452
|
urlMocks,
|
|
6323
6453
|
directoryContentMagicName,
|
|
6324
6454
|
directoryUrl: firstExistingDirectoryUrl,
|
|
@@ -6331,16 +6461,16 @@ const generateDirectoryListingInjection = (
|
|
|
6331
6461
|
},
|
|
6332
6462
|
};
|
|
6333
6463
|
};
|
|
6334
|
-
const getFirstExistingDirectoryUrl = (
|
|
6335
|
-
let
|
|
6336
|
-
while (!existsSync(
|
|
6337
|
-
|
|
6338
|
-
if (!
|
|
6339
|
-
|
|
6464
|
+
const getFirstExistingDirectoryUrl = (urlBase, serverRootDirectoryUrl) => {
|
|
6465
|
+
let directoryUrlCandidate = new URL("./", urlBase);
|
|
6466
|
+
while (!existsSync(directoryUrlCandidate)) {
|
|
6467
|
+
directoryUrlCandidate = new URL("../", directoryUrlCandidate);
|
|
6468
|
+
if (!urlIsOrIsInsideOf(directoryUrlCandidate, serverRootDirectoryUrl)) {
|
|
6469
|
+
directoryUrlCandidate = new URL(serverRootDirectoryUrl);
|
|
6340
6470
|
break;
|
|
6341
6471
|
}
|
|
6342
6472
|
}
|
|
6343
|
-
return
|
|
6473
|
+
return directoryUrlCandidate;
|
|
6344
6474
|
};
|
|
6345
6475
|
const getDirectoryContentItems = ({
|
|
6346
6476
|
serverRootDirectoryUrl,
|
|
@@ -6384,6 +6514,7 @@ const getDirectoryContentItems = ({
|
|
|
6384
6514
|
};
|
|
6385
6515
|
|
|
6386
6516
|
const jsenvPluginFsRedirection = ({
|
|
6517
|
+
spa,
|
|
6387
6518
|
directoryContentMagicName,
|
|
6388
6519
|
magicExtensions = ["inherit", ".js"],
|
|
6389
6520
|
magicDirectoryIndex = true,
|
|
@@ -6473,11 +6604,19 @@ const jsenvPluginFsRedirection = ({
|
|
|
6473
6604
|
// 3. The url pathname does not ends with "/"
|
|
6474
6605
|
// In that case we assume client explicitely asks to load a directory
|
|
6475
6606
|
if (
|
|
6607
|
+
spa &&
|
|
6476
6608
|
!urlToExtension(urlObject) &&
|
|
6477
6609
|
!urlToPathname(urlObject).endsWith("/")
|
|
6478
6610
|
) {
|
|
6479
|
-
const {
|
|
6611
|
+
const { requestedUrl, rootDirectoryUrl, mainFilePath } =
|
|
6480
6612
|
reference.ownerUrlInfo.context;
|
|
6613
|
+
const closestHtmlRootFile = getClosestHtmlRootFile(
|
|
6614
|
+
requestedUrl,
|
|
6615
|
+
rootDirectoryUrl,
|
|
6616
|
+
);
|
|
6617
|
+
if (closestHtmlRootFile) {
|
|
6618
|
+
return closestHtmlRootFile;
|
|
6619
|
+
}
|
|
6481
6620
|
return new URL(mainFilePath, rootDirectoryUrl);
|
|
6482
6621
|
}
|
|
6483
6622
|
return null;
|
|
@@ -6527,9 +6666,29 @@ const resolveSymlink = (fileUrl) => {
|
|
|
6527
6666
|
return realUrlObject.href;
|
|
6528
6667
|
};
|
|
6529
6668
|
|
|
6669
|
+
const getClosestHtmlRootFile = (requestedUrl, serverRootDirectoryUrl) => {
|
|
6670
|
+
let directoryUrl = new URL("./", requestedUrl);
|
|
6671
|
+
while (true) {
|
|
6672
|
+
const indexHtmlFileUrl = new URL(`index.html`, directoryUrl);
|
|
6673
|
+
if (existsSync(indexHtmlFileUrl)) {
|
|
6674
|
+
return indexHtmlFileUrl.href;
|
|
6675
|
+
}
|
|
6676
|
+
const filename = urlToFilename(directoryUrl);
|
|
6677
|
+
const htmlFileUrlCandidate = new URL(`${filename}.html`, directoryUrl);
|
|
6678
|
+
if (existsSync(htmlFileUrlCandidate)) {
|
|
6679
|
+
return htmlFileUrlCandidate.href;
|
|
6680
|
+
}
|
|
6681
|
+
if (!urlIsOrIsInsideOf(directoryUrl, serverRootDirectoryUrl)) {
|
|
6682
|
+
return null;
|
|
6683
|
+
}
|
|
6684
|
+
directoryUrl = new URL("../", directoryUrl);
|
|
6685
|
+
}
|
|
6686
|
+
};
|
|
6687
|
+
|
|
6530
6688
|
const directoryContentMagicName = "...";
|
|
6531
6689
|
|
|
6532
6690
|
const jsenvPluginProtocolFile = ({
|
|
6691
|
+
spa = true,
|
|
6533
6692
|
magicExtensions,
|
|
6534
6693
|
magicDirectoryIndex,
|
|
6535
6694
|
preserveSymlinks,
|
|
@@ -6541,6 +6700,7 @@ const jsenvPluginProtocolFile = ({
|
|
|
6541
6700
|
}) => {
|
|
6542
6701
|
return [
|
|
6543
6702
|
jsenvPluginFsRedirection({
|
|
6703
|
+
spa,
|
|
6544
6704
|
directoryContentMagicName,
|
|
6545
6705
|
magicExtensions,
|
|
6546
6706
|
magicDirectoryIndex,
|
|
@@ -6596,6 +6756,7 @@ const jsenvPluginProtocolFile = ({
|
|
|
6596
6756
|
...(directoryListing
|
|
6597
6757
|
? [
|
|
6598
6758
|
jsenvPluginDirectoryListing({
|
|
6759
|
+
spa,
|
|
6599
6760
|
...directoryListing,
|
|
6600
6761
|
directoryContentMagicName,
|
|
6601
6762
|
rootDirectoryUrl,
|
|
@@ -6872,6 +7033,69 @@ const jsenvPluginDirectoryReferenceEffect = (
|
|
|
6872
7033
|
};
|
|
6873
7034
|
};
|
|
6874
7035
|
|
|
7036
|
+
const jsenvPluginInjections = (rawAssociations) => {
|
|
7037
|
+
const getDefaultInjections = (urlInfo) => {
|
|
7038
|
+
if (urlInfo.context.dev && urlInfo.type === "html") {
|
|
7039
|
+
const relativeUrl = urlToRelativeUrl(
|
|
7040
|
+
urlInfo.url,
|
|
7041
|
+
urlInfo.context.rootDirectoryUrl,
|
|
7042
|
+
);
|
|
7043
|
+
return {
|
|
7044
|
+
HTML_ROOT_PATHNAME: INJECTIONS.global(`/${relativeUrl}`),
|
|
7045
|
+
};
|
|
7046
|
+
}
|
|
7047
|
+
return null;
|
|
7048
|
+
};
|
|
7049
|
+
let getInjections = null;
|
|
7050
|
+
|
|
7051
|
+
return {
|
|
7052
|
+
name: "jsenv:injections",
|
|
7053
|
+
appliesDuring: "*",
|
|
7054
|
+
init: (context) => {
|
|
7055
|
+
if (rawAssociations && Object.keys(rawAssociations).length > 0) {
|
|
7056
|
+
const resolvedAssociations = URL_META.resolveAssociations(
|
|
7057
|
+
{ injectionsGetter: rawAssociations },
|
|
7058
|
+
context.rootDirectoryUrl,
|
|
7059
|
+
);
|
|
7060
|
+
getInjections = (urlInfo) => {
|
|
7061
|
+
const { injectionsGetter } = URL_META.applyAssociations({
|
|
7062
|
+
url: asUrlWithoutSearch(urlInfo.url),
|
|
7063
|
+
associations: resolvedAssociations,
|
|
7064
|
+
});
|
|
7065
|
+
if (!injectionsGetter) {
|
|
7066
|
+
return null;
|
|
7067
|
+
}
|
|
7068
|
+
if (typeof injectionsGetter !== "function") {
|
|
7069
|
+
throw new TypeError("injectionsGetter must be a function");
|
|
7070
|
+
}
|
|
7071
|
+
return injectionsGetter(urlInfo);
|
|
7072
|
+
};
|
|
7073
|
+
}
|
|
7074
|
+
},
|
|
7075
|
+
transformUrlContent: async (urlInfo) => {
|
|
7076
|
+
const defaultInjections = getDefaultInjections(urlInfo);
|
|
7077
|
+
if (!getInjections) {
|
|
7078
|
+
return {
|
|
7079
|
+
contentInjections: defaultInjections,
|
|
7080
|
+
};
|
|
7081
|
+
}
|
|
7082
|
+
const injectionsResult = getInjections(urlInfo);
|
|
7083
|
+
if (!injectionsResult) {
|
|
7084
|
+
return {
|
|
7085
|
+
contentInjections: defaultInjections,
|
|
7086
|
+
};
|
|
7087
|
+
}
|
|
7088
|
+
const injections = await injectionsResult;
|
|
7089
|
+
return {
|
|
7090
|
+
contentInjections: {
|
|
7091
|
+
...defaultInjections,
|
|
7092
|
+
...injections,
|
|
7093
|
+
},
|
|
7094
|
+
};
|
|
7095
|
+
},
|
|
7096
|
+
};
|
|
7097
|
+
};
|
|
7098
|
+
|
|
6875
7099
|
const jsenvPluginInliningAsDataUrl = () => {
|
|
6876
7100
|
return {
|
|
6877
7101
|
name: "jsenv:inlining_as_data_url",
|
|
@@ -7317,6 +7541,8 @@ const babelPluginMetadataExpressionPaths = (
|
|
|
7317
7541
|
* - replaced by true: When scenario matches (import.meta.dev and it's the dev server)
|
|
7318
7542
|
* - left as is to be evaluated to undefined (import.meta.build but it's the dev server)
|
|
7319
7543
|
* - replaced by undefined (import.meta.dev but it's build; the goal is to ensure it's tree-shaked)
|
|
7544
|
+
*
|
|
7545
|
+
* TODO: ideally during dev we would keep import.meta.dev and ensure we set it to true rather than replacing it with true?
|
|
7320
7546
|
*/
|
|
7321
7547
|
|
|
7322
7548
|
|
|
@@ -7422,14 +7648,12 @@ const babelPluginMetadataImportMetaScenarios = () => {
|
|
|
7422
7648
|
|
|
7423
7649
|
const jsenvPluginGlobalScenarios = () => {
|
|
7424
7650
|
const transformIfNeeded = (urlInfo) => {
|
|
7425
|
-
return
|
|
7426
|
-
|
|
7427
|
-
{
|
|
7651
|
+
return {
|
|
7652
|
+
contentInjections: {
|
|
7428
7653
|
__DEV__: INJECTIONS.optional(urlInfo.context.dev),
|
|
7429
7654
|
__BUILD__: INJECTIONS.optional(urlInfo.context.build),
|
|
7430
7655
|
},
|
|
7431
|
-
|
|
7432
|
-
);
|
|
7656
|
+
};
|
|
7433
7657
|
};
|
|
7434
7658
|
|
|
7435
7659
|
return {
|
|
@@ -7862,7 +8086,7 @@ const jsenvPluginAutoreloadServer = ({
|
|
|
7862
8086
|
serverEvents: {
|
|
7863
8087
|
reload: (serverEventInfo) => {
|
|
7864
8088
|
const formatUrlForClient = (url) => {
|
|
7865
|
-
if (
|
|
8089
|
+
if (urlIsOrIsInsideOf(url, serverEventInfo.rootDirectoryUrl)) {
|
|
7866
8090
|
return urlToRelativeUrl(url, serverEventInfo.rootDirectoryUrl);
|
|
7867
8091
|
}
|
|
7868
8092
|
if (url.startsWith("file:")) {
|
|
@@ -8437,6 +8661,7 @@ const jsenvPluginChromeDevtoolsJson = () => {
|
|
|
8437
8661
|
devServerRoutes: [
|
|
8438
8662
|
{
|
|
8439
8663
|
endpoint: "GET /.well-known/appspecific/com.chrome.devtools.json",
|
|
8664
|
+
declarationSource: import.meta.url,
|
|
8440
8665
|
fetch: (request, { kitchen }) => {
|
|
8441
8666
|
const { rootDirectoryUrl } = kitchen.context;
|
|
8442
8667
|
return Response.json({
|
|
@@ -8672,6 +8897,7 @@ const getCorePlugins = ({
|
|
|
8672
8897
|
transpilation = true,
|
|
8673
8898
|
inlining = true,
|
|
8674
8899
|
http = false,
|
|
8900
|
+
spa,
|
|
8675
8901
|
|
|
8676
8902
|
clientAutoreload,
|
|
8677
8903
|
clientAutoreloadOnServerRestart,
|
|
@@ -8701,7 +8927,7 @@ const getCorePlugins = ({
|
|
|
8701
8927
|
|
|
8702
8928
|
return [
|
|
8703
8929
|
jsenvPluginReferenceAnalysis(referenceAnalysis),
|
|
8704
|
-
|
|
8930
|
+
jsenvPluginInjections(injections),
|
|
8705
8931
|
jsenvPluginTranspilation(transpilation),
|
|
8706
8932
|
// "jsenvPluginInlining" must be very soon because all other plugins will react differently once they see the file is inlined
|
|
8707
8933
|
...(inlining ? [jsenvPluginInlining()] : []),
|
|
@@ -8714,6 +8940,7 @@ const getCorePlugins = ({
|
|
|
8714
8940
|
*/
|
|
8715
8941
|
jsenvPluginProtocolHttp(http),
|
|
8716
8942
|
jsenvPluginProtocolFile({
|
|
8943
|
+
spa,
|
|
8717
8944
|
magicExtensions,
|
|
8718
8945
|
magicDirectoryIndex,
|
|
8719
8946
|
directoryListing,
|
|
@@ -8956,6 +9183,7 @@ const startDevServer = async ({
|
|
|
8956
9183
|
ribbon = true,
|
|
8957
9184
|
// toolbar = false,
|
|
8958
9185
|
onKitchenCreated = () => {},
|
|
9186
|
+
spa,
|
|
8959
9187
|
|
|
8960
9188
|
sourcemaps = "inline",
|
|
8961
9189
|
sourcemapsSourcesContent,
|
|
@@ -9106,7 +9334,7 @@ const startDevServer = async ({
|
|
|
9106
9334
|
read: readPackageAtOrNull,
|
|
9107
9335
|
};
|
|
9108
9336
|
|
|
9109
|
-
const devServerPluginStore = createPluginStore([
|
|
9337
|
+
const devServerPluginStore = await createPluginStore([
|
|
9110
9338
|
jsenvPluginServerEvents({ clientAutoreload }),
|
|
9111
9339
|
...plugins,
|
|
9112
9340
|
...getCorePlugins({
|
|
@@ -9124,6 +9352,7 @@ const startDevServer = async ({
|
|
|
9124
9352
|
supervisor,
|
|
9125
9353
|
injections,
|
|
9126
9354
|
transpilation,
|
|
9355
|
+
spa,
|
|
9127
9356
|
|
|
9128
9357
|
clientAutoreload,
|
|
9129
9358
|
clientAutoreloadOnServerRestart,
|
|
@@ -9131,7 +9360,7 @@ const startDevServer = async ({
|
|
|
9131
9360
|
ribbon,
|
|
9132
9361
|
}),
|
|
9133
9362
|
]);
|
|
9134
|
-
const getOrCreateKitchen = (request) => {
|
|
9363
|
+
const getOrCreateKitchen = async (request) => {
|
|
9135
9364
|
const { runtimeName, runtimeVersion } = parseUserAgentHeader(
|
|
9136
9365
|
request.headers["user-agent"] || "",
|
|
9137
9366
|
);
|
|
@@ -9253,7 +9482,7 @@ const startDevServer = async ({
|
|
|
9253
9482
|
);
|
|
9254
9483
|
},
|
|
9255
9484
|
);
|
|
9256
|
-
const devServerPluginController = createPluginController(
|
|
9485
|
+
const devServerPluginController = await createPluginController(
|
|
9257
9486
|
devServerPluginStore,
|
|
9258
9487
|
kitchen,
|
|
9259
9488
|
);
|
|
@@ -9269,8 +9498,8 @@ const startDevServer = async ({
|
|
|
9269
9498
|
|
|
9270
9499
|
finalServices.push({
|
|
9271
9500
|
name: "jsenv:dev_server_routes",
|
|
9272
|
-
augmentRouteFetchSecondArg: (request) => {
|
|
9273
|
-
const kitchen = getOrCreateKitchen(request);
|
|
9501
|
+
augmentRouteFetchSecondArg: async (request) => {
|
|
9502
|
+
const kitchen = await getOrCreateKitchen(request);
|
|
9274
9503
|
return { kitchen };
|
|
9275
9504
|
},
|
|
9276
9505
|
routes: [
|
|
@@ -9473,6 +9702,7 @@ const startDevServer = async ({
|
|
|
9473
9702
|
},
|
|
9474
9703
|
],
|
|
9475
9704
|
});
|
|
9705
|
+
finalServices.push(...devServerPluginStore.allDevServerServices);
|
|
9476
9706
|
}
|
|
9477
9707
|
// jsenv error handler service
|
|
9478
9708
|
{
|
|
@@ -9501,7 +9731,7 @@ const startDevServer = async ({
|
|
|
9501
9731
|
body: response.body,
|
|
9502
9732
|
});
|
|
9503
9733
|
return {
|
|
9504
|
-
status:
|
|
9734
|
+
status: response.status,
|
|
9505
9735
|
headers: {
|
|
9506
9736
|
"content-type": "application/json",
|
|
9507
9737
|
"content-length": Buffer.byteLength(body),
|