@jsenv/core 41.4.17 → 41.4.19

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 (30) hide show
  1. package/dist/build/build.js +693 -509
  2. package/dist/build/jsenv_core_packages.js +3 -187
  3. package/dist/client/directory_listing/js/directory_listing.js +23 -2
  4. package/dist/start_dev_server/start_dev_server.js +813 -625
  5. package/package.json +8 -8
  6. package/src/build/build.js +66 -15
  7. package/src/build/build_content_report.js +24 -15
  8. package/src/build/build_specifier_manager.js +2 -2
  9. package/src/dev/dev_server_plugins/dev_server_plugin_omega_error_handler.js +4 -1
  10. package/src/dev/dev_server_plugins/dev_server_plugin_serve_source_files.js +116 -68
  11. package/src/dev/start_dev_server.js +24 -4
  12. package/src/helpers/watch_source_files.js +25 -20
  13. package/src/kitchen/kitchen.js +6 -0
  14. package/src/kitchen/prepend_content.js +4 -4
  15. package/src/kitchen/url_graph/references.js +30 -13
  16. package/src/kitchen/url_graph/url_graph.js +0 -1
  17. package/src/kitchen/url_graph/url_info_transformations.js +16 -6
  18. package/src/plugins/import_meta_css/jsenv_plugin_import_meta_css.js +10 -40
  19. package/src/plugins/import_meta_hot/import_meta_hot_analysis.js +70 -0
  20. package/src/plugins/import_meta_hot/jsenv_plugin_import_meta_hot.js +35 -20
  21. package/src/plugins/import_meta_scenarios/jsenv_plugin_import_meta_scenarios.js +22 -56
  22. package/src/plugins/modulepreload/jsenv_plugin_modulepreload.js +128 -0
  23. package/src/plugins/plugins.js +3 -0
  24. package/src/plugins/protocol_file/client/directory_listing.jsx +27 -1
  25. package/src/plugins/protocol_file/jsenv_plugin_directory_listing.js +20 -0
  26. package/src/plugins/protocol_file/jsenv_plugin_fs_redirection.js +32 -15
  27. package/src/plugins/protocol_file/jsenv_plugin_protocol_file.js +11 -2
  28. package/src/plugins/reference_analysis/js/jsenv_plugin_js_reference_analysis.js +1 -2
  29. package/src/plugins/resolution_node_esm/node_esm_resolver.js +0 -5
  30. package/src/plugins/import_meta_hot/babel_plugin_metadata_import_meta_hot.js +0 -97
@@ -2,24 +2,27 @@ import {
2
2
  lookupPackageDirectory,
3
3
  registerDirectoryLifecycle,
4
4
  } from "@jsenv/filesystem";
5
- import { urlToRelativeUrl } from "@jsenv/urls";
5
+ import { urlIsOrIsInsideOf, urlToRelativeUrl } from "@jsenv/urls";
6
6
  import { readFileSync } from "node:fs";
7
7
 
8
8
  export const getDirectoryWatchPatterns = (
9
9
  directoryUrl,
10
10
  watchedDirectoryUrl,
11
- { sourceFilesConfig },
11
+ { sourceFilesConfig, defaultPatterns = true },
12
12
  ) => {
13
13
  const directoryUrlRelativeToWatchedDirectory = urlToRelativeUrl(
14
14
  directoryUrl,
15
15
  watchedDirectoryUrl,
16
16
  );
17
- const watchPatterns = {
18
- [`${directoryUrlRelativeToWatchedDirectory}**/*`]: true, // by default watch everything inside the source directory
19
- [`${directoryUrlRelativeToWatchedDirectory}**/.*`]: false, // file starting with a dot -> do not watch
20
- [`${directoryUrlRelativeToWatchedDirectory}**/.*/`]: false, // directory starting with a dot -> do not watch
21
- [`${directoryUrlRelativeToWatchedDirectory}**/node_modules/`]: false, // node_modules directory -> do not watch
22
- };
17
+ const watchPatterns = {};
18
+ if (defaultPatterns) {
19
+ Object.assign(watchPatterns, {
20
+ [`${directoryUrlRelativeToWatchedDirectory}**/*`]: true, // by default watch everything inside the source directory
21
+ [`${directoryUrlRelativeToWatchedDirectory}**/.*`]: false, // file starting with a dot -> do not watch
22
+ [`${directoryUrlRelativeToWatchedDirectory}**/.*/`]: false, // directory starting with a dot -> do not watch
23
+ [`${directoryUrlRelativeToWatchedDirectory}**/node_modules/`]: false, // node_modules directory -> do not watch
24
+ });
25
+ }
23
26
  for (const key of Object.keys(sourceFilesConfig)) {
24
27
  watchPatterns[`${directoryUrlRelativeToWatchedDirectory}${key}`] =
25
28
  sourceFilesConfig[key];
@@ -40,11 +43,12 @@ export const watchSourceFiles = (
40
43
  // And jsenv should not consider these as source files and watch them (to not hurt performances)
41
44
  const watchPatterns = {};
42
45
  let watchedDirectoryUrl = "";
43
- const addDirectoryToWatch = (directoryUrl) => {
46
+ const addDirectoryToWatch = (directoryUrl, { defaultPatterns } = {}) => {
44
47
  Object.assign(
45
48
  watchPatterns,
46
49
  getDirectoryWatchPatterns(directoryUrl, watchedDirectoryUrl, {
47
50
  sourceFilesConfig,
51
+ defaultPatterns,
48
52
  }),
49
53
  );
50
54
  };
@@ -76,7 +80,6 @@ export const watchSourceFiles = (
76
80
  },
77
81
  },
78
82
  );
79
- stopWatchingSourceFiles.watchPatterns = watchPatterns;
80
83
  return stopWatchingSourceFiles;
81
84
  };
82
85
 
@@ -96,16 +99,18 @@ export const watchSourceFiles = (
96
99
  }
97
100
  watchedDirectoryUrl = packageDirectoryUrl;
98
101
  for (const workspace of workspaces) {
99
- if (workspace.endsWith("*")) {
100
- const workspaceDirectoryUrl = new URL(
101
- workspace.slice(0, -1),
102
- packageDirectoryUrl,
103
- );
104
- addDirectoryToWatch(workspaceDirectoryUrl);
105
- } else {
106
- const workspaceRelativeUrl = new URL(workspace, packageDirectoryUrl);
107
- addDirectoryToWatch(workspaceRelativeUrl);
108
- }
102
+ const workspaceDirectoryUrl = workspace.endsWith("*")
103
+ ? new URL(workspace.slice(0, -1), packageDirectoryUrl)
104
+ : new URL(workspace, packageDirectoryUrl);
105
+ addDirectoryToWatch(workspaceDirectoryUrl, {
106
+ // The source directory patterns already cover a workspace inside it.
107
+ // Every pattern is tested against every file found while watching, so
108
+ // the same patterns rooted at each workspace only multiply that work.
109
+ defaultPatterns: !urlIsOrIsInsideOf(
110
+ workspaceDirectoryUrl,
111
+ sourceDirectoryUrl,
112
+ ),
113
+ });
109
114
  }
110
115
  // we are updating the root directory
111
116
  // we must make the patterns relative to source directory relative to the new root directory
@@ -118,6 +118,12 @@ export const createKitchen = ({
118
118
  INJECTIONS,
119
119
  getPluginMeta: null,
120
120
  sourcemaps,
121
+ // a plugin producing a sourcemap can skip the work when it would be
122
+ // thrown away (see shouldHandleSourcemap in url_info_transformations.js)
123
+ sourcemapsEnabled:
124
+ sourcemaps === "inline" ||
125
+ sourcemaps === "file" ||
126
+ sourcemaps === "programmatic",
121
127
  outDirectoryUrl,
122
128
  },
123
129
  resolve: (specifier, importer = rootDirectoryUrl) => {
@@ -67,10 +67,9 @@ const prependJsClassicInJsClassic = (jsUrlInfo, urlInfoToPrepend) => {
67
67
  const magicSource = createMagicSource(jsUrlInfo.content);
68
68
  magicSource.prepend(`${urlInfoToPrepend.content}\n\n`);
69
69
  const magicResult = magicSource.toContentAndSourcemap();
70
- const sourcemap = composeTwoSourcemaps(
71
- jsUrlInfo.sourcemap,
72
- magicResult.sourcemap,
73
- );
70
+ const sourcemap = jsUrlInfo.context.sourcemapsEnabled
71
+ ? composeTwoSourcemaps(jsUrlInfo.sourcemap, magicResult.sourcemap)
72
+ : null;
74
73
  jsUrlInfo.mutateContent({
75
74
  content: magicResult.content,
76
75
  sourcemap,
@@ -88,6 +87,7 @@ const prependJsClassicInJsModule = async (jsUrlInfo, urlInfoToPrepend) => {
88
87
  input: jsUrlInfo.content,
89
88
  inputIsJsModule: true,
90
89
  inputUrl: jsUrlInfo.originalUrl,
90
+ options: { sourceMaps: jsUrlInfo.context.sourcemapsEnabled },
91
91
  });
92
92
  jsUrlInfo.mutateContent({
93
93
  content: code,
@@ -634,20 +634,37 @@ const checkForDependencyRemovalEffects = (reference) => {
634
634
  };
635
635
 
636
636
  const traceFromUrlSite = (urlSite) => {
637
- const codeFrame = urlSite.content
638
- ? generateContentFrame({
639
- content: urlSite.content,
640
- line: urlSite.line,
641
- column: urlSite.column,
642
- })
643
- : "";
644
- return {
645
- codeFrame,
646
- message: stringifyUrlSite(urlSite),
647
- url: urlSite.url,
648
- line: urlSite.line,
649
- column: urlSite.column,
637
+ const { url, line, column, content } = urlSite;
638
+ const trace = { url, line, column };
639
+ // A file references many urls and an error on one of them is rare: the code
640
+ // frame (and the message embedding it) is built the first time it is read,
641
+ // not for every reference found.
642
+ defineLazyProperty(trace, "codeFrame", () =>
643
+ content ? generateContentFrame({ content, line, column }) : "",
644
+ );
645
+ defineLazyProperty(trace, "message", () => stringifyUrlSite(urlSite));
646
+ return trace;
647
+ };
648
+
649
+ const defineLazyProperty = (object, property, compute) => {
650
+ const setValue = (value) => {
651
+ Object.defineProperty(object, property, {
652
+ enumerable: true,
653
+ configurable: true,
654
+ writable: true,
655
+ value,
656
+ });
650
657
  };
658
+ Object.defineProperty(object, property, {
659
+ enumerable: true,
660
+ configurable: true,
661
+ get: () => {
662
+ const value = compute();
663
+ setValue(value);
664
+ return value;
665
+ },
666
+ set: setValue,
667
+ });
651
668
  };
652
669
 
653
670
  const adjustUrlSite = (urlInfo, { url, line, column }) => {
@@ -185,7 +185,6 @@ const createUrlInfo = (url, context) => {
185
185
  dereferencedTimestamp: 0,
186
186
  originalContentEtag: null,
187
187
  contentEtag: null,
188
- isWatched: false,
189
188
  isValid: () => false,
190
189
  data: {}, // plugins can put whatever they want here
191
190
  referenceToOthersSet: new Set(),
@@ -199,7 +199,6 @@ export const createUrlInfoTransformer = ({
199
199
  contentAst, // undefined most of the time
200
200
  contentEtag, // in practice always undefined
201
201
  contentLength,
202
- sourcemap,
203
202
  sourcemapIsWrong,
204
203
  contentInjections,
205
204
  } = transformations;
@@ -226,11 +225,14 @@ export const createUrlInfoTransformer = ({
226
225
  contentLength,
227
226
  });
228
227
  }
229
- if (
230
- sourcemap &&
231
- mayHaveSourcemap(urlInfo) &&
232
- shouldHandleSourcemap(urlInfo)
233
- ) {
228
+ // "sourcemap" is read last, and only when it will be used: a plugin can
229
+ // hand it back as a getter that generates the map on first read, so that
230
+ // nothing is generated for a kitchen that throws sourcemaps away
231
+ const sourcemap =
232
+ mayHaveSourcemap(urlInfo) && shouldHandleSourcemap(urlInfo)
233
+ ? transformations.sourcemap
234
+ : null;
235
+ if (sourcemap) {
234
236
  const sourcemapNormalized = normalizeSourcemap(urlInfo, sourcemap);
235
237
  let currentSourcemap = urlInfo.sourcemap;
236
238
  const finalSourcemap = composeTwoSourcemaps(
@@ -263,6 +265,14 @@ export const createUrlInfoTransformer = ({
263
265
  writeInsideOutDirectory(urlInfo);
264
266
  };
265
267
 
268
+ // Written synchronously on purpose, and measured: async is the tempting
269
+ // choice, but here it loses. A cold load cooks hundreds of files; their
270
+ // synchronous writes block the event loop ~160ms in total, while
271
+ // asynchronous writes queue in the threadpool behind tens of MB of content
272
+ // and sourcemaps, and a response that waits for its own write then waits
273
+ // ~36ms on average (18s summed over 500 responses). Not waiting is not an
274
+ // option either: the last write lands after the test that cooked it has
275
+ // ended, and the side-effect snapshots lose it.
266
276
  const writeInsideOutDirectory = (urlInfo) => {
267
277
  // writing result inside ".jsenv" directory (debug purposes)
268
278
  if (!outDirectoryUrl) {
@@ -23,7 +23,11 @@
23
23
  *
24
24
  */
25
25
 
26
- import { applyBabelPlugins } from "@jsenv/ast";
26
+ import {
27
+ applyBabelPlugins,
28
+ getImportMetaPropertyName,
29
+ visitJsAstUntil,
30
+ } from "@jsenv/ast";
27
31
 
28
32
  export const jsenvPluginImportMetaCss = () => {
29
33
  const importMetaCssDevClientFileUrl = import.meta
@@ -47,15 +51,10 @@ export const jsenvPluginImportMetaCss = () => {
47
51
  if (!urlInfo.content.includes("import.meta.css")) {
48
52
  return null;
49
53
  }
50
- const { metadata } = await applyBabelPlugins({
51
- babelPlugins: [babelPluginMetadataUsesImportMetaCss],
52
- input: urlInfo.content,
53
- inputIsJsModule: true,
54
- inputUrl: urlInfo.originalUrl,
55
- outputUrl: urlInfo.generatedUrl,
54
+ const importMetaCssNode = visitJsAstUntil(urlInfo.contentAst, {
55
+ MemberExpression: (node) => getImportMetaPropertyName(node) === "css",
56
56
  });
57
- const { usesImportMetaCss } = metadata;
58
- if (!usesImportMetaCss) {
57
+ if (!importMetaCssNode) {
59
58
  return null;
60
59
  }
61
60
  if (urlInfo.context.build) {
@@ -76,6 +75,8 @@ export const jsenvPluginImportMetaCss = () => {
76
75
  inputIsJsModule: true,
77
76
  inputUrl: urlInfo.originalUrl,
78
77
  outputUrl: urlInfo.generatedUrl,
78
+ // the map would be dropped: the content sent back carries none
79
+ options: { sourceMaps: false },
79
80
  });
80
81
  if (code === urlInfo.content) {
81
82
  // all assignments were already in array form (pre-built file) — nothing to do
@@ -135,37 +136,6 @@ const babelPluginRewriteImportMetaCssAssignment = (
135
136
  };
136
137
  };
137
138
 
138
- const babelPluginMetadataUsesImportMetaCss = () => {
139
- return {
140
- name: "metadata-uses-import-meta-css",
141
- visitor: {
142
- Program(programPath, state) {
143
- let usesImportMetaCss = false;
144
- programPath.traverse({
145
- MemberExpression(path) {
146
- const { node } = path;
147
- const { object } = node;
148
- if (object.type !== "MetaProperty") {
149
- return;
150
- }
151
- const { property: objectProperty } = object;
152
- if (objectProperty.name !== "meta") {
153
- return;
154
- }
155
- const { property } = node;
156
- const { name } = property;
157
- if (name === "css") {
158
- usesImportMetaCss = true;
159
- path.stop();
160
- }
161
- },
162
- });
163
- state.file.metadata.usesImportMetaCss = usesImportMetaCss;
164
- },
165
- },
166
- };
167
- };
168
-
169
139
  const injectImportMetaCss = (
170
140
  urlInfo,
171
141
  { content, importFrom, importName, importAs, hot },
@@ -0,0 +1,70 @@
1
+ import { getImportMetaPropertyName, visitJsAst } from "@jsenv/ast";
2
+
3
+ export const analyzeImportMetaHot = (ast) => {
4
+ const importMetaHotNodes = [];
5
+ let hotDecline = false;
6
+ let hotAcceptSelf = false;
7
+ let hotAcceptSpecifiers = [];
8
+ visitJsAst(ast, {
9
+ MemberExpression: (node) => {
10
+ if (getImportMetaPropertyName(node) === "hot") {
11
+ importMetaHotNodes.push(node);
12
+ }
13
+ },
14
+ CallExpression: (node) => {
15
+ const methodName = getImportMetaHotMethodName(node);
16
+ if (methodName === "accept") {
17
+ const args = node.arguments;
18
+ if (args.length === 0) {
19
+ hotAcceptSelf = true;
20
+ return;
21
+ }
22
+ const [firstArg] = args;
23
+ if (isStringLiteral(firstArg)) {
24
+ hotAcceptSpecifiers = [firstArg.value];
25
+ return;
26
+ }
27
+ if (firstArg.type === "ArrayExpression") {
28
+ hotAcceptSpecifiers = firstArg.elements.map((element) => {
29
+ if (!isStringLiteral(element)) {
30
+ throw new Error(
31
+ `all array elements must be strings in "import.meta.hot.accept(array)"`,
32
+ );
33
+ }
34
+ return element.value;
35
+ });
36
+ return;
37
+ }
38
+ // accept first arg can be "anything" such as
39
+ // `const cb = () => {}; import.meta.hot.accept(cb)`
40
+ hotAcceptSelf = true;
41
+ return;
42
+ }
43
+ if (methodName === "decline") {
44
+ hotDecline = true;
45
+ }
46
+ },
47
+ });
48
+ return {
49
+ importMetaHotNodes,
50
+ hotDecline,
51
+ hotAcceptSelf,
52
+ hotAcceptSpecifiers,
53
+ };
54
+ };
55
+
56
+ // "import.meta.hot.<method>(...)"
57
+ const getImportMetaHotMethodName = (callNode) => {
58
+ const { callee } = callNode;
59
+ if (callee.type !== "MemberExpression" || callee.computed) {
60
+ return null;
61
+ }
62
+ if (getImportMetaPropertyName(callee.object) !== "hot") {
63
+ return null;
64
+ }
65
+ return callee.property.name;
66
+ };
67
+
68
+ const isStringLiteral = (node) => {
69
+ return node.type === "Literal" && typeof node.value === "string";
70
+ };
@@ -1,8 +1,8 @@
1
- import { applyBabelPlugins, parseHtml } from "@jsenv/ast";
1
+ import { parseHtml } from "@jsenv/ast";
2
2
  import { createMagicSource } from "@jsenv/sourcemap";
3
3
 
4
- import { babelPluginMetadataImportMetaHot } from "./babel_plugin_metadata_import_meta_hot.js";
5
4
  import { collectHotDataFromHtmlAst } from "./html_hot_dependencies.js";
5
+ import { analyzeImportMetaHot } from "./import_meta_hot_analysis.js";
6
6
 
7
7
  export const jsenvPluginImportMetaHot = () => {
8
8
  const importMetaHotClientFileUrl = import.meta
@@ -52,7 +52,7 @@ export const jsenvPluginImportMetaHot = () => {
52
52
  cssUrlInfo.data.hotAcceptSelf = false;
53
53
  cssUrlInfo.data.hotAcceptDependencies = [];
54
54
  },
55
- js_module: async (urlInfo) => {
55
+ js_module: (urlInfo) => {
56
56
  // Do not scan node modules for import.meta.hot
57
57
  // - unlikely to be there
58
58
  // - we don't watch node modules (too expensive)
@@ -64,27 +64,22 @@ export const jsenvPluginImportMetaHot = () => {
64
64
  if (!urlInfo.content.includes("import.meta.hot")) {
65
65
  return null;
66
66
  }
67
- const { metadata } = await applyBabelPlugins({
68
- babelPlugins: [babelPluginMetadataImportMetaHot],
69
- input: urlInfo.content,
70
- inputIsJsModule: true,
71
- inputUrl: urlInfo.originalUrl,
72
- outputUrl: urlInfo.generatedUrl,
73
- });
74
67
  const {
75
- importMetaHotPaths,
68
+ importMetaHotNodes,
76
69
  hotDecline,
77
70
  hotAcceptSelf,
78
- hotAcceptDependencies,
79
- } = metadata;
71
+ hotAcceptSpecifiers,
72
+ } = analyzeImportMetaHot(urlInfo.contentAst);
80
73
  urlInfo.data.hotDecline = hotDecline;
81
74
  urlInfo.data.hotAcceptSelf = hotAcceptSelf;
82
- urlInfo.data.hotAcceptDependencies = hotAcceptDependencies;
83
- if (importMetaHotPaths.length === 0) {
75
+ urlInfo.data.hotAcceptDependencies = hotAcceptSpecifiers.map(
76
+ (specifier) => resolveHotAcceptSpecifier(urlInfo, specifier),
77
+ );
78
+ if (importMetaHotNodes.length === 0) {
84
79
  return null;
85
80
  }
86
81
  if (urlInfo.context.build) {
87
- return removeImportMetaHots(urlInfo, importMetaHotPaths);
82
+ return removeImportMetaHots(urlInfo, importMetaHotNodes);
88
83
  }
89
84
  return injectImportMetaHot(urlInfo, importMetaHotClientFileUrl);
90
85
  },
@@ -92,12 +87,32 @@ export const jsenvPluginImportMetaHot = () => {
92
87
  };
93
88
  };
94
89
 
95
- const removeImportMetaHots = (urlInfo, importMetaHotPaths) => {
90
+ // The specifier given to import.meta.hot.accept() is one the file imports,
91
+ // so its url is known from that import (autoreload compares urls). A
92
+ // specifier the file does not import accepts nothing: it is kept as a plain
93
+ // url, never resolved as a dependency (that could throw on a bare specifier).
94
+ const resolveHotAcceptSpecifier = (urlInfo, specifier) => {
95
+ for (const referenceToOther of urlInfo.referenceToOthersSet) {
96
+ if (
97
+ referenceToOther.type === "js_import" &&
98
+ referenceToOther.specifier === specifier
99
+ ) {
100
+ return referenceToOther.url;
101
+ }
102
+ }
103
+ try {
104
+ return new URL(specifier, urlInfo.url).href;
105
+ } catch {
106
+ return specifier;
107
+ }
108
+ };
109
+
110
+ const removeImportMetaHots = (urlInfo, importMetaHotNodes) => {
96
111
  const magicSource = createMagicSource(urlInfo.content);
97
- importMetaHotPaths.forEach((path) => {
112
+ importMetaHotNodes.forEach((node) => {
98
113
  magicSource.replace({
99
- start: path.node.start,
100
- end: path.node.end,
114
+ start: node.start,
115
+ end: node.end,
101
116
  replacement: "undefined",
102
117
  });
103
118
  });
@@ -10,7 +10,7 @@
10
10
  * TODO: ideally during dev we would keep import.meta.dev and ensure we set it to true rather than replacing it with true?
11
11
  */
12
12
 
13
- import { applyBabelPlugins } from "@jsenv/ast";
13
+ import { getImportMetaPropertyName, visitJsAst } from "@jsenv/ast";
14
14
  import { createMagicSource } from "@jsenv/sourcemap";
15
15
 
16
16
  export const jsenvPluginImportMetaScenarios = () => {
@@ -18,7 +18,7 @@ export const jsenvPluginImportMetaScenarios = () => {
18
18
  name: "jsenv:import_meta_scenario",
19
19
  appliesDuring: "*",
20
20
  transformUrlContent: {
21
- js_module: async (urlInfo) => {
21
+ js_module: (urlInfo) => {
22
22
  // Do not scan node modules for import.meta.dev/import.meta.build
23
23
  // - node modules won't have this in their code
24
24
  // - ;or should use other an other technic as this one won't be available
@@ -34,40 +34,42 @@ export const jsenvPluginImportMetaScenarios = () => {
34
34
  ) {
35
35
  return null;
36
36
  }
37
- const { metadata } = await applyBabelPlugins({
38
- babelPlugins: [babelPluginMetadataImportMetaScenarios],
39
- input: urlInfo.content,
40
- inputIsJsModule: true,
41
- inputUrl: urlInfo.originalUrl,
42
- outputUrl: urlInfo.generatedUrl,
37
+ const importMetaScenarioNodes = { dev: [], build: [] };
38
+ visitJsAst(urlInfo.contentAst, {
39
+ MemberExpression: (node) => {
40
+ const name = getImportMetaPropertyName(node);
41
+ if (name === "dev" || name === "build") {
42
+ importMetaScenarioNodes[name].push(node);
43
+ }
44
+ },
43
45
  });
44
- const { dev = [], build = [] } = metadata.importMetaScenarios;
46
+ const { dev, build } = importMetaScenarioNodes;
45
47
  const replacements = [];
46
- const replace = (path, value) => {
47
- replacements.push({ path, value });
48
+ const replace = (node, value) => {
49
+ replacements.push({ node, value });
48
50
  };
49
51
  if (urlInfo.context.build) {
50
52
  // during build ensure replacement for tree-shaking
51
- dev.forEach((path) => {
52
- replace(path, "undefined");
53
+ dev.forEach((node) => {
54
+ replace(node, "undefined");
53
55
  });
54
- build.forEach((path) => {
55
- replace(path, "true");
56
+ build.forEach((node) => {
57
+ replace(node, "true");
56
58
  });
57
59
  } else {
58
60
  // during dev we can let "import.meta.build" untouched
59
61
  // it will be evaluated to undefined.
60
62
  // Moreover it can be surprising to see some "undefined"
61
63
  // when source file contains "import.meta.build"
62
- dev.forEach((path) => {
63
- replace(path, "true");
64
+ dev.forEach((node) => {
65
+ replace(node, "true");
64
66
  });
65
67
  }
66
68
  const magicSource = createMagicSource(urlInfo.content);
67
- replacements.forEach(({ path, value }) => {
69
+ replacements.forEach(({ node, value }) => {
68
70
  magicSource.replace({
69
- start: path.node.start,
70
- end: path.node.end,
71
+ start: node.start,
72
+ end: node.end,
71
73
  replacement: value,
72
74
  });
73
75
  });
@@ -76,39 +78,3 @@ export const jsenvPluginImportMetaScenarios = () => {
76
78
  },
77
79
  };
78
80
  };
79
-
80
- const babelPluginMetadataImportMetaScenarios = () => {
81
- return {
82
- name: "metadata-import-meta-scenarios",
83
- visitor: {
84
- Program(programPath, state) {
85
- const importMetas = {};
86
- programPath.traverse({
87
- MemberExpression(path) {
88
- const { node } = path;
89
- const { object } = node;
90
- if (object.type !== "MetaProperty") {
91
- return;
92
- }
93
- const { property: objectProperty } = object;
94
- if (objectProperty.name !== "meta") {
95
- return;
96
- }
97
- const { property } = node;
98
- const { name } = property;
99
- const importMetaPaths = importMetas[name];
100
- if (importMetaPaths) {
101
- importMetaPaths.push(path);
102
- } else {
103
- importMetas[name] = [path];
104
- }
105
- },
106
- });
107
- state.file.metadata.importMetaScenarios = {
108
- dev: importMetas.dev,
109
- build: importMetas.build,
110
- };
111
- },
112
- },
113
- };
114
- };