@jsenv/core 23.8.2 → 23.8.7

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 (133) hide show
  1. package/dist/jsenv_browser_system.js +46 -39
  2. package/dist/jsenv_browser_system.js.map +14 -14
  3. package/dist/jsenv_compile_proxy.js.map +6 -6
  4. package/dist/jsenv_exploring_index.js.map +5 -5
  5. package/dist/jsenv_exploring_redirector.js.map +12 -12
  6. package/dist/jsenv_toolbar.js.map +7 -7
  7. package/dist/jsenv_toolbar_injector.js.map +5 -5
  8. package/helpers/babel/.eslintrc.cjs +24 -24
  9. package/helpers/babel/AsyncGenerator/AsyncGenerator.js +81 -81
  10. package/helpers/babel/AwaitValue/AwaitValue.js +3 -3
  11. package/helpers/babel/applyDecoratorDescriptor/applyDecoratorDescriptor.js +33 -33
  12. package/helpers/babel/arrayLikeToArray/arrayLikeToArray.js +7 -7
  13. package/helpers/babel/arrayWithHoles/arrayWithHoles.js +4 -4
  14. package/helpers/babel/arrayWithoutHoles/arrayWithoutHoles.js +6 -6
  15. package/helpers/babel/assertThisInitialized/assertThisInitialized.js +7 -7
  16. package/helpers/babel/asyncGeneratorDelegate/asyncGeneratorDelegate.js +40 -40
  17. package/helpers/babel/asyncIterator/asyncIterator.js +12 -12
  18. package/helpers/babel/asyncToGenerator/asyncToGenerator.js +34 -34
  19. package/helpers/babel/awaitAsyncGenerator/awaitAsyncGenerator.js +5 -5
  20. package/helpers/babel/classApplyDescriptorDestructureSet/classApplyDescriptorDestructureSet.js +20 -20
  21. package/helpers/babel/classApplyDescriptorGet/classApplyDescriptorGet.js +6 -6
  22. package/helpers/babel/classApplyDescriptorSet/classApplyDescriptorSet.js +13 -13
  23. package/helpers/babel/classCallCheck/classCallCheck.js +5 -5
  24. package/helpers/babel/classCheckPrivateStaticAccess/classCheckPrivateStaticAccess.js +5 -5
  25. package/helpers/babel/classCheckPrivateStaticFieldDescriptor/classCheckPrivateStaticFieldDescriptor.js +6 -6
  26. package/helpers/babel/classExtractFieldDescriptor/classExtractFieldDescriptor.js +7 -7
  27. package/helpers/babel/classNameTDZError/classNameTDZError.js +4 -4
  28. package/helpers/babel/classPrivateFieldDestructureSet/classPrivateFieldDestructureSet.js +7 -7
  29. package/helpers/babel/classPrivateFieldGet/classPrivateFieldGet.js +7 -7
  30. package/helpers/babel/classPrivateFieldLooseBase/classPrivateFieldLooseBase.js +6 -6
  31. package/helpers/babel/classPrivateFieldLooseKey/classPrivateFieldLooseKey.js +5 -5
  32. package/helpers/babel/classPrivateFieldSet/classPrivateFieldSet.js +8 -8
  33. package/helpers/babel/classPrivateMethodGet/classPrivateMethodGet.js +6 -6
  34. package/helpers/babel/classPrivateMethodSet/classPrivateMethodSet.js +3 -3
  35. package/helpers/babel/classStaticPrivateFieldSpecGet/classStaticPrivateFieldSpecGet.js +9 -9
  36. package/helpers/babel/classStaticPrivateFieldSpecSet/classStaticPrivateFieldSpecSet.js +15 -15
  37. package/helpers/babel/classStaticPrivateMethodGet/classStaticPrivateMethodGet.js +6 -6
  38. package/helpers/babel/classStaticPrivateMethodSet/classStaticPrivateMethodSet.js +3 -3
  39. package/helpers/babel/construct/construct.js +16 -16
  40. package/helpers/babel/createClass/createClass.js +15 -15
  41. package/helpers/babel/createForOfIteratorHelper/createForOfIteratorHelper.js +60 -60
  42. package/helpers/babel/createForOfIteratorHelperLoose/createForOfIteratorHelperLoose.js +23 -23
  43. package/helpers/babel/createRawReactElement/createRawReactElement.js +50 -50
  44. package/helpers/babel/createSuper/createSuper.js +22 -22
  45. package/helpers/babel/decorate/decorate.js +403 -403
  46. package/helpers/babel/defaults/defaults.js +11 -11
  47. package/helpers/babel/defineEnumerableProperties/defineEnumerableProperties.js +23 -23
  48. package/helpers/babel/defineProperty/defineProperty.js +18 -18
  49. package/helpers/babel/extends/extends.js +14 -14
  50. package/helpers/babel/get/get.js +13 -13
  51. package/helpers/babel/getPrototypeOf/getPrototypeOf.js +4 -4
  52. package/helpers/babel/inherits/inherits.js +15 -15
  53. package/helpers/babel/inheritsLoose/inheritsLoose.js +7 -7
  54. package/helpers/babel/initializerDefineProperty/initializerDefineProperty.js +10 -10
  55. package/helpers/babel/initializerWarningHelper/initializerWarningHelper.js +6 -6
  56. package/helpers/babel/instanceof/instanceof.js +6 -6
  57. package/helpers/babel/interopRequireDefault/interopRequireDefault.js +3 -3
  58. package/helpers/babel/interopRequireWildcard/interopRequireWildcard.js +37 -37
  59. package/helpers/babel/isNativeFunction/isNativeFunction.js +4 -4
  60. package/helpers/babel/isNativeReflectConstruct/isNativeReflectConstruct.js +21 -21
  61. package/helpers/babel/iterableToArray/iterableToArray.js +7 -7
  62. package/helpers/babel/iterableToArrayLimit/iterableToArrayLimit.js +36 -36
  63. package/helpers/babel/iterableToArrayLimitLoose/iterableToArrayLimitLoose.js +10 -10
  64. package/helpers/babel/jsx/jsx.js +45 -45
  65. package/helpers/babel/maybeArrayLike/maybeArrayLike.js +10 -10
  66. package/helpers/babel/newArrowCheck/newArrowCheck.js +5 -5
  67. package/helpers/babel/nonIterableRest/nonIterableRest.js +5 -5
  68. package/helpers/babel/nonIterableSpread/nonIterableSpread.js +5 -5
  69. package/helpers/babel/objectDestructuringEmpty/objectDestructuringEmpty.js +3 -3
  70. package/helpers/babel/objectSpread/objectSpread.js +23 -23
  71. package/helpers/babel/objectSpread2/objectSpread2.js +33 -33
  72. package/helpers/babel/objectWithoutProperties/objectWithoutProperties.js +19 -19
  73. package/helpers/babel/objectWithoutPropertiesLoose/objectWithoutPropertiesLoose.js +13 -13
  74. package/helpers/babel/possibleConstructorReturn/possibleConstructorReturn.js +10 -10
  75. package/helpers/babel/readOnlyError/readOnlyError.js +4 -4
  76. package/helpers/babel/readme.md +9 -9
  77. package/helpers/babel/set/set.js +44 -44
  78. package/helpers/babel/setPrototypeOf/setPrototypeOf.js +6 -6
  79. package/helpers/babel/skipFirstGeneratorNext/skipFirstGeneratorNext.js +8 -8
  80. package/helpers/babel/slicedToArray/slicedToArray.js +10 -10
  81. package/helpers/babel/slicedToArrayLoose/slicedToArrayLoose.js +13 -13
  82. package/helpers/babel/superPropBase/superPropBase.js +10 -10
  83. package/helpers/babel/taggedTemplateLiteral/taggedTemplateLiteral.js +10 -10
  84. package/helpers/babel/taggedTemplateLiteralLoose/taggedTemplateLiteralLoose.js +7 -7
  85. package/helpers/babel/tdz/tdz.js +4 -4
  86. package/helpers/babel/temporalRef/temporalRef.js +6 -6
  87. package/helpers/babel/temporalUndefined/temporalUndefined.js +3 -3
  88. package/helpers/babel/toArray/toArray.js +10 -10
  89. package/helpers/babel/toConsumableArray/toConsumableArray.js +10 -10
  90. package/helpers/babel/toPrimitive/toPrimitive.js +10 -10
  91. package/helpers/babel/toPropertyKey/toPropertyKey.js +6 -6
  92. package/helpers/babel/typeof/typeof.js +14 -14
  93. package/helpers/babel/unsupportedIterableToArray/unsupportedIterableToArray.js +12 -12
  94. package/helpers/babel/wrapAsyncGenerator/wrapAsyncGenerator.js +8 -8
  95. package/helpers/babel/wrapNativeSuper/wrapNativeSuper.js +30 -30
  96. package/helpers/babel/wrapRegExp/wrapRegExp.js +63 -63
  97. package/helpers/babel/writeOnlyError/writeOnlyError.js +4 -4
  98. package/helpers/regenerator-runtime/regenerator-runtime.js +748 -748
  99. package/{LICENSE → license} +21 -21
  100. package/package.json +2 -2
  101. package/src/buildProject.js +300 -300
  102. package/src/execute.js +184 -184
  103. package/src/internal/browser-launcher/jsenv-browser-system.js +203 -199
  104. package/src/internal/building/buildUsingRollup.js +2 -10
  105. package/src/internal/compiling/babel_plugin_import_assertions.js +121 -121
  106. package/src/internal/compiling/babel_plugin_import_metadata.js +22 -22
  107. package/src/internal/compiling/babel_plugin_import_visitor.js +84 -84
  108. package/src/internal/compiling/compile-directory/getOrGenerateCompiledFile.js +268 -268
  109. package/src/internal/compiling/compile-directory/updateMeta.js +154 -154
  110. package/src/internal/compiling/compile-directory/validateCache.js +265 -265
  111. package/src/internal/compiling/compileFile.js +233 -224
  112. package/src/internal/compiling/compileHtml.js +550 -550
  113. package/src/internal/compiling/createCompiledFileService.js +291 -291
  114. package/src/internal/compiling/html_source_file_service.js +403 -404
  115. package/src/internal/compiling/js-compilation-service/jsenvTransform.js +272 -270
  116. package/src/internal/compiling/jsenvCompilerForHtml.js +374 -308
  117. package/src/internal/compiling/jsenvCompilerForJavaScript.js +2 -0
  118. package/src/internal/compiling/startCompileServer.js +1086 -1048
  119. package/src/internal/compiling/transformResultToCompilationResult.js +220 -220
  120. package/src/internal/executing/coverage/babel_plugin_instrument.js +90 -90
  121. package/src/internal/executing/coverage/reportToCoverage.js +193 -187
  122. package/src/internal/executing/executePlan.js +183 -183
  123. package/src/internal/executing/launchAndExecute.js +458 -458
  124. package/src/internal/generateGroupMap/featuresCompatMap.js +29 -0
  125. package/src/internal/generateGroupMap/jsenvBabelPluginCompatMap.js +1 -8
  126. package/src/internal/runtime/createBrowserRuntime/scanBrowserRuntimeFeatures.js +246 -246
  127. package/src/internal/runtime/createNodeRuntime/scanNodeRuntimeFeatures.js +112 -112
  128. package/src/internal/runtime/s.js +727 -727
  129. package/src/internal/toolbar/jsenv-logo.svg +144 -144
  130. package/src/internal/toolbar/toolbar.main.css +196 -196
  131. package/src/internal/toolbar/toolbar.main.js +227 -227
  132. package/src/internal/url_conversion.js +317 -317
  133. package/src/startExploring.js +309 -309
@@ -1,404 +1,403 @@
1
- /**
2
- * Send a modified version of the html files instead of serving
3
- * the source html files
4
- * - force inlining of importmap
5
- * - inject a script into html head to have window.__jsenv__
6
- * - <script type="module" src="file.js">
7
- * into
8
- * <script type="module">
9
- * window.__jsenv__.executeFileUsingDynamicImport('file.js')
10
- * </script>
11
- */
12
-
13
- import {
14
- resolveUrl,
15
- urlToRelativeUrl,
16
- urlToExtension,
17
- readFile,
18
- urlToFilename,
19
- urlIsInsideOf,
20
- } from "@jsenv/filesystem"
21
- import { moveImportMap } from "@jsenv/importmap"
22
- import { createDetailedMessage } from "@jsenv/logger"
23
-
24
- import { fetchUrl } from "@jsenv/core/src/internal/fetchUrl.js"
25
- import {
26
- jsenvToolbarHtmlFileInfo,
27
- jsenvBrowserSystemFileInfo,
28
- jsenvToolbarInjectorFileInfo,
29
- } from "@jsenv/core/src/internal/jsenvInternalFiles.js"
30
- import { stringifyDataUrl } from "@jsenv/core/src/internal/dataUrl.utils.js"
31
- import {
32
- parseHtmlString,
33
- parseHtmlAstRessources,
34
- collectHtmlDependenciesFromAst,
35
- getHtmlNodeAttributeByName,
36
- replaceHtmlNode,
37
- stringifyHtmlAst,
38
- manipulateHtmlAst,
39
- removeHtmlNodeAttribute,
40
- getHtmlNodeTextNode,
41
- setHtmlNodeText,
42
- getUniqueNameForInlineHtmlNode,
43
- } from "./compileHtml.js"
44
-
45
- export const createTransformHtmlSourceFileService = ({
46
- logger,
47
- projectDirectoryUrl,
48
- inlineImportMapIntoHTML,
49
- jsenvScriptInjection,
50
- jsenvToolbarInjection,
51
- }) => {
52
- /**
53
- * htmlInlineScriptMap is composed as below
54
- * "file:///project_directory/index.html.10.js": {
55
- * "htmlFileUrl": "file:///project_directory/index.html",
56
- * "scriptContent": "console.log(`Hello world`)"
57
- * }
58
- * It is used to serve the inline script as if they where inside a file
59
- * Every time the html file is retransformed, the list of inline script inside it
60
- * are deleted so that when html file and page is reloaded, the inline script are updated
61
- */
62
- const htmlInlineScriptMap = new Map()
63
-
64
- return async (request, { pushResponse }) => {
65
- const { ressource } = request
66
- const relativeUrl = ressource.slice(1)
67
- const fileUrl = resolveUrl(relativeUrl, projectDirectoryUrl)
68
-
69
- const inlineScript = htmlInlineScriptMap.get(fileUrl)
70
- if (inlineScript) {
71
- return {
72
- status: 200,
73
- headers: {
74
- "content-type": "application/javascript",
75
- "content-length": Buffer.byteLength(inlineScript.scriptContent),
76
- },
77
- body: inlineScript.scriptContent,
78
- }
79
- }
80
-
81
- if (urlToExtension(fileUrl) !== ".html") {
82
- return null
83
- }
84
-
85
- let fileContent
86
- try {
87
- fileContent = await readFile(fileUrl, { as: "string" })
88
- } catch (e) {
89
- if (e.code === "ENOENT") {
90
- return {
91
- status: 404,
92
- }
93
- }
94
- throw e
95
- }
96
- htmlInlineScriptMap.forEach((inlineScript, inlineScriptUrl) => {
97
- if (inlineScript.htmlFileUrl === fileUrl) {
98
- htmlInlineScriptMap.delete(inlineScriptUrl)
99
- }
100
- })
101
- const htmlTransformed = await transformHTMLSourceFile({
102
- logger,
103
- projectDirectoryUrl,
104
- fileUrl,
105
- fileContent,
106
- request,
107
- pushResponse,
108
- inlineImportMapIntoHTML,
109
- jsenvScriptInjection,
110
- jsenvToolbarInjection,
111
- onInlineModuleScript: ({ scriptContent, scriptIdentifier }) => {
112
- const inlineScriptUrl = resolveUrl(scriptIdentifier, fileUrl)
113
- htmlInlineScriptMap.set(inlineScriptUrl, {
114
- htmlFileUrl: fileUrl,
115
- scriptContent,
116
- })
117
- },
118
- })
119
- return {
120
- status: 200,
121
- headers: {
122
- "content-type": "text/html",
123
- "content-length": Buffer.byteLength(htmlTransformed),
124
- "cache-control": "no-cache",
125
- },
126
- body: htmlTransformed,
127
- }
128
- }
129
- }
130
-
131
- const transformHTMLSourceFile = async ({
132
- logger,
133
- projectDirectoryUrl,
134
- fileUrl,
135
- fileContent,
136
- request,
137
- pushResponse,
138
- inlineImportMapIntoHTML,
139
- jsenvScriptInjection,
140
- jsenvToolbarInjection,
141
- onInlineModuleScript = () => {},
142
- }) => {
143
- const htmlAst = parseHtmlString(fileContent)
144
- if (inlineImportMapIntoHTML) {
145
- await inlineImportmapScripts({
146
- logger,
147
- htmlAst,
148
- htmlFileUrl: fileUrl,
149
- projectDirectoryUrl,
150
- })
151
- }
152
-
153
- const jsenvBrowserBuildUrlRelativeToProject = urlToRelativeUrl(
154
- jsenvBrowserSystemFileInfo.jsenvBuildUrl,
155
- projectDirectoryUrl,
156
- )
157
- const jsenvToolbarInjectorBuildRelativeUrlForProject = urlToRelativeUrl(
158
- jsenvToolbarInjectorFileInfo.jsenvBuildUrl,
159
- projectDirectoryUrl,
160
- )
161
- manipulateHtmlAst(htmlAst, {
162
- scriptInjections: [
163
- ...(fileUrl !== jsenvToolbarHtmlFileInfo.url && jsenvScriptInjection
164
- ? [
165
- {
166
- src: `/${jsenvBrowserBuildUrlRelativeToProject}`,
167
- },
168
- ]
169
- : []),
170
- ...(fileUrl !== jsenvToolbarHtmlFileInfo.url && jsenvToolbarInjection
171
- ? [
172
- {
173
- src: `/${jsenvToolbarInjectorBuildRelativeUrlForProject}`,
174
- },
175
- ]
176
- : []),
177
- ],
178
- })
179
-
180
- if (request.http2) {
181
- const htmlDependencies = collectHtmlDependenciesFromAst(htmlAst)
182
- htmlDependencies.forEach(({ specifier }) => {
183
- const requestUrl = resolveUrl(request.ressource, request.origin)
184
- const dependencyUrl = resolveUrl(specifier, requestUrl)
185
- if (!urlIsInsideOf(dependencyUrl, request.origin)) {
186
- // ignore external urls
187
- return
188
- }
189
- if (dependencyUrl.startsWith("data:")) {
190
- return
191
- }
192
- const dependencyRelativeUrl = urlToRelativeUrl(
193
- dependencyUrl,
194
- request.origin,
195
- )
196
- pushResponse({ path: `/${dependencyRelativeUrl}` })
197
- })
198
- }
199
-
200
- if (jsenvScriptInjection) {
201
- const { scripts } = parseHtmlAstRessources(htmlAst)
202
- scripts.forEach((script) => {
203
- const typeAttribute = getHtmlNodeAttributeByName(script, "type")
204
- const srcAttribute = getHtmlNodeAttributeByName(script, "src")
205
-
206
- // remote
207
- if (typeAttribute && typeAttribute.value === "module" && srcAttribute) {
208
- removeHtmlNodeAttribute(script, srcAttribute)
209
- setHtmlNodeText(
210
- script,
211
- `window.__jsenv__.executeFileUsingDynamicImport(${JSON.stringify(
212
- srcAttribute.value,
213
- )})`,
214
- )
215
- return
216
- }
217
- // inline
218
- const textNode = getHtmlNodeTextNode(script)
219
- if (typeAttribute && typeAttribute.value === "module" && textNode) {
220
- const scriptIdentifier = getUniqueNameForInlineHtmlNode(
221
- script,
222
- scripts,
223
- `${urlToFilename(fileUrl)}__inline__[id].js`,
224
- )
225
- onInlineModuleScript({
226
- scriptContent: textNode.value,
227
- scriptIdentifier,
228
- })
229
- setHtmlNodeText(
230
- script,
231
- `window.__jsenv__.executeFileUsingDynamicImport(${JSON.stringify(
232
- `./${scriptIdentifier}`,
233
- )})`,
234
- )
235
- return
236
- }
237
- })
238
- }
239
-
240
- await forceInlineRessources({
241
- logger,
242
- htmlAst,
243
- htmlFileUrl: fileUrl,
244
- projectDirectoryUrl,
245
- })
246
-
247
- return stringifyHtmlAst(htmlAst)
248
- }
249
-
250
- const inlineImportmapScripts = async ({ logger, htmlAst, htmlFileUrl }) => {
251
- const { scripts } = parseHtmlAstRessources(htmlAst)
252
- const remoteImportmapScripts = scripts.filter((script) => {
253
- const typeAttribute = getHtmlNodeAttributeByName(script, "type")
254
- const srcAttribute = getHtmlNodeAttributeByName(script, "src")
255
- if (typeAttribute && typeAttribute.value === "importmap" && srcAttribute) {
256
- return true
257
- }
258
- return false
259
- })
260
-
261
- await Promise.all(
262
- remoteImportmapScripts.map(async (remoteImportmapScript) => {
263
- const srcAttribute = getHtmlNodeAttributeByName(
264
- remoteImportmapScript,
265
- "src",
266
- )
267
- const importMapUrl = resolveUrl(srcAttribute.value, htmlFileUrl)
268
- const importMapResponse = await fetchUrl(importMapUrl)
269
- if (importMapResponse.status !== 200) {
270
- logger.warn(
271
- createDetailedMessage(
272
- importMapResponse.status === 404
273
- ? `Cannot inline importmap script because file cannot be found.`
274
- : `Cannot inline importmap script due to unexpected response status (${importMapResponse.status}).`,
275
- {
276
- "importmap script src": srcAttribute.value,
277
- "importmap url": importMapUrl,
278
- "html url": htmlFileUrl,
279
- },
280
- ),
281
- )
282
-
283
- return
284
- }
285
-
286
- const importMapContent = await importMapResponse.json()
287
- const importMapInlined = moveImportMap(
288
- importMapContent,
289
- importMapUrl,
290
- htmlFileUrl,
291
- )
292
-
293
- replaceHtmlNode(
294
- remoteImportmapScript,
295
- `<script type="importmap">${JSON.stringify(
296
- importMapInlined,
297
- null,
298
- " ",
299
- )}</script>`,
300
- {
301
- attributesToIgnore: ["src"],
302
- },
303
- )
304
- }),
305
- )
306
- }
307
-
308
- const forceInlineRessources = async ({
309
- htmlAst,
310
- htmlFileUrl,
311
- projectDirectoryUrl,
312
- }) => {
313
- const { scripts, links, imgs } = parseHtmlAstRessources(htmlAst)
314
-
315
- const inlineOperations = []
316
- scripts.forEach((script) => {
317
- const forceInlineAttribute = getJsenvForceInlineAttribute(script)
318
- if (!forceInlineAttribute) {
319
- return
320
- }
321
- const srcAttribute = getHtmlNodeAttributeByName(script, "src")
322
- const src = srcAttribute ? srcAttribute.value : ""
323
- if (!src) {
324
- return
325
- }
326
- inlineOperations.push({
327
- specifier: src,
328
- mutateHtml: async (response) => {
329
- replaceHtmlNode(script, `<script>${await response.text()}</script>`, {
330
- attributesToIgnore: ["src"],
331
- })
332
- },
333
- })
334
- })
335
- links.forEach((link) => {
336
- const forceInlineAttribute = getJsenvForceInlineAttribute(link)
337
- if (!forceInlineAttribute) {
338
- return
339
- }
340
- const relAttribute = getHtmlNodeAttributeByName(link, "rel")
341
- const rel = relAttribute ? relAttribute.value : ""
342
- if (rel !== "stylesheet") {
343
- return
344
- }
345
- const hrefAttribute = getHtmlNodeAttributeByName(link, "href")
346
- const href = hrefAttribute ? hrefAttribute.value : ""
347
- if (!href) {
348
- return
349
- }
350
- inlineOperations.push({
351
- specifier: href,
352
- mutateHtml: async (response) => {
353
- replaceHtmlNode(link, `<style>${await response.text()}</style>`, {
354
- attributesToIgnore: ["rel", "href"],
355
- })
356
- },
357
- })
358
- })
359
- imgs.forEach((img) => {
360
- const forceInlineAttribute = getJsenvForceInlineAttribute(img)
361
- if (!forceInlineAttribute) {
362
- return
363
- }
364
- const srcAttribute = getHtmlNodeAttributeByName(img, "src")
365
- const src = srcAttribute ? srcAttribute.value : ""
366
- if (!src) {
367
- return
368
- }
369
- inlineOperations.push({
370
- specifier: src,
371
- mutateHtml: async (response) => {
372
- const responseArrayBuffer = await response.arrayBuffer()
373
- const responseAsBase64 = stringifyDataUrl({
374
- data: responseArrayBuffer,
375
- base64Flag: true,
376
- mediaType: response.headers["content-type"],
377
- })
378
- replaceHtmlNode(img, `<img src=${responseAsBase64} />`)
379
- },
380
- })
381
- })
382
-
383
- await Promise.all(
384
- inlineOperations.map(async (inlineOperation) => {
385
- const url = resolveUrl(inlineOperation.specifier, htmlFileUrl)
386
- if (!urlIsInsideOf(url, projectDirectoryUrl)) {
387
- return
388
- }
389
- const response = await fetchUrl(url)
390
- if (response.status !== 200) {
391
- return
392
- }
393
- await inlineOperation.mutateHtml(response)
394
- }),
395
- )
396
- }
397
-
398
- const getJsenvForceInlineAttribute = (htmlNode) => {
399
- const jsenvForceInlineAttribute = getHtmlNodeAttributeByName(
400
- htmlNode,
401
- "data-jsenv-force-inline",
402
- )
403
- return jsenvForceInlineAttribute
404
- }
1
+ /**
2
+ * Send a modified version of the html files instead of serving
3
+ * the source html files
4
+ * - force inlining of importmap
5
+ * - inject a script into html head to have window.__jsenv__
6
+ * - <script type="module" src="file.js">
7
+ * into
8
+ * <script type="module">
9
+ * window.__jsenv__.executeFileUsingDynamicImport('file.js')
10
+ * </script>
11
+ */
12
+
13
+ import {
14
+ resolveUrl,
15
+ urlToRelativeUrl,
16
+ urlToExtension,
17
+ readFile,
18
+ urlToFilename,
19
+ urlIsInsideOf,
20
+ } from "@jsenv/filesystem"
21
+ import { moveImportMap } from "@jsenv/importmap"
22
+ import { createDetailedMessage } from "@jsenv/logger"
23
+
24
+ import { fetchUrl } from "@jsenv/core/src/internal/fetchUrl.js"
25
+ import {
26
+ jsenvToolbarHtmlFileInfo,
27
+ jsenvBrowserSystemFileInfo,
28
+ jsenvToolbarInjectorFileInfo,
29
+ } from "@jsenv/core/src/internal/jsenvInternalFiles.js"
30
+ import { stringifyDataUrl } from "@jsenv/core/src/internal/dataUrl.utils.js"
31
+ import {
32
+ parseHtmlString,
33
+ parseHtmlAstRessources,
34
+ collectHtmlDependenciesFromAst,
35
+ getHtmlNodeAttributeByName,
36
+ replaceHtmlNode,
37
+ stringifyHtmlAst,
38
+ manipulateHtmlAst,
39
+ removeHtmlNodeAttribute,
40
+ getHtmlNodeTextNode,
41
+ setHtmlNodeText,
42
+ getUniqueNameForInlineHtmlNode,
43
+ } from "./compileHtml.js"
44
+
45
+ export const createTransformHtmlSourceFileService = ({
46
+ logger,
47
+ projectDirectoryUrl,
48
+ inlineImportMapIntoHTML,
49
+ jsenvScriptInjection,
50
+ jsenvToolbarInjection,
51
+ }) => {
52
+ /**
53
+ * htmlInlineScriptMap is composed as below
54
+ * "file:///project_directory/index.html.10.js": {
55
+ * "htmlFileUrl": "file:///project_directory/index.html",
56
+ * "scriptContent": "console.log(`Hello world`)"
57
+ * }
58
+ * It is used to serve the inline script as if they where inside a file
59
+ * Every time the html file is retransformed, the list of inline script inside it
60
+ * are deleted so that when html file and page is reloaded, the inline script are updated
61
+ */
62
+ const htmlInlineScriptMap = new Map()
63
+
64
+ return async (request, { pushResponse }) => {
65
+ const { ressource } = request
66
+ const relativeUrl = ressource.slice(1)
67
+ const fileUrl = resolveUrl(relativeUrl, projectDirectoryUrl)
68
+
69
+ const inlineScript = htmlInlineScriptMap.get(fileUrl)
70
+ if (inlineScript) {
71
+ return {
72
+ status: 200,
73
+ headers: {
74
+ "content-type": "application/javascript",
75
+ "content-length": Buffer.byteLength(inlineScript.scriptContent),
76
+ },
77
+ body: inlineScript.scriptContent,
78
+ }
79
+ }
80
+
81
+ if (urlToExtension(fileUrl) !== ".html") {
82
+ return null
83
+ }
84
+
85
+ let fileContent
86
+ try {
87
+ fileContent = await readFile(fileUrl, { as: "string" })
88
+ } catch (e) {
89
+ if (e.code === "ENOENT") {
90
+ return {
91
+ status: 404,
92
+ }
93
+ }
94
+ throw e
95
+ }
96
+ htmlInlineScriptMap.forEach((inlineScript, inlineScriptUrl) => {
97
+ if (inlineScript.htmlFileUrl === fileUrl) {
98
+ htmlInlineScriptMap.delete(inlineScriptUrl)
99
+ }
100
+ })
101
+ const htmlTransformed = await transformHTMLSourceFile({
102
+ logger,
103
+ projectDirectoryUrl,
104
+ fileUrl,
105
+ fileContent,
106
+ request,
107
+ pushResponse,
108
+ inlineImportMapIntoHTML,
109
+ jsenvScriptInjection,
110
+ jsenvToolbarInjection,
111
+ onInlineModuleScript: ({ scriptContent, scriptIdentifier }) => {
112
+ const inlineScriptUrl = resolveUrl(scriptIdentifier, fileUrl)
113
+ htmlInlineScriptMap.set(inlineScriptUrl, {
114
+ htmlFileUrl: fileUrl,
115
+ scriptContent,
116
+ })
117
+ },
118
+ })
119
+ return {
120
+ status: 200,
121
+ headers: {
122
+ "content-type": "text/html",
123
+ "content-length": Buffer.byteLength(htmlTransformed),
124
+ "cache-control": "no-cache",
125
+ },
126
+ body: htmlTransformed,
127
+ }
128
+ }
129
+ }
130
+
131
+ const transformHTMLSourceFile = async ({
132
+ logger,
133
+ projectDirectoryUrl,
134
+ fileUrl,
135
+ fileContent,
136
+ request,
137
+ pushResponse,
138
+ inlineImportMapIntoHTML,
139
+ jsenvScriptInjection,
140
+ jsenvToolbarInjection,
141
+ onInlineModuleScript = () => {},
142
+ }) => {
143
+ const htmlAst = parseHtmlString(fileContent)
144
+ if (inlineImportMapIntoHTML) {
145
+ await inlineImportmapScripts({
146
+ logger,
147
+ htmlAst,
148
+ htmlFileUrl: fileUrl,
149
+ projectDirectoryUrl,
150
+ })
151
+ }
152
+
153
+ const jsenvBrowserBuildUrlRelativeToProject = urlToRelativeUrl(
154
+ jsenvBrowserSystemFileInfo.jsenvBuildUrl,
155
+ projectDirectoryUrl,
156
+ )
157
+ const jsenvToolbarInjectorBuildRelativeUrlForProject = urlToRelativeUrl(
158
+ jsenvToolbarInjectorFileInfo.jsenvBuildUrl,
159
+ projectDirectoryUrl,
160
+ )
161
+ manipulateHtmlAst(htmlAst, {
162
+ scriptInjections: [
163
+ ...(fileUrl !== jsenvToolbarHtmlFileInfo.url && jsenvScriptInjection
164
+ ? [
165
+ {
166
+ src: `/${jsenvBrowserBuildUrlRelativeToProject}`,
167
+ },
168
+ ]
169
+ : []),
170
+ ...(fileUrl !== jsenvToolbarHtmlFileInfo.url && jsenvToolbarInjection
171
+ ? [
172
+ {
173
+ src: `/${jsenvToolbarInjectorBuildRelativeUrlForProject}`,
174
+ },
175
+ ]
176
+ : []),
177
+ ],
178
+ })
179
+
180
+ if (request.http2) {
181
+ const htmlDependencies = collectHtmlDependenciesFromAst(htmlAst)
182
+ htmlDependencies.forEach(({ specifier }) => {
183
+ const requestUrl = resolveUrl(request.ressource, request.origin)
184
+ const dependencyUrl = resolveUrl(specifier, requestUrl)
185
+ if (!urlIsInsideOf(dependencyUrl, request.origin)) {
186
+ // ignore external urls
187
+ return
188
+ }
189
+ if (dependencyUrl.startsWith("data:")) {
190
+ return
191
+ }
192
+ const dependencyRelativeUrl = urlToRelativeUrl(
193
+ dependencyUrl,
194
+ request.origin,
195
+ )
196
+ pushResponse({ path: `/${dependencyRelativeUrl}` })
197
+ })
198
+ }
199
+
200
+ if (jsenvScriptInjection) {
201
+ const { scripts } = parseHtmlAstRessources(htmlAst)
202
+ scripts.forEach((script) => {
203
+ const typeAttribute = getHtmlNodeAttributeByName(script, "type")
204
+ const srcAttribute = getHtmlNodeAttributeByName(script, "src")
205
+
206
+ // remote
207
+ if (typeAttribute && typeAttribute.value === "module" && srcAttribute) {
208
+ removeHtmlNodeAttribute(script, srcAttribute)
209
+ setHtmlNodeText(
210
+ script,
211
+ `window.__jsenv__.executeFileUsingDynamicImport(${JSON.stringify(
212
+ srcAttribute.value,
213
+ )})`,
214
+ )
215
+ return
216
+ }
217
+ // inline
218
+ const textNode = getHtmlNodeTextNode(script)
219
+ if (typeAttribute && typeAttribute.value === "module" && textNode) {
220
+ const scriptIdentifier = getUniqueNameForInlineHtmlNode(
221
+ script,
222
+ scripts,
223
+ `${urlToFilename(fileUrl)}__inline__[id].js`,
224
+ )
225
+ onInlineModuleScript({
226
+ scriptContent: textNode.value,
227
+ scriptIdentifier,
228
+ })
229
+ setHtmlNodeText(
230
+ script,
231
+ `window.__jsenv__.executeFileUsingDynamicImport(${JSON.stringify(
232
+ `./${scriptIdentifier}`,
233
+ )})`,
234
+ )
235
+ return
236
+ }
237
+ })
238
+ }
239
+
240
+ await forceInlineRessources({
241
+ logger,
242
+ htmlAst,
243
+ htmlFileUrl: fileUrl,
244
+ projectDirectoryUrl,
245
+ })
246
+
247
+ return stringifyHtmlAst(htmlAst)
248
+ }
249
+
250
+ const inlineImportmapScripts = async ({ logger, htmlAst, htmlFileUrl }) => {
251
+ const { scripts } = parseHtmlAstRessources(htmlAst)
252
+ const remoteImportmapScripts = scripts.filter((script) => {
253
+ const typeAttribute = getHtmlNodeAttributeByName(script, "type")
254
+ const srcAttribute = getHtmlNodeAttributeByName(script, "src")
255
+ if (typeAttribute && typeAttribute.value === "importmap" && srcAttribute) {
256
+ return true
257
+ }
258
+ return false
259
+ })
260
+
261
+ await Promise.all(
262
+ remoteImportmapScripts.map(async (remoteImportmapScript) => {
263
+ const srcAttribute = getHtmlNodeAttributeByName(
264
+ remoteImportmapScript,
265
+ "src",
266
+ )
267
+ const importMapUrl = resolveUrl(srcAttribute.value, htmlFileUrl)
268
+ const importMapResponse = await fetchUrl(importMapUrl)
269
+ if (importMapResponse.status !== 200) {
270
+ logger.warn(
271
+ createDetailedMessage(
272
+ importMapResponse.status === 404
273
+ ? `Cannot inline importmap script because file cannot be found.`
274
+ : `Cannot inline importmap script due to unexpected response status (${importMapResponse.status}).`,
275
+ {
276
+ "importmap script src": srcAttribute.value,
277
+ "importmap url": importMapUrl,
278
+ "html url": htmlFileUrl,
279
+ },
280
+ ),
281
+ )
282
+ return
283
+ }
284
+
285
+ const importMapContent = await importMapResponse.json()
286
+ const importMapInlined = moveImportMap(
287
+ importMapContent,
288
+ importMapUrl,
289
+ htmlFileUrl,
290
+ )
291
+
292
+ replaceHtmlNode(
293
+ remoteImportmapScript,
294
+ `<script type="importmap">${JSON.stringify(
295
+ importMapInlined,
296
+ null,
297
+ " ",
298
+ )}</script>`,
299
+ {
300
+ attributesToIgnore: ["src"],
301
+ },
302
+ )
303
+ }),
304
+ )
305
+ }
306
+
307
+ const forceInlineRessources = async ({
308
+ htmlAst,
309
+ htmlFileUrl,
310
+ projectDirectoryUrl,
311
+ }) => {
312
+ const { scripts, links, imgs } = parseHtmlAstRessources(htmlAst)
313
+
314
+ const inlineOperations = []
315
+ scripts.forEach((script) => {
316
+ const forceInlineAttribute = getJsenvForceInlineAttribute(script)
317
+ if (!forceInlineAttribute) {
318
+ return
319
+ }
320
+ const srcAttribute = getHtmlNodeAttributeByName(script, "src")
321
+ const src = srcAttribute ? srcAttribute.value : ""
322
+ if (!src) {
323
+ return
324
+ }
325
+ inlineOperations.push({
326
+ specifier: src,
327
+ mutateHtml: async (response) => {
328
+ replaceHtmlNode(script, `<script>${await response.text()}</script>`, {
329
+ attributesToIgnore: ["src"],
330
+ })
331
+ },
332
+ })
333
+ })
334
+ links.forEach((link) => {
335
+ const forceInlineAttribute = getJsenvForceInlineAttribute(link)
336
+ if (!forceInlineAttribute) {
337
+ return
338
+ }
339
+ const relAttribute = getHtmlNodeAttributeByName(link, "rel")
340
+ const rel = relAttribute ? relAttribute.value : ""
341
+ if (rel !== "stylesheet") {
342
+ return
343
+ }
344
+ const hrefAttribute = getHtmlNodeAttributeByName(link, "href")
345
+ const href = hrefAttribute ? hrefAttribute.value : ""
346
+ if (!href) {
347
+ return
348
+ }
349
+ inlineOperations.push({
350
+ specifier: href,
351
+ mutateHtml: async (response) => {
352
+ replaceHtmlNode(link, `<style>${await response.text()}</style>`, {
353
+ attributesToIgnore: ["rel", "href"],
354
+ })
355
+ },
356
+ })
357
+ })
358
+ imgs.forEach((img) => {
359
+ const forceInlineAttribute = getJsenvForceInlineAttribute(img)
360
+ if (!forceInlineAttribute) {
361
+ return
362
+ }
363
+ const srcAttribute = getHtmlNodeAttributeByName(img, "src")
364
+ const src = srcAttribute ? srcAttribute.value : ""
365
+ if (!src) {
366
+ return
367
+ }
368
+ inlineOperations.push({
369
+ specifier: src,
370
+ mutateHtml: async (response) => {
371
+ const responseArrayBuffer = await response.arrayBuffer()
372
+ const responseAsBase64 = stringifyDataUrl({
373
+ data: responseArrayBuffer,
374
+ base64Flag: true,
375
+ mediaType: response.headers["content-type"],
376
+ })
377
+ replaceHtmlNode(img, `<img src=${responseAsBase64} />`)
378
+ },
379
+ })
380
+ })
381
+
382
+ await Promise.all(
383
+ inlineOperations.map(async (inlineOperation) => {
384
+ const url = resolveUrl(inlineOperation.specifier, htmlFileUrl)
385
+ if (!urlIsInsideOf(url, projectDirectoryUrl)) {
386
+ return
387
+ }
388
+ const response = await fetchUrl(url)
389
+ if (response.status !== 200) {
390
+ return
391
+ }
392
+ await inlineOperation.mutateHtml(response)
393
+ }),
394
+ )
395
+ }
396
+
397
+ const getJsenvForceInlineAttribute = (htmlNode) => {
398
+ const jsenvForceInlineAttribute = getHtmlNodeAttributeByName(
399
+ htmlNode,
400
+ "data-jsenv-force-inline",
401
+ )
402
+ return jsenvForceInlineAttribute
403
+ }