@jsenv/core 23.6.1 → 23.8.0

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 (135) hide show
  1. package/{license → LICENSE} +21 -21
  2. package/dist/jsenv_browser_system.js +35 -35
  3. package/dist/jsenv_browser_system.js.map +14 -14
  4. package/dist/jsenv_compile_proxy.js +47 -128
  5. package/dist/jsenv_compile_proxy.js.map +25 -62
  6. package/dist/jsenv_exploring_index.js.map +5 -5
  7. package/dist/jsenv_exploring_redirector.js +47 -54
  8. package/dist/jsenv_exploring_redirector.js.map +17 -19
  9. package/dist/jsenv_toolbar.js +47 -130
  10. package/dist/jsenv_toolbar.js.map +13 -48
  11. package/dist/jsenv_toolbar_injector.js.map +5 -5
  12. package/helpers/babel/.eslintrc.cjs +24 -24
  13. package/helpers/babel/AsyncGenerator/AsyncGenerator.js +81 -81
  14. package/helpers/babel/AwaitValue/AwaitValue.js +3 -3
  15. package/helpers/babel/applyDecoratorDescriptor/applyDecoratorDescriptor.js +33 -33
  16. package/helpers/babel/arrayLikeToArray/arrayLikeToArray.js +7 -7
  17. package/helpers/babel/arrayWithHoles/arrayWithHoles.js +4 -4
  18. package/helpers/babel/arrayWithoutHoles/arrayWithoutHoles.js +6 -6
  19. package/helpers/babel/assertThisInitialized/assertThisInitialized.js +7 -7
  20. package/helpers/babel/asyncGeneratorDelegate/asyncGeneratorDelegate.js +40 -40
  21. package/helpers/babel/asyncIterator/asyncIterator.js +12 -12
  22. package/helpers/babel/asyncToGenerator/asyncToGenerator.js +34 -34
  23. package/helpers/babel/awaitAsyncGenerator/awaitAsyncGenerator.js +5 -5
  24. package/helpers/babel/classApplyDescriptorDestructureSet/classApplyDescriptorDestructureSet.js +20 -20
  25. package/helpers/babel/classApplyDescriptorGet/classApplyDescriptorGet.js +6 -6
  26. package/helpers/babel/classApplyDescriptorSet/classApplyDescriptorSet.js +13 -13
  27. package/helpers/babel/classCallCheck/classCallCheck.js +5 -5
  28. package/helpers/babel/classCheckPrivateStaticAccess/classCheckPrivateStaticAccess.js +5 -5
  29. package/helpers/babel/classCheckPrivateStaticFieldDescriptor/classCheckPrivateStaticFieldDescriptor.js +6 -6
  30. package/helpers/babel/classExtractFieldDescriptor/classExtractFieldDescriptor.js +7 -7
  31. package/helpers/babel/classNameTDZError/classNameTDZError.js +4 -4
  32. package/helpers/babel/classPrivateFieldDestructureSet/classPrivateFieldDestructureSet.js +7 -7
  33. package/helpers/babel/classPrivateFieldGet/classPrivateFieldGet.js +7 -7
  34. package/helpers/babel/classPrivateFieldLooseBase/classPrivateFieldLooseBase.js +6 -6
  35. package/helpers/babel/classPrivateFieldLooseKey/classPrivateFieldLooseKey.js +5 -5
  36. package/helpers/babel/classPrivateFieldSet/classPrivateFieldSet.js +8 -8
  37. package/helpers/babel/classPrivateMethodGet/classPrivateMethodGet.js +6 -6
  38. package/helpers/babel/classPrivateMethodSet/classPrivateMethodSet.js +3 -3
  39. package/helpers/babel/classStaticPrivateFieldSpecGet/classStaticPrivateFieldSpecGet.js +9 -9
  40. package/helpers/babel/classStaticPrivateFieldSpecSet/classStaticPrivateFieldSpecSet.js +15 -15
  41. package/helpers/babel/classStaticPrivateMethodGet/classStaticPrivateMethodGet.js +6 -6
  42. package/helpers/babel/classStaticPrivateMethodSet/classStaticPrivateMethodSet.js +3 -3
  43. package/helpers/babel/construct/construct.js +16 -16
  44. package/helpers/babel/createClass/createClass.js +15 -15
  45. package/helpers/babel/createForOfIteratorHelper/createForOfIteratorHelper.js +60 -60
  46. package/helpers/babel/createForOfIteratorHelperLoose/createForOfIteratorHelperLoose.js +23 -23
  47. package/helpers/babel/createRawReactElement/createRawReactElement.js +50 -50
  48. package/helpers/babel/createSuper/createSuper.js +22 -22
  49. package/helpers/babel/decorate/decorate.js +403 -403
  50. package/helpers/babel/defaults/defaults.js +11 -11
  51. package/helpers/babel/defineEnumerableProperties/defineEnumerableProperties.js +23 -23
  52. package/helpers/babel/defineProperty/defineProperty.js +18 -18
  53. package/helpers/babel/extends/extends.js +14 -14
  54. package/helpers/babel/get/get.js +13 -13
  55. package/helpers/babel/getPrototypeOf/getPrototypeOf.js +4 -4
  56. package/helpers/babel/inherits/inherits.js +15 -15
  57. package/helpers/babel/inheritsLoose/inheritsLoose.js +7 -7
  58. package/helpers/babel/initializerDefineProperty/initializerDefineProperty.js +10 -10
  59. package/helpers/babel/initializerWarningHelper/initializerWarningHelper.js +6 -6
  60. package/helpers/babel/instanceof/instanceof.js +6 -6
  61. package/helpers/babel/interopRequireDefault/interopRequireDefault.js +3 -3
  62. package/helpers/babel/interopRequireWildcard/interopRequireWildcard.js +37 -37
  63. package/helpers/babel/isNativeFunction/isNativeFunction.js +4 -4
  64. package/helpers/babel/isNativeReflectConstruct/isNativeReflectConstruct.js +21 -21
  65. package/helpers/babel/iterableToArray/iterableToArray.js +7 -7
  66. package/helpers/babel/iterableToArrayLimit/iterableToArrayLimit.js +36 -36
  67. package/helpers/babel/iterableToArrayLimitLoose/iterableToArrayLimitLoose.js +10 -10
  68. package/helpers/babel/jsx/jsx.js +45 -45
  69. package/helpers/babel/maybeArrayLike/maybeArrayLike.js +10 -10
  70. package/helpers/babel/newArrowCheck/newArrowCheck.js +5 -5
  71. package/helpers/babel/nonIterableRest/nonIterableRest.js +5 -5
  72. package/helpers/babel/nonIterableSpread/nonIterableSpread.js +5 -5
  73. package/helpers/babel/objectDestructuringEmpty/objectDestructuringEmpty.js +3 -3
  74. package/helpers/babel/objectSpread/objectSpread.js +23 -23
  75. package/helpers/babel/objectSpread2/objectSpread2.js +33 -33
  76. package/helpers/babel/objectWithoutProperties/objectWithoutProperties.js +19 -19
  77. package/helpers/babel/objectWithoutPropertiesLoose/objectWithoutPropertiesLoose.js +13 -13
  78. package/helpers/babel/possibleConstructorReturn/possibleConstructorReturn.js +10 -10
  79. package/helpers/babel/readOnlyError/readOnlyError.js +4 -4
  80. package/helpers/babel/readme.md +9 -9
  81. package/helpers/babel/set/set.js +44 -44
  82. package/helpers/babel/setPrototypeOf/setPrototypeOf.js +6 -6
  83. package/helpers/babel/skipFirstGeneratorNext/skipFirstGeneratorNext.js +8 -8
  84. package/helpers/babel/slicedToArray/slicedToArray.js +10 -10
  85. package/helpers/babel/slicedToArrayLoose/slicedToArrayLoose.js +13 -13
  86. package/helpers/babel/superPropBase/superPropBase.js +10 -10
  87. package/helpers/babel/taggedTemplateLiteral/taggedTemplateLiteral.js +10 -10
  88. package/helpers/babel/taggedTemplateLiteralLoose/taggedTemplateLiteralLoose.js +7 -7
  89. package/helpers/babel/tdz/tdz.js +4 -4
  90. package/helpers/babel/temporalRef/temporalRef.js +6 -6
  91. package/helpers/babel/temporalUndefined/temporalUndefined.js +3 -3
  92. package/helpers/babel/toArray/toArray.js +10 -10
  93. package/helpers/babel/toConsumableArray/toConsumableArray.js +10 -10
  94. package/helpers/babel/toPrimitive/toPrimitive.js +10 -10
  95. package/helpers/babel/toPropertyKey/toPropertyKey.js +6 -6
  96. package/helpers/babel/typeof/typeof.js +14 -14
  97. package/helpers/babel/unsupportedIterableToArray/unsupportedIterableToArray.js +12 -12
  98. package/helpers/babel/wrapAsyncGenerator/wrapAsyncGenerator.js +8 -8
  99. package/helpers/babel/wrapNativeSuper/wrapNativeSuper.js +30 -30
  100. package/helpers/babel/wrapRegExp/wrapRegExp.js +63 -63
  101. package/helpers/babel/writeOnlyError/writeOnlyError.js +4 -4
  102. package/helpers/regenerator-runtime/regenerator-runtime.js +748 -748
  103. package/package.json +5 -3
  104. package/src/buildProject.js +300 -300
  105. package/src/execute.js +184 -184
  106. package/src/internal/browser-launcher/jsenv-browser-system.js +199 -199
  107. package/src/internal/compiling/babel_plugin_import_assertions.js +121 -100
  108. package/src/internal/compiling/babel_plugin_import_metadata.js +22 -0
  109. package/src/internal/compiling/babel_plugin_import_visitor.js +84 -0
  110. package/src/internal/compiling/compile-directory/getOrGenerateCompiledFile.js +268 -265
  111. package/src/internal/compiling/compile-directory/updateMeta.js +154 -150
  112. package/src/internal/compiling/compile-directory/validateCache.js +265 -265
  113. package/src/internal/compiling/compileFile.js +215 -200
  114. package/src/internal/compiling/compileHtml.js +550 -494
  115. package/src/internal/compiling/createCompiledFileService.js +291 -290
  116. package/src/internal/compiling/html_source_file_service.js +403 -379
  117. package/src/internal/compiling/js-compilation-service/jsenvTransform.js +270 -269
  118. package/src/internal/compiling/jsenvCompilerForHtml.js +300 -293
  119. package/src/internal/compiling/startCompileServer.js +1048 -1131
  120. package/src/internal/compiling/transformResultToCompilationResult.js +220 -217
  121. package/src/internal/executing/coverage/babel_plugin_instrument.js +90 -90
  122. package/src/internal/executing/coverage/reportToCoverage.js +187 -187
  123. package/src/internal/executing/executePlan.js +183 -183
  124. package/src/internal/executing/launchAndExecute.js +458 -450
  125. package/src/internal/runtime/createBrowserRuntime/scanBrowserRuntimeFeatures.js +246 -250
  126. package/src/internal/runtime/createNodeRuntime/scanNodeRuntimeFeatures.js +112 -115
  127. package/src/internal/runtime/s.js +727 -727
  128. package/src/internal/toolbar/jsenv-logo.svg +144 -144
  129. package/src/internal/toolbar/toolbar.main.css +196 -188
  130. package/src/internal/toolbar/toolbar.main.js +227 -228
  131. package/src/internal/url_conversion.js +317 -317
  132. package/src/startExploring.js +309 -309
  133. package/src/internal/compiling/babel_plugin_transform_import_specifier.js +0 -86
  134. package/src/internal/toolbar/animation/animation.css +0 -5
  135. package/src/internal/toolbar/variant/variant.css +0 -3
@@ -1,494 +1,550 @@
1
- /**
2
-
3
- An important concern here:
4
-
5
- All script type="module" will be converted to inline script.
6
- These inline script execution order is non predictible it depends
7
- which one is being done first
8
-
9
- */
10
-
11
- import { createHash } from "crypto"
12
- import { require } from "../require.js"
13
- import { renderNamePattern } from "../renderNamePattern.js"
14
-
15
- // https://github.com/inikulin/parse5/blob/master/packages/parse5/lib/tree-adapters/default.js
16
- // eslint-disable-next-line import/no-unresolved
17
- // const treeAdapter = require("parse5/lib/tree-adapters/default.js")
18
-
19
- export const parseHtmlString = (htmlString) => {
20
- const parse5 = require("parse5")
21
- const htmlAst = parse5.parse(htmlString, { sourceCodeLocationInfo: true })
22
- return htmlAst
23
- }
24
-
25
- export const parseSvgString = (svgString) => {
26
- const parse5 = require("parse5")
27
- const svgAst = parse5.parseFragment(svgString, {
28
- sourceCodeLocationInfo: true,
29
- })
30
- return svgAst
31
- }
32
-
33
- export const stringifyHtmlAst = (htmlAst) => {
34
- const parse5 = require("parse5")
35
- const htmlString = parse5.serialize(htmlAst)
36
- return htmlString
37
- }
38
-
39
- export const findNode = (htmlStringOrAst, predicate) => {
40
- const htmlAst =
41
- typeof htmlStringOrAst === "string"
42
- ? parseHtmlString(htmlStringOrAst)
43
- : htmlStringOrAst
44
- let nodeMatching = null
45
- visitHtmlAst(htmlAst, (node) => {
46
- if (predicate(node)) {
47
- nodeMatching = node
48
- return "stop"
49
- }
50
- return null
51
- })
52
- return nodeMatching
53
- }
54
-
55
- export const findNodes = (htmlString, predicate) => {
56
- const htmlAst = parseHtmlString(htmlString)
57
- const nodes = []
58
- visitHtmlAst(htmlAst, (node) => {
59
- if (predicate(node)) {
60
- nodes.push(node)
61
- }
62
- return null
63
- })
64
- return nodes
65
- }
66
-
67
- export const findNodeByTagName = (htmlString, tagName) =>
68
- findNode(htmlString, (node) => node.nodeName === tagName)
69
-
70
- export const findHtmlNodeById = (htmlString, id) => {
71
- return findNode(htmlString, (node) => {
72
- const idAttribute = getHtmlNodeAttributeByName(node, "id")
73
- return idAttribute && idAttribute.value === id
74
- })
75
- }
76
-
77
- export const findAllNodeByTagName = (htmlString, tagName) =>
78
- findNodes(htmlString, (node) => node.nodeName === tagName)
79
-
80
- export const findFirstImportMapNode = (htmlStringOrAst) =>
81
- findNode(htmlStringOrAst, htmlNodeIsScriptImportmap)
82
-
83
- export const getHtmlNodeAttributeByName = (htmlNode, attributeName) => {
84
- const attrs = htmlNode.attrs
85
- return attrs && attrs.find((attr) => attr.name === attributeName)
86
- }
87
-
88
- export const removeHtmlNodeAttribute = (htmlNode, attributeToRemove) => {
89
- const attrIndex = htmlNode.attrs.indexOf(attributeToRemove)
90
- if (attrIndex === -1) {
91
- return false
92
- }
93
- htmlNode.attrs.splice(attrIndex, 1)
94
- return true
95
- }
96
-
97
- export const addHtmlNodeAttribute = (htmlNode, attributeToSet) => {
98
- if (typeof attributeToSet !== "object") {
99
- throw new TypeError(
100
- `addHtmlNodeAttribute attribute must be an object {name, value}`,
101
- )
102
- }
103
-
104
- const existingAttributeIndex = htmlNode.attrs.findIndex(
105
- (attr) => attr.name === attributeToSet.name,
106
- )
107
- if (existingAttributeIndex === -1) {
108
- htmlNode.attrs.push(attributeToSet)
109
- } else {
110
- htmlNode.attrs[existingAttributeIndex] = attributeToSet
111
- }
112
- }
113
-
114
- export const getHtmlNodeTextNode = (htmlNode) => {
115
- const firstChild = htmlNode.childNodes[0]
116
- return firstChild && firstChild.nodeName === "#text" ? firstChild : null
117
- }
118
-
119
- export const setHtmlNodeText = (htmlNode, textContent) => {
120
- const textNode = getHtmlNodeTextNode(htmlNode)
121
- if (textNode) {
122
- textNode.value = textContent
123
- } else {
124
- const newTextNode = {
125
- nodeName: "#text",
126
- value: textContent,
127
- parentNode: htmlNode,
128
- }
129
- htmlNode.childNodes.splice(0, 0, newTextNode)
130
- }
131
- }
132
-
133
- export const removeHtmlNodeText = (htmlNode) => {
134
- const textNode = getHtmlNodeTextNode(htmlNode)
135
- if (textNode) {
136
- htmlNode.childNodes = []
137
- }
138
- }
139
-
140
- export const removeHtmlNode = (htmlNode) => {
141
- const { childNodes } = htmlNode.parentNode
142
- childNodes.splice(childNodes.indexOf(htmlNode), 1)
143
- }
144
-
145
- export const getHtmlNodeLocation = (htmlNode, htmlAttributeName) => {
146
- const { sourceCodeLocation } = htmlNode
147
- if (!sourceCodeLocation) {
148
- return null
149
- }
150
-
151
- if (!htmlAttributeName) {
152
- const { startLine, startCol } = sourceCodeLocation
153
- return {
154
- line: startLine,
155
- column: startCol,
156
- }
157
- }
158
-
159
- const attributeSourceCodeLocation =
160
- sourceCodeLocation.attrs[htmlAttributeName]
161
- if (!attributeSourceCodeLocation) {
162
- return null
163
- }
164
- const { startLine, startCol } = attributeSourceCodeLocation
165
- return {
166
- line: startLine,
167
- column: startCol,
168
- }
169
- }
170
-
171
- export const findHtmlNode = (htmlAst, predicate) => {
172
- let nodeFound = null
173
- visitHtmlAst(htmlAst, (node) => {
174
- if (predicate(node)) {
175
- nodeFound = node
176
- return "stop"
177
- }
178
- return null
179
- })
180
- return nodeFound
181
- }
182
-
183
- export const htmlNodeIsScriptModule = (htmlNode) => {
184
- if (htmlNode.nodeName !== "script") {
185
- return false
186
- }
187
- const typeAttribute = getHtmlNodeAttributeByName(htmlNode, "type")
188
- if (!typeAttribute) {
189
- return false
190
- }
191
- return typeAttribute.value === "module"
192
- }
193
-
194
- export const htmlNodeIsScriptImportmap = (htmlNode) => {
195
- if (htmlNode.nodeName !== "script") {
196
- return false
197
- }
198
- const typeAttribute = getHtmlNodeAttributeByName(htmlNode, "type")
199
- if (!typeAttribute) {
200
- return false
201
- }
202
- return typeAttribute.value === "importmap"
203
- }
204
-
205
- export const parseSrcset = (srcsetString) => {
206
- const srcsetParts = []
207
- srcsetString.split(",").forEach((set) => {
208
- const [specifier, descriptor] = set.trim().split(" ")
209
- srcsetParts.push({
210
- specifier,
211
- descriptor,
212
- })
213
- })
214
- return srcsetParts
215
- }
216
-
217
- export const stringifySrcset = (srcsetParts) => {
218
- const srcsetString = srcsetParts
219
- .map(({ specifier, descriptor }) => `${specifier} ${descriptor}`)
220
- .join(", ")
221
- return srcsetString
222
- }
223
-
224
- // let's <img>, <link for favicon>, <link for css>, <styles>
225
- // <audio> <video> <picture> supports comes for free by detecting
226
- // <source src> attribute
227
- // if srcset is used we should parse it and collect all src referenced in it
228
- // also <link ref="preload">
229
- // ideally relative iframe should recursively fetch (not needed so lets ignore)
230
- // <svg> ideally looks for external ressources inside them
231
-
232
- // but right now we will focus on: <link href> and <style> tags
233
- // on veut vérifier qu'on les récupere bien
234
- // dans rollup pour chaque css on feras le transformcss + l'ajout des assets reférencés
235
- // pour le style inline on le parse aussi et on le remettra inline dans le html
236
- // ensuite qu'on est capable de les mettre a jour
237
- // ce qui veut dire de mettre a jour link.ref et style.text
238
- export const parseHtmlAstRessources = (htmlAst) => {
239
- const links = []
240
- const styles = []
241
- const scripts = []
242
- const imgs = []
243
- const images = []
244
- const uses = []
245
- const sources = []
246
-
247
- visitHtmlAst(htmlAst, (node) => {
248
- if (node.nodeName === "link") {
249
- links.push(node)
250
- return
251
- }
252
-
253
- if (node.nodeName === "style") {
254
- styles.push(node)
255
- return
256
- }
257
-
258
- if (node.nodeName === "script") {
259
- scripts.push(node)
260
- return
261
- }
262
-
263
- if (node.nodeName === "img") {
264
- imgs.push(node)
265
- return
266
- }
267
-
268
- if (node.nodeName === "image") {
269
- images.push(node)
270
- return
271
- }
272
-
273
- if (node.nodeName === "use") {
274
- uses.push(node)
275
- return
276
- }
277
-
278
- if (node.nodeName === "source") {
279
- sources.push(node)
280
- return
281
- }
282
- })
283
-
284
- return {
285
- links,
286
- styles,
287
- scripts,
288
- imgs,
289
- images,
290
- uses,
291
- sources,
292
- }
293
- }
294
-
295
- export const replaceHtmlNode = (
296
- node,
297
- replacement,
298
- { attributesInherit = true, attributesToIgnore = [] } = {},
299
- ) => {
300
- let newNode
301
- if (typeof replacement === "string") {
302
- newNode = parseHtmlAsSingleElement(replacement)
303
- } else {
304
- newNode = replacement
305
- }
306
-
307
- if (attributesInherit) {
308
- const attributeMap = {}
309
- // inherit attributes except thoos listed in attributesToIgnore
310
- node.attrs.forEach((attribute) => {
311
- if (attributesToIgnore.includes(attribute.name)) {
312
- return
313
- }
314
- attributeMap[attribute.name] = attribute
315
- })
316
- newNode.attrs.forEach((newAttribute) => {
317
- attributeMap[newAttribute.name] = newAttribute
318
- })
319
- const attributes = []
320
- Object.keys(attributeMap).forEach((attributeName) => {
321
- attributes.push(attributeMap[attributeName])
322
- })
323
- newNode.attrs = attributes
324
- }
325
-
326
- replaceNode(node, newNode)
327
- }
328
-
329
- export const manipulateHtmlAst = (htmlAst, { scriptInjections = [] }) => {
330
- if (scriptInjections.length === 0) {
331
- return
332
- }
333
-
334
- const htmlNode = htmlAst.childNodes.find((node) => node.nodeName === "html")
335
- const headNode = htmlNode.childNodes[0]
336
- const bodyNode = htmlNode.childNodes[1]
337
-
338
- const scriptsToPreprendInHead = []
339
- scriptInjections.forEach((script) => {
340
- const scriptExistingInHead = findExistingScript(headNode, script)
341
- if (scriptExistingInHead) {
342
- replaceNode(scriptExistingInHead, scriptToNode(script))
343
- return
344
- }
345
- const scriptExistingInBody = findExistingScript(bodyNode, script)
346
- if (scriptExistingInBody) {
347
- replaceNode(scriptExistingInBody, scriptToNode(script))
348
- return
349
- }
350
- scriptsToPreprendInHead.push(script)
351
- })
352
- const headScriptsFragment = scriptsToFragment(scriptsToPreprendInHead)
353
- insertFragmentBefore(
354
- headNode,
355
- headScriptsFragment,
356
- findChild(headNode, (node) => node.nodeName === "script"),
357
- )
358
- }
359
-
360
- const insertFragmentBefore = (node, fragment, childNode) => {
361
- const { childNodes = [] } = node
362
-
363
- if (childNode) {
364
- const childNodeIndex = childNodes.indexOf(childNode)
365
- node.childNodes = [
366
- ...childNodes.slice(0, childNodeIndex),
367
- ...fragment.childNodes.map((child) => {
368
- return { ...child, parentNode: node }
369
- }),
370
- ...childNodes.slice(childNodeIndex),
371
- ]
372
- } else {
373
- node.childNodes = [
374
- ...childNodes,
375
- ...fragment.childNodes.map((child) => {
376
- return { ...child, parentNode: node }
377
- }),
378
- ]
379
- }
380
- }
381
-
382
- const scriptToNode = (script) => {
383
- return scriptsToFragment([script]).childNodes[0]
384
- }
385
-
386
- const scriptsToFragment = (scripts) => {
387
- const html = scripts.reduce((previous, script) => {
388
- const { text = "", ...attributes } = script
389
- const scriptAttributes = objectToHtmlAttributes(attributes)
390
- return `${previous}<script ${scriptAttributes}>${text}</script>
391
- `
392
- }, "")
393
- const parse5 = require("parse5")
394
- const fragment = parse5.parseFragment(html)
395
- return fragment
396
- }
397
-
398
- const findExistingScript = (node, script) =>
399
- findChild(node, (childNode) => {
400
- return childNode.nodeName === "script" && sameScript(childNode, script)
401
- })
402
-
403
- const findChild = ({ childNodes = [] }, predicate) => childNodes.find(predicate)
404
-
405
- const sameScript = (node, { type = "text/javascript", src }) => {
406
- const typeAttribute = getHtmlNodeAttributeByName(node, "type")
407
- const leftScriptType = typeAttribute ? typeAttribute.value : "text/javascript"
408
- if (leftScriptType !== type) {
409
- return false
410
- }
411
-
412
- const srcAttribute = getHtmlNodeAttributeByName(node, "src")
413
- if (!srcAttribute && src) {
414
- return false
415
- }
416
- if (srcAttribute && srcAttribute.value !== src) {
417
- return false
418
- }
419
-
420
- return true
421
- }
422
-
423
- const objectToHtmlAttributes = (object) => {
424
- return Object.keys(object)
425
- .map((key) => `${key}=${valueToHtmlAttributeValue(object[key])}`)
426
- .join(" ")
427
- }
428
-
429
- const valueToHtmlAttributeValue = (value) => {
430
- if (typeof value === "string") {
431
- return JSON.stringify(value)
432
- }
433
- return `"${JSON.stringify(value)}"`
434
- }
435
-
436
- export const createInlineScriptHash = (script) => {
437
- const hash = createHash("sha256")
438
- hash.update(getHtmlNodeTextNode(script).value)
439
- return hash.digest("hex").slice(0, 8)
440
- }
441
-
442
- export const getUniqueNameForInlineHtmlNode = (node, nodes, pattern) => {
443
- return renderNamePattern(pattern, {
444
- id: () => {
445
- const idAttribute = getHtmlNodeAttributeByName(node, "id")
446
- if (idAttribute) {
447
- return idAttribute.value
448
- }
449
-
450
- const { line, column } = getHtmlNodeLocation(node) || {}
451
- const lineTaken = nodes.some((nodeCandidate) => {
452
- if (nodeCandidate === node) return false
453
- const htmlNodeLocation = getHtmlNodeLocation(nodeCandidate)
454
- if (!htmlNodeLocation) return false
455
- return htmlNodeLocation.line === line
456
- })
457
- if (lineTaken) {
458
- return `${line}.${column}`
459
- }
460
-
461
- return line
462
- },
463
- })
464
- }
465
-
466
- const parseHtmlAsSingleElement = (html) => {
467
- const parse5 = require("parse5")
468
- const fragment = parse5.parseFragment(html)
469
- return fragment.childNodes[0]
470
- }
471
-
472
- const replaceNode = (node, newNode) => {
473
- const { parentNode } = node
474
- const parentNodeChildNodes = parentNode.childNodes
475
- const nodeIndex = parentNodeChildNodes.indexOf(node)
476
- parentNodeChildNodes[nodeIndex] = newNode
477
- }
478
-
479
- export const visitHtmlAst = (htmlAst, callback) => {
480
- const visitNode = (node) => {
481
- const callbackReturnValue = callback(node)
482
- if (callbackReturnValue === "stop") {
483
- return
484
- }
485
- const { childNodes } = node
486
- if (childNodes) {
487
- let i = 0
488
- while (i < childNodes.length) {
489
- visitNode(childNodes[i++])
490
- }
491
- }
492
- }
493
- visitNode(htmlAst)
494
- }
1
+ /**
2
+
3
+ An important concern here:
4
+
5
+ All script type="module" will be converted to inline script.
6
+ These inline script execution order is non predictible it depends
7
+ which one is being done first
8
+
9
+ */
10
+
11
+ import { createHash } from "crypto"
12
+ import { require } from "../require.js"
13
+ import { renderNamePattern } from "../renderNamePattern.js"
14
+
15
+ // https://github.com/inikulin/parse5/blob/master/packages/parse5/lib/tree-adapters/default.js
16
+ // eslint-disable-next-line import/no-unresolved
17
+ // const treeAdapter = require("parse5/lib/tree-adapters/default.js")
18
+
19
+ export const parseHtmlString = (htmlString) => {
20
+ const parse5 = require("parse5")
21
+ const htmlAst = parse5.parse(htmlString, { sourceCodeLocationInfo: true })
22
+ return htmlAst
23
+ }
24
+
25
+ export const parseSvgString = (svgString) => {
26
+ const parse5 = require("parse5")
27
+ const svgAst = parse5.parseFragment(svgString, {
28
+ sourceCodeLocationInfo: true,
29
+ })
30
+ return svgAst
31
+ }
32
+
33
+ export const stringifyHtmlAst = (htmlAst) => {
34
+ const parse5 = require("parse5")
35
+ const htmlString = parse5.serialize(htmlAst)
36
+ return htmlString
37
+ }
38
+
39
+ export const findNode = (htmlStringOrAst, predicate) => {
40
+ const htmlAst =
41
+ typeof htmlStringOrAst === "string"
42
+ ? parseHtmlString(htmlStringOrAst)
43
+ : htmlStringOrAst
44
+ let nodeMatching = null
45
+ visitHtmlAst(htmlAst, (node) => {
46
+ if (predicate(node)) {
47
+ nodeMatching = node
48
+ return "stop"
49
+ }
50
+ return null
51
+ })
52
+ return nodeMatching
53
+ }
54
+
55
+ export const findNodes = (htmlString, predicate) => {
56
+ const htmlAst = parseHtmlString(htmlString)
57
+ const nodes = []
58
+ visitHtmlAst(htmlAst, (node) => {
59
+ if (predicate(node)) {
60
+ nodes.push(node)
61
+ }
62
+ return null
63
+ })
64
+ return nodes
65
+ }
66
+
67
+ export const findNodeByTagName = (htmlString, tagName) =>
68
+ findNode(htmlString, (node) => node.nodeName === tagName)
69
+
70
+ export const findHtmlNodeById = (htmlString, id) => {
71
+ return findNode(htmlString, (node) => {
72
+ const idAttribute = getHtmlNodeAttributeByName(node, "id")
73
+ return idAttribute && idAttribute.value === id
74
+ })
75
+ }
76
+
77
+ export const findAllNodeByTagName = (htmlString, tagName) =>
78
+ findNodes(htmlString, (node) => node.nodeName === tagName)
79
+
80
+ export const findFirstImportMapNode = (htmlStringOrAst) =>
81
+ findNode(htmlStringOrAst, htmlNodeIsScriptImportmap)
82
+
83
+ export const getHtmlNodeAttributeByName = (htmlNode, attributeName) => {
84
+ const attrs = htmlNode.attrs
85
+ return attrs && attrs.find((attr) => attr.name === attributeName)
86
+ }
87
+
88
+ export const removeHtmlNodeAttribute = (htmlNode, attributeToRemove) => {
89
+ const attrIndex = htmlNode.attrs.indexOf(attributeToRemove)
90
+ if (attrIndex === -1) {
91
+ return false
92
+ }
93
+ htmlNode.attrs.splice(attrIndex, 1)
94
+ return true
95
+ }
96
+
97
+ export const addHtmlNodeAttribute = (htmlNode, attributeToSet) => {
98
+ if (typeof attributeToSet !== "object") {
99
+ throw new TypeError(
100
+ `addHtmlNodeAttribute attribute must be an object {name, value}`,
101
+ )
102
+ }
103
+
104
+ const existingAttributeIndex = htmlNode.attrs.findIndex(
105
+ (attr) => attr.name === attributeToSet.name,
106
+ )
107
+ if (existingAttributeIndex === -1) {
108
+ htmlNode.attrs.push(attributeToSet)
109
+ } else {
110
+ htmlNode.attrs[existingAttributeIndex] = attributeToSet
111
+ }
112
+ }
113
+
114
+ export const getHtmlNodeTextNode = (htmlNode) => {
115
+ const firstChild = htmlNode.childNodes[0]
116
+ return firstChild && firstChild.nodeName === "#text" ? firstChild : null
117
+ }
118
+
119
+ export const setHtmlNodeText = (htmlNode, textContent) => {
120
+ const textNode = getHtmlNodeTextNode(htmlNode)
121
+ if (textNode) {
122
+ textNode.value = textContent
123
+ } else {
124
+ const newTextNode = {
125
+ nodeName: "#text",
126
+ value: textContent,
127
+ parentNode: htmlNode,
128
+ }
129
+ htmlNode.childNodes.splice(0, 0, newTextNode)
130
+ }
131
+ }
132
+
133
+ export const removeHtmlNodeText = (htmlNode) => {
134
+ const textNode = getHtmlNodeTextNode(htmlNode)
135
+ if (textNode) {
136
+ htmlNode.childNodes = []
137
+ }
138
+ }
139
+
140
+ export const removeHtmlNode = (htmlNode) => {
141
+ const { childNodes } = htmlNode.parentNode
142
+ childNodes.splice(childNodes.indexOf(htmlNode), 1)
143
+ }
144
+
145
+ export const getHtmlNodeLocation = (htmlNode, htmlAttributeName) => {
146
+ const { sourceCodeLocation } = htmlNode
147
+ if (!sourceCodeLocation) {
148
+ return null
149
+ }
150
+
151
+ if (!htmlAttributeName) {
152
+ const { startLine, startCol } = sourceCodeLocation
153
+ return {
154
+ line: startLine,
155
+ column: startCol,
156
+ }
157
+ }
158
+
159
+ const attributeSourceCodeLocation =
160
+ sourceCodeLocation.attrs[htmlAttributeName]
161
+ if (!attributeSourceCodeLocation) {
162
+ return null
163
+ }
164
+ const { startLine, startCol } = attributeSourceCodeLocation
165
+ return {
166
+ line: startLine,
167
+ column: startCol,
168
+ }
169
+ }
170
+
171
+ export const findHtmlNode = (htmlAst, predicate) => {
172
+ let nodeFound = null
173
+ visitHtmlAst(htmlAst, (node) => {
174
+ if (predicate(node)) {
175
+ nodeFound = node
176
+ return "stop"
177
+ }
178
+ return null
179
+ })
180
+ return nodeFound
181
+ }
182
+
183
+ export const htmlNodeIsScriptModule = (htmlNode) => {
184
+ if (htmlNode.nodeName !== "script") {
185
+ return false
186
+ }
187
+ const typeAttribute = getHtmlNodeAttributeByName(htmlNode, "type")
188
+ if (!typeAttribute) {
189
+ return false
190
+ }
191
+ return typeAttribute.value === "module"
192
+ }
193
+
194
+ export const htmlNodeIsScriptImportmap = (htmlNode) => {
195
+ if (htmlNode.nodeName !== "script") {
196
+ return false
197
+ }
198
+ const typeAttribute = getHtmlNodeAttributeByName(htmlNode, "type")
199
+ if (!typeAttribute) {
200
+ return false
201
+ }
202
+ return typeAttribute.value === "importmap"
203
+ }
204
+
205
+ export const parseSrcset = (srcsetString) => {
206
+ const srcsetParts = []
207
+ srcsetString.split(",").forEach((set) => {
208
+ const [specifier, descriptor] = set.trim().split(" ")
209
+ srcsetParts.push({
210
+ specifier,
211
+ descriptor,
212
+ })
213
+ })
214
+ return srcsetParts
215
+ }
216
+
217
+ export const stringifySrcset = (srcsetParts) => {
218
+ const srcsetString = srcsetParts
219
+ .map(({ specifier, descriptor }) => `${specifier} ${descriptor}`)
220
+ .join(", ")
221
+ return srcsetString
222
+ }
223
+
224
+ // <img>, <link for favicon>, <link for css>, <styles>
225
+ // <audio> <video> <picture> supports comes for free by detecting
226
+ // <source src> attribute
227
+ // ideally relative iframe should recursively fetch (not needed so lets ignore)
228
+ export const parseHtmlAstRessources = (htmlAst) => {
229
+ const links = []
230
+ const styles = []
231
+ const scripts = []
232
+ const imgs = []
233
+ const images = []
234
+ const uses = []
235
+ const sources = []
236
+
237
+ visitHtmlAst(htmlAst, (node) => {
238
+ if (node.nodeName === "link") {
239
+ links.push(node)
240
+ return
241
+ }
242
+
243
+ if (node.nodeName === "style") {
244
+ styles.push(node)
245
+ return
246
+ }
247
+
248
+ if (node.nodeName === "script") {
249
+ scripts.push(node)
250
+ return
251
+ }
252
+
253
+ if (node.nodeName === "img") {
254
+ imgs.push(node)
255
+ return
256
+ }
257
+
258
+ if (node.nodeName === "image") {
259
+ images.push(node)
260
+ return
261
+ }
262
+
263
+ if (node.nodeName === "use") {
264
+ uses.push(node)
265
+ return
266
+ }
267
+
268
+ if (node.nodeName === "source") {
269
+ sources.push(node)
270
+ return
271
+ }
272
+ })
273
+
274
+ return {
275
+ links,
276
+ styles,
277
+ scripts,
278
+ imgs,
279
+ images,
280
+ uses,
281
+ sources,
282
+ }
283
+ }
284
+
285
+ export const collectHtmlDependenciesFromAst = (htmlAst) => {
286
+ const { links, scripts, imgs, images, uses, sources } =
287
+ parseHtmlAstRessources(htmlAst)
288
+
289
+ const dependencies = []
290
+ const visitSrcAttribute = (htmlNode) => {
291
+ const srcAttribute = getHtmlNodeAttributeByName(htmlNode, "src")
292
+ const src = srcAttribute ? srcAttribute.value : undefined
293
+ if (src) {
294
+ dependencies.push({
295
+ htmlNode,
296
+ attribute: srcAttribute,
297
+ specifier: src,
298
+ })
299
+ }
300
+ }
301
+ const visitHrefAttribute = (htmlNode) => {
302
+ const hrefAttribute = getHtmlNodeAttributeByName(htmlNode, "href")
303
+ const href = hrefAttribute ? hrefAttribute.value : undefined
304
+ if (href && href[0] !== "#") {
305
+ dependencies.push({
306
+ htmlNode,
307
+ attribute: hrefAttribute,
308
+ specifier: href,
309
+ })
310
+ }
311
+ }
312
+ const visitSrcSetAttribute = (htmlNode) => {
313
+ const srcsetAttribute = getHtmlNodeAttributeByName(htmlNode, "srcset")
314
+ if (srcsetAttribute) {
315
+ const srcsetParts = parseSrcset(srcsetAttribute.value)
316
+ srcsetParts.forEeach(({ specifier }) => {
317
+ dependencies.push({
318
+ htmlNode,
319
+ attribute: srcsetAttribute,
320
+ specifier,
321
+ })
322
+ })
323
+ }
324
+ }
325
+
326
+ links.forEach((link) => {
327
+ visitHrefAttribute(link)
328
+ })
329
+ scripts.forEach((script) => {
330
+ visitSrcAttribute(script)
331
+ })
332
+ imgs.forEach((img) => {
333
+ visitSrcAttribute(img)
334
+ visitSrcSetAttribute(img)
335
+ })
336
+ sources.forEach((source) => {
337
+ visitSrcAttribute(source)
338
+ visitSrcSetAttribute(source)
339
+ })
340
+ // svg <image> tag
341
+ images.forEach((image) => {
342
+ visitHrefAttribute(image)
343
+ })
344
+ uses.forEach((use) => {
345
+ visitHrefAttribute(use)
346
+ })
347
+
348
+ return dependencies
349
+ }
350
+
351
+ export const replaceHtmlNode = (
352
+ node,
353
+ replacement,
354
+ { attributesInherit = true, attributesToIgnore = [] } = {},
355
+ ) => {
356
+ let newNode
357
+ if (typeof replacement === "string") {
358
+ newNode = parseHtmlAsSingleElement(replacement)
359
+ } else {
360
+ newNode = replacement
361
+ }
362
+
363
+ if (attributesInherit) {
364
+ const attributeMap = {}
365
+ // inherit attributes except thoos listed in attributesToIgnore
366
+ node.attrs.forEach((attribute) => {
367
+ if (attributesToIgnore.includes(attribute.name)) {
368
+ return
369
+ }
370
+ attributeMap[attribute.name] = attribute
371
+ })
372
+ newNode.attrs.forEach((newAttribute) => {
373
+ attributeMap[newAttribute.name] = newAttribute
374
+ })
375
+ const attributes = []
376
+ Object.keys(attributeMap).forEach((attributeName) => {
377
+ attributes.push(attributeMap[attributeName])
378
+ })
379
+ newNode.attrs = attributes
380
+ }
381
+
382
+ replaceNode(node, newNode)
383
+ }
384
+
385
+ export const manipulateHtmlAst = (htmlAst, { scriptInjections = [] }) => {
386
+ if (scriptInjections.length === 0) {
387
+ return
388
+ }
389
+
390
+ const htmlNode = htmlAst.childNodes.find((node) => node.nodeName === "html")
391
+ const headNode = htmlNode.childNodes[0]
392
+ const bodyNode = htmlNode.childNodes[1]
393
+
394
+ const scriptsToPreprendInHead = []
395
+ scriptInjections.forEach((script) => {
396
+ const scriptExistingInHead = findExistingScript(headNode, script)
397
+ if (scriptExistingInHead) {
398
+ replaceNode(scriptExistingInHead, scriptToNode(script))
399
+ return
400
+ }
401
+ const scriptExistingInBody = findExistingScript(bodyNode, script)
402
+ if (scriptExistingInBody) {
403
+ replaceNode(scriptExistingInBody, scriptToNode(script))
404
+ return
405
+ }
406
+ scriptsToPreprendInHead.push(script)
407
+ })
408
+ const headScriptsFragment = scriptsToFragment(scriptsToPreprendInHead)
409
+ insertFragmentBefore(
410
+ headNode,
411
+ headScriptsFragment,
412
+ findChild(headNode, (node) => node.nodeName === "script"),
413
+ )
414
+ }
415
+
416
+ const insertFragmentBefore = (node, fragment, childNode) => {
417
+ const { childNodes = [] } = node
418
+
419
+ if (childNode) {
420
+ const childNodeIndex = childNodes.indexOf(childNode)
421
+ node.childNodes = [
422
+ ...childNodes.slice(0, childNodeIndex),
423
+ ...fragment.childNodes.map((child) => {
424
+ return { ...child, parentNode: node }
425
+ }),
426
+ ...childNodes.slice(childNodeIndex),
427
+ ]
428
+ } else {
429
+ node.childNodes = [
430
+ ...childNodes,
431
+ ...fragment.childNodes.map((child) => {
432
+ return { ...child, parentNode: node }
433
+ }),
434
+ ]
435
+ }
436
+ }
437
+
438
+ const scriptToNode = (script) => {
439
+ return scriptsToFragment([script]).childNodes[0]
440
+ }
441
+
442
+ const scriptsToFragment = (scripts) => {
443
+ const html = scripts.reduce((previous, script) => {
444
+ const { text = "", ...attributes } = script
445
+ const scriptAttributes = objectToHtmlAttributes(attributes)
446
+ return `${previous}<script ${scriptAttributes}>${text}</script>
447
+ `
448
+ }, "")
449
+ const parse5 = require("parse5")
450
+ const fragment = parse5.parseFragment(html)
451
+ return fragment
452
+ }
453
+
454
+ const findExistingScript = (node, script) =>
455
+ findChild(node, (childNode) => {
456
+ return childNode.nodeName === "script" && sameScript(childNode, script)
457
+ })
458
+
459
+ const findChild = ({ childNodes = [] }, predicate) => childNodes.find(predicate)
460
+
461
+ const sameScript = (node, { type = "text/javascript", src }) => {
462
+ const typeAttribute = getHtmlNodeAttributeByName(node, "type")
463
+ const leftScriptType = typeAttribute ? typeAttribute.value : "text/javascript"
464
+ if (leftScriptType !== type) {
465
+ return false
466
+ }
467
+
468
+ const srcAttribute = getHtmlNodeAttributeByName(node, "src")
469
+ if (!srcAttribute && src) {
470
+ return false
471
+ }
472
+ if (srcAttribute && srcAttribute.value !== src) {
473
+ return false
474
+ }
475
+
476
+ return true
477
+ }
478
+
479
+ const objectToHtmlAttributes = (object) => {
480
+ return Object.keys(object)
481
+ .map((key) => `${key}=${valueToHtmlAttributeValue(object[key])}`)
482
+ .join(" ")
483
+ }
484
+
485
+ const valueToHtmlAttributeValue = (value) => {
486
+ if (typeof value === "string") {
487
+ return JSON.stringify(value)
488
+ }
489
+ return `"${JSON.stringify(value)}"`
490
+ }
491
+
492
+ export const createInlineScriptHash = (script) => {
493
+ const hash = createHash("sha256")
494
+ hash.update(getHtmlNodeTextNode(script).value)
495
+ return hash.digest("hex").slice(0, 8)
496
+ }
497
+
498
+ export const getUniqueNameForInlineHtmlNode = (node, nodes, pattern) => {
499
+ return renderNamePattern(pattern, {
500
+ id: () => {
501
+ const idAttribute = getHtmlNodeAttributeByName(node, "id")
502
+ if (idAttribute) {
503
+ return idAttribute.value
504
+ }
505
+
506
+ const { line, column } = getHtmlNodeLocation(node) || {}
507
+ const lineTaken = nodes.some((nodeCandidate) => {
508
+ if (nodeCandidate === node) return false
509
+ const htmlNodeLocation = getHtmlNodeLocation(nodeCandidate)
510
+ if (!htmlNodeLocation) return false
511
+ return htmlNodeLocation.line === line
512
+ })
513
+ if (lineTaken) {
514
+ return `${line}.${column}`
515
+ }
516
+
517
+ return line
518
+ },
519
+ })
520
+ }
521
+
522
+ const parseHtmlAsSingleElement = (html) => {
523
+ const parse5 = require("parse5")
524
+ const fragment = parse5.parseFragment(html)
525
+ return fragment.childNodes[0]
526
+ }
527
+
528
+ const replaceNode = (node, newNode) => {
529
+ const { parentNode } = node
530
+ const parentNodeChildNodes = parentNode.childNodes
531
+ const nodeIndex = parentNodeChildNodes.indexOf(node)
532
+ parentNodeChildNodes[nodeIndex] = newNode
533
+ }
534
+
535
+ export const visitHtmlAst = (htmlAst, callback) => {
536
+ const visitNode = (node) => {
537
+ const callbackReturnValue = callback(node)
538
+ if (callbackReturnValue === "stop") {
539
+ return
540
+ }
541
+ const { childNodes } = node
542
+ if (childNodes) {
543
+ let i = 0
544
+ while (i < childNodes.length) {
545
+ visitNode(childNodes[i++])
546
+ }
547
+ }
548
+ }
549
+ visitNode(htmlAst)
550
+ }