@jsenv/core 27.0.0-alpha.46 → 27.0.0-alpha.49

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.
@@ -69,62 +69,135 @@ export const installHtmlSupervisor = ({ logs, measurePerf }) => {
69
69
  }
70
70
  }
71
71
 
72
- __html_supervisor__.addExecution = async ({
73
- type,
72
+ const performExecution = async ({
74
73
  src,
74
+ type,
75
75
  currentScript,
76
- promise,
76
+ execute,
77
+ // https://developer.mozilla.org/en-US/docs/web/html/element/script
77
78
  }) => {
78
79
  if (logs) {
79
80
  console.group(`[jsenv] loading ${type} ${src}`)
80
81
  }
81
82
  onExecutionStart(src)
82
- promise.then(
83
- (namespace) => {
84
- const executionResult = {
85
- status: "completed",
86
- namespace,
87
- coverage: window.__coverage__,
88
- }
89
- onExecutionSettled(src, executionResult)
90
- if (logs) {
91
- console.log(`${type} load ended`)
92
- console.groupEnd()
93
- }
94
- },
95
- async (e) => {
96
- let error = e
97
- const executionResult = {
98
- status: "errored",
99
- coverage: window.__coverage__,
100
- }
101
- let errorExposureInConsole = true
102
- if (e.name === "SyntaxError") {
103
- // errorExposureInConsole = false
104
- }
105
- if (errorTransformer) {
106
- try {
107
- error = await errorTransformer(e)
108
- } catch (e) {}
109
- }
110
- executionResult.error = error
111
- onExecutionSettled(src, executionResult)
112
- onExecutionError(executionResult, {
113
- currentScript,
114
- })
115
- if (errorExposureInConsole) {
116
- if (typeof window.reportError === "function") {
117
- window.reportError(error)
118
- } else {
119
- console.error(error)
120
- }
121
- }
122
- if (logs) {
123
- console.groupEnd()
83
+ let completed
84
+ let result
85
+ let error
86
+ try {
87
+ result = await execute()
88
+ completed = true
89
+ } catch (e) {
90
+ completed = false
91
+ error = e
92
+ }
93
+ if (completed) {
94
+ const executionResult = {
95
+ status: "completed",
96
+ namespace: result,
97
+ coverage: window.__coverage__,
98
+ }
99
+ onExecutionSettled(src, executionResult)
100
+ if (logs) {
101
+ console.log(`${type} load ended`)
102
+ console.groupEnd()
103
+ }
104
+ return
105
+ }
106
+ const executionResult = {
107
+ status: "errored",
108
+ coverage: window.__coverage__,
109
+ }
110
+ let errorExposureInConsole = true
111
+ if (error.name === "SyntaxError") {
112
+ // errorExposureInConsole = false
113
+ }
114
+ if (errorTransformer) {
115
+ try {
116
+ error = await errorTransformer(error)
117
+ } catch (e) {}
118
+ }
119
+ executionResult.error = error
120
+ onExecutionSettled(src, executionResult)
121
+ onExecutionError(executionResult, {
122
+ currentScript,
123
+ })
124
+ if (errorExposureInConsole) {
125
+ if (typeof window.reportError === "function") {
126
+ window.reportError(error)
127
+ } else {
128
+ console.error(error)
129
+ }
130
+ }
131
+ if (logs) {
132
+ console.groupEnd()
133
+ }
134
+ }
135
+
136
+ const queue = []
137
+ let previousDonePromise = null
138
+ const dequeue = () => {
139
+ const next = queue.shift()
140
+ if (next) {
141
+ __html_supervisor__.addScriptToExecute(next)
142
+ } else {
143
+ const nextDefered = deferQueue.shift()
144
+ if (nextDefered) {
145
+ __html_supervisor__.addScriptToExecute(nextDefered)
146
+ }
147
+ }
148
+ }
149
+ const deferQueue = []
150
+ let previousDeferDonePromise = null
151
+ __html_supervisor__.addScriptToExecute = async (scriptToExecute) => {
152
+ if (scriptToExecute.async) {
153
+ performExecution(scriptToExecute)
154
+ return
155
+ }
156
+ const useDeferQueue =
157
+ scriptToExecute.defer || scriptToExecute.type === "js_module"
158
+ if (useDeferQueue) {
159
+ if (document.readyState !== "interactive") {
160
+ deferQueue.push(scriptToExecute)
161
+ return
162
+ }
163
+ if (previousDonePromise) {
164
+ // defer must wait for the regular script to be done
165
+ deferQueue.push(scriptToExecute)
166
+ return
167
+ }
168
+ if (previousDeferDonePromise) {
169
+ deferQueue.push(scriptToExecute)
170
+ return
171
+ }
172
+ previousDeferDonePromise = performExecution(scriptToExecute)
173
+ await previousDeferDonePromise
174
+ previousDeferDonePromise = null
175
+ dequeue()
176
+ return
177
+ }
178
+ if (previousDonePromise) {
179
+ queue.push(scriptToExecute)
180
+ return
181
+ }
182
+ previousDonePromise = performExecution(scriptToExecute)
183
+ await previousDonePromise
184
+ previousDonePromise = null
185
+ dequeue()
186
+ }
187
+ if (
188
+ document.readyState !== "intractive" &&
189
+ document.readyState !== "complete"
190
+ ) {
191
+ document.addEventListener("readystatechange", () => {
192
+ if (document.readyState === "interactive") {
193
+ const nextDefered = deferQueue.shift()
194
+ if (nextDefered) {
195
+ __html_supervisor__.addScriptToExecute(nextDefered)
124
196
  }
125
- },
126
- )
197
+ }
198
+ })
127
199
  }
200
+
128
201
  __html_supervisor__.collectScriptResults = async () => {
129
202
  collectCalled = true
130
203
  if (pendingExecutionCount === 0) {
@@ -150,20 +223,20 @@ export const installHtmlSupervisor = ({ logs, measurePerf }) => {
150
223
  scriptExecutionResults,
151
224
  }
152
225
  }
226
+
227
+ const { scriptsToExecute } = __html_supervisor__
228
+ const copy = scriptsToExecute.slice()
229
+ scriptsToExecute.length = 0
230
+ copy.forEach((scriptToExecute) => {
231
+ __html_supervisor__.addScriptToExecute(scriptToExecute)
232
+ })
153
233
  }
154
234
 
155
- export const superviseScriptTypeModule = ({ src }) => {
156
- __html_supervisor__.addExecution({
157
- type: "js_module",
158
- currentScript: null,
159
- improveErrorWithFetch: true,
235
+ export const superviseScriptTypeModule = ({ src, isInline }) => {
236
+ __html_supervisor__.addScriptToExecute({
160
237
  src,
161
- promise: import(new URL(src, document.location.href).href),
238
+ type: "js_module",
239
+ isInline,
240
+ execute: () => import(new URL(src, document.location.href).href),
162
241
  })
163
242
  }
164
-
165
- const { executions } = __html_supervisor__
166
- executions.forEach((execution) => {
167
- __html_supervisor__.addExecution(execution)
168
- })
169
- executions.length = 0
@@ -1,62 +1,61 @@
1
1
  window.__html_supervisor__ = {
2
2
  // "html_supervisor_installer.js" will implement
3
- // - "addExecution"
4
- // - "collectScriptResults"
3
+ // - "addScriptToExecute"
5
4
  // - "superviseScriptTypeModule"
6
- // and take all executions in "executions" and implement their supervision
7
- executions: [],
8
- addExecution: (execution) => {
9
- window.__html_supervisor__.executions.push(execution)
10
- },
11
- collectScriptResults: () => {
12
- throw new Error("htmlSupervisor not installed")
13
- },
14
- superviseScriptTypeModule: () => {
15
- throw new Error("htmlSupervisor not installed")
5
+ // - "collectScriptResults"
6
+ // and take all executions in "scriptsToExecute" and implement their supervision
7
+ scriptsToExecute: [],
8
+ addScriptToExecute: (scriptToExecute) => {
9
+ window.__html_supervisor__.scriptsToExecute.push(scriptToExecute)
16
10
  },
17
- superviseScript: ({ src, crossorigin, integrity }) => {
18
- window.__html_supervisor__.addExecution({
11
+ superviseScript: ({ src, isInline, crossorigin, integrity }) => {
12
+ window.__html_supervisor__.addScriptToExecute({
13
+ src,
19
14
  type: "js_classic",
20
- improveErrorWithFetch: true,
15
+ isInline,
21
16
  currentScript: document.currentScript,
22
- src,
23
- promise: new Promise((resolve, reject) => {
24
- const script = document.createElement("script")
25
- if (crossorigin) {
26
- script.crossorigin = crossorigin
27
- }
28
- if (integrity) {
29
- script.integrity = integrity
30
- }
31
- script.src = src
32
- const scriptUrl = new URL(src, window.location).href
33
- let lastWindowErrorUrl
34
- let lastWindowError
35
- const windowErrorCallback = (e) => {
36
- lastWindowErrorUrl = e.filename
37
- lastWindowError = e.error
38
- }
39
- const cleanup = () => {
40
- document.body.removeChild(script)
41
- window.removeEventListener("error", windowErrorCallback)
42
- }
43
- window.addEventListener("error", windowErrorCallback)
44
- script.addEventListener("error", () => {
45
- cleanup()
46
- reject(src)
47
- })
48
- script.addEventListener("load", () => {
49
- cleanup()
50
- if (lastWindowErrorUrl === scriptUrl) {
51
- reject(lastWindowError)
52
- } else {
53
- resolve()
17
+ execute: () => {
18
+ return new Promise((resolve, reject) => {
19
+ const script = document.createElement("script")
20
+ if (crossorigin) {
21
+ script.crossorigin = crossorigin
22
+ }
23
+ if (integrity) {
24
+ script.integrity = integrity
25
+ }
26
+ script.src = src
27
+ const scriptUrl = new URL(src, window.location).href
28
+ let lastWindowErrorUrl
29
+ let lastWindowError
30
+ const windowErrorCallback = (e) => {
31
+ lastWindowErrorUrl = e.filename
32
+ lastWindowError = e.error
54
33
  }
34
+ const cleanup = () => {
35
+ document.body.removeChild(script)
36
+ window.removeEventListener("error", windowErrorCallback)
37
+ }
38
+ window.addEventListener("error", windowErrorCallback)
39
+ script.addEventListener("error", () => {
40
+ cleanup()
41
+ reject(src)
42
+ })
43
+ script.addEventListener("load", () => {
44
+ cleanup()
45
+ if (lastWindowErrorUrl === scriptUrl) {
46
+ reject(lastWindowError)
47
+ } else {
48
+ resolve()
49
+ }
50
+ })
51
+ document.body.appendChild(script)
55
52
  })
56
- document.body.appendChild(script)
57
- }),
53
+ },
58
54
  })
59
55
  },
56
+ superviseScriptTypeModule: () => {
57
+ throw new Error("htmlSupervisor not installed")
58
+ },
60
59
  getScriptExecutionResults: () => {
61
60
  // wait for page to load before collecting script execution results
62
61
  const htmlReadyPromise = new Promise((resolve) => {
@@ -74,4 +73,7 @@ window.__html_supervisor__ = {
74
73
  return window.__html_supervisor__.collectScriptResults()
75
74
  })
76
75
  },
76
+ collectScriptResults: () => {
77
+ throw new Error("htmlSupervisor not installed")
78
+ },
77
79
  }
@@ -68,9 +68,9 @@ export const jsenvPluginHtmlSupervisor = ({
68
68
  expectedType: { classic: "js_classic", module: "js_module" }[
69
69
  scriptCategory
70
70
  ],
71
- line: line - 1,
72
- column,
73
71
  isOriginalPosition: isOriginal,
72
+ specifierLine: line - 1,
73
+ specifierColumn: column,
74
74
  specifier: inlineScriptUrl,
75
75
  contentType: "text/javascript",
76
76
  content: textNode.value,
@@ -99,11 +99,17 @@ export const jsenvPluginHtmlSupervisor = ({
99
99
  const crossorigin = crossoriginAttribute
100
100
  ? crossoriginAttribute.value
101
101
  : undefined
102
+ const deferAttribute = getHtmlNodeAttributeByName(node, "crossorigin")
103
+ const defer = deferAttribute ? deferAttribute.value : undefined
104
+ const asyncAttribute = getHtmlNodeAttributeByName(node, "crossorigin")
105
+ const async = asyncAttribute ? asyncAttribute.value : undefined
102
106
  removeHtmlNodeAttributeByName(node, "src")
103
107
  scriptsToSupervise.push({
104
108
  node,
105
109
  type: scriptCategory,
106
110
  src: srcAttribute.value,
111
+ defer,
112
+ async,
107
113
  integrity,
108
114
  crossorigin,
109
115
  })
@@ -183,11 +189,23 @@ export const jsenvPluginHtmlSupervisor = ({
183
189
  }),
184
190
  )
185
191
  scriptsToSupervise.forEach(
186
- ({ node, isInline, type, src, integrity, crossorigin }) => {
192
+ ({
193
+ node,
194
+ isInline,
195
+ type,
196
+ src,
197
+ defer,
198
+ async,
199
+ integrity,
200
+ crossorigin,
201
+ }) => {
187
202
  setHtmlNodeGeneratedText(node, {
188
203
  generatedText: generateCodeToSuperviseScript({
189
204
  type,
190
205
  src,
206
+ isInline,
207
+ defer,
208
+ async,
191
209
  integrity,
192
210
  crossorigin,
193
211
  htmlSupervisorInstallerSpecifier:
@@ -213,11 +231,21 @@ export const jsenvPluginHtmlSupervisor = ({
213
231
  const generateCodeToSuperviseScript = ({
214
232
  type,
215
233
  src,
234
+ isInline,
235
+ defer,
236
+ async,
216
237
  integrity,
217
238
  crossorigin,
218
239
  htmlSupervisorInstallerSpecifier,
219
240
  }) => {
220
- const paramsAsJson = JSON.stringify({ src, integrity, crossorigin })
241
+ const paramsAsJson = JSON.stringify({
242
+ src,
243
+ isInline,
244
+ defer,
245
+ async,
246
+ integrity,
247
+ crossorigin,
248
+ })
221
249
  if (type === "module") {
222
250
  return `
223
251
  import { superviseScriptTypeModule } from ${htmlSupervisorInstallerSpecifier}
@@ -1,16 +1,20 @@
1
+ import { urlIsInsideOf } from "@jsenv/filesystem"
2
+
1
3
  import { createMagicSource } from "@jsenv/utils/sourcemap/magic_source.js"
2
4
  import { parseHtmlString } from "@jsenv/utils/html_ast/html_ast.js"
3
5
  import { applyBabelPlugins } from "@jsenv/utils/js_ast/apply_babel_plugins.js"
4
-
6
+ import { jsenvRootDirectoryUrl } from "@jsenv/core/src/jsenv_root_directory_url.js"
5
7
  import { collectHotDataFromHtmlAst } from "./html_hot_dependencies.js"
6
8
  import { babelPluginMetadataImportMetaHot } from "./babel_plugin_metadata_import_meta_hot.js"
7
9
 
8
- const importMetaHotClientFileUrl = new URL(
9
- "./client/import_meta_hot.js",
10
- import.meta.url,
11
- ).href
10
+ export const jsenvPluginImportMetaHot = ({ rootDirectoryUrl }) => {
11
+ const preferSourceFiles =
12
+ rootDirectoryUrl === jsenvRootDirectoryUrl ||
13
+ urlIsInsideOf(rootDirectoryUrl, jsenvRootDirectoryUrl)
14
+ const importMetaHotClientFileUrl = preferSourceFiles
15
+ ? new URL("./client/import_meta_hot.js", import.meta.url).href
16
+ : new URL("./dist/import_meta_hot.js", jsenvRootDirectoryUrl).href
12
17
 
13
- export const jsenvPluginImportMetaHot = () => {
14
18
  return {
15
19
  name: "jsenv:import_meta_hot",
16
20
  appliesDuring: "*",
@@ -62,7 +66,7 @@ export const jsenvPluginImportMetaHot = () => {
62
66
  if (context.scenario === "build") {
63
67
  return removeImportMetaHots(urlInfo, importMetaHotPaths)
64
68
  }
65
- return injectImportMetaHot(urlInfo, context)
69
+ return injectImportMetaHot(urlInfo, context, importMetaHotClientFileUrl)
66
70
  },
67
71
  },
68
72
  }
@@ -84,7 +88,7 @@ const removeImportMetaHots = (urlInfo, importMetaHotPaths) => {
84
88
  // better sourcemap than doing the equivalent with babel
85
89
  // I suspect it's because I was doing injectAstAfterImport(programPath, ast.program.body[0])
86
90
  // which is likely not well supported by babel
87
- const injectImportMetaHot = (urlInfo, context) => {
91
+ const injectImportMetaHot = (urlInfo, context, importMetaHotClientFileUrl) => {
88
92
  const [importMetaHotClientFileReference] = context.referenceUtils.inject({
89
93
  parentUrl: urlInfo.url,
90
94
  type: "js_import_export",
@@ -123,9 +123,9 @@ export const jsenvPluginImportmap = () => {
123
123
  const [inlineImportmapReference, inlineImportmapUrlInfo] =
124
124
  context.referenceUtils.foundInline({
125
125
  type: "script_src",
126
- line: line - 1,
127
- column,
128
126
  isOriginalPosition: isOriginal,
127
+ specifierLine: line - 1,
128
+ specifierColumn: column,
129
129
  specifier: inlineImportmapUrl,
130
130
  contentType: "application/importmap+json",
131
131
  content: textNode.value,
@@ -44,12 +44,12 @@ export const jsenvPluginHtmlInlineContent = ({ analyzeConvertedScripts }) => {
44
44
  context.referenceUtils.foundInline({
45
45
  type: "link_href",
46
46
  expectedType: "css",
47
+ isOriginalPosition: isOriginal,
47
48
  // we remove 1 to the line because imagine the following html:
48
49
  // <style>body { color: red; }</style>
49
50
  // -> content starts same line as <style>
50
- line: line - 1,
51
- column,
52
- isOriginalPosition: isOriginal,
51
+ specifierLine: line - 1,
52
+ specifierColumn: column,
53
53
  specifier: inlineStyleUrl,
54
54
  contentType: "text/css",
55
55
  content: textNode.value,
@@ -127,8 +127,8 @@ export const jsenvPluginHtmlInlineContent = ({ analyzeConvertedScripts }) => {
127
127
  // we remove 1 to the line because imagine the following html:
128
128
  // <script>console.log('ok')</script>
129
129
  // -> content starts same line as <script>
130
- line: line - 1,
131
- column,
130
+ specifierLine: line - 1,
131
+ specifierColumn: column,
132
132
  isOriginalPosition: isOriginal,
133
133
  specifier: inlineScriptUrl,
134
134
  contentType,
@@ -40,8 +40,8 @@ export const jsenvPluginJsInlineContent = ({ allowEscapeForVersioning }) => {
40
40
  type: "js_inline_content",
41
41
  subtype: inlineContentInfo.type, // "new_blob_first_arg", "new_inline_content_first_arg", "json_parse_first_arg"
42
42
  isOriginalPosition: urlInfo.content === urlInfo.originalContent,
43
- line: inlineContentInfo.line,
44
- column: inlineContentInfo.column,
43
+ specifierLine: inlineContentInfo.line,
44
+ specifierColumn: inlineContentInfo.column,
45
45
  specifier: inlineUrl,
46
46
  contentType: inlineContentInfo.contentType,
47
47
  content: inlineContentInfo.content,
@@ -73,7 +73,9 @@ export const getCorePlugins = ({
73
73
  jsenvPluginBundling(bundling),
74
74
  jsenvPluginMinification(minification),
75
75
 
76
- jsenvPluginImportMetaHot(),
76
+ jsenvPluginImportMetaHot({
77
+ rootDirectoryUrl,
78
+ }),
77
79
  ...(clientAutoreload
78
80
  ? [
79
81
  jsenvPluginAutoreload({
@@ -202,12 +202,12 @@ export const jsenvPluginScriptTypeModuleAsClassic = ({
202
202
  node: moduleScriptNode,
203
203
  type: "script_src",
204
204
  expectedType: "js_module",
205
+ isOriginalPosition: isOriginal,
205
206
  // we remove 1 to the line because imagine the following html:
206
207
  // <script>console.log('ok')</script>
207
208
  // -> content starts same line as <script>
208
- line: line - 1,
209
- column,
210
- isOriginalPosition: isOriginal,
209
+ specifierLine: line - 1,
210
+ specifierColumn: column,
211
211
  specifier: inlineScriptUrl,
212
212
  contentType: "application/javascript",
213
213
  content: textNode.value,