@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.
Files changed (36) hide show
  1. package/dist/build/browserslist_index/browserslist_index.js +62 -48
  2. package/dist/build/build.js +412 -185
  3. package/dist/build/jsenv_core_packages.js +103 -105
  4. package/dist/client/directory_listing/js/directory_listing.js +41 -26
  5. package/dist/client/ribbon/ribbon.js +40 -37
  6. package/dist/jsenv_core.js +4 -0
  7. package/dist/start_build_server/jsenv_core_packages.js +29 -29
  8. package/dist/start_dev_server/jsenv_core_packages.js +103 -105
  9. package/dist/start_dev_server/start_dev_server.js +412 -182
  10. package/package.json +21 -12
  11. package/src/build/build.js +9 -9
  12. package/src/build/build_specifier_manager.js +3 -3
  13. package/src/build/build_urls_generator.js +2 -2
  14. package/src/dev/start_dev_server.js +11 -8
  15. package/src/helpers/web_url_converter.js +2 -2
  16. package/src/kitchen/errors.js +1 -1
  17. package/src/kitchen/kitchen.js +2 -0
  18. package/src/kitchen/out_directory_url.js +2 -2
  19. package/src/kitchen/url_graph/url_graph.js +1 -0
  20. package/src/kitchen/url_graph/url_info_injections.js +172 -0
  21. package/src/kitchen/url_graph/url_info_transformations.js +28 -7
  22. package/src/main.js +1 -1
  23. package/src/plugins/autoreload/jsenv_plugin_autoreload_server.js +2 -2
  24. package/src/plugins/chrome_devtools_json/jsenv_plugin_chrome_devtools_json.js +1 -0
  25. package/src/plugins/global_scenarios/jsenv_plugin_global_scenarios.js +4 -9
  26. package/src/plugins/import_meta_scenarios/jsenv_plugin_import_meta_scenarios.js +2 -0
  27. package/src/plugins/injections/jsenv_plugin_injections.js +51 -85
  28. package/src/plugins/plugin_controller.js +28 -7
  29. package/src/plugins/plugins.js +3 -1
  30. package/src/plugins/protocol_file/client/directory_listing.jsx +42 -23
  31. package/src/plugins/protocol_file/file_and_server_urls_converter.js +2 -5
  32. package/src/plugins/protocol_file/jsenv_plugin_directory_listing.js +65 -49
  33. package/src/plugins/protocol_file/jsenv_plugin_fs_redirection.js +36 -3
  34. package/src/plugins/protocol_file/jsenv_plugin_protocol_file.js +3 -0
  35. package/src/plugins/ribbon/client/ribbon.js +40 -37
  36. package/src/plugins/injections/internal/inject_globals.js +0 -52
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "40.6.2",
3
+ "version": "40.7.1",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -77,23 +77,28 @@
77
77
  "playwright:install": "npx playwright install-deps && npx playwright install",
78
78
  "https:setup": "npx @jsenv/https-local setup",
79
79
  "prepublishOnly": "npm run build",
80
+ "database:install": "npx @jsenv/database install",
81
+ "database:start": "npx @jsenv/database start",
82
+ "database:stop": "npx @jsenv/database stop",
83
+ "database:setup": "npx @jsenv/database setup",
80
84
  "oto:start": "npm run start -w oto"
81
85
  },
82
86
  "dependencies": {
83
87
  "@financial-times/polyfill-useragent-normaliser": "1.10.2",
84
- "@jsenv/ast": "6.7.3",
85
- "@jsenv/js-module-fallback": "1.4.13",
86
- "@jsenv/plugin-bundling": "2.9.7",
88
+ "@jsenv/ast": "6.7.6",
89
+ "@jsenv/js-module-fallback": "1.4.18",
90
+ "@jsenv/plugin-bundling": "2.9.8",
87
91
  "@jsenv/plugin-minification": "1.7.0",
88
- "@jsenv/plugin-supervisor": "1.7.1",
89
- "@jsenv/plugin-transpilation": "1.5.20",
92
+ "@jsenv/plugin-supervisor": "1.7.3",
93
+ "@jsenv/plugin-transpilation": "1.5.50",
90
94
  "@jsenv/server": "16.1.2",
91
- "@jsenv/sourcemap": "1.3.8"
95
+ "@jsenv/sourcemap": "1.3.9",
96
+ "react-table": "7.8.0"
92
97
  },
93
98
  "devDependencies": {
94
- "@babel/plugin-syntax-decorators": "7.25.9",
95
- "@babel/plugin-syntax-import-attributes": "7.26.0",
96
- "@babel/plugin-syntax-optional-chaining-assign": "7.25.9",
99
+ "@babel/plugin-syntax-decorators": "7.27.1",
100
+ "@babel/plugin-syntax-import-attributes": "7.27.1",
101
+ "@babel/plugin-syntax-optional-chaining-assign": "7.27.1",
97
102
  "@jsenv/abort": "workspace:*",
98
103
  "@jsenv/assert": "workspace:*",
99
104
  "@jsenv/cli": "workspace:*",
@@ -111,6 +116,7 @@
111
116
  "@jsenv/os-metrics": "workspace:*",
112
117
  "@jsenv/performance-impact": "workspace:*",
113
118
  "@jsenv/plugin-as-js-classic": "workspace:*",
119
+ "@jsenv/plugin-database-manager": "workspace:*",
114
120
  "@jsenv/router": "workspace:*",
115
121
  "@jsenv/runtime-compat": "workspace:*",
116
122
  "@jsenv/snapshot": "workspace:*",
@@ -123,12 +129,15 @@
123
129
  "@playwright/browser-firefox": "1.52.0",
124
130
  "@playwright/browser-webkit": "1.52.0",
125
131
  "babel-plugin-transform-async-to-promises": "0.8.18",
126
- "eslint": "9.25.1",
127
- "open": "10.1.1",
132
+ "eslint": "9.26.0",
133
+ "open": "10.1.2",
128
134
  "playwright": "1.52.0",
129
135
  "preact": "10.26.5",
136
+ "preact-iso": "2.9.1",
130
137
  "prettier": "3.5.3",
138
+ "prettier-plugin-embed": "0.5.0",
131
139
  "prettier-plugin-organize-imports": "4.1.0",
140
+ "prettier-plugin-sql": "0.19.0",
132
141
  "strip-ansi": "7.1.0"
133
142
  }
134
143
  }
@@ -52,7 +52,7 @@ import {
52
52
  nodeDefaultRuntimeCompat,
53
53
  } from "@jsenv/runtime-compat";
54
54
  import {
55
- urlIsInsideOf,
55
+ urlIsOrIsInsideOf,
56
56
  urlToBasename,
57
57
  urlToExtension,
58
58
  urlToFilename,
@@ -208,7 +208,7 @@ export const build = async ({
208
208
  );
209
209
  }
210
210
  }
211
- if (!urlIsInsideOf(sourceUrl, sourceDirectoryUrl)) {
211
+ if (!urlIsOrIsInsideOf(sourceUrl, sourceDirectoryUrl)) {
212
212
  throw new Error(
213
213
  `The key "${key}" in "entryPoints" is invalid: it must be inside the source directory at ${sourceDirectoryUrl}.`,
214
214
  );
@@ -259,7 +259,7 @@ export const build = async ({
259
259
  `The buildRelativeUrl "${buildRelativeUrl}"${forEntryPointOrEmpty} is invalid: it must be a relative url.`,
260
260
  );
261
261
  }
262
- if (!urlIsInsideOf(buildUrl, buildDirectoryUrl)) {
262
+ if (!urlIsOrIsInsideOf(buildUrl, buildDirectoryUrl)) {
263
263
  throw new Error(
264
264
  `The buildRelativeUrl "${buildRelativeUrl}"${forEntryPointOrEmpty} is invalid: it must be inside the build directory at ${buildDirectoryUrl}.`,
265
265
  );
@@ -552,7 +552,7 @@ export const build = async ({
552
552
  if (
553
553
  process.env.CAPTURING_SIDE_EFFECTS ||
554
554
  (!import.meta.build &&
555
- urlIsInsideOf(sourceDirectoryUrl, jsenvCoreDirectoryUrl))
555
+ urlIsOrIsInsideOf(sourceDirectoryUrl, jsenvCoreDirectoryUrl))
556
556
  ) {
557
557
  outDirectoryUrl = new URL("../.jsenv_b/", sourceDirectoryUrl).href;
558
558
  } else if (packageDirectory.url) {
@@ -761,7 +761,7 @@ export const build = async ({
761
761
  let hasSomeOutdatedSideEffectUrl = false;
762
762
  for (const packageSideEffectUrl of packageSideEffectUrlSet) {
763
763
  if (
764
- urlIsInsideOf(packageSideEffectUrl, buildDirectoryUrl) &&
764
+ urlIsOrIsInsideOf(packageSideEffectUrl, buildDirectoryUrl) &&
765
765
  !buildSideEffectUrlSet.has(packageSideEffectUrl)
766
766
  ) {
767
767
  hasSomeOutdatedSideEffectUrl = true;
@@ -1061,7 +1061,7 @@ const prepareEntryPointBuild = async (
1061
1061
  });
1062
1062
 
1063
1063
  let _getOtherEntryBuildInfo;
1064
- const rawPluginStore = createPluginStore([
1064
+ const rawPluginStore = await createPluginStore([
1065
1065
  ...(mappings ? [jsenvPluginMappings(mappings)] : []),
1066
1066
  {
1067
1067
  name: "jsenv:other_entry_point_build_during_craft",
@@ -1106,7 +1106,7 @@ const prepareEntryPointBuild = async (
1106
1106
  packageSideEffects,
1107
1107
  }),
1108
1108
  ]);
1109
- const rawPluginController = createPluginController(
1109
+ const rawPluginController = await createPluginController(
1110
1110
  rawPluginStore,
1111
1111
  rawKitchen,
1112
1112
  );
@@ -1177,7 +1177,7 @@ const prepareEntryPointBuild = async (
1177
1177
  rawKitchen.graph.getUrlInfo(entryReference.url).type === "html" &&
1178
1178
  rawKitchen.context.isSupportedOnCurrentClients("importmap"),
1179
1179
  });
1180
- const finalPluginStore = createPluginStore([
1180
+ const finalPluginStore = await createPluginStore([
1181
1181
  jsenvPluginReferenceAnalysis({
1182
1182
  ...referenceAnalysis,
1183
1183
  fetchInlineUrls: false,
@@ -1210,7 +1210,7 @@ const prepareEntryPointBuild = async (
1210
1210
  },
1211
1211
  buildSpecifierManager.jsenvPluginMoveToBuildDirectory,
1212
1212
  ]);
1213
- const finalPluginController = createPluginController(
1213
+ const finalPluginController = await createPluginController(
1214
1214
  finalPluginStore,
1215
1215
  finalKitchen,
1216
1216
  {
@@ -16,7 +16,7 @@ import {
16
16
  ensurePathnameTrailingSlash,
17
17
  injectQueryParamIntoSpecifierWithoutEncoding,
18
18
  renderUrlOrRelativeUrlFilename,
19
- urlIsInsideOf,
19
+ urlIsOrIsInsideOf,
20
20
  urlToRelativeUrl,
21
21
  } from "@jsenv/urls";
22
22
  import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js";
@@ -676,7 +676,7 @@ export const createBuildSpecifierManager = ({
676
676
  // const urlInfoInsideThisDirectorySet = new Set();
677
677
  const versionsInfluencingThisDirectorySet = new Set();
678
678
  for (const [url, urlInfo] of finalKitchen.graph.urlInfoMap) {
679
- if (!urlIsInsideOf(url, directoryUrl)) {
679
+ if (!urlIsOrIsInsideOf(url, directoryUrl)) {
680
680
  continue;
681
681
  }
682
682
  // ideally we should exclude eventual directories as the are redundant
@@ -1075,7 +1075,7 @@ export const createBuildSpecifierManager = ({
1075
1075
  }
1076
1076
  if (
1077
1077
  urlInfo.type === "asset" &&
1078
- urlIsInsideOf(urlInfo.url, buildDirectoryUrl)
1078
+ urlIsOrIsInsideOf(urlInfo.url, buildDirectoryUrl)
1079
1079
  ) {
1080
1080
  return;
1081
1081
  }
@@ -1,7 +1,7 @@
1
1
  // import { ANSI } from "@jsenv/humanize";
2
2
  import {
3
3
  injectQueryParams,
4
- urlIsInsideOf,
4
+ urlIsOrIsInsideOf,
5
5
  urlToFilename,
6
6
  urlToRelativeUrl,
7
7
  } from "@jsenv/urls";
@@ -36,7 +36,7 @@ export const createBuildUrlsGenerator = ({
36
36
  if (buildUrlFromMap) {
37
37
  return buildUrlFromMap;
38
38
  }
39
- if (urlIsInsideOf(url, buildDirectoryUrl)) {
39
+ if (urlIsOrIsInsideOf(url, buildDirectoryUrl)) {
40
40
  if (ownerUrlInfo.searchParams.has("dynamic_import_id")) {
41
41
  const ownerDirectoryPath = determineDirectoryPath({
42
42
  sourceDirectoryUrl,
@@ -15,7 +15,7 @@ import {
15
15
  } from "@jsenv/server";
16
16
  import { convertFileSystemErrorToResponseProperties } from "@jsenv/server/src/internal/convertFileSystemErrorToResponseProperties.js";
17
17
  import { URL_META } from "@jsenv/url-meta";
18
- import { urlIsInsideOf, urlToRelativeUrl } from "@jsenv/urls";
18
+ import { urlIsOrIsInsideOf, urlToRelativeUrl } from "@jsenv/urls";
19
19
  import { existsSync, readFileSync } from "node:fs";
20
20
  import { defaultRuntimeCompat } from "../build/build_params.js";
21
21
  import { createEventEmitter } from "../helpers/event_emitter.js";
@@ -96,6 +96,7 @@ export const startDevServer = async ({
96
96
  ribbon = true,
97
97
  // toolbar = false,
98
98
  onKitchenCreated = () => {},
99
+ spa,
99
100
 
100
101
  sourcemaps = "inline",
101
102
  sourcemapsSourcesContent,
@@ -130,7 +131,7 @@ export const startDevServer = async ({
130
131
  if (
131
132
  process.env.CAPTURING_SIDE_EFFECTS ||
132
133
  (!import.meta.build &&
133
- urlIsInsideOf(sourceDirectoryUrl, jsenvCoreDirectoryUrl))
134
+ urlIsOrIsInsideOf(sourceDirectoryUrl, jsenvCoreDirectoryUrl))
134
135
  ) {
135
136
  outDirectoryUrl = new URL("../.jsenv/", sourceDirectoryUrl);
136
137
  } else {
@@ -247,7 +248,7 @@ export const startDevServer = async ({
247
248
  read: readPackageAtOrNull,
248
249
  };
249
250
 
250
- const devServerPluginStore = createPluginStore([
251
+ const devServerPluginStore = await createPluginStore([
251
252
  jsenvPluginServerEvents({ clientAutoreload }),
252
253
  ...plugins,
253
254
  ...getCorePlugins({
@@ -265,6 +266,7 @@ export const startDevServer = async ({
265
266
  supervisor,
266
267
  injections,
267
268
  transpilation,
269
+ spa,
268
270
 
269
271
  clientAutoreload,
270
272
  clientAutoreloadOnServerRestart,
@@ -272,7 +274,7 @@ export const startDevServer = async ({
272
274
  ribbon,
273
275
  }),
274
276
  ]);
275
- const getOrCreateKitchen = (request) => {
277
+ const getOrCreateKitchen = async (request) => {
276
278
  const { runtimeName, runtimeVersion } = parseUserAgentHeader(
277
279
  request.headers["user-agent"] || "",
278
280
  );
@@ -395,7 +397,7 @@ export const startDevServer = async ({
395
397
  );
396
398
  },
397
399
  );
398
- const devServerPluginController = createPluginController(
400
+ const devServerPluginController = await createPluginController(
399
401
  devServerPluginStore,
400
402
  kitchen,
401
403
  );
@@ -411,8 +413,8 @@ export const startDevServer = async ({
411
413
 
412
414
  finalServices.push({
413
415
  name: "jsenv:dev_server_routes",
414
- augmentRouteFetchSecondArg: (request) => {
415
- const kitchen = getOrCreateKitchen(request);
416
+ augmentRouteFetchSecondArg: async (request) => {
417
+ const kitchen = await getOrCreateKitchen(request);
416
418
  return { kitchen };
417
419
  },
418
420
  routes: [
@@ -615,6 +617,7 @@ export const startDevServer = async ({
615
617
  },
616
618
  ],
617
619
  });
620
+ finalServices.push(...devServerPluginStore.allDevServerServices);
618
621
  }
619
622
  // jsenv error handler service
620
623
  {
@@ -643,7 +646,7 @@ export const startDevServer = async ({
643
646
  body: response.body,
644
647
  });
645
648
  return {
646
- status: 200,
649
+ status: response.status,
647
650
  headers: {
648
651
  "content-type": "application/json",
649
652
  "content-length": Buffer.byteLength(body),
@@ -1,9 +1,9 @@
1
1
  import { ensureWindowsDriveLetter } from "@jsenv/filesystem";
2
- import { moveUrl, urlIsInsideOf } from "@jsenv/urls";
2
+ import { moveUrl, urlIsOrIsInsideOf } from "@jsenv/urls";
3
3
 
4
4
  export const WEB_URL_CONVERTER = {
5
5
  asWebUrl: (fileUrl, webServer) => {
6
- if (urlIsInsideOf(fileUrl, webServer.rootDirectoryUrl)) {
6
+ if (urlIsOrIsInsideOf(fileUrl, webServer.rootDirectoryUrl)) {
7
7
  return moveUrl({
8
8
  url: fileUrl,
9
9
  from: webServer.rootDirectoryUrl,
@@ -176,7 +176,7 @@ ${error.message}`,
176
176
  name: "TRANSFORM_URL_CONTENT_ERROR",
177
177
  code: "PARSE_ERROR",
178
178
  reason: error.message,
179
- stack: error.stack,
179
+ stack: transformError.stack,
180
180
  trace,
181
181
  asResponse: error.asResponse,
182
182
  });
@@ -17,6 +17,7 @@ import {
17
17
  determineSourcemapFileUrl,
18
18
  } from "./out_directory_url.js";
19
19
  import { createUrlGraph } from "./url_graph/url_graph.js";
20
+ import { isPlaceholderInjection } from "./url_graph/url_info_injections.js";
20
21
  import { createUrlInfoTransformer } from "./url_graph/url_info_transformations.js";
21
22
  import { urlSpecifierEncoding } from "./url_graph/url_specifier_encoding.js";
22
23
 
@@ -102,6 +103,7 @@ export const createKitchen = ({
102
103
  inlineContentClientFileUrl,
103
104
  isSupportedOnCurrentClients: memoizeIsSupported(clientRuntimeCompat),
104
105
  isSupportedOnFutureClients: memoizeIsSupported(runtimeCompat),
106
+ isPlaceholderInjection,
105
107
  getPluginMeta: null,
106
108
  sourcemaps,
107
109
  outDirectoryUrl,
@@ -1,6 +1,6 @@
1
1
  import { ensureWindowsDriveLetter } from "@jsenv/filesystem";
2
2
  import { generateSourcemapFileUrl } from "@jsenv/sourcemap";
3
- import { moveUrl, setUrlFilename, urlIsInsideOf } from "@jsenv/urls";
3
+ import { moveUrl, setUrlFilename, urlIsOrIsInsideOf } from "@jsenv/urls";
4
4
 
5
5
  export const determineFileUrlForOutDirectory = (urlInfo) => {
6
6
  let { url, filenameHint } = urlInfo;
@@ -11,7 +11,7 @@ export const determineFileUrlForOutDirectory = (urlInfo) => {
11
11
  if (!url.startsWith("file:")) {
12
12
  return url;
13
13
  }
14
- if (!urlIsInsideOf(url, rootDirectoryUrl)) {
14
+ if (!urlIsOrIsInsideOf(url, rootDirectoryUrl)) {
15
15
  const fsRootUrl = ensureWindowsDriveLetter("file:///", url);
16
16
  url = `${rootDirectoryUrl}@fs/${url.slice(fsRootUrl.length)}`;
17
17
  }
@@ -213,6 +213,7 @@ const createUrlInfo = (url, context) => {
213
213
  contentLength: undefined,
214
214
  contentFinalized: false,
215
215
  contentSideEffects: [],
216
+ contentInjections: {},
216
217
 
217
218
  sourcemap: null,
218
219
  sourcemapIsWrong: false,
@@ -0,0 +1,172 @@
1
+ import { injectJsenvScript, parseHtml, stringifyHtmlAst } from "@jsenv/ast";
2
+ import { composeTwoSourcemaps, createMagicSource } from "@jsenv/sourcemap";
3
+
4
+ const injectionSymbol = Symbol.for("jsenv_injection");
5
+ export const INJECTIONS = {
6
+ global: (value) => {
7
+ return { [injectionSymbol]: "global", value };
8
+ },
9
+ optional: (value) => {
10
+ return { [injectionSymbol]: "optional", value };
11
+ },
12
+ };
13
+
14
+ export const isPlaceholderInjection = (value) => {
15
+ return (
16
+ !value || !value[injectionSymbol] || value[injectionSymbol] !== "global"
17
+ );
18
+ };
19
+
20
+ export const applyContentInjections = (content, contentInjections, urlInfo) => {
21
+ const keys = Object.keys(contentInjections);
22
+ const globals = {};
23
+ const placeholderReplacements = [];
24
+ for (const key of keys) {
25
+ const contentInjection = contentInjections[key];
26
+ if (contentInjection && contentInjection[injectionSymbol]) {
27
+ const valueBehindSymbol = contentInjection[injectionSymbol];
28
+ if (valueBehindSymbol === "global") {
29
+ globals[key] = contentInjection.value;
30
+ } else if (valueBehindSymbol === "optional") {
31
+ placeholderReplacements.push({
32
+ key,
33
+ isOptional: true,
34
+ value: contentInjection.value,
35
+ });
36
+ } else {
37
+ throw new Error(`unknown injection type "${valueBehindSymbol}"`);
38
+ }
39
+ } else {
40
+ placeholderReplacements.push({
41
+ key,
42
+ value: contentInjection,
43
+ });
44
+ }
45
+ }
46
+
47
+ const needGlobalsInjection = Object.keys(globals).length > 0;
48
+ const needPlaceholderReplacements = placeholderReplacements.length > 0;
49
+
50
+ if (needGlobalsInjection && needPlaceholderReplacements) {
51
+ const globalInjectionResult = injectGlobals(content, globals, urlInfo);
52
+ const replaceInjectionResult = injectPlaceholderReplacements(
53
+ globalInjectionResult.content,
54
+ placeholderReplacements,
55
+ urlInfo,
56
+ );
57
+ return {
58
+ content: replaceInjectionResult.content,
59
+ sourcemap: composeTwoSourcemaps(
60
+ globalInjectionResult.sourcemap,
61
+ replaceInjectionResult.sourcemap,
62
+ ),
63
+ };
64
+ }
65
+ if (needGlobalsInjection) {
66
+ return injectGlobals(content, globals, urlInfo);
67
+ }
68
+ if (needPlaceholderReplacements) {
69
+ return injectPlaceholderReplacements(
70
+ content,
71
+ placeholderReplacements,
72
+ urlInfo,
73
+ );
74
+ }
75
+ return null;
76
+ };
77
+
78
+ export const injectPlaceholderReplacements = (
79
+ content,
80
+ placeholderReplacements,
81
+ urlInfo,
82
+ ) => {
83
+ const magicSource = createMagicSource(content);
84
+ for (const { key, isOptional, value } of placeholderReplacements) {
85
+ let index = content.indexOf(key);
86
+ if (index === -1) {
87
+ if (!isOptional) {
88
+ urlInfo.context.logger.warn(
89
+ `placeholder "${key}" not found in ${urlInfo.url}.
90
+ --- suggestion a ---
91
+ Add "${key}" in that file.
92
+ --- suggestion b ---
93
+ Fix eventual typo in "${key}"?
94
+ --- suggestion c ---
95
+ Mark injection as optional using INJECTIONS.optional():
96
+ import { INJECTIONS } from "@jsenv/core";
97
+
98
+ return {
99
+ "${key}": INJECTIONS.optional(${JSON.stringify(value)}),
100
+ };`,
101
+ );
102
+ }
103
+ continue;
104
+ }
105
+
106
+ while (index !== -1) {
107
+ const start = index;
108
+ const end = index + key.length;
109
+ magicSource.replace({
110
+ start,
111
+ end,
112
+ replacement:
113
+ urlInfo.type === "js_classic" ||
114
+ urlInfo.type === "js_module" ||
115
+ urlInfo.type === "html"
116
+ ? JSON.stringify(value, null, " ")
117
+ : value,
118
+ });
119
+ index = content.indexOf(key, end);
120
+ }
121
+ }
122
+ return magicSource.toContentAndSourcemap();
123
+ };
124
+
125
+ export const injectGlobals = (content, globals, urlInfo) => {
126
+ if (urlInfo.type === "html") {
127
+ return globalInjectorOnHtml(content, globals, urlInfo);
128
+ }
129
+ if (urlInfo.type === "js_classic" || urlInfo.type === "js_module") {
130
+ return globalsInjectorOnJs(content, globals, urlInfo);
131
+ }
132
+ throw new Error(`cannot inject globals into "${urlInfo.type}"`);
133
+ };
134
+ const globalInjectorOnHtml = (content, globals, urlInfo) => {
135
+ // ideally we would inject an importmap but browser support is too low
136
+ // (even worse for worker/service worker)
137
+ // so for now we inject code into entry points
138
+ const htmlAst = parseHtml({
139
+ html: content,
140
+ url: urlInfo.url,
141
+ storeOriginalPositions: false,
142
+ });
143
+ const clientCode = generateClientCodeForGlobals(globals, {
144
+ isWebWorker: false,
145
+ });
146
+ injectJsenvScript(htmlAst, {
147
+ content: clientCode,
148
+ pluginName: "jsenv:inject_globals",
149
+ });
150
+ return {
151
+ content: stringifyHtmlAst(htmlAst),
152
+ };
153
+ };
154
+ const globalsInjectorOnJs = (content, globals, urlInfo) => {
155
+ const clientCode = generateClientCodeForGlobals(globals, {
156
+ isWebWorker:
157
+ urlInfo.subtype === "worker" ||
158
+ urlInfo.subtype === "service_worker" ||
159
+ urlInfo.subtype === "shared_worker",
160
+ });
161
+ const magicSource = createMagicSource(content);
162
+ magicSource.prepend(clientCode);
163
+ return magicSource.toContentAndSourcemap();
164
+ };
165
+ const generateClientCodeForGlobals = (globals, { isWebWorker = false }) => {
166
+ const globalName = isWebWorker ? "self" : "window";
167
+ return `Object.assign(${globalName}, ${JSON.stringify(
168
+ globals,
169
+ null,
170
+ " ",
171
+ )});`;
172
+ };
@@ -17,6 +17,7 @@ import {
17
17
  defineGettersOnPropertiesDerivedFromContent,
18
18
  defineGettersOnPropertiesDerivedFromOriginalContent,
19
19
  } from "./url_content.js";
20
+ import { applyContentInjections } from "./url_info_injections.js";
20
21
 
21
22
  export const createUrlInfoTransformer = ({
22
23
  logger,
@@ -195,6 +196,7 @@ export const createUrlInfoTransformer = ({
195
196
  contentLength,
196
197
  sourcemap,
197
198
  sourcemapIsWrong,
199
+ contentInjections,
198
200
  } = transformations;
199
201
  if (type) {
200
202
  urlInfo.type = type;
@@ -202,13 +204,23 @@ export const createUrlInfoTransformer = ({
202
204
  if (contentType) {
203
205
  urlInfo.contentType = contentType;
204
206
  }
205
- const contentModified = setContentProperties(urlInfo, {
206
- content,
207
- contentAst,
208
- contentEtag,
209
- contentLength,
210
- });
211
-
207
+ if (Object.hasOwn(transformations, "contentInjections")) {
208
+ if (contentInjections) {
209
+ Object.assign(urlInfo.contentInjections, contentInjections);
210
+ }
211
+ if (content === undefined) {
212
+ return;
213
+ }
214
+ }
215
+ let contentModified;
216
+ if (Object.hasOwn(transformations, "content")) {
217
+ contentModified = setContentProperties(urlInfo, {
218
+ content,
219
+ contentAst,
220
+ contentEtag,
221
+ contentLength,
222
+ });
223
+ }
212
224
  if (
213
225
  sourcemap &&
214
226
  mayHaveSourcemap(urlInfo) &&
@@ -400,6 +412,15 @@ export const createUrlInfoTransformer = ({
400
412
  if (transformations) {
401
413
  applyTransformations(urlInfo, transformations);
402
414
  }
415
+ const { contentInjections } = urlInfo;
416
+ if (contentInjections && Object.keys(contentInjections).length > 0) {
417
+ const injectionTransformations = applyContentInjections(
418
+ urlInfo.content,
419
+ contentInjections,
420
+ urlInfo,
421
+ );
422
+ applyTransformations(urlInfo, injectionTransformations);
423
+ }
403
424
  applyContentEffects(urlInfo);
404
425
  urlInfo.contentFinalized = true;
405
426
  };
package/src/main.js CHANGED
@@ -15,4 +15,4 @@ export const startBuildServer = async (...args) => {
15
15
  };
16
16
 
17
17
  // others
18
- export { INJECTIONS } from "./plugins/injections/jsenv_plugin_injections.js";
18
+ export { INJECTIONS } from "./kitchen/url_graph/url_info_injections.js";
@@ -1,4 +1,4 @@
1
- import { urlIsInsideOf, urlToRelativeUrl } from "@jsenv/urls";
1
+ import { urlIsOrIsInsideOf, urlToRelativeUrl } from "@jsenv/urls";
2
2
 
3
3
  export const jsenvPluginAutoreloadServer = ({
4
4
  clientFileChangeEventEmitter,
@@ -10,7 +10,7 @@ export const jsenvPluginAutoreloadServer = ({
10
10
  serverEvents: {
11
11
  reload: (serverEventInfo) => {
12
12
  const formatUrlForClient = (url) => {
13
- if (urlIsInsideOf(url, serverEventInfo.rootDirectoryUrl)) {
13
+ if (urlIsOrIsInsideOf(url, serverEventInfo.rootDirectoryUrl)) {
14
14
  return urlToRelativeUrl(url, serverEventInfo.rootDirectoryUrl);
15
15
  }
16
16
  if (url.startsWith("file:")) {
@@ -28,6 +28,7 @@ export const jsenvPluginChromeDevtoolsJson = () => {
28
28
  devServerRoutes: [
29
29
  {
30
30
  endpoint: "GET /.well-known/appspecific/com.chrome.devtools.json",
31
+ declarationSource: import.meta.url,
31
32
  fetch: (request, { kitchen }) => {
32
33
  const { rootDirectoryUrl } = kitchen.context;
33
34
  return Response.json({
@@ -5,21 +5,16 @@
5
5
  * That will be replaced with true/false
6
6
  */
7
7
 
8
- import {
9
- INJECTIONS,
10
- replacePlaceholders,
11
- } from "../injections/jsenv_plugin_injections.js";
8
+ import { INJECTIONS } from "../../kitchen/url_graph/url_info_injections.js";
12
9
 
13
10
  export const jsenvPluginGlobalScenarios = () => {
14
11
  const transformIfNeeded = (urlInfo) => {
15
- return replacePlaceholders(
16
- urlInfo.content,
17
- {
12
+ return {
13
+ contentInjections: {
18
14
  __DEV__: INJECTIONS.optional(urlInfo.context.dev),
19
15
  __BUILD__: INJECTIONS.optional(urlInfo.context.build),
20
16
  },
21
- urlInfo,
22
- );
17
+ };
23
18
  };
24
19
 
25
20
  return {
@@ -6,6 +6,8 @@
6
6
  * - replaced by true: When scenario matches (import.meta.dev and it's the dev server)
7
7
  * - left as is to be evaluated to undefined (import.meta.build but it's the dev server)
8
8
  * - replaced by undefined (import.meta.dev but it's build; the goal is to ensure it's tree-shaked)
9
+ *
10
+ * TODO: ideally during dev we would keep import.meta.dev and ensure we set it to true rather than replacing it with true?
9
11
  */
10
12
 
11
13
  import { applyBabelPlugins } from "@jsenv/ast";