@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.
@@ -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, injectJsenvScript } from "@jsenv/ast";
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
- const contentModified = setContentProperties(urlInfo, {
2453
- content,
2454
- contentAst,
2455
- contentEtag,
2456
- contentLength,
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$1(htmlForSyntaxError, replacers);
3827
+ const html = replacePlaceholders(htmlForSyntaxError, replacers);
3636
3828
  return html;
3637
3829
  };
3638
- const replacePlaceholders$1 = (html, replacers) => {
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
- return replacePlaceholders(
6087
- urlInfo.content,
6184
+ const directoryListingInjections = generateDirectoryListingInjection(
6185
+ requestedUrl,
6088
6186
  {
6089
- ...generateDirectoryListingInjection(requestedUrl, {
6090
- autoreload,
6091
- request,
6092
- urlMocks,
6093
- directoryContentMagicName,
6094
- rootDirectoryUrl,
6095
- mainFilePath,
6096
- packageDirectory,
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
- getFirstExistingDirectoryUrl(requestedUrl);
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 firstExistingDirectoryUrl = new URL("./", requestedUrl);
6336
- while (!existsSync(firstExistingDirectoryUrl)) {
6337
- firstExistingDirectoryUrl = new URL("../", firstExistingDirectoryUrl);
6338
- if (!urlIsInsideOf(firstExistingDirectoryUrl, serverRootDirectoryUrl)) {
6339
- firstExistingDirectoryUrl = new URL(serverRootDirectoryUrl);
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 firstExistingDirectoryUrl;
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 { mainFilePath, rootDirectoryUrl } =
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 replacePlaceholders(
7426
- urlInfo.content,
7427
- {
7625
+ return {
7626
+ contentInjections: {
7428
7627
  __DEV__: INJECTIONS.optional(urlInfo.context.dev),
7429
7628
  __BUILD__: INJECTIONS.optional(urlInfo.context.build),
7430
7629
  },
7431
- urlInfo,
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
- ...(injections ? [jsenvPluginInjections(injections)] : []),
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: 200,
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.6.2",
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.13",
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.1",
89
- "@jsenv/plugin-transpilation": "1.5.20",
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
  },