@jsenv/core 27.0.0-alpha.82 → 27.0.0-alpha.85

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 (76) hide show
  1. package/dist/js/event_source_client.js +208 -4
  2. package/dist/js/s.js +2 -2
  3. package/dist/main.js +1430 -615
  4. package/dist/s.js +2 -2
  5. package/dist/s.js.map +2 -1
  6. package/package.json +6 -2
  7. package/src/build/build.js +5 -8
  8. package/src/build/build_urls_generator.js +1 -2
  9. package/src/build/inject_global_version_mappings.js +4 -4
  10. package/src/build/inject_service_worker_urls.js +2 -2
  11. package/src/build/resync_ressource_hints.js +17 -18
  12. package/src/build/start_build_server.js +33 -26
  13. package/src/dev/plugins/explorer/jsenv_plugin_explorer.js +1 -2
  14. package/src/dev/plugins/toolbar/client/util/fetching.js +1 -1
  15. package/src/dev/plugins/toolbar/jsenv_plugin_toolbar.js +3 -3
  16. package/src/dev/start_dev_server.js +38 -30
  17. package/src/execute/runtimes/browsers/from_playwright.js +5 -4
  18. package/src/execute/runtimes/node/node_process.js +2 -2
  19. package/src/helpers/command/command.js +73 -0
  20. package/src/helpers/event_source/event_source.js +197 -0
  21. package/src/helpers/event_source/sse_service.js +53 -0
  22. package/src/helpers/worker_reload.js +57 -0
  23. package/src/omega/compat/runtime_compat.js +2 -1
  24. package/src/omega/kitchen.js +4 -1
  25. package/src/omega/server/user_agent.js +2 -1
  26. package/src/omega/url_graph/sort_by_dependencies.js +27 -0
  27. package/src/omega/url_graph/url_info_transformations.js +24 -14
  28. package/src/plugins/autoreload/dev_sse/client/event_source_client.js +1 -1
  29. package/src/plugins/autoreload/dev_sse/client/reload.js +6 -3
  30. package/src/plugins/autoreload/dev_sse/jsenv_plugin_dev_sse_client.js +3 -3
  31. package/src/plugins/autoreload/dev_sse/jsenv_plugin_dev_sse_server.js +1 -1
  32. package/src/plugins/bundling/css/bundle_css.js +4 -4
  33. package/src/plugins/bundling/js_module/bundle_js_module.js +86 -67
  34. package/src/plugins/commonjs_globals/jsenv_plugin_commonjs_globals.js +2 -2
  35. package/src/plugins/file_urls/jsenv_plugin_file_urls.js +4 -5
  36. package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +62 -74
  37. package/src/plugins/import_meta_hot/html_hot_dependencies.js +9 -15
  38. package/src/plugins/import_meta_hot/jsenv_plugin_import_meta_hot.js +3 -3
  39. package/src/plugins/import_meta_scenarios/jsenv_plugin_import_meta_scenarios.js +2 -2
  40. package/src/plugins/importmap/jsenv_plugin_importmap.js +25 -27
  41. package/src/plugins/inject_globals/inject_globals.js +4 -4
  42. package/src/plugins/inline/jsenv_plugin_data_urls.js +1 -1
  43. package/src/plugins/inline/jsenv_plugin_html_inline_content.js +41 -43
  44. package/src/plugins/inline/jsenv_plugin_js_inline_content.js +4 -4
  45. package/src/plugins/minification/css/minify_css.js +1 -1
  46. package/src/plugins/transpilation/as_js_classic/client/s.js +2 -2
  47. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic.js +2 -4
  48. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic_html.js +45 -67
  49. package/src/plugins/transpilation/babel/global_this/babel_plugin_global_this_as_jsenv_import.js +2 -3
  50. package/src/plugins/transpilation/babel/helpers/babel_plugin_babel_helpers_as_jsenv_imports.js +3 -4
  51. package/src/plugins/transpilation/babel/jsenv_plugin_babel.js +1 -1
  52. package/src/plugins/transpilation/babel/new_stylesheet/babel_plugin_new_stylesheet_as_jsenv_import.js +2 -3
  53. package/src/plugins/transpilation/babel/regenerator_runtime/babel_plugin_regenerator_runtime_as_jsenv_import.js +2 -3
  54. package/src/plugins/transpilation/css_parcel/jsenv_plugin_css_parcel.js +1 -1
  55. package/src/plugins/transpilation/import_assertions/jsenv_plugin_import_assertions.js +1 -1
  56. package/src/plugins/transpilation/jsenv_plugin_top_level_await.js +2 -1
  57. package/src/plugins/url_analysis/css/css_urls.js +2 -3
  58. package/src/plugins/url_analysis/html/html_urls.js +98 -113
  59. package/src/plugins/url_analysis/js/js_urls.js +3 -2
  60. package/src/test/coverage/babel_plugin_instrument.js +82 -0
  61. package/src/test/coverage/coverage_reporter_html_directory.js +36 -0
  62. package/src/test/coverage/coverage_reporter_json_file.js +22 -0
  63. package/src/test/coverage/coverage_reporter_text_log.js +19 -0
  64. package/src/test/coverage/empty_coverage_factory.js +52 -0
  65. package/src/test/coverage/file_by_file_coverage.js +25 -0
  66. package/src/test/coverage/istanbul_coverage_composition.js +28 -0
  67. package/src/test/coverage/istanbul_coverage_map_from_coverage.js +16 -0
  68. package/src/test/coverage/list_files_not_covered.js +15 -0
  69. package/src/test/coverage/missing_coverage.js +41 -0
  70. package/src/test/coverage/report_to_coverage.js +196 -0
  71. package/src/test/coverage/v8_and_istanbul.js +37 -0
  72. package/src/test/coverage/v8_coverage_composition.js +24 -0
  73. package/src/test/coverage/v8_coverage_from_directory.js +87 -0
  74. package/src/test/coverage/v8_coverage_to_istanbul.js +99 -0
  75. package/src/test/execute_plan.js +2 -2
  76. package/src/test/execute_test_plan.js +3 -3
package/dist/main.js CHANGED
@@ -1,56 +1,89 @@
1
- import { parentPort } from "node:worker_threads";
2
- import { registerFileLifecycle, readFileSync as readFileSync$1, bufferToEtag, writeFileSync, ensureWindowsDriveLetter, collectFiles, assertAndNormalizeDirectoryUrl, registerDirectoryLifecycle, writeFile, ensureEmptyDirectory, writeDirectory } from "@jsenv/filesystem";
3
- import { createDetailedMessage, createLogger, createTaskLog, loggerToLevels, ANSI, msAsDuration, msAsEllapsedTime, byteAsMemoryUsage, UNICODE, createLog, startSpinner, distributePercentages, byteAsFileSize } from "@jsenv/log";
1
+ import { createSSERoom, timeStart, fetchFileSystem, composeTwoResponses, serveDirectory, startServer, pluginCORS, jsenvAccessControlAllowedHeaders, pluginServerTiming, pluginRequestWaitingCheck, composeServices, findFreePort } from "@jsenv/server";
2
+ import { registerFileLifecycle, readFileSync as readFileSync$1, bufferToEtag, writeFileSync, ensureWindowsDriveLetter, collectFiles, assertAndNormalizeDirectoryUrl, registerDirectoryLifecycle, writeFile, readFile, readDirectory, ensureEmptyDirectory, writeDirectory } from "@jsenv/filesystem";
3
+ import { createCallbackListNotifiedOnce, createCallbackList, Abort, raceProcessTeardownEvents, raceCallbacks } from "@jsenv/abort";
4
+ import { createDetailedMessage, createLogger, createTaskLog, loggerToLevels, byteAsFileSize, ANSI, msAsDuration, msAsEllapsedTime, byteAsMemoryUsage, UNICODE, createLog, startSpinner, distributePercentages } from "@jsenv/log";
4
5
  import { urlToRelativeUrl, generateInlineContentUrl, ensurePathnameTrailingSlash, urlIsInsideOf, urlToFilename, DATA_URL, injectQueryParams, injectQueryParamsIntoSpecifier, fileSystemPathToUrl, urlToFileSystemPath, isFileSystemPath, normalizeUrl, stringifyUrlSite, setUrlFilename, moveUrl, getCallerPosition, resolveUrl, resolveDirectoryUrl, asUrlWithoutSearch, asUrlUntilPathname, urlToBasename, urlToExtension } from "@jsenv/urls";
5
- import { initReloadableProcess } from "@jsenv/utils/process_reload/process_reload.js";
6
+ import { fileURLToPath, pathToFileURL } from "node:url";
7
+ import { workerData, Worker } from "node:worker_threads";
6
8
  import { URL_META } from "@jsenv/url-meta";
7
- import { parseHtmlString, stringifyHtmlAst, visitHtmlAst, getHtmlNodeAttributeByName, htmlNodePosition, findNode, getHtmlNodeTextNode, removeHtmlNode, setHtmlNodeGeneratedText, removeHtmlNodeAttributeByName, parseScriptNode, injectScriptAsEarlyAsPossible, createHtmlNode, removeHtmlNodeText, assignHtmlNodeAttributes, parseLinkNode } from "@jsenv/utils/html_ast/html_ast.js";
8
- import { htmlAttributeSrcSet } from "@jsenv/utils/html_ast/html_attribute_src_set.js";
9
- import { createMagicSource } from "@jsenv/utils/sourcemap/magic_source.js";
10
- import { applyPostCss } from "@jsenv/utils/css_ast/apply_post_css.js";
11
- import { postCssPluginUrlVisitor } from "@jsenv/utils/css_ast/postcss_plugin_url_visitor.js";
12
- import { parseJsUrls } from "@jsenv/utils/js_ast/parse_js_urls.js";
9
+ import { parseHtmlString, stringifyHtmlAst, visitHtmlNodes, getHtmlNodeAttribute, setHtmlNodeAttributes, parseSrcSet, getHtmlNodePosition, getHtmlNodeAttributePosition, applyPostCss, postCssPluginUrlVisitor, parseJsUrls, findHtmlNode, getHtmlNodeText, removeHtmlNode, setHtmlNodeText, analyzeScriptNode, applyBabelPlugins, injectScriptNodeAsEarlyAsPossible, createHtmlNode, removeHtmlNodeText, transpileWithParcel, injectJsImport, minifyWithParcel, analyzeLinkNode } from "@jsenv/ast";
10
+ import { createMagicSource, composeTwoSourcemaps, sourcemapConverter, SOURCEMAP, generateSourcemapFileUrl, generateSourcemapDataUrl } from "@jsenv/sourcemap";
13
11
  import { resolveImport, normalizeImportMap, composeTwoImportMaps } from "@jsenv/importmap";
14
12
  import { applyNodeEsmResolution, defaultLookupPackageScope, defaultReadPackageJson, readCustomConditionsFromProcessArgs, applyFileSystemMagicResolution, getExtensionsToTry } from "@jsenv/node-esm-resolution";
15
13
  import { statSync, realpathSync, readdirSync, readFileSync, existsSync } from "node:fs";
16
- import { pathToFileURL } from "node:url";
17
- import { CONTENT_TYPE } from "@jsenv/utils/content_type/content_type.js";
18
- import { JS_QUOTES } from "@jsenv/utils/string/js_quotes.js";
19
- import { applyBabelPlugins } from "@jsenv/utils/js_ast/apply_babel_plugins.js";
20
- import { transpileWithParcel, minifyWithParcel } from "@jsenv/utils/css_ast/parcel_css.js";
14
+ import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js";
15
+ import { JS_QUOTES } from "@jsenv/utils/src/string/js_quotes.js";
21
16
  import { createRequire } from "node:module";
22
- import { composeTwoSourcemaps } from "@jsenv/utils/sourcemap/sourcemap_composition_v3.js";
23
17
  import babelParser from "@babel/parser";
24
- import { findHighestVersion } from "@jsenv/utils/semantic_versioning/highest_version.js";
25
- import { injectImport } from "@jsenv/utils/js_ast/babel_utils.js";
26
- import { sortByDependencies } from "@jsenv/utils/graph/sort_by_dependencies.js";
27
- import { applyRollupPlugins } from "@jsenv/utils/js_ast/apply_rollup_plugins.js";
28
- import { sourcemapConverter } from "@jsenv/utils/sourcemap/sourcemap_converter.js";
29
- import { createCallbackList, createCallbackListNotifiedOnce, Abort, raceCallbacks, raceProcessTeardownEvents } from "@jsenv/abort";
30
- import { createSSEService } from "@jsenv/utils/event_source/sse_service.js";
31
- import { timeStart, fetchFileSystem, composeTwoResponses, serveDirectory, startServer, pluginCORS, jsenvAccessControlAllowedHeaders, pluginServerTiming, pluginRequestWaitingCheck, composeServices, findFreePort } from "@jsenv/server";
32
- import { SOURCEMAP, generateSourcemapUrl, sourcemapToBase64Url } from "@jsenv/utils/sourcemap/sourcemap_utils.js";
18
+ import { findHighestVersion } from "@jsenv/utils/src/semantic_versioning/highest_version.js";
33
19
  import { validateResponseIntegrity } from "@jsenv/integrity";
34
20
  import { convertFileSystemErrorToResponseProperties } from "@jsenv/server/src/internal/convertFileSystemErrorToResponseProperties.js";
35
- import { memoizeByFirstArgument } from "@jsenv/utils/memoize/memoize_by_first_argument.js";
36
- import { generateCoverageJsonFile } from "@jsenv/utils/coverage/coverage_reporter_json_file.js";
37
- import { generateCoverageHtmlDirectory } from "@jsenv/utils/coverage/coverage_reporter_html_directory.js";
38
- import { generateCoverageTextLog } from "@jsenv/utils/coverage/coverage_reporter_text_log.js";
21
+ import { memoizeByFirstArgument } from "@jsenv/utils/src/memoize/memoize_by_first_argument.js";
39
22
  import { memoryUsage } from "node:process";
40
23
  import wrapAnsi from "wrap-ansi";
41
24
  import stripAnsi from "strip-ansi";
42
25
  import cuid from "cuid";
43
- import { babelPluginInstrument } from "@jsenv/utils/coverage/babel_plugin_instrument.js";
44
- import { reportToCoverage } from "@jsenv/utils/coverage/report_to_coverage.js";
45
26
  import v8 from "node:v8";
46
27
  import { runInNewContext, Script } from "node:vm";
47
- import { memoize } from "@jsenv/utils/memoize/memoize.js";
48
- import { filterV8Coverage } from "@jsenv/utils/coverage/v8_coverage_from_directory.js";
49
- import { composeTwoFileByFileIstanbulCoverages } from "@jsenv/utils/coverage/istanbul_coverage_composition.js";
50
- import { escapeRegexpSpecialChars } from "@jsenv/utils/string/escape_regexp_special_chars.js";
28
+ import { memoize } from "@jsenv/utils/src/memoize/memoize.js";
29
+ import { escapeRegexpSpecialChars } from "@jsenv/utils/src/string/escape_regexp_special_chars.js";
51
30
  import { fork } from "node:child_process";
52
31
  import { uneval } from "@jsenv/uneval";
53
- import { createVersionGenerator } from "@jsenv/utils/versioning/version_generator.js";
32
+ import { createVersionGenerator } from "@jsenv/utils/src/versioning/version_generator.js";
33
+
34
+ const createReloadableWorker = (workerFileUrl, options = {}) => {
35
+ const workerFilePath = fileURLToPath(workerFileUrl);
36
+ const isPrimary = !workerData || workerData.workerFilePath !== workerFilePath;
37
+ let worker;
38
+
39
+ const terminate = async () => {
40
+ if (worker) {
41
+ let _worker = worker;
42
+ worker = null;
43
+ const exitPromise = new Promise(resolve => {
44
+ _worker.once("exit", resolve);
45
+ });
46
+
47
+ _worker.terminate();
48
+
49
+ await exitPromise;
50
+ }
51
+ };
52
+
53
+ const load = async () => {
54
+ if (!isPrimary) {
55
+ throw new Error(`worker can be loaded from primary file only`);
56
+ }
57
+
58
+ worker = new Worker(workerFilePath, { ...options,
59
+ workerData: { ...options.workerData,
60
+ workerFilePath
61
+ }
62
+ });
63
+ worker.once("error", error => {
64
+ console.error(error);
65
+ });
66
+ await new Promise(resolve => {
67
+ worker.once("online", resolve);
68
+ });
69
+ worker.once("exit", () => {
70
+ worker = null;
71
+ });
72
+ return worker;
73
+ };
74
+
75
+ const reload = async () => {
76
+ await terminate();
77
+ await load();
78
+ };
79
+
80
+ return {
81
+ isPrimary,
82
+ load,
83
+ reload,
84
+ terminate
85
+ };
86
+ };
54
87
 
55
88
  const parseAndTransformHtmlUrls = async (urlInfo, context) => {
56
89
  const url = urlInfo.originalUrl;
@@ -74,9 +107,14 @@ const parseAndTransformHtmlUrls = async (urlInfo, context) => {
74
107
  column,
75
108
  originalLine,
76
109
  originalColumn,
77
- specifier,
78
- attribute
110
+ node,
111
+ attributeName,
112
+ specifier
79
113
  }) => {
114
+ const {
115
+ crossorigin,
116
+ integrity
117
+ } = readFetchMetas(node);
80
118
  const isRessourceHint = ["preconnect", "dns-prefetch", "prefetch", "preload", "modulepreload"].includes(subtype);
81
119
  const [reference] = referenceUtils.found({
82
120
  type,
@@ -86,10 +124,14 @@ const parseAndTransformHtmlUrls = async (urlInfo, context) => {
86
124
  specifier,
87
125
  specifierLine: line,
88
126
  specifierColumn: column,
89
- isRessourceHint
127
+ isRessourceHint,
128
+ crossorigin,
129
+ integrity
90
130
  });
91
131
  actions.push(async () => {
92
- attribute.value = await referenceUtils.readGeneratedSpecifier(reference);
132
+ setHtmlNodeAttributes(node, {
133
+ [attributeName]: await referenceUtils.readGeneratedSpecifier(reference)
134
+ });
93
135
  });
94
136
  }
95
137
  });
@@ -103,6 +145,24 @@ const parseAndTransformHtmlUrls = async (urlInfo, context) => {
103
145
  content: stringifyHtmlAst(htmlAst)
104
146
  };
105
147
  };
148
+ const crossOriginCompatibleTagNames = ["script", "link", "img", "source"];
149
+ const integrityCompatibleTagNames = ["script", "link", "img", "source"];
150
+
151
+ const readFetchMetas = node => {
152
+ const meta = {};
153
+
154
+ if (crossOriginCompatibleTagNames.includes(node.nodeName)) {
155
+ const crossorigin = getHtmlNodeAttribute(node, "crossorigin") !== undefined;
156
+ meta.crossorigin = crossorigin;
157
+ }
158
+
159
+ if (integrityCompatibleTagNames.includes(node.nodeName)) {
160
+ const integrity = getHtmlNodeAttribute(node, "integrity");
161
+ meta.integrity = integrity;
162
+ }
163
+
164
+ return meta;
165
+ };
106
166
 
107
167
  const visitHtmlUrls = ({
108
168
  url,
@@ -114,18 +174,18 @@ const visitHtmlUrls = ({
114
174
  subtype,
115
175
  expectedType,
116
176
  node,
117
- attribute,
177
+ attributeName,
118
178
  specifier
119
179
  }) => {
120
- const generatedFromInlineContent = Boolean(getHtmlNodeAttributeByName(node, "generated-from-inline-content"));
180
+ const generatedFromInlineContent = getHtmlNodeAttribute(node, "generated-from-inline-content") !== undefined;
121
181
  let position;
122
182
 
123
183
  if (generatedFromInlineContent) {
124
184
  // when generated from inline content,
125
185
  // line, column is not "src" nor "generated-from-src" but "original-position"
126
- position = htmlNodePosition.readNodePosition(node);
186
+ position = getHtmlNodePosition(node);
127
187
  } else {
128
- position = htmlNodePosition.readAttributePosition(node, attribute.name);
188
+ position = getHtmlNodeAttributePosition(node, attributeName);
129
189
  }
130
190
 
131
191
  const {
@@ -141,19 +201,68 @@ const visitHtmlUrls = ({
141
201
  column,
142
202
  // originalLine, originalColumn
143
203
  specifier,
144
- attribute,
145
- // injected:Boolean(getHtmlNodeAttributeByName(node, "injected-by"))
146
- // srcGeneratedFromInlineContent
147
- ...readFetchMetas(node)
204
+ node,
205
+ attributeName
148
206
  });
149
207
  };
150
208
 
151
- const visitors = {
209
+ const visitAttributeAsUrlSpecifier = ({
210
+ node,
211
+ attributeName,
212
+ ...rest
213
+ }) => {
214
+ const value = getHtmlNodeAttribute(node, attributeName);
215
+
216
+ if (value) {
217
+ const generatedBy = getHtmlNodeAttribute(node, "generated-by");
218
+
219
+ if (generatedBy !== undefined) {
220
+ // during build the importmap is inlined
221
+ // and shoud not be considered as a dependency anymore
222
+ return;
223
+ }
224
+
225
+ addDependency({ ...rest,
226
+ node,
227
+ attributeName,
228
+ specifier: attributeName === "generated-from-src" || attributeName === "generated-from-href" ? new URL(value, url).href : value
229
+ });
230
+ } else if (attributeName === "src") {
231
+ visitAttributeAsUrlSpecifier({ ...rest,
232
+ node,
233
+ attributeName: "generated-from-src"
234
+ });
235
+ } else if (attributeName === "href") {
236
+ visitAttributeAsUrlSpecifier({ ...rest,
237
+ node,
238
+ attributeName: "generated-from-href"
239
+ });
240
+ }
241
+ };
242
+
243
+ const visitSrcset = ({
244
+ type,
245
+ node
246
+ }) => {
247
+ const srcset = getHtmlNodeAttribute(node, "srcset");
248
+
249
+ if (srcset) {
250
+ const srcCandidates = parseSrcSet(srcset);
251
+ srcCandidates.forEach(srcCandidate => {
252
+ addDependency({
253
+ type,
254
+ node,
255
+ attributeName: "srcset",
256
+ specifier: srcCandidate.specifier
257
+ });
258
+ });
259
+ }
260
+ };
261
+
262
+ visitHtmlNodes(htmlAst, {
152
263
  link: node => {
153
- const relAttribute = getHtmlNodeAttributeByName(node, "rel");
154
- const rel = relAttribute ? relAttribute.value : undefined;
155
- const typeAttribute = getHtmlNodeAttributeByName(node, "type");
156
- const type = typeAttribute ? typeAttribute.value : undefined;
264
+ const rel = getHtmlNodeAttribute(node, "rel");
265
+ const type = getHtmlNodeAttribute(node, "type");
157
266
  visitAttributeAsUrlSpecifier({
158
267
  type: "link_href",
159
268
  subtype: rel,
@@ -169,15 +278,16 @@ const visitHtmlUrls = ({
169
278
  },
170
279
  // style: () => {},
171
280
  script: node => {
172
- const typeAttributeNode = getHtmlNodeAttributeByName(node, "type");
281
+ const type = getHtmlNodeAttribute(node, "type");
282
+ const expectedType = {
283
+ "undefined": "js_classic",
284
+ "text/javascript": "js_classic",
285
+ "module": "js_module",
286
+ "importmap": "importmap"
287
+ }[type];
173
288
  visitAttributeAsUrlSpecifier({
174
289
  type: "script_src",
175
- expectedType: {
176
- "undefined": "js_classic",
177
- "text/javascript": "js_classic",
178
- "module": "js_module",
179
- "importmap": "importmap"
180
- }[typeAttributeNode ? typeAttributeNode.value : undefined],
290
+ expectedType,
181
291
  node,
182
292
  attributeName: "src"
183
293
  });
@@ -233,102 +343,9 @@ const visitHtmlUrls = ({
233
343
  attributeName: "href"
234
344
  });
235
345
  }
236
- };
237
-
238
- const visitAttributeAsUrlSpecifier = ({
239
- type,
240
- subtype,
241
- expectedType,
242
- node,
243
- attributeName
244
- }) => {
245
- const attribute = getHtmlNodeAttributeByName(node, attributeName);
246
- const value = attribute ? attribute.value : undefined;
247
-
248
- if (value) {
249
- const generatedBy = getHtmlNodeAttributeByName(node, "generated-by");
250
-
251
- if (generatedBy) {
252
- // during build the importmap is inlined
253
- // and shoud not be considered as a dependency anymore
254
- return;
255
- }
256
-
257
- addDependency({
258
- type,
259
- subtype,
260
- expectedType,
261
- node,
262
- attribute,
263
- specifier: attributeName === "generated-from-src" || attributeName === "generated-from-href" ? new URL(value, url).href : value
264
- });
265
- } else if (attributeName === "src") {
266
- visitAttributeAsUrlSpecifier({
267
- type,
268
- subtype,
269
- expectedType,
270
- node,
271
- attributeName: "generated-from-src"
272
- });
273
- } else if (attributeName === "href") {
274
- visitAttributeAsUrlSpecifier({
275
- type,
276
- subtype,
277
- expectedType,
278
- node,
279
- attributeName: "generated-from-href"
280
- });
281
- }
282
- };
283
-
284
- const visitSrcset = ({
285
- type,
286
- node
287
- }) => {
288
- const srcsetAttribute = getHtmlNodeAttributeByName(node, "srcset");
289
- const srcset = srcsetAttribute ? srcsetAttribute.value : undefined;
290
-
291
- if (srcset) {
292
- const srcCandidates = htmlAttributeSrcSet.parse(srcset);
293
- srcCandidates.forEach(srcCandidate => {
294
- addDependency({
295
- type,
296
- node,
297
- attribute: srcsetAttribute,
298
- specifier: srcCandidate.specifier
299
- });
300
- });
301
- }
302
- };
303
-
304
- visitHtmlAst(htmlAst, node => {
305
- const visitor = visitors[node.nodeName];
306
-
307
- if (visitor) {
308
- visitor(node);
309
- }
310
346
  });
311
347
  };
312
348
 
313
- const crossOriginCompatibleTagNames = ["script", "link", "img", "source"];
314
- const integrityCompatibleTagNames = ["script", "link", "img", "source"];
315
-
316
- const readFetchMetas = node => {
317
- const meta = {};
318
-
319
- if (crossOriginCompatibleTagNames.includes(node.nodeName)) {
320
- const crossoriginAttribute = getHtmlNodeAttributeByName(node, "crossorigin");
321
- meta.crossorigin = crossoriginAttribute ? crossoriginAttribute.value : undefined;
322
- }
323
-
324
- if (integrityCompatibleTagNames.includes(node.nodeName)) {
325
- const integrityAttribute = getHtmlNodeAttributeByName(node, "integrity");
326
- meta.integrity = integrityAttribute ? integrityAttribute.value : undefined;
327
- }
328
-
329
- return meta;
330
- };
331
-
332
349
  /*
333
350
  * https://github.com/parcel-bundler/parcel/blob/v2/packages/transformers/css/src/CSSTransformer.js
334
351
  */
@@ -704,14 +721,14 @@ const jsenvPluginImportmap = () => {
704
721
  transformUrlContent: {
705
722
  html: async (htmlUrlInfo, context) => {
706
723
  const htmlAst = parseHtmlString(htmlUrlInfo.content);
707
- const importmap = findNode(htmlAst, node => {
724
+ const importmap = findHtmlNode(htmlAst, node => {
708
725
  if (node.nodeName !== "script") {
709
726
  return false;
710
727
  }
711
728
 
712
- const typeAttribute = getHtmlNodeAttributeByName(node, "type");
729
+ const type = getHtmlNodeAttribute(node, "type");
713
730
 
714
- if (!typeAttribute || typeAttribute.value !== "importmap") {
731
+ if (type === undefined || type !== "importmap") {
715
732
  return false;
716
733
  }
717
734
 
@@ -730,7 +747,7 @@ const jsenvPluginImportmap = () => {
730
747
  lineEnd,
731
748
  columnEnd,
732
749
  isOriginal
733
- } = htmlNodePosition.readNodePosition(importmap, {
750
+ } = getHtmlNodePosition(importmap, {
734
751
  preferOriginal: true
735
752
  });
736
753
  const inlineImportmapUrl = generateInlineContentUrl({
@@ -753,9 +770,9 @@ const jsenvPluginImportmap = () => {
753
770
  await context.cook(inlineImportmapUrlInfo, {
754
771
  reference: inlineImportmapReference
755
772
  });
756
- setHtmlNodeGeneratedText(importmap, {
757
- generatedText: inlineImportmapUrlInfo.content,
758
- generatedBy: "jsenv:importmap"
773
+ setHtmlNodeText(importmap, inlineImportmapUrlInfo.content);
774
+ setHtmlNodeAttributes(importmap, {
775
+ "generated-by": "jsenv:importmap"
759
776
  });
760
777
  onHtmlImportmapParsed(JSON.parse(inlineImportmapUrlInfo.content), htmlUrlInfo.url);
761
778
  };
@@ -772,11 +789,11 @@ const jsenvPluginImportmap = () => {
772
789
  reference: importmapReference
773
790
  });
774
791
  onHtmlImportmapParsed(JSON.parse(importmapUrlInfo.content), htmlUrlInfo.url);
775
- removeHtmlNodeAttributeByName(importmap, "src");
776
- setHtmlNodeGeneratedText(importmap, {
777
- generatedText: importmapUrlInfo.content,
778
- generatedBy: "jsenv:importmap",
779
- generatedFromSrc: src
792
+ setHtmlNodeText(importmap, importmapUrlInfo.content);
793
+ setHtmlNodeAttributes(importmap, {
794
+ "src": undefined,
795
+ "generated-by": "jsenv:importmap",
796
+ "generated-from-src": src
780
797
  });
781
798
  const {
782
799
  line,
@@ -784,7 +801,7 @@ const jsenvPluginImportmap = () => {
784
801
  lineEnd,
785
802
  columnEnd,
786
803
  isOriginal
787
- } = htmlNodePosition.readNodePosition(importmap, {
804
+ } = getHtmlNodePosition(importmap, {
788
805
  preferOriginal: true
789
806
  });
790
807
  const inlineImportmapUrl = generateInlineContentUrl({
@@ -805,16 +822,15 @@ const jsenvPluginImportmap = () => {
805
822
  });
806
823
  };
807
824
 
808
- const srcAttribute = getHtmlNodeAttributeByName(importmap, "src");
809
- const src = srcAttribute ? srcAttribute.value : undefined;
825
+ const src = getHtmlNodeAttribute(importmap, "src");
810
826
 
811
827
  if (src) {
812
828
  await handleImportmapWithSrc(importmap, src);
813
829
  } else {
814
- const textNode = getHtmlNodeTextNode(importmap);
830
+ const htmlNodeText = getHtmlNodeText(importmap);
815
831
 
816
- if (textNode) {
817
- await handleInlineImportmap(importmap, textNode);
832
+ if (htmlNodeText) {
833
+ await handleInlineImportmap(importmap, htmlNodeText);
818
834
  }
819
835
  } // once this plugin knows the importmap, it will use it
820
836
  // to map imports. These import specifiers will be normalized
@@ -1063,7 +1079,7 @@ const jsenvPluginUrlVersion = () => {
1063
1079
  const jsenvPluginFileUrls = ({
1064
1080
  magicExtensions = ["inherit", ".js"],
1065
1081
  magicDirectoryIndex = true,
1066
- preservesSymlink = true,
1082
+ preserveSymlinks = false,
1067
1083
  directoryReferenceAllowed = false
1068
1084
  }) => {
1069
1085
  return [{
@@ -1118,7 +1134,7 @@ const jsenvPluginFileUrls = ({
1118
1134
  if (foundADirectory && directoryReferenceAllowed) {
1119
1135
  reference.data.foundADirectory = true;
1120
1136
  const directoryFacadeUrl = urlObject.href;
1121
- const directoryUrlRaw = preservesSymlink ? directoryFacadeUrl : resolveSymlink(directoryFacadeUrl);
1137
+ const directoryUrlRaw = preserveSymlinks ? directoryFacadeUrl : resolveSymlink(directoryFacadeUrl);
1122
1138
  const directoryUrl = `${directoryUrlRaw}${search}${hash}`;
1123
1139
  return directoryUrl;
1124
1140
  }
@@ -1137,7 +1153,7 @@ const jsenvPluginFileUrls = ({
1137
1153
 
1138
1154
  reference.data.foundADirectory = filesystemResolution.isDirectory;
1139
1155
  const fileFacadeUrl = filesystemResolution.url;
1140
- const fileUrlRaw = preservesSymlink ? fileFacadeUrl : resolveSymlink(fileFacadeUrl);
1156
+ const fileUrlRaw = preserveSymlinks ? fileFacadeUrl : resolveSymlink(fileFacadeUrl);
1141
1157
  const fileUrl = `${fileUrlRaw}${search}${hash}`;
1142
1158
  return fileUrl;
1143
1159
  }
@@ -1257,13 +1273,9 @@ const jsenvPluginHtmlInlineContent = ({
1257
1273
  const actions = [];
1258
1274
 
1259
1275
  const handleInlineStyle = node => {
1260
- if (node.nodeName !== "style") {
1261
- return;
1262
- }
1276
+ const htmlNodeText = getHtmlNodeText(node);
1263
1277
 
1264
- const textNode = getHtmlNodeTextNode(node);
1265
-
1266
- if (!textNode) {
1278
+ if (!htmlNodeText) {
1267
1279
  return;
1268
1280
  }
1269
1281
 
@@ -1274,7 +1286,7 @@ const jsenvPluginHtmlInlineContent = ({
1274
1286
  lineEnd,
1275
1287
  columnEnd,
1276
1288
  isOriginal
1277
- } = htmlNodePosition.readNodePosition(node, {
1289
+ } = getHtmlNodePosition(node, {
1278
1290
  preferOriginal: true
1279
1291
  });
1280
1292
  const inlineStyleUrl = generateInlineContentUrl({
@@ -1296,55 +1308,47 @@ const jsenvPluginHtmlInlineContent = ({
1296
1308
  specifierColumn: column,
1297
1309
  specifier: inlineStyleUrl,
1298
1310
  contentType: "text/css",
1299
- content: textNode.value
1311
+ content: htmlNodeText
1300
1312
  });
1301
1313
  await context.cook(inlineStyleUrlInfo, {
1302
1314
  reference: inlineStyleReference
1303
1315
  });
1304
- setHtmlNodeGeneratedText(node, {
1305
- generatedText: inlineStyleUrlInfo.content,
1306
- generatedBy: "jsenv:html_inline_content"
1316
+ setHtmlNodeText(node, inlineStyleUrlInfo.content);
1317
+ setHtmlNodeAttributes(node, {
1318
+ "generated-by": "jsenv:html_inline_content"
1307
1319
  });
1308
1320
  });
1309
1321
  };
1310
1322
 
1311
1323
  const handleInlineScript = node => {
1312
- if (node.nodeName !== "script") {
1313
- return;
1314
- }
1324
+ const htmlNodeText = getHtmlNodeText(node);
1315
1325
 
1316
- const textNode = getHtmlNodeTextNode(node);
1317
-
1318
- if (!textNode) {
1326
+ if (!htmlNodeText) {
1319
1327
  return;
1320
1328
  } // If the inline script was already handled by an other plugin, ignore it
1321
1329
  // - we want to preserve inline scripts generated by html supervisor during dev
1322
1330
  // - we want to avoid cooking twice a script during build
1323
1331
 
1324
1332
 
1325
- const generatedBy = getHtmlNodeAttributeByName(node, "generated-by");
1333
+ const generatedBy = getHtmlNodeAttribute(node, "generated-by");
1326
1334
 
1327
- if (generatedBy) {
1328
- if (generatedBy.value === "jsenv:as_js_classic_html") {
1329
- if (!analyzeConvertedScripts) {
1330
- return;
1331
- }
1332
- }
1335
+ if (generatedBy === "jsenv:as_js_classic_html" && !analyzeConvertedScripts) {
1336
+ return;
1337
+ }
1333
1338
 
1334
- if (generatedBy.value === "jsenv:html_supervisor") {
1335
- return;
1336
- }
1339
+ if (generatedBy === "jsenv:html_supervisor") {
1340
+ return;
1337
1341
  }
1338
1342
 
1339
1343
  actions.push(async () => {
1340
- const scriptCategory = parseScriptNode(node);
1344
+ const scriptCategory = analyzeScriptNode(node);
1341
1345
  const {
1342
1346
  line,
1343
1347
  column,
1344
1348
  lineEnd,
1345
1349
  columnEnd,
1346
1350
  isOriginal
1347
- } = htmlNodePosition.readNodePosition(node, {
1351
+ } = getHtmlNodePosition(node, {
1348
1352
  preferOriginal: true
1349
1353
  }); // from MDN about [type] attribute:
1350
1354
  // "Any other value: The embedded content is treated as a data block
@@ -1380,21 +1384,25 @@ const jsenvPluginHtmlInlineContent = ({
1380
1384
  isOriginalPosition: isOriginal,
1381
1385
  specifier: inlineScriptUrl,
1382
1386
  contentType,
1383
- content: textNode.value
1387
+ content: htmlNodeText
1384
1388
  });
1385
1389
  await context.cook(inlineScriptUrlInfo, {
1386
1390
  reference: inlineScriptReference
1387
1391
  });
1388
- setHtmlNodeGeneratedText(node, {
1389
- generatedText: inlineScriptUrlInfo.content,
1390
- generatedBy: "jsenv:html_inline_content"
1392
+ setHtmlNodeText(node, inlineScriptUrlInfo.content);
1393
+ setHtmlNodeAttributes(node, {
1394
+ "generated-by": "jsenv:html_inline_content"
1391
1395
  });
1392
1396
  });
1393
1397
  };
1394
1398
 
1395
- visitHtmlAst(htmlAst, node => {
1396
- handleInlineStyle(node);
1397
- handleInlineScript(node);
1399
+ visitHtmlNodes(htmlAst, {
1400
+ style: node => {
1401
+ handleInlineStyle(node);
1402
+ },
1403
+ script: node => {
1404
+ handleInlineScript(node);
1405
+ }
1398
1406
  });
1399
1407
 
1400
1408
  if (actions.length === 0) {
@@ -1934,15 +1942,15 @@ const jsenvPluginHtmlSupervisor = ({
1934
1942
  const htmlAst = parseHtmlString(content);
1935
1943
  const scriptsToSupervise = [];
1936
1944
 
1937
- const handleInlineScript = (node, textNode) => {
1938
- const scriptCategory = parseScriptNode(node);
1945
+ const handleInlineScript = (node, htmlNodeText) => {
1946
+ const scriptCategory = analyzeScriptNode(node);
1939
1947
  const {
1940
1948
  line,
1941
1949
  column,
1942
1950
  lineEnd,
1943
1951
  columnEnd,
1944
1952
  isOriginal
1945
- } = htmlNodePosition.readNodePosition(node, {
1953
+ } = getHtmlNodePosition(node, {
1946
1954
  preferOriginal: true
1947
1955
  });
1948
1956
  let inlineScriptUrl = generateInlineContentUrl({
@@ -1964,7 +1972,7 @@ const jsenvPluginHtmlSupervisor = ({
1964
1972
  specifierColumn: column,
1965
1973
  specifier: inlineScriptUrl,
1966
1974
  contentType: "text/javascript",
1967
- content: textNode.value
1975
+ content: htmlNodeText
1968
1976
  });
1969
1977
  removeHtmlNodeText(node);
1970
1978
  scriptsToSupervise.push({
@@ -1975,21 +1983,19 @@ const jsenvPluginHtmlSupervisor = ({
1975
1983
  });
1976
1984
  };
1977
1985
 
1978
- const handleScriptWithSrc = (node, srcAttribute) => {
1979
- const scriptCategory = parseScriptNode(node);
1980
- const integrityAttribute = getHtmlNodeAttributeByName(node, "integrity");
1981
- const integrity = integrityAttribute ? integrityAttribute.value : undefined;
1982
- const crossoriginAttribute = getHtmlNodeAttributeByName(node, "crossorigin");
1983
- const crossorigin = crossoriginAttribute ? crossoriginAttribute.value : undefined;
1984
- const deferAttribute = getHtmlNodeAttributeByName(node, "crossorigin");
1985
- const defer = deferAttribute ? deferAttribute.value : undefined;
1986
- const asyncAttribute = getHtmlNodeAttributeByName(node, "crossorigin");
1987
- const async = asyncAttribute ? asyncAttribute.value : undefined;
1988
- removeHtmlNodeAttributeByName(node, "src");
1986
+ const handleScriptWithSrc = (node, src) => {
1987
+ const scriptCategory = analyzeScriptNode(node);
1988
+ const integrity = getHtmlNodeAttribute(node, "integrity");
1989
+ const crossorigin = getHtmlNodeAttribute(node, "crossorigin") !== undefined;
1990
+ const defer = getHtmlNodeAttribute(node, "defer") !== undefined;
1991
+ const async = getHtmlNodeAttribute(node, "async") !== undefined;
1992
+ setHtmlNodeAttributes(node, {
1993
+ src: undefined
1994
+ });
1989
1995
  scriptsToSupervise.push({
1990
1996
  node,
1991
1997
  type: scriptCategory,
1992
- src: srcAttribute.value,
1998
+ src,
1993
1999
  defer,
1994
2000
  async,
1995
2001
  integrity,
@@ -1997,41 +2003,39 @@ const jsenvPluginHtmlSupervisor = ({
1997
2003
  });
1998
2004
  };
1999
2005
 
2000
- visitHtmlAst(htmlAst, node => {
2001
- if (node.nodeName !== "script") {
2002
- return;
2003
- }
2004
-
2005
- const scriptCategory = parseScriptNode(node);
2006
+ visitHtmlNodes(htmlAst, {
2007
+ script: node => {
2008
+ const scriptCategory = analyzeScriptNode(node);
2006
2009
 
2007
- if (scriptCategory !== "classic" && scriptCategory !== "module") {
2008
- return;
2009
- }
2010
+ if (scriptCategory !== "classic" && scriptCategory !== "module") {
2011
+ return;
2012
+ }
2010
2013
 
2011
- const injectedByAttribute = getHtmlNodeAttributeByName(node, "injected-by");
2014
+ const injectedBy = getHtmlNodeAttribute(node, "injected-by");
2012
2015
 
2013
- if (injectedByAttribute) {
2014
- return;
2015
- }
2016
+ if (injectedBy !== undefined) {
2017
+ return;
2018
+ }
2016
2019
 
2017
- const noHtmlSupervisor = getHtmlNodeAttributeByName(node, "no-html-supervisor");
2020
+ const noHtmlSupervisor = getHtmlNodeAttribute(node, "no-html-supervisor");
2018
2021
 
2019
- if (noHtmlSupervisor) {
2020
- return;
2021
- }
2022
+ if (noHtmlSupervisor !== undefined) {
2023
+ return;
2024
+ }
2022
2025
 
2023
- const textNode = getHtmlNodeTextNode(node);
2026
+ const htmlNodeText = getHtmlNodeText(node);
2024
2027
 
2025
- if (textNode) {
2026
- handleInlineScript(node, textNode);
2027
- return;
2028
- }
2028
+ if (htmlNodeText) {
2029
+ handleInlineScript(node, htmlNodeText);
2030
+ return;
2031
+ }
2029
2032
 
2030
- const srcAttribute = getHtmlNodeAttributeByName(node, "src");
2033
+ const src = getHtmlNodeAttribute(node, "src");
2031
2034
 
2032
- if (srcAttribute) {
2033
- handleScriptWithSrc(node, srcAttribute);
2034
- return;
2035
+ if (src) {
2036
+ handleScriptWithSrc(node, src);
2037
+ return;
2038
+ }
2035
2039
  }
2036
2040
  });
2037
2041
  const [htmlSupervisorInstallerFileReference] = referenceUtils.inject({
@@ -2039,7 +2043,7 @@ const jsenvPluginHtmlSupervisor = ({
2039
2043
  expectedType: "js_module",
2040
2044
  specifier: htmlSupervisorInstallerFileUrl
2041
2045
  });
2042
- injectScriptAsEarlyAsPossible(htmlAst, createHtmlNode({
2046
+ injectScriptNodeAsEarlyAsPossible(htmlAst, createHtmlNode({
2043
2047
  "tagName": "script",
2044
2048
  "type": "module",
2045
2049
  "textContent": `
@@ -2055,7 +2059,7 @@ const jsenvPluginHtmlSupervisor = ({
2055
2059
  expectedType: "js_classic",
2056
2060
  specifier: htmlSupervisorSetupFileUrl
2057
2061
  });
2058
- injectScriptAsEarlyAsPossible(htmlAst, createHtmlNode({
2062
+ injectScriptNodeAsEarlyAsPossible(htmlAst, createHtmlNode({
2059
2063
  "tagName": "script",
2060
2064
  "src": htmlSupervisorSetupFileReference.generatedSpecifier,
2061
2065
  "injected-by": "jsenv:html_supervisor"
@@ -2070,20 +2074,24 @@ const jsenvPluginHtmlSupervisor = ({
2070
2074
  integrity,
2071
2075
  crossorigin
2072
2076
  }) => {
2073
- setHtmlNodeGeneratedText(node, {
2074
- generatedText: generateCodeToSuperviseScript({
2075
- type,
2076
- src,
2077
- isInline,
2078
- defer,
2079
- async,
2080
- integrity,
2081
- crossorigin,
2082
- htmlSupervisorInstallerSpecifier: htmlSupervisorInstallerFileReference.generatedSpecifier
2083
- }),
2084
- generatedBy: "jsenv:html_supervisor",
2085
- generatedFromSrc: src,
2086
- generatedFromInlineContent: isInline
2077
+ setHtmlNodeText(node, generateCodeToSuperviseScript({
2078
+ type,
2079
+ src,
2080
+ isInline,
2081
+ defer,
2082
+ async,
2083
+ integrity,
2084
+ crossorigin,
2085
+ htmlSupervisorInstallerSpecifier: htmlSupervisorInstallerFileReference.generatedSpecifier
2086
+ }));
2087
+ setHtmlNodeAttributes(node, {
2088
+ "generated-by": "jsenv:html_supervisor",
2089
+ ...(src ? {
2090
+ "generated-from-src": src
2091
+ } : {}),
2092
+ ...(isInline ? {
2093
+ "generated-from-inline-content": ""
2094
+ } : {})
2087
2095
  });
2088
2096
  });
2089
2097
  const htmlModified = stringifyHtmlAst(htmlAst);
@@ -2698,12 +2706,7 @@ const jsenvPluginAsJsClassicHtml = ({
2698
2706
  const classicScriptNodes = [];
2699
2707
 
2700
2708
  const visitLinkNodes = node => {
2701
- if (node.nodeName !== "link") {
2702
- return;
2703
- }
2704
-
2705
- const relAttribute = getHtmlNodeAttributeByName(node, "rel");
2706
- const rel = relAttribute ? relAttribute.value : undefined;
2709
+ const rel = getHtmlNodeAttribute(node, "rel");
2707
2710
 
2708
2711
  if (rel === "modulepreload") {
2709
2712
  modulePreloadNodes.push(node);
@@ -2711,8 +2714,7 @@ const jsenvPluginAsJsClassicHtml = ({
2711
2714
  }
2712
2715
 
2713
2716
  if (rel === "preload") {
2714
- const asAttribute = getHtmlNodeAttributeByName(node, "as");
2715
- const asValue = asAttribute ? asAttribute.value : undefined;
2717
+ const asValue = getHtmlNodeAttribute(node, "as");
2716
2718
 
2717
2719
  if (asValue === "script") {
2718
2720
  preloadAsScriptNodes.push(node);
@@ -2723,12 +2725,7 @@ const jsenvPluginAsJsClassicHtml = ({
2723
2725
  };
2724
2726
 
2725
2727
  const visitScriptNodes = node => {
2726
- if (node.nodeName !== "script") {
2727
- return;
2728
- }
2729
-
2730
- const typeAttribute = getHtmlNodeAttributeByName(node, "type");
2731
- const type = typeAttribute ? typeAttribute.value : undefined;
2728
+ const type = getHtmlNodeAttribute(node, "type");
2732
2729
 
2733
2730
  if (type === "module") {
2734
2731
  moduleScriptNodes.push(node);
@@ -2741,9 +2738,13 @@ const jsenvPluginAsJsClassicHtml = ({
2741
2738
  }
2742
2739
  };
2743
2740
 
2744
- visitHtmlAst(htmlAst, node => {
2745
- visitLinkNodes(node);
2746
- visitScriptNodes(node);
2741
+ visitHtmlNodes(htmlAst, {
2742
+ link: node => {
2743
+ visitLinkNodes(node);
2744
+ },
2745
+ script: node => {
2746
+ visitScriptNodes(node);
2747
+ }
2747
2748
  });
2748
2749
  const actions = [];
2749
2750
  const jsModuleUrls = [];
@@ -2785,10 +2786,10 @@ const jsenvPluginAsJsClassicHtml = ({
2785
2786
  };
2786
2787
 
2787
2788
  classicScriptNodes.forEach(classicScriptNode => {
2788
- const srcAttribute = getHtmlNodeAttributeByName(classicScriptNode, "src");
2789
+ const src = getHtmlNodeAttribute(classicScriptNode, "src");
2789
2790
 
2790
- if (srcAttribute) {
2791
- const reference = urlInfo.references.find(ref => ref.generatedSpecifier === srcAttribute.value && ref.type === "script_src");
2791
+ if (src !== undefined) {
2792
+ const reference = urlInfo.references.find(ref => ref.generatedSpecifier === src && ref.type === "script_src");
2792
2793
  const urlObject = new URL(reference.url);
2793
2794
 
2794
2795
  if (urlObject.searchParams.has("as_js_classic")) {
@@ -2807,10 +2808,10 @@ const jsenvPluginAsJsClassicHtml = ({
2807
2808
  }
2808
2809
  });
2809
2810
  moduleScriptNodes.forEach(moduleScriptNode => {
2810
- const srcAttribute = getHtmlNodeAttributeByName(moduleScriptNode, "src");
2811
+ const src = getHtmlNodeAttribute(moduleScriptNode, "src");
2811
2812
 
2812
- if (srcAttribute) {
2813
- const reference = urlInfo.references.find(ref => ref.generatedSpecifier === srcAttribute.value && ref.type === "script_src" && ref.expectedType === "js_module");
2813
+ if (src !== undefined) {
2814
+ const reference = urlInfo.references.find(ref => ref.generatedSpecifier === src && ref.type === "script_src" && ref.expectedType === "js_module");
2814
2815
  jsModuleUrls.push(reference.url);
2815
2816
 
2816
2817
  if (shouldTransformScriptTypeModule) {
@@ -2818,8 +2819,10 @@ const jsenvPluginAsJsClassicHtml = ({
2818
2819
  const [newReference] = await getReferenceAsJsClassic(reference, {
2819
2820
  cookIt: true
2820
2821
  });
2821
- removeHtmlNodeAttributeByName(moduleScriptNode, "type");
2822
- srcAttribute.value = newReference.generatedSpecifier;
2822
+ setHtmlNodeAttributes(moduleScriptNode, {
2823
+ type: undefined,
2824
+ src: newReference.generatedSpecifier
2825
+ });
2823
2826
  });
2824
2827
  }
2825
2828
 
@@ -2827,7 +2830,7 @@ const jsenvPluginAsJsClassicHtml = ({
2827
2830
  }
2828
2831
 
2829
2832
  if (shouldTransformScriptTypeModule) {
2830
- const textNode = getHtmlNodeTextNode(moduleScriptNode);
2833
+ const htmlNodeText = getHtmlNodeText(moduleScriptNode);
2831
2834
  actions.push(async () => {
2832
2835
  const {
2833
2836
  line,
@@ -2835,7 +2838,7 @@ const jsenvPluginAsJsClassicHtml = ({
2835
2838
  lineEnd,
2836
2839
  columnEnd,
2837
2840
  isOriginal
2838
- } = htmlNodePosition.readNodePosition(moduleScriptNode, {
2841
+ } = getHtmlNodePosition(moduleScriptNode, {
2839
2842
  preferOriginal: true
2840
2843
  });
2841
2844
  let inlineScriptUrl = generateInlineContentUrl({
@@ -2858,15 +2861,15 @@ const jsenvPluginAsJsClassicHtml = ({
2858
2861
  specifierColumn: column,
2859
2862
  specifier: inlineScriptUrl,
2860
2863
  contentType: "text/javascript",
2861
- content: textNode.value
2864
+ content: htmlNodeText
2862
2865
  });
2863
2866
  const [, newUrlInfo] = await getReferenceAsJsClassic(inlineReference, {
2864
2867
  cookIt: true
2865
2868
  });
2866
- removeHtmlNodeAttributeByName(moduleScriptNode, "type");
2867
- setHtmlNodeGeneratedText(moduleScriptNode, {
2868
- generatedText: newUrlInfo.content,
2869
- generatedBy: "jsenv:as_js_classic_html"
2869
+ setHtmlNodeText(moduleScriptNode, newUrlInfo.content);
2870
+ setHtmlNodeAttributes(moduleScriptNode, {
2871
+ "type": undefined,
2872
+ "generated-by": "jsenv:as_js_classic_html"
2870
2873
  });
2871
2874
  });
2872
2875
  }
@@ -2874,8 +2877,7 @@ const jsenvPluginAsJsClassicHtml = ({
2874
2877
 
2875
2878
  if (shouldTransformScriptTypeModule) {
2876
2879
  preloadAsScriptNodes.forEach(preloadAsScriptNode => {
2877
- const hrefAttribute = getHtmlNodeAttributeByName(preloadAsScriptNode, "href");
2878
- const href = hrefAttribute.value;
2880
+ const href = getHtmlNodeAttribute(preloadAsScriptNode, "href");
2879
2881
  const reference = urlInfo.references.find(ref => ref.generatedSpecifier === href && ref.type === "link_href" && ref.expectedType === undefined);
2880
2882
  const expectedScriptType = jsModuleUrls.includes(reference.url) ? "module" : "classic";
2881
2883
 
@@ -2890,16 +2892,15 @@ const jsenvPluginAsJsClassicHtml = ({
2890
2892
  [newReference] = await getReferenceAsJsClassic(reference);
2891
2893
  }
2892
2894
 
2893
- assignHtmlNodeAttributes(preloadAsScriptNode, {
2894
- href: newReference.generatedSpecifier
2895
+ setHtmlNodeAttributes(preloadAsScriptNode, {
2896
+ href: newReference.generatedSpecifier,
2897
+ crossorigin: undefined
2895
2898
  });
2896
- removeHtmlNodeAttributeByName(preloadAsScriptNode, "crossorigin");
2897
2899
  });
2898
2900
  }
2899
2901
  });
2900
2902
  modulePreloadNodes.forEach(modulePreloadNode => {
2901
- const hrefAttribute = getHtmlNodeAttributeByName(modulePreloadNode, "href");
2902
- const href = hrefAttribute.value;
2903
+ const href = getHtmlNodeAttribute(modulePreloadNode, "href");
2903
2904
  const reference = urlInfo.references.find(ref => ref.generatedSpecifier === href && ref.type === "link_href" && ref.expectedType === "js_module");
2904
2905
  actions.push(async () => {
2905
2906
  let newReference;
@@ -2910,7 +2911,7 @@ const jsenvPluginAsJsClassicHtml = ({
2910
2911
  [newReference] = await getReferenceAsJsClassic(reference);
2911
2912
  }
2912
2913
 
2913
- assignHtmlNodeAttributes(modulePreloadNode, {
2914
+ setHtmlNodeAttributes(modulePreloadNode, {
2914
2915
  rel: "preload",
2915
2916
  as: "script",
2916
2917
  href: newReference.generatedSpecifier
@@ -2934,7 +2935,7 @@ const jsenvPluginAsJsClassicHtml = ({
2934
2935
  expectedType: "js_classic",
2935
2936
  specifier: systemJsClientFileUrl
2936
2937
  });
2937
- injectScriptAsEarlyAsPossible(htmlAst, createHtmlNode({
2938
+ injectScriptNodeAsEarlyAsPossible(htmlAst, createHtmlNode({
2938
2939
  "tagName": "script",
2939
2940
  "src": systemJsReference.generatedSpecifier,
2940
2941
  "injected-by": "jsenv:as_js_classic_html"
@@ -4013,7 +4014,7 @@ const babelPluginBabelHelpersAsJsenvImports = (babel, {
4013
4014
  }
4014
4015
 
4015
4016
  const babelHelperImportSpecifier = getBabelHelperFileUrl(name);
4016
- const helper = injectImport({
4017
+ const helper = injectJsImport({
4017
4018
  programPath: file.path,
4018
4019
  from: getImportSpecifier(babelHelperImportSpecifier),
4019
4020
  nameHint: `_${name}`,
@@ -4116,7 +4117,7 @@ const babelPluginNewStylesheetAsJsenvImport = (babel, {
4116
4117
  });
4117
4118
 
4118
4119
  if (usesNewStylesheet) {
4119
- injectImport({
4120
+ injectJsImport({
4120
4121
  programPath,
4121
4122
  from: getImportSpecifier(newStylesheetClientFileUrl),
4122
4123
  sideEffect: true
@@ -4181,7 +4182,7 @@ const babelPluginGlobalThisAsJsenvImport = (babel, {
4181
4182
  } = path; // we should do this once, tree shaking will remote it but still
4182
4183
 
4183
4184
  if (node.name === "globalThis") {
4184
- injectImport({
4185
+ injectJsImport({
4185
4186
  programPath: path.scope.getProgramParent().path,
4186
4187
  from: getImportSpecifier(globalThisClientFileUrl),
4187
4188
  sideEffect: true
@@ -4215,7 +4216,7 @@ const babelPluginRegeneratorRuntimeAsJsenvImport = (babel, {
4215
4216
  } = path;
4216
4217
 
4217
4218
  if (node.name === "regeneratorRuntime") {
4218
- injectImport({
4219
+ injectJsImport({
4219
4220
  programPath: path.scope.getProgramParent().path,
4220
4221
  from: getImportSpecifier(regeneratorRuntimeClientFileUrl),
4221
4222
  sideEffect: true
@@ -4459,6 +4460,39 @@ const jsenvPluginNodeRuntime = ({
4459
4460
  };
4460
4461
  };
4461
4462
 
4463
+ const sortByDependencies = nodes => {
4464
+ const visited = [];
4465
+ const sorted = [];
4466
+ const circular = [];
4467
+
4468
+ const visit = url => {
4469
+ const isSorted = sorted.includes(url);
4470
+
4471
+ if (isSorted) {
4472
+ return;
4473
+ }
4474
+
4475
+ const isVisited = visited.includes(url);
4476
+
4477
+ if (isVisited) {
4478
+ circular.push(url);
4479
+ sorted.push(url);
4480
+ } else {
4481
+ visited.push(url);
4482
+ nodes[url].dependencies.forEach(dependencyUrl => {
4483
+ visit(dependencyUrl);
4484
+ });
4485
+ sorted.push(url);
4486
+ }
4487
+ };
4488
+
4489
+ Object.keys(nodes).forEach(url => {
4490
+ visit(url);
4491
+ });
4492
+ sorted.circular = circular;
4493
+ return sorted;
4494
+ };
4495
+
4462
4496
  /*
4463
4497
  * Each @import found in css is replaced by the file content
4464
4498
  * - There is no need to worry about urls (such as background-image: url())
@@ -4676,85 +4710,20 @@ const bundleJsModule = async ({
4676
4710
  });
4677
4711
  return jsModuleBundleUrlInfos;
4678
4712
  };
4679
- const buildWithRollup = async ({
4680
- signal,
4681
- logger,
4713
+
4714
+ const rollupPluginJsenv = ({
4715
+ // logger,
4682
4716
  rootDirectoryUrl,
4683
4717
  buildDirectoryUrl,
4684
4718
  urlGraph,
4685
4719
  jsModuleUrlInfos,
4686
- runtimeCompat,
4687
4720
  sourcemaps,
4688
4721
  include,
4689
- babelHelpersChunk
4722
+ babelHelpersChunk,
4723
+ resultRef
4690
4724
  }) => {
4691
- const resultRef = {
4692
- current: null
4693
- };
4694
-
4695
- try {
4696
- await applyRollupPlugins({
4697
- rollupPlugins: [rollupPluginJsenv({
4698
- signal,
4699
- logger,
4700
- rootDirectoryUrl,
4701
- buildDirectoryUrl,
4702
- urlGraph,
4703
- jsModuleUrlInfos,
4704
- runtimeCompat,
4705
- sourcemaps,
4706
- include,
4707
- babelHelpersChunk,
4708
- resultRef
4709
- })],
4710
- inputOptions: {
4711
- input: [],
4712
- onwarn: warning => {
4713
- if (warning.code === "CIRCULAR_DEPENDENCY") {
4714
- return;
4715
- }
4716
-
4717
- if (warning.code === "THIS_IS_UNDEFINED" && pathToFileURL(warning.id).href === globalThisClientFileUrl) {
4718
- return;
4719
- }
4720
-
4721
- if (warning.code === "EVAL") {
4722
- // ideally we should disable only for jsenv files
4723
- return;
4724
- }
4725
-
4726
- logger.warn(String(warning));
4727
- }
4728
- }
4729
- });
4730
- return resultRef.current;
4731
- } catch (e) {
4732
- if (e.code === "MISSING_EXPORT") {
4733
- const detailedMessage = createDetailedMessage(e.message, {
4734
- frame: e.frame
4735
- });
4736
- throw new Error(detailedMessage, {
4737
- cause: e
4738
- });
4739
- }
4740
-
4741
- throw e;
4742
- }
4743
- };
4744
-
4745
- const rollupPluginJsenv = ({
4746
- // logger,
4747
- rootDirectoryUrl,
4748
- buildDirectoryUrl,
4749
- urlGraph,
4750
- jsModuleUrlInfos,
4751
- sourcemaps,
4752
- include,
4753
- babelHelpersChunk,
4754
- resultRef
4755
- }) => {
4756
- let _rollupEmitFile = () => {
4757
- throw new Error("not implemented");
4725
+ let _rollupEmitFile = () => {
4726
+ throw new Error("not implemented");
4758
4727
  };
4759
4728
 
4760
4729
  const emitChunk = chunk => {
@@ -4966,6 +4935,91 @@ const rollupPluginJsenv = ({
4966
4935
  };
4967
4936
  };
4968
4937
 
4938
+ const buildWithRollup = async ({
4939
+ signal,
4940
+ logger,
4941
+ rootDirectoryUrl,
4942
+ buildDirectoryUrl,
4943
+ urlGraph,
4944
+ jsModuleUrlInfos,
4945
+ runtimeCompat,
4946
+ sourcemaps,
4947
+ include,
4948
+ babelHelpersChunk
4949
+ }) => {
4950
+ const resultRef = {
4951
+ current: null
4952
+ };
4953
+
4954
+ try {
4955
+ await applyRollupPlugins({
4956
+ rollupPlugins: [rollupPluginJsenv({
4957
+ signal,
4958
+ logger,
4959
+ rootDirectoryUrl,
4960
+ buildDirectoryUrl,
4961
+ urlGraph,
4962
+ jsModuleUrlInfos,
4963
+ runtimeCompat,
4964
+ sourcemaps,
4965
+ include,
4966
+ babelHelpersChunk,
4967
+ resultRef
4968
+ })],
4969
+ inputOptions: {
4970
+ input: [],
4971
+ onwarn: warning => {
4972
+ if (warning.code === "CIRCULAR_DEPENDENCY") {
4973
+ return;
4974
+ }
4975
+
4976
+ if (warning.code === "THIS_IS_UNDEFINED" && pathToFileURL(warning.id).href === globalThisClientFileUrl) {
4977
+ return;
4978
+ }
4979
+
4980
+ if (warning.code === "EVAL") {
4981
+ // ideally we should disable only for jsenv files
4982
+ return;
4983
+ }
4984
+
4985
+ logger.warn(String(warning));
4986
+ }
4987
+ }
4988
+ });
4989
+ return resultRef.current;
4990
+ } catch (e) {
4991
+ if (e.code === "MISSING_EXPORT") {
4992
+ const detailedMessage = createDetailedMessage(e.message, {
4993
+ frame: e.frame
4994
+ });
4995
+ throw new Error(detailedMessage, {
4996
+ cause: e
4997
+ });
4998
+ }
4999
+
5000
+ throw e;
5001
+ }
5002
+ };
5003
+
5004
+ const applyRollupPlugins = async ({
5005
+ rollupPlugins,
5006
+ inputOptions = {},
5007
+ outputOptions = {}
5008
+ }) => {
5009
+ const {
5010
+ rollup
5011
+ } = await import("rollup");
5012
+ const {
5013
+ importAssertions
5014
+ } = await import("acorn-import-assertions");
5015
+ const rollupReturnValue = await rollup({ ...inputOptions,
5016
+ plugins: rollupPlugins,
5017
+ acornInjectPlugins: [importAssertions, ...(inputOptions.acornInjectPlugins || [])]
5018
+ });
5019
+ const rollupOutputArray = await rollupReturnValue.generate(outputOptions);
5020
+ return rollupOutputArray;
5021
+ };
5022
+
4969
5023
  const willBeInsideJsDirectory = ({
4970
5024
  chunkInfo,
4971
5025
  fileUrlConverter,
@@ -5208,8 +5262,7 @@ const collectHotDataFromHtmlAst = htmlAst => {
5208
5262
  attributeName,
5209
5263
  hotAccepted
5210
5264
  }) => {
5211
- const attribute = getHtmlNodeAttributeByName(node, attributeName);
5212
- const value = attribute ? attribute.value : undefined;
5265
+ const value = getHtmlNodeAttribute(node, attributeName);
5213
5266
 
5214
5267
  if (value) {
5215
5268
  onSpecifier({
@@ -5256,11 +5309,10 @@ const collectHotDataFromHtmlAst = htmlAst => {
5256
5309
  }
5257
5310
 
5258
5311
  if (nodeNamesWithSrcset.includes(node.nodeName)) {
5259
- const srcsetAttribute = getHtmlNodeAttributeByName(node, "srcset");
5260
- const srcset = srcsetAttribute ? srcsetAttribute.value : undefined;
5312
+ const srcset = getHtmlNodeAttribute(node, "srcset");
5261
5313
 
5262
5314
  if (srcset) {
5263
- const srcCandidates = htmlAttributeSrcSet.parse(srcset);
5315
+ const srcCandidates = parseSrcSet(srcset);
5264
5316
  srcCandidates.forEach(srcCandidate => {
5265
5317
  onSpecifier({
5266
5318
  node,
@@ -5301,15 +5353,15 @@ const nodeNamesWithSrcset = ["img", "source"];
5301
5353
 
5302
5354
  const getNodeContext = node => {
5303
5355
  const context = {};
5304
- const hotAcceptAttribute = getHtmlNodeAttributeByName(node, "hot-accept");
5356
+ const hotAccept = getHtmlNodeAttribute(node, "hot-accept");
5305
5357
 
5306
- if (hotAcceptAttribute) {
5358
+ if (hotAccept !== undefined) {
5307
5359
  context.hotAccepted = true;
5308
5360
  }
5309
5361
 
5310
- const hotDeclineAttribute = getHtmlNodeAttributeByName(node, "hot-decline");
5362
+ const hotDecline = getHtmlNodeAttribute(node, "hot-decline");
5311
5363
 
5312
- if (hotDeclineAttribute) {
5364
+ if (hotDecline !== undefined) {
5313
5365
  context.hotAccepted = false;
5314
5366
  }
5315
5367
 
@@ -5322,7 +5374,7 @@ const htmlNodeCanHotReload = node => {
5322
5374
  isStylesheet,
5323
5375
  isRessourceHint,
5324
5376
  rel
5325
- } = parseLinkNode(node);
5377
+ } = analyzeLinkNode(node);
5326
5378
 
5327
5379
  if (isStylesheet) {
5328
5380
  // stylesheets can be hot replaced by default
@@ -5622,7 +5674,7 @@ const jsenvPluginDevSSEClient = () => {
5622
5674
  expectedType: "js_module",
5623
5675
  specifier: eventSourceClientFileUrl
5624
5676
  });
5625
- injectScriptAsEarlyAsPossible(htmlAst, createHtmlNode({
5677
+ injectScriptNodeAsEarlyAsPossible(htmlAst, createHtmlNode({
5626
5678
  "tagName": "script",
5627
5679
  "type": "module",
5628
5680
  "src": eventSourceClientReference.generatedSpecifier,
@@ -5637,6 +5689,60 @@ const jsenvPluginDevSSEClient = () => {
5637
5689
  };
5638
5690
  };
5639
5691
 
5692
+ const createSSEService = ({
5693
+ serverEventCallbackList
5694
+ }) => {
5695
+ const destroyCallbackList = createCallbackListNotifiedOnce();
5696
+ const cache = [];
5697
+ const sseRoomLimit = 100;
5698
+
5699
+ const getOrCreateSSERoom = request => {
5700
+ const htmlFileRelativeUrl = request.ressource.slice(1);
5701
+ const cacheEntry = cache.find(cacheEntryCandidate => cacheEntryCandidate.htmlFileRelativeUrl === htmlFileRelativeUrl);
5702
+
5703
+ if (cacheEntry) {
5704
+ return cacheEntry.sseRoom;
5705
+ }
5706
+
5707
+ const sseRoom = createSSERoom({
5708
+ retryDuration: 2000,
5709
+ historyLength: 100,
5710
+ welcomeEventEnabled: true,
5711
+ effect: () => {
5712
+ return serverEventCallbackList.add(event => {
5713
+ sseRoom.sendEvent(event);
5714
+ });
5715
+ }
5716
+ });
5717
+ const removeSSECleanupCallback = destroyCallbackList.add(() => {
5718
+ removeSSECleanupCallback();
5719
+ sseRoom.close();
5720
+ });
5721
+ cache.push({
5722
+ htmlFileRelativeUrl,
5723
+ sseRoom,
5724
+ cleanup: () => {
5725
+ removeSSECleanupCallback();
5726
+ sseRoom.close();
5727
+ }
5728
+ });
5729
+
5730
+ if (cache.length >= sseRoomLimit) {
5731
+ const firstCacheEntry = cache.shift();
5732
+ firstCacheEntry.cleanup();
5733
+ }
5734
+
5735
+ return sseRoom;
5736
+ };
5737
+
5738
+ return {
5739
+ getOrCreateSSERoom,
5740
+ destroy: () => {
5741
+ destroyCallbackList.notify();
5742
+ }
5743
+ };
5744
+ };
5745
+
5640
5746
  const jsenvPluginDevSSEServer = ({
5641
5747
  rootDirectoryUrl,
5642
5748
  urlGraph,
@@ -6608,13 +6714,27 @@ const createUrlInfoTransformer = ({
6608
6714
  const sourcemapsEnabled = sourcemaps === "inline" || sourcemaps === "file" || sourcemaps === "programmatic";
6609
6715
 
6610
6716
  const normalizeSourcemap = (urlInfo, sourcemap) => {
6717
+ let {
6718
+ sources
6719
+ } = sourcemap;
6720
+
6721
+ if (sources) {
6722
+ sources = sources.map(source => {
6723
+ if (source && isFileSystemPath(source)) {
6724
+ return String(pathToFileURL(source));
6725
+ }
6726
+
6727
+ return source;
6728
+ });
6729
+ }
6730
+
6611
6731
  const wantSourcesContent = // for inline content (<script> insdide html)
6612
6732
  // chrome won't be able to fetch the file as it does not exists
6613
6733
  // so sourcemap must contain sources
6614
- sourcemapsSourcesContent || urlInfo.isInline || sourcemap.sources && sourcemap.sources.some(source => !source || !source.startsWith("file:"));
6734
+ sourcemapsSourcesContent || urlInfo.isInline || sources && sources.some(source => !source || !source.startsWith("file:"));
6615
6735
 
6616
- if (sourcemap.sources && sourcemap.sources.length > 1) {
6617
- sourcemap.sources = sourcemap.sources.map(source => new URL(source, urlInfo.originalUrl).href);
6736
+ if (sources && sources.length > 1) {
6737
+ sourcemap.sources = sources.map(source => new URL(source, urlInfo.originalUrl).href);
6618
6738
 
6619
6739
  if (!wantSourcesContent) {
6620
6740
  sourcemap.sourcesContent = undefined;
@@ -6650,7 +6770,7 @@ const createUrlInfoTransformer = ({
6650
6770
  // but otherwise it's generatedUrl to be inside .jsenv/ directory
6651
6771
 
6652
6772
 
6653
- urlInfo.sourcemapGeneratedUrl = generateSourcemapUrl(urlInfo.generatedUrl);
6773
+ urlInfo.sourcemapGeneratedUrl = generateSourcemapFileUrl(urlInfo.generatedUrl);
6654
6774
  const [sourcemapReference, sourcemapUrlInfo] = injectSourcemapPlaceholder({
6655
6775
  urlInfo,
6656
6776
  specifier: urlInfo.sourcemapGeneratedUrl
@@ -6756,7 +6876,7 @@ const createUrlInfoTransformer = ({
6756
6876
  sourcemapUrlInfo.content = JSON.stringify(sourcemap, null, " ");
6757
6877
 
6758
6878
  if (sourcemaps === "inline") {
6759
- sourcemapReference.generatedSpecifier = sourcemapToBase64Url(sourcemap);
6879
+ sourcemapReference.generatedSpecifier = generateSourcemapDataUrl(sourcemap);
6760
6880
  }
6761
6881
 
6762
6882
  if (sourcemaps === "file" || sourcemaps === "inline") {
@@ -7661,6 +7781,8 @@ const memoizeCook = cook => {
7661
7781
  const applyReferenceEffectsOnUrlInfo = (reference, urlInfo, context) => {
7662
7782
  if (reference.shouldHandle) {
7663
7783
  urlInfo.shouldHandle = true;
7784
+ } else {
7785
+ urlInfo.shouldHandle = false;
7664
7786
  }
7665
7787
 
7666
7788
  urlInfo.originalUrl = urlInfo.originalUrl || reference.url;
@@ -8295,7 +8417,7 @@ const jsenvPluginExplorer = ({
8295
8417
 
8296
8418
  const startDevServer = async ({
8297
8419
  signal = new AbortController().signal,
8298
- handleSIGINT,
8420
+ handleSIGINT = true,
8299
8421
  logLevel = "info",
8300
8422
  omegaServerLogLevel = "warn",
8301
8423
  port = 3456,
@@ -8315,9 +8437,9 @@ const startDevServer = async ({
8315
8437
  devServerMainFile = getCallerPosition().url,
8316
8438
  // force disable server autoreload when this code is executed:
8317
8439
  // - inside a forked child process
8318
- // - inside a worker thread
8319
- // (because node cluster won't work)
8320
- devServerAutoreload = typeof process.send !== "function" && !parentPort && !process.env.VSCODE_INSPECTOR_OPTIONS,
8440
+ // - debugged by vscode
8441
+ // otherwise we get net:ERR_CONNECTION_REFUSED
8442
+ devServerAutoreload = typeof process.send !== "function" && !process.env.VSCODE_INSPECTOR_OPTIONS,
8321
8443
  clientFiles = {
8322
8444
  "./src/": true,
8323
8445
  "./test/": true
@@ -8349,198 +8471,882 @@ const startDevServer = async ({
8349
8471
  test: {
8350
8472
  "./test/**/*.test.html": true
8351
8473
  }
8352
- },
8353
- // toolbar = false,
8354
- writeGeneratedFiles = true
8474
+ },
8475
+ // toolbar = false,
8476
+ writeGeneratedFiles = true
8477
+ }) => {
8478
+ const logger = createLogger({
8479
+ logLevel
8480
+ });
8481
+ rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl);
8482
+ const operation = Abort.startOperation();
8483
+ operation.addAbortSignal(signal);
8484
+
8485
+ if (handleSIGINT) {
8486
+ operation.addAbortSource(abort => {
8487
+ return raceProcessTeardownEvents({
8488
+ SIGINT: true
8489
+ }, abort);
8490
+ });
8491
+ }
8492
+
8493
+ if (port === 0) {
8494
+ port = await findFreePort(port, {
8495
+ signal: operation.signal
8496
+ });
8497
+ }
8498
+
8499
+ const reloadableWorker = createReloadableWorker(devServerMainFile);
8500
+
8501
+ if (devServerAutoreload && reloadableWorker.isPrimary) {
8502
+ const devServerFileChangeCallback = ({
8503
+ relativeUrl,
8504
+ event
8505
+ }) => {
8506
+ const url = new URL(relativeUrl, rootDirectoryUrl).href;
8507
+ logger.info(`file ${event} ${url} -> restarting server...`);
8508
+ reloadableWorker.reload();
8509
+ };
8510
+
8511
+ const stopWatchingDevServerFiles = registerDirectoryLifecycle(rootDirectoryUrl, {
8512
+ watchPatterns: {
8513
+ [devServerMainFile]: true,
8514
+ ...devServerFiles
8515
+ },
8516
+ cooldownBetweenFileEvents,
8517
+ keepProcessAlive: false,
8518
+ recursive: true,
8519
+ added: ({
8520
+ relativeUrl
8521
+ }) => {
8522
+ devServerFileChangeCallback({
8523
+ relativeUrl,
8524
+ event: "added"
8525
+ });
8526
+ },
8527
+ updated: ({
8528
+ relativeUrl
8529
+ }) => {
8530
+ devServerFileChangeCallback({
8531
+ relativeUrl,
8532
+ event: "modified"
8533
+ });
8534
+ },
8535
+ removed: ({
8536
+ relativeUrl
8537
+ }) => {
8538
+ devServerFileChangeCallback({
8539
+ relativeUrl,
8540
+ event: "removed"
8541
+ });
8542
+ }
8543
+ });
8544
+ operation.addAbortCallback(() => {
8545
+ stopWatchingDevServerFiles();
8546
+ reloadableWorker.terminate();
8547
+ });
8548
+ const worker = await reloadableWorker.load();
8549
+
8550
+ if (!keepProcessAlive) {
8551
+ worker.unref();
8552
+ }
8553
+
8554
+ return {
8555
+ origin: `${protocol}://127.0.0.1:${port}`,
8556
+ stop: () => {
8557
+ stopWatchingDevServerFiles();
8558
+ reloadableWorker.terminate();
8559
+ }
8560
+ };
8561
+ }
8562
+
8563
+ const startDevServerTask = createTaskLog("start dev server", {
8564
+ disabled: !loggerToLevels(logger).info
8565
+ });
8566
+ const clientFileChangeCallbackList = [];
8567
+ const clientFilesPruneCallbackList = [];
8568
+
8569
+ const clientFileChangeCallback = ({
8570
+ relativeUrl,
8571
+ event
8572
+ }) => {
8573
+ const url = new URL(relativeUrl, rootDirectoryUrl).href;
8574
+ clientFileChangeCallbackList.forEach(callback => {
8575
+ callback({
8576
+ url,
8577
+ event
8578
+ });
8579
+ });
8580
+ };
8581
+
8582
+ const stopWatchingClientFiles = registerDirectoryLifecycle(rootDirectoryUrl, {
8583
+ watchPatterns: clientFiles,
8584
+ cooldownBetweenFileEvents,
8585
+ keepProcessAlive: false,
8586
+ recursive: true,
8587
+ added: ({
8588
+ relativeUrl
8589
+ }) => {
8590
+ clientFileChangeCallback({
8591
+ event: "added",
8592
+ relativeUrl
8593
+ });
8594
+ },
8595
+ updated: ({
8596
+ relativeUrl
8597
+ }) => {
8598
+ clientFileChangeCallback({
8599
+ event: "modified",
8600
+ relativeUrl
8601
+ });
8602
+ },
8603
+ removed: ({
8604
+ relativeUrl
8605
+ }) => {
8606
+ clientFileChangeCallback({
8607
+ event: "removed",
8608
+ relativeUrl
8609
+ });
8610
+ }
8611
+ });
8612
+ const urlGraph = createUrlGraph({
8613
+ clientFileChangeCallbackList,
8614
+ clientFilesPruneCallbackList
8615
+ });
8616
+ const kitchen = createKitchen({
8617
+ signal,
8618
+ logger,
8619
+ rootDirectoryUrl,
8620
+ urlGraph,
8621
+ scenario: "dev",
8622
+ runtimeCompat,
8623
+ sourcemaps,
8624
+ writeGeneratedFiles,
8625
+ plugins: [...plugins, ...getCorePlugins({
8626
+ rootDirectoryUrl,
8627
+ urlGraph,
8628
+ scenario: "dev",
8629
+ runtimeCompat,
8630
+ urlAnalysis,
8631
+ htmlSupervisor,
8632
+ nodeEsmResolution,
8633
+ fileSystemMagicResolution,
8634
+ transpilation,
8635
+ clientAutoreload,
8636
+ clientFileChangeCallbackList,
8637
+ clientFilesPruneCallbackList
8638
+ }), jsenvPluginExplorer({
8639
+ groups: explorerGroups
8640
+ }) // ...(toolbar ? [jsenvPluginToolbar(toolbar)] : []),
8641
+ ]
8642
+ });
8643
+ const server = await startOmegaServer({
8644
+ logLevel: omegaServerLogLevel,
8645
+ keepProcessAlive,
8646
+ listenAnyIp,
8647
+ port,
8648
+ protocol,
8649
+ http2,
8650
+ certificate,
8651
+ privateKey,
8652
+ rootDirectoryUrl,
8653
+ urlGraph,
8654
+ kitchen,
8655
+ scenario: "dev"
8656
+ });
8657
+ startDevServerTask.done();
8658
+ logger.info(``);
8659
+ Object.keys(server.origins).forEach(key => {
8660
+ logger.info(`- ${server.origins[key]}`);
8661
+ });
8662
+ logger.info(``);
8663
+ server.addEffect(() => {
8664
+ return () => {
8665
+ kitchen.pluginController.callHooks("destroy", {});
8666
+ };
8667
+ });
8668
+ return {
8669
+ origin: server.origin,
8670
+ stop: () => {
8671
+ stopWatchingClientFiles();
8672
+ server.stop();
8673
+ }
8674
+ };
8675
+ };
8676
+
8677
+ const generateCoverageJsonFile = async ({
8678
+ coverage,
8679
+ coverageJsonFileUrl,
8680
+ coverageJsonFileLog,
8681
+ logger
8682
+ }) => {
8683
+ const coverageAsText = JSON.stringify(coverage, null, " ");
8684
+
8685
+ if (coverageJsonFileLog) {
8686
+ logger.info(`-> ${urlToFileSystemPath(coverageJsonFileUrl)} (${byteAsFileSize(Buffer.byteLength(coverageAsText))})`);
8687
+ }
8688
+
8689
+ await writeFile(coverageJsonFileUrl, coverageAsText);
8690
+ };
8691
+
8692
+ const istanbulCoverageMapFromCoverage = coverage => {
8693
+ const {
8694
+ createCoverageMap
8695
+ } = requireFromJsenv("istanbul-lib-coverage");
8696
+ const coverageAdjusted = {};
8697
+ Object.keys(coverage).forEach(key => {
8698
+ coverageAdjusted[key.slice(2)] = { ...coverage[key],
8699
+ path: key.slice(2)
8700
+ };
8701
+ });
8702
+ const coverageMap = createCoverageMap(coverageAdjusted);
8703
+ return coverageMap;
8704
+ };
8705
+
8706
+ const generateCoverageHtmlDirectory = async (coverage, {
8707
+ rootDirectoryUrl,
8708
+ coverageHtmlDirectoryRelativeUrl,
8709
+ coverageSkipEmpty,
8710
+ coverageSkipFull
8711
+ }) => {
8712
+ const libReport = requireFromJsenv("istanbul-lib-report");
8713
+ const reports = requireFromJsenv("istanbul-reports");
8714
+ const context = libReport.createContext({
8715
+ dir: urlToFileSystemPath(rootDirectoryUrl),
8716
+ coverageMap: istanbulCoverageMapFromCoverage(coverage),
8717
+ sourceFinder: path => {
8718
+ return readFileSync(urlToFileSystemPath(resolveUrl(path, rootDirectoryUrl)), "utf8");
8719
+ }
8720
+ });
8721
+ const report = reports.create("html", {
8722
+ skipEmpty: coverageSkipEmpty,
8723
+ skipFull: coverageSkipFull,
8724
+ subdir: coverageHtmlDirectoryRelativeUrl
8725
+ });
8726
+ report.execute(context);
8727
+ };
8728
+
8729
+ const generateCoverageTextLog = (coverage, {
8730
+ coverageSkipEmpty,
8731
+ coverageSkipFull
8732
+ }) => {
8733
+ const libReport = requireFromJsenv("istanbul-lib-report");
8734
+ const reports = requireFromJsenv("istanbul-reports");
8735
+ const context = libReport.createContext({
8736
+ coverageMap: istanbulCoverageMapFromCoverage(coverage)
8737
+ });
8738
+ const report = reports.create("text", {
8739
+ skipEmpty: coverageSkipEmpty,
8740
+ skipFull: coverageSkipFull
8741
+ });
8742
+ report.execute(context);
8743
+ };
8744
+
8745
+ const babelPluginInstrument = (api, {
8746
+ rootDirectoryUrl,
8747
+ useInlineSourceMaps = false,
8748
+ coverageConfig = {
8749
+ "./**/*": true
8750
+ }
8751
+ }) => {
8752
+ const {
8753
+ programVisitor
8754
+ } = requireFromJsenv("istanbul-lib-instrument");
8755
+ const {
8756
+ types
8757
+ } = api;
8758
+ const associations = URL_META.resolveAssociations({
8759
+ cover: coverageConfig
8760
+ }, rootDirectoryUrl);
8761
+
8762
+ const shouldInstrument = url => {
8763
+ return URL_META.applyAssociations({
8764
+ url,
8765
+ associations
8766
+ }).cover;
8767
+ };
8768
+
8769
+ return {
8770
+ name: "transform-instrument",
8771
+ visitor: {
8772
+ Program: {
8773
+ enter(path) {
8774
+ const {
8775
+ file
8776
+ } = this;
8777
+ const {
8778
+ opts
8779
+ } = file;
8780
+
8781
+ if (!opts.sourceFileName) {
8782
+ console.warn(`cannot instrument file when "sourceFileName" option is not set`);
8783
+ return;
8784
+ }
8785
+
8786
+ const fileUrl = fileSystemPathToUrl(opts.sourceFileName);
8787
+
8788
+ if (!shouldInstrument(fileUrl)) {
8789
+ return;
8790
+ }
8791
+
8792
+ this.__dv__ = null;
8793
+ let inputSourceMap;
8794
+
8795
+ if (useInlineSourceMaps) {
8796
+ // https://github.com/istanbuljs/babel-plugin-istanbul/commit/a9e15643d249a2985e4387e4308022053b2cd0ad#diff-1fdf421c05c1140f6d71444ea2b27638R65
8797
+ inputSourceMap = opts.inputSourceMap || file.inputMap ? file.inputMap.sourcemap : null;
8798
+ } else {
8799
+ inputSourceMap = opts.inputSourceMap;
8800
+ }
8801
+
8802
+ this.__dv__ = programVisitor(types, opts.filenameRelative || opts.filename, {
8803
+ coverageVariable: "__coverage__",
8804
+ inputSourceMap
8805
+ });
8806
+
8807
+ this.__dv__.enter(path);
8808
+ },
8809
+
8810
+ exit(path) {
8811
+ if (!this.__dv__) {
8812
+ return;
8813
+ }
8814
+
8815
+ const object = this.__dv__.exit(path); // object got two properties: fileCoverage and sourceMappingURL
8816
+
8817
+
8818
+ this.file.metadata.coverage = object.fileCoverage;
8819
+ }
8820
+
8821
+ }
8822
+ }
8823
+ };
8824
+ };
8825
+
8826
+ const visitNodeV8Directory = async ({
8827
+ logger,
8828
+ signal,
8829
+ NODE_V8_COVERAGE,
8830
+ onV8Coverage,
8831
+ maxMsWaitingForNodeToWriteCoverageFile = 2000
8832
+ }) => {
8833
+ const operation = Abort.startOperation();
8834
+ operation.addAbortSignal(signal);
8835
+
8836
+ const tryReadDirectory = async () => {
8837
+ const dirContent = await readDirectory(NODE_V8_COVERAGE);
8838
+
8839
+ if (dirContent.length > 0) {
8840
+ return dirContent;
8841
+ }
8842
+
8843
+ logger.warn(`v8 coverage directory is empty at ${NODE_V8_COVERAGE}`);
8844
+ return dirContent;
8845
+ };
8846
+
8847
+ try {
8848
+ operation.throwIfAborted();
8849
+ const dirContent = await tryReadDirectory();
8850
+ const coverageDirectoryUrl = assertAndNormalizeDirectoryUrl(NODE_V8_COVERAGE);
8851
+ await dirContent.reduce(async (previous, dirEntry) => {
8852
+ operation.throwIfAborted();
8853
+ await previous;
8854
+ const dirEntryUrl = resolveUrl(dirEntry, coverageDirectoryUrl);
8855
+
8856
+ const tryReadJsonFile = async (timeSpentTrying = 0) => {
8857
+ const fileContent = await readFile(dirEntryUrl, {
8858
+ as: "string"
8859
+ });
8860
+
8861
+ if (fileContent === "") {
8862
+ if (timeSpentTrying < 400) {
8863
+ await new Promise(resolve => setTimeout(resolve, 200));
8864
+ return tryReadJsonFile(timeSpentTrying + 200);
8865
+ }
8866
+
8867
+ console.warn(`Coverage JSON file is empty at ${dirEntryUrl}`);
8868
+ return null;
8869
+ }
8870
+
8871
+ try {
8872
+ const fileAsJson = JSON.parse(fileContent);
8873
+ return fileAsJson;
8874
+ } catch (e) {
8875
+ if (timeSpentTrying < maxMsWaitingForNodeToWriteCoverageFile) {
8876
+ await new Promise(resolve => setTimeout(resolve, 200));
8877
+ return tryReadJsonFile(timeSpentTrying + 200);
8878
+ }
8879
+
8880
+ console.warn(createDetailedMessage(`Error while reading coverage file`, {
8881
+ "error stack": e.stack,
8882
+ "file": dirEntryUrl
8883
+ }));
8884
+ return null;
8885
+ }
8886
+ };
8887
+
8888
+ const fileContent = await tryReadJsonFile();
8889
+
8890
+ if (fileContent) {
8891
+ onV8Coverage(fileContent);
8892
+ }
8893
+ }, Promise.resolve());
8894
+ } finally {
8895
+ await operation.end();
8896
+ }
8897
+ };
8898
+ const filterV8Coverage = (v8Coverage, {
8899
+ urlShouldBeCovered
8900
+ }) => {
8901
+ const v8CoverageFiltered = { ...v8Coverage,
8902
+ result: v8Coverage.result.filter(fileReport => urlShouldBeCovered(fileReport.url))
8903
+ };
8904
+ return v8CoverageFiltered;
8905
+ };
8906
+
8907
+ const composeTwoV8Coverages = (firstV8Coverage, secondV8Coverage) => {
8908
+ if (secondV8Coverage.result.length === 0) {
8909
+ return firstV8Coverage;
8910
+ } // eslint-disable-next-line import/no-unresolved
8911
+
8912
+
8913
+ const {
8914
+ mergeProcessCovs
8915
+ } = requireFromJsenv("@c88/v8-coverage"); // "mergeProcessCovs" do not preserves source-map-cache during the merge
8916
+ // so we store sourcemap cache now
8917
+
8918
+ const sourceMapCache = {};
8919
+
8920
+ const visit = coverageReport => {
8921
+ if (coverageReport["source-map-cache"]) {
8922
+ Object.assign(sourceMapCache, coverageReport["source-map-cache"]);
8923
+ }
8924
+ };
8925
+
8926
+ visit(firstV8Coverage);
8927
+ visit(secondV8Coverage);
8928
+ const v8Coverage = mergeProcessCovs([firstV8Coverage, secondV8Coverage]);
8929
+ v8Coverage["source-map-cache"] = sourceMapCache;
8930
+ return v8Coverage;
8931
+ };
8932
+
8933
+ const composeTwoFileByFileIstanbulCoverages = (firstFileByFileIstanbulCoverage, secondFileByFileIstanbulCoverage) => {
8934
+ const fileByFileIstanbulCoverage = {};
8935
+ Object.keys(firstFileByFileIstanbulCoverage).forEach(key => {
8936
+ fileByFileIstanbulCoverage[key] = firstFileByFileIstanbulCoverage[key];
8937
+ });
8938
+ Object.keys(secondFileByFileIstanbulCoverage).forEach(key => {
8939
+ const firstCoverage = firstFileByFileIstanbulCoverage[key];
8940
+ const secondCoverage = secondFileByFileIstanbulCoverage[key];
8941
+ fileByFileIstanbulCoverage[key] = firstCoverage ? merge(firstCoverage, secondCoverage) : secondCoverage;
8942
+ });
8943
+ return fileByFileIstanbulCoverage;
8944
+ };
8945
+
8946
+ const merge = (firstIstanbulCoverage, secondIstanbulCoverage) => {
8947
+ const {
8948
+ createFileCoverage
8949
+ } = requireFromJsenv("istanbul-lib-coverage");
8950
+ const istanbulFileCoverageObject = createFileCoverage(firstIstanbulCoverage);
8951
+ istanbulFileCoverageObject.merge(secondIstanbulCoverage);
8952
+ const istanbulCoverage = istanbulFileCoverageObject.toJSON();
8953
+ return istanbulCoverage;
8954
+ };
8955
+
8956
+ const v8CoverageToIstanbul = async (v8Coverage, {
8957
+ signal
8958
+ }) => {
8959
+ const operation = Abort.startOperation();
8960
+ operation.addAbortSignal(signal);
8961
+
8962
+ try {
8963
+ const v8ToIstanbul = requireFromJsenv("v8-to-istanbul");
8964
+ const sourcemapCache = v8Coverage["source-map-cache"];
8965
+ let istanbulCoverageComposed = null;
8966
+ await v8Coverage.result.reduce(async (previous, fileV8Coverage) => {
8967
+ operation.throwIfAborted();
8968
+ await previous;
8969
+ const {
8970
+ source
8971
+ } = fileV8Coverage;
8972
+ let sources; // when v8 coverage comes from playwright (chromium) v8Coverage.source is set
8973
+
8974
+ if (typeof source === "string") {
8975
+ sources = {
8976
+ source
8977
+ };
8978
+ } // when v8 coverage comes from Node.js, the source can be read from sourcemapCache
8979
+ else if (sourcemapCache) {
8980
+ sources = sourcesFromSourceMapCache(fileV8Coverage.url, sourcemapCache);
8981
+ }
8982
+
8983
+ const path = urlToFileSystemPath(fileV8Coverage.url);
8984
+ const converter = v8ToIstanbul(path, // wrapperLength is undefined we don't need it
8985
+ // https://github.com/istanbuljs/v8-to-istanbul/blob/2b54bc97c5edf8a37b39a171ec29134ba9bfd532/lib/v8-to-istanbul.js#L27
8986
+ undefined, sources);
8987
+ await converter.load();
8988
+ converter.applyCoverage(fileV8Coverage.functions);
8989
+ const istanbulCoverage = converter.toIstanbul();
8990
+ istanbulCoverageComposed = istanbulCoverageComposed ? composeTwoFileByFileIstanbulCoverages(istanbulCoverageComposed, istanbulCoverage) : istanbulCoverage;
8991
+ }, Promise.resolve());
8992
+
8993
+ if (!istanbulCoverageComposed) {
8994
+ return {};
8995
+ }
8996
+
8997
+ istanbulCoverageComposed = markAsConvertedFromV8(istanbulCoverageComposed);
8998
+ return istanbulCoverageComposed;
8999
+ } finally {
9000
+ await operation.end();
9001
+ }
9002
+ };
9003
+
9004
+ const markAsConvertedFromV8 = fileByFileCoverage => {
9005
+ const fileByFileMarked = {};
9006
+ Object.keys(fileByFileCoverage).forEach(key => {
9007
+ const fileCoverage = fileByFileCoverage[key];
9008
+ fileByFileMarked[key] = { ...fileCoverage,
9009
+ fromV8: true
9010
+ };
9011
+ });
9012
+ return fileByFileMarked;
9013
+ };
9014
+
9015
+ const sourcesFromSourceMapCache = (url, sourceMapCache) => {
9016
+ const sourceMapAndLineLengths = sourceMapCache[url];
9017
+
9018
+ if (!sourceMapAndLineLengths) {
9019
+ return {};
9020
+ }
9021
+
9022
+ const {
9023
+ data,
9024
+ lineLengths
9025
+ } = sourceMapAndLineLengths; // See: https://github.com/nodejs/node/pull/34305
9026
+
9027
+ if (!data) {
9028
+ return undefined;
9029
+ }
9030
+
9031
+ const sources = {
9032
+ sourcemap: data,
9033
+ ...(lineLengths ? {
9034
+ source: sourcesFromLineLengths(lineLengths)
9035
+ } : {})
9036
+ };
9037
+ return sources;
9038
+ };
9039
+
9040
+ const sourcesFromLineLengths = lineLengths => {
9041
+ let source = "";
9042
+ lineLengths.forEach(length => {
9043
+ source += `${"".padEnd(length, ".")}\n`;
9044
+ });
9045
+ return source;
9046
+ };
9047
+
9048
+ const composeV8AndIstanbul = (v8FileByFileCoverage, istanbulFileByFileCoverage, {
9049
+ coverageV8ConflictWarning
9050
+ }) => {
9051
+ const fileByFileCoverage = {};
9052
+ const v8Files = Object.keys(v8FileByFileCoverage);
9053
+ const istanbulFiles = Object.keys(istanbulFileByFileCoverage);
9054
+ v8Files.forEach(key => {
9055
+ fileByFileCoverage[key] = v8FileByFileCoverage[key];
9056
+ });
9057
+ istanbulFiles.forEach(key => {
9058
+ const v8Coverage = v8FileByFileCoverage[key];
9059
+
9060
+ if (v8Coverage) {
9061
+ if (coverageV8ConflictWarning) {
9062
+ console.warn(createDetailedMessage(`Coverage conflict on "${key}", found two coverage that cannot be merged together: v8 and istanbul. The istanbul coverage will be ignored.`, {
9063
+ details: `This happens when a file is executed on a runtime using v8 coverage (node or chromium) and on runtime using istanbul coverage (firefox or webkit)`,
9064
+ suggestion: "You can disable this warning with coverageV8ConflictWarning: false"
9065
+ }));
9066
+ }
9067
+
9068
+ fileByFileCoverage[key] = v8Coverage;
9069
+ } else {
9070
+ fileByFileCoverage[key] = istanbulFileByFileCoverage[key];
9071
+ }
9072
+ });
9073
+ return fileByFileCoverage;
9074
+ };
9075
+
9076
+ const normalizeFileByFileCoveragePaths = (fileByFileCoverage, rootDirectoryUrl) => {
9077
+ const fileByFileNormalized = {};
9078
+ Object.keys(fileByFileCoverage).forEach(key => {
9079
+ const fileCoverage = fileByFileCoverage[key];
9080
+ const {
9081
+ path
9082
+ } = fileCoverage;
9083
+ const url = isFileSystemPath(path) ? fileSystemPathToUrl(path) : new URL(path, rootDirectoryUrl).href;
9084
+ const relativeUrl = urlToRelativeUrl(url, rootDirectoryUrl);
9085
+ fileByFileNormalized[`./${relativeUrl}`] = { ...fileCoverage,
9086
+ path: `./${relativeUrl}`
9087
+ };
9088
+ });
9089
+ return fileByFileNormalized;
9090
+ };
9091
+
9092
+ const listRelativeFileUrlToCover = async ({
9093
+ signal,
9094
+ rootDirectoryUrl,
9095
+ coverageConfig
9096
+ }) => {
9097
+ const matchingFileResultArray = await collectFiles({
9098
+ signal,
9099
+ directoryUrl: rootDirectoryUrl,
9100
+ associations: {
9101
+ cover: coverageConfig
9102
+ },
9103
+ predicate: ({
9104
+ cover
9105
+ }) => cover
9106
+ });
9107
+ return matchingFileResultArray.map(({
9108
+ relativeUrl
9109
+ }) => relativeUrl);
9110
+ };
9111
+
9112
+ const relativeUrlToEmptyCoverage = async (relativeUrl, {
9113
+ signal,
9114
+ rootDirectoryUrl
9115
+ }) => {
9116
+ const operation = Abort.startOperation();
9117
+ operation.addAbortSignal(signal);
9118
+
9119
+ try {
9120
+ const fileUrl = resolveUrl(relativeUrl, rootDirectoryUrl);
9121
+ const content = await readFile(fileUrl, {
9122
+ as: "string"
9123
+ });
9124
+ operation.throwIfAborted();
9125
+ const {
9126
+ metadata
9127
+ } = await applyBabelPlugins({
9128
+ babelPlugins: [[babelPluginInstrument, {
9129
+ rootDirectoryUrl
9130
+ }]],
9131
+ urlInfo: {
9132
+ originalUrl: fileUrl,
9133
+ content
9134
+ }
9135
+ });
9136
+ const {
9137
+ coverage
9138
+ } = metadata;
9139
+
9140
+ if (!coverage) {
9141
+ throw new Error(`missing coverage for file`);
9142
+ } // https://github.com/gotwarlost/istanbul/blob/bc84c315271a5dd4d39bcefc5925cfb61a3d174a/lib/command/common/run-with-cover.js#L229
9143
+
9144
+
9145
+ Object.keys(coverage.s).forEach(function (key) {
9146
+ coverage.s[key] = 0;
9147
+ });
9148
+ return coverage;
9149
+ } catch (e) {
9150
+ if (e && e.code === "PARSE_ERROR") {
9151
+ // return an empty coverage for that file when
9152
+ // it contains a syntax error
9153
+ return createEmptyCoverage(relativeUrl);
9154
+ }
9155
+
9156
+ throw e;
9157
+ } finally {
9158
+ await operation.end();
9159
+ }
9160
+ };
9161
+
9162
+ const createEmptyCoverage = relativeUrl => {
9163
+ const {
9164
+ createFileCoverage
9165
+ } = requireFromJsenv("istanbul-lib-coverage");
9166
+ return createFileCoverage(relativeUrl).toJSON();
9167
+ };
9168
+
9169
+ const getMissingFileByFileCoverage = async ({
9170
+ signal,
9171
+ rootDirectoryUrl,
9172
+ coverageConfig,
9173
+ fileByFileCoverage
8355
9174
  }) => {
8356
- const logger = createLogger({
8357
- logLevel
8358
- });
8359
- rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl);
8360
- const reloadableProcess = await initReloadableProcess({
9175
+ const relativeUrlsToCover = await listRelativeFileUrlToCover({
8361
9176
  signal,
8362
- handleSIGINT,
8363
- ...(devServerAutoreload ? {
8364
- enabled: true,
8365
- logLevel: "warn",
8366
- fileToRestart: devServerMainFile
8367
- } : {
8368
- enabled: false
8369
- })
9177
+ rootDirectoryUrl,
9178
+ coverageConfig
8370
9179
  });
9180
+ const relativeUrlsMissing = relativeUrlsToCover.filter(relativeUrlToCover => Object.keys(fileByFileCoverage).every(key => {
9181
+ return key !== `./${relativeUrlToCover}`;
9182
+ }));
9183
+ const operation = Abort.startOperation();
9184
+ operation.addAbortSignal(signal);
9185
+ const missingFileByFileCoverage = {};
9186
+ await relativeUrlsMissing.reduce(async (previous, relativeUrlMissing) => {
9187
+ operation.throwIfAborted();
9188
+ await previous;
9189
+ await operation.withSignal(async signal => {
9190
+ const emptyCoverage = await relativeUrlToEmptyCoverage(relativeUrlMissing, {
9191
+ signal,
9192
+ rootDirectoryUrl
9193
+ });
9194
+ missingFileByFileCoverage[`./${relativeUrlMissing}`] = emptyCoverage;
9195
+ });
9196
+ }, Promise.resolve());
9197
+ return missingFileByFileCoverage;
9198
+ };
8371
9199
 
8372
- if (reloadableProcess.isPrimary) {
8373
- const devServerFileChangeCallback = ({
8374
- relativeUrl,
8375
- event
9200
+ const reportToCoverage = async (report, {
9201
+ signal,
9202
+ logger,
9203
+ rootDirectoryUrl,
9204
+ coverageConfig,
9205
+ coverageIncludeMissing,
9206
+ urlShouldBeCovered,
9207
+ coverageForceIstanbul,
9208
+ coverageV8ConflictWarning
9209
+ }) => {
9210
+ // collect v8 and istanbul coverage from executions
9211
+ let {
9212
+ v8Coverage,
9213
+ fileByFileIstanbulCoverage
9214
+ } = await getCoverageFromReport({
9215
+ signal,
9216
+ report,
9217
+ onMissing: ({
9218
+ file,
9219
+ executionResult,
9220
+ executionName
8376
9221
  }) => {
8377
- const url = new URL(relativeUrl, rootDirectoryUrl).href;
8378
-
8379
- if (devServerAutoreload) {
8380
- logger.info(`file ${event} ${url} -> restarting server...`);
8381
- reloadableProcess.reload();
9222
+ // several reasons not to have coverage here:
9223
+ // 1. the file we executed did not import an instrumented file.
9224
+ // - a test file without import
9225
+ // - a test file importing only file excluded from coverage
9226
+ // - a coverDescription badly configured so that we don't realize
9227
+ // a file should be covered
9228
+ // 2. the file we wanted to executed timedout
9229
+ // - infinite loop
9230
+ // - too extensive operation
9231
+ // - a badly configured or too low allocatedMs for that execution.
9232
+ // 3. the file we wanted to execute contains syntax-error
9233
+ // in any scenario we are fine because
9234
+ // coverDescription will generate empty coverage for files
9235
+ // that were suppose to be coverage but were not.
9236
+ if (executionResult.status === "completed" && executionResult.runtimeName !== "node" && !process.env.NODE_V8_COVERAGE) {
9237
+ logger.warn(`No execution.coverageFileUrl from execution named "${executionName}" of ${file}`);
8382
9238
  }
8383
- };
9239
+ }
9240
+ });
8384
9241
 
8385
- const unregisterDevServerFilesWatcher = registerDirectoryLifecycle(rootDirectoryUrl, {
8386
- watchPatterns: {
8387
- [devServerMainFile]: true,
8388
- ...devServerFiles
8389
- },
8390
- cooldownBetweenFileEvents,
8391
- keepProcessAlive: false,
8392
- recursive: true,
8393
- added: ({
8394
- relativeUrl
8395
- }) => {
8396
- devServerFileChangeCallback({
8397
- relativeUrl,
8398
- event: "added"
8399
- });
8400
- },
8401
- updated: ({
8402
- relativeUrl
8403
- }) => {
8404
- devServerFileChangeCallback({
8405
- relativeUrl,
8406
- event: "modified"
8407
- });
8408
- },
8409
- removed: ({
8410
- relativeUrl
8411
- }) => {
8412
- devServerFileChangeCallback({
8413
- relativeUrl,
8414
- event: "removed"
9242
+ if (!coverageForceIstanbul && process.env.NODE_V8_COVERAGE) {
9243
+ await visitNodeV8Directory({
9244
+ logger,
9245
+ signal,
9246
+ NODE_V8_COVERAGE: process.env.NODE_V8_COVERAGE,
9247
+ onV8Coverage: nodeV8Coverage => {
9248
+ const nodeV8CoverageLight = filterV8Coverage(nodeV8Coverage, {
9249
+ urlShouldBeCovered
8415
9250
  });
9251
+ v8Coverage = v8Coverage ? composeTwoV8Coverages(v8Coverage, nodeV8CoverageLight) : nodeV8CoverageLight;
8416
9252
  }
8417
9253
  });
8418
- signal.addEventListener("abort", () => {
8419
- unregisterDevServerFilesWatcher();
8420
- });
8421
- return {
8422
- origin: `${protocol}://127.0.0.1:${port}`,
8423
- stop: () => {
8424
- unregisterDevServerFilesWatcher();
8425
- reloadableProcess.stop();
8426
- }
8427
- };
8428
- }
9254
+ } // try to merge v8 with istanbul, if any
8429
9255
 
8430
- const startDevServerTask = createTaskLog("start dev server", {
8431
- disabled: !loggerToLevels(logger).info
8432
- });
8433
- const clientFileChangeCallbackList = [];
8434
- const clientFilesPruneCallbackList = [];
8435
9256
 
8436
- const clientFileChangeCallback = ({
8437
- relativeUrl,
8438
- event
8439
- }) => {
8440
- const url = new URL(relativeUrl, rootDirectoryUrl).href;
8441
- clientFileChangeCallbackList.forEach(callback => {
8442
- callback({
8443
- url,
8444
- event
8445
- });
9257
+ let fileByFileCoverage;
9258
+
9259
+ if (v8Coverage) {
9260
+ let v8FileByFileCoverage = await v8CoverageToIstanbul(v8Coverage, {
9261
+ signal
8446
9262
  });
8447
- };
9263
+ v8FileByFileCoverage = normalizeFileByFileCoveragePaths(v8FileByFileCoverage, rootDirectoryUrl);
8448
9264
 
8449
- const stopWatchingClientFiles = registerDirectoryLifecycle(rootDirectoryUrl, {
8450
- watchPatterns: clientFiles,
8451
- cooldownBetweenFileEvents,
8452
- keepProcessAlive: false,
8453
- recursive: true,
8454
- added: ({
8455
- relativeUrl
8456
- }) => {
8457
- clientFileChangeCallback({
8458
- event: "added",
8459
- relativeUrl
8460
- });
8461
- },
8462
- updated: ({
8463
- relativeUrl
8464
- }) => {
8465
- clientFileChangeCallback({
8466
- event: "modified",
8467
- relativeUrl
8468
- });
8469
- },
8470
- removed: ({
8471
- relativeUrl
8472
- }) => {
8473
- clientFileChangeCallback({
8474
- event: "removed",
8475
- relativeUrl
9265
+ if (fileByFileIstanbulCoverage) {
9266
+ fileByFileIstanbulCoverage = normalizeFileByFileCoveragePaths(fileByFileIstanbulCoverage, rootDirectoryUrl);
9267
+ fileByFileCoverage = composeV8AndIstanbul(v8FileByFileCoverage, fileByFileIstanbulCoverage, {
9268
+ coverageV8ConflictWarning
8476
9269
  });
9270
+ } else {
9271
+ fileByFileCoverage = v8FileByFileCoverage;
8477
9272
  }
8478
- });
8479
- const urlGraph = createUrlGraph({
8480
- clientFileChangeCallbackList,
8481
- clientFilesPruneCallbackList
8482
- });
8483
- const kitchen = createKitchen({
8484
- signal,
8485
- logger,
8486
- rootDirectoryUrl,
8487
- urlGraph,
8488
- scenario: "dev",
8489
- runtimeCompat,
8490
- sourcemaps,
8491
- writeGeneratedFiles,
8492
- plugins: [...plugins, ...getCorePlugins({
9273
+ } // get istanbul only
9274
+ else if (fileByFileIstanbulCoverage) {
9275
+ fileByFileCoverage = normalizeFileByFileCoveragePaths(fileByFileIstanbulCoverage, rootDirectoryUrl);
9276
+ } // no coverage found in execution (or zero file where executed)
9277
+ else {
9278
+ fileByFileCoverage = {};
9279
+ } // now add coverage for file not covered
9280
+
9281
+
9282
+ if (coverageIncludeMissing) {
9283
+ const missingFileByFileCoverage = await getMissingFileByFileCoverage({
9284
+ signal,
8493
9285
  rootDirectoryUrl,
8494
- urlGraph,
8495
- scenario: "dev",
8496
- runtimeCompat,
8497
- urlAnalysis,
8498
- htmlSupervisor,
8499
- nodeEsmResolution,
8500
- fileSystemMagicResolution,
8501
- transpilation,
8502
- clientAutoreload,
8503
- clientFileChangeCallbackList,
8504
- clientFilesPruneCallbackList
8505
- }), jsenvPluginExplorer({
8506
- groups: explorerGroups
8507
- }) // ...(toolbar ? [jsenvPluginToolbar(toolbar)] : []),
8508
- ]
8509
- });
8510
- const server = await startOmegaServer({
8511
- logLevel: omegaServerLogLevel,
8512
- keepProcessAlive,
8513
- listenAnyIp,
8514
- port,
8515
- protocol,
8516
- http2,
8517
- certificate,
8518
- privateKey,
8519
- rootDirectoryUrl,
8520
- urlGraph,
8521
- kitchen,
8522
- scenario: "dev"
8523
- });
8524
- startDevServerTask.done();
8525
- logger.info(``);
8526
- Object.keys(server.origins).forEach(key => {
8527
- logger.info(`- ${server.origins[key]}`);
8528
- });
8529
- logger.info(``);
8530
- server.addEffect(() => {
8531
- return () => {
8532
- kitchen.pluginController.callHooks("destroy", {});
9286
+ coverageConfig,
9287
+ fileByFileCoverage
9288
+ });
9289
+ Object.assign(fileByFileCoverage, normalizeFileByFileCoveragePaths(missingFileByFileCoverage, rootDirectoryUrl));
9290
+ }
9291
+
9292
+ return fileByFileCoverage;
9293
+ };
9294
+
9295
+ const getCoverageFromReport = async ({
9296
+ signal,
9297
+ report,
9298
+ onMissing
9299
+ }) => {
9300
+ const operation = Abort.startOperation();
9301
+ operation.addAbortSignal(signal);
9302
+
9303
+ try {
9304
+ let v8Coverage;
9305
+ let fileByFileIstanbulCoverage; // collect v8 and istanbul coverage from executions
9306
+
9307
+ await Object.keys(report).reduce(async (previous, file) => {
9308
+ operation.throwIfAborted();
9309
+ await previous;
9310
+ const executionResultForFile = report[file];
9311
+ await Object.keys(executionResultForFile).reduce(async (previous, executionName) => {
9312
+ operation.throwIfAborted();
9313
+ await previous;
9314
+ const executionResultForFileOnRuntime = executionResultForFile[executionName];
9315
+ const {
9316
+ coverageFileUrl
9317
+ } = executionResultForFileOnRuntime;
9318
+
9319
+ if (!coverageFileUrl) {
9320
+ onMissing({
9321
+ executionName,
9322
+ file,
9323
+ executionResult: executionResultForFileOnRuntime
9324
+ });
9325
+ return;
9326
+ }
9327
+
9328
+ const executionCoverage = await readFile(coverageFileUrl, {
9329
+ as: "json"
9330
+ });
9331
+
9332
+ if (isV8Coverage(executionCoverage)) {
9333
+ v8Coverage = v8Coverage ? composeTwoV8Coverages(v8Coverage, executionCoverage) : executionCoverage;
9334
+ } else {
9335
+ fileByFileIstanbulCoverage = fileByFileIstanbulCoverage ? composeTwoFileByFileIstanbulCoverages(fileByFileIstanbulCoverage, executionCoverage) : executionCoverage;
9336
+ }
9337
+ }, Promise.resolve());
9338
+ }, Promise.resolve());
9339
+ return {
9340
+ v8Coverage,
9341
+ fileByFileIstanbulCoverage
8533
9342
  };
8534
- });
8535
- return {
8536
- origin: server.origin,
8537
- stop: () => {
8538
- stopWatchingClientFiles();
8539
- server.stop();
8540
- }
8541
- };
9343
+ } finally {
9344
+ await operation.end();
9345
+ }
8542
9346
  };
8543
9347
 
9348
+ const isV8Coverage = coverage => Boolean(coverage.result);
9349
+
8544
9350
  const run = async ({
8545
9351
  signal = new AbortController().signal,
8546
9352
  logger,
@@ -11540,7 +12346,7 @@ const injectors = {
11540
12346
  const htmlAst = parseHtmlString(urlInfo.content, {
11541
12347
  storeOriginalPositions: false
11542
12348
  });
11543
- injectScriptAsEarlyAsPossible(htmlAst, createHtmlNode({
12349
+ injectScriptNodeAsEarlyAsPossible(htmlAst, createHtmlNode({
11544
12350
  "tagName": "script",
11545
12351
  "textContent": generateClientCodeForVersionMappings(versionMappings, {
11546
12352
  globalName: "window"
@@ -11668,15 +12474,12 @@ const resyncRessourceHints = async ({
11668
12474
  });
11669
12475
  const actions = [];
11670
12476
 
11671
- const visitLinkWithHref = (linkNode, hrefAttribute) => {
11672
- const href = hrefAttribute.value;
11673
-
12477
+ const visitLinkWithHref = (linkNode, href) => {
11674
12478
  if (!href || href.startsWith("data:")) {
11675
12479
  return;
11676
12480
  }
11677
12481
 
11678
- const relAttribute = getHtmlNodeAttributeByName(linkNode, "rel");
11679
- const rel = relAttribute ? relAttribute.value : undefined;
12482
+ const rel = getHtmlNodeAttribute(linkNode, "rel");
11680
12483
  const isRessourceHint = ["preconnect", "dns-prefetch", "prefetch", "preload", "modulepreload"].includes(rel);
11681
12484
 
11682
12485
  if (!isRessourceHint) {
@@ -11720,22 +12523,20 @@ const resyncRessourceHints = async ({
11720
12523
  }
11721
12524
 
11722
12525
  actions.push(() => {
11723
- hrefAttribute.value = urlInfo.data.buildUrlSpecifier;
12526
+ setHtmlNodeAttributes(linkNode, {
12527
+ href: urlInfo.data.buildUrlSpecifier
12528
+ });
11724
12529
  });
11725
12530
  };
11726
12531
 
11727
- visitHtmlAst(htmlAst, node => {
11728
- if (node.nodeName !== "link") {
11729
- return;
11730
- }
12532
+ visitHtmlNodes(htmlAst, {
12533
+ link: node => {
12534
+ const href = getHtmlNodeAttribute(node, "href");
11731
12535
 
11732
- const hrefAttribute = getHtmlNodeAttributeByName(node, "href");
11733
-
11734
- if (!hrefAttribute) {
11735
- return;
12536
+ if (href !== undefined) {
12537
+ visitLinkWithHref(node, href);
12538
+ }
11736
12539
  }
11737
-
11738
- visitLinkWithHref(node, hrefAttribute);
11739
12540
  });
11740
12541
 
11741
12542
  if (actions.length) {
@@ -12300,7 +13101,7 @@ build ${entryPointKeys.length} entry points`);
12300
13101
 
12301
13102
  if (reference.type === "sourcemap_comment") {
12302
13103
  // inherit parent build url
12303
- return generateSourcemapUrl(reference.parentUrl);
13104
+ return generateSourcemapFileUrl(reference.parentUrl);
12304
13105
  } // files generated during the final graph:
12305
13106
  // - sourcemaps
12306
13107
  // const finalUrlInfo = finalGraph.getUrlInfo(url)
@@ -13026,6 +13827,7 @@ const startBuildServer = async ({
13026
13827
  ip,
13027
13828
  port = 9779,
13028
13829
  services = {},
13830
+ keepProcessAlive = true,
13029
13831
  rootDirectoryUrl,
13030
13832
  buildDirectoryUrl,
13031
13833
  mainBuildFileUrl = "/index.html",
@@ -13036,9 +13838,9 @@ const startBuildServer = async ({
13036
13838
  buildServerMainFile = getCallerPosition().url,
13037
13839
  // force disable server autoreload when this code is executed:
13038
13840
  // - inside a forked child process
13039
- // - inside a worker thread
13040
- // (because node cluster won't work)
13041
- buildServerAutoreload = typeof process.send !== "function" && !parentPort && !process.env.VSCODE_INSPECTOR_OPTIONS,
13841
+ // - debugged by vscode
13842
+ // otherwise we get net:ERR_CONNECTION_REFUSED
13843
+ buildServerAutoreload = typeof process.send !== "function" && !process.env.VSCODE_INSPECTOR_OPTIONS,
13042
13844
  cooldownBetweenFileEvents
13043
13845
  }) => {
13044
13846
  const logger = createLogger({
@@ -13046,19 +13848,26 @@ const startBuildServer = async ({
13046
13848
  });
13047
13849
  rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl);
13048
13850
  buildDirectoryUrl = assertAndNormalizeDirectoryUrl(buildDirectoryUrl);
13049
- const reloadableProcess = await initReloadableProcess({
13050
- signal,
13051
- handleSIGINT,
13052
- ...(buildServerAutoreload ? {
13053
- enabled: true,
13054
- logLevel: "info",
13055
- fileToRestart: buildServerMainFile
13056
- } : {
13057
- enabled: false
13058
- })
13059
- });
13851
+ const operation = Abort.startOperation();
13852
+ operation.addAbortSignal(signal);
13853
+
13854
+ if (handleSIGINT) {
13855
+ operation.addAbortSource(abort => {
13856
+ return raceProcessTeardownEvents({
13857
+ SIGINT: true
13858
+ }, abort);
13859
+ });
13860
+ }
13861
+
13862
+ if (port === 0) {
13863
+ port = await findFreePort(port, {
13864
+ signal: operation.signal
13865
+ });
13866
+ }
13060
13867
 
13061
- if (reloadableProcess.isPrimary) {
13868
+ const reloadableWorker = createReloadableWorker(buildServerMainFile);
13869
+
13870
+ if (buildServerAutoreload && reloadableWorker.isPrimary) {
13062
13871
  const buildServerFileChangeCallback = ({
13063
13872
  relativeUrl,
13064
13873
  event
@@ -13067,7 +13876,7 @@ const startBuildServer = async ({
13067
13876
 
13068
13877
  if (buildServerAutoreload) {
13069
13878
  logger.info(`file ${event} ${url} -> restarting server...`);
13070
- reloadableProcess.reload();
13879
+ reloadableWorker.reload();
13071
13880
  }
13072
13881
  };
13073
13882
 
@@ -13104,19 +13913,25 @@ const startBuildServer = async ({
13104
13913
  });
13105
13914
  }
13106
13915
  });
13107
- signal.addEventListener("abort", () => {
13916
+ operation.addAbortCallback(() => {
13108
13917
  stopWatchingBuildServerFiles();
13918
+ reloadableWorker.terminate();
13109
13919
  });
13920
+ const worker = await reloadableWorker.load();
13921
+
13922
+ if (!keepProcessAlive) {
13923
+ worker.unref();
13924
+ }
13925
+
13110
13926
  return {
13111
13927
  origin: `${protocol}://127.0.0.1:${port}`,
13112
13928
  stop: () => {
13113
13929
  stopWatchingBuildServerFiles();
13114
- reloadableProcess.stop();
13930
+ reloadableWorker.terminate();
13115
13931
  }
13116
13932
  };
13117
13933
  }
13118
13934
 
13119
- signal = reloadableProcess.signal;
13120
13935
  const startBuildServerTask = createTaskLog("start build server", {
13121
13936
  disabled: !loggerToLevels(logger).info
13122
13937
  });
@@ -13365,7 +14180,7 @@ const globalInjectorOnHtml = async (urlInfo, globals) => {
13365
14180
  globals,
13366
14181
  isWebWorker: false
13367
14182
  });
13368
- injectScriptAsEarlyAsPossible(htmlAst, createHtmlNode({
14183
+ injectScriptNodeAsEarlyAsPossible(htmlAst, createHtmlNode({
13369
14184
  "tagName": "script",
13370
14185
  "textContent": clientCode,
13371
14186
  "injected-by": "jsenv:inject_globals"