@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.
- package/dist/jsenv_browser_system.js +46 -39
- package/dist/jsenv_browser_system.js.map +14 -14
- package/dist/jsenv_compile_proxy.js.map +6 -6
- package/dist/jsenv_exploring_index.js.map +5 -5
- package/dist/jsenv_exploring_redirector.js.map +12 -12
- package/dist/jsenv_toolbar.js.map +7 -7
- package/dist/jsenv_toolbar_injector.js.map +5 -5
- package/helpers/babel/.eslintrc.cjs +24 -24
- package/helpers/babel/AsyncGenerator/AsyncGenerator.js +81 -81
- package/helpers/babel/AwaitValue/AwaitValue.js +3 -3
- package/helpers/babel/applyDecoratorDescriptor/applyDecoratorDescriptor.js +33 -33
- package/helpers/babel/arrayLikeToArray/arrayLikeToArray.js +7 -7
- package/helpers/babel/arrayWithHoles/arrayWithHoles.js +4 -4
- package/helpers/babel/arrayWithoutHoles/arrayWithoutHoles.js +6 -6
- package/helpers/babel/assertThisInitialized/assertThisInitialized.js +7 -7
- package/helpers/babel/asyncGeneratorDelegate/asyncGeneratorDelegate.js +40 -40
- package/helpers/babel/asyncIterator/asyncIterator.js +12 -12
- package/helpers/babel/asyncToGenerator/asyncToGenerator.js +34 -34
- package/helpers/babel/awaitAsyncGenerator/awaitAsyncGenerator.js +5 -5
- package/helpers/babel/classApplyDescriptorDestructureSet/classApplyDescriptorDestructureSet.js +20 -20
- package/helpers/babel/classApplyDescriptorGet/classApplyDescriptorGet.js +6 -6
- package/helpers/babel/classApplyDescriptorSet/classApplyDescriptorSet.js +13 -13
- package/helpers/babel/classCallCheck/classCallCheck.js +5 -5
- package/helpers/babel/classCheckPrivateStaticAccess/classCheckPrivateStaticAccess.js +5 -5
- package/helpers/babel/classCheckPrivateStaticFieldDescriptor/classCheckPrivateStaticFieldDescriptor.js +6 -6
- package/helpers/babel/classExtractFieldDescriptor/classExtractFieldDescriptor.js +7 -7
- package/helpers/babel/classNameTDZError/classNameTDZError.js +4 -4
- package/helpers/babel/classPrivateFieldDestructureSet/classPrivateFieldDestructureSet.js +7 -7
- package/helpers/babel/classPrivateFieldGet/classPrivateFieldGet.js +7 -7
- package/helpers/babel/classPrivateFieldLooseBase/classPrivateFieldLooseBase.js +6 -6
- package/helpers/babel/classPrivateFieldLooseKey/classPrivateFieldLooseKey.js +5 -5
- package/helpers/babel/classPrivateFieldSet/classPrivateFieldSet.js +8 -8
- package/helpers/babel/classPrivateMethodGet/classPrivateMethodGet.js +6 -6
- package/helpers/babel/classPrivateMethodSet/classPrivateMethodSet.js +3 -3
- package/helpers/babel/classStaticPrivateFieldSpecGet/classStaticPrivateFieldSpecGet.js +9 -9
- package/helpers/babel/classStaticPrivateFieldSpecSet/classStaticPrivateFieldSpecSet.js +15 -15
- package/helpers/babel/classStaticPrivateMethodGet/classStaticPrivateMethodGet.js +6 -6
- package/helpers/babel/classStaticPrivateMethodSet/classStaticPrivateMethodSet.js +3 -3
- package/helpers/babel/construct/construct.js +16 -16
- package/helpers/babel/createClass/createClass.js +15 -15
- package/helpers/babel/createForOfIteratorHelper/createForOfIteratorHelper.js +60 -60
- package/helpers/babel/createForOfIteratorHelperLoose/createForOfIteratorHelperLoose.js +23 -23
- package/helpers/babel/createRawReactElement/createRawReactElement.js +50 -50
- package/helpers/babel/createSuper/createSuper.js +22 -22
- package/helpers/babel/decorate/decorate.js +403 -403
- package/helpers/babel/defaults/defaults.js +11 -11
- package/helpers/babel/defineEnumerableProperties/defineEnumerableProperties.js +23 -23
- package/helpers/babel/defineProperty/defineProperty.js +18 -18
- package/helpers/babel/extends/extends.js +14 -14
- package/helpers/babel/get/get.js +13 -13
- package/helpers/babel/getPrototypeOf/getPrototypeOf.js +4 -4
- package/helpers/babel/inherits/inherits.js +15 -15
- package/helpers/babel/inheritsLoose/inheritsLoose.js +7 -7
- package/helpers/babel/initializerDefineProperty/initializerDefineProperty.js +10 -10
- package/helpers/babel/initializerWarningHelper/initializerWarningHelper.js +6 -6
- package/helpers/babel/instanceof/instanceof.js +6 -6
- package/helpers/babel/interopRequireDefault/interopRequireDefault.js +3 -3
- package/helpers/babel/interopRequireWildcard/interopRequireWildcard.js +37 -37
- package/helpers/babel/isNativeFunction/isNativeFunction.js +4 -4
- package/helpers/babel/isNativeReflectConstruct/isNativeReflectConstruct.js +21 -21
- package/helpers/babel/iterableToArray/iterableToArray.js +7 -7
- package/helpers/babel/iterableToArrayLimit/iterableToArrayLimit.js +36 -36
- package/helpers/babel/iterableToArrayLimitLoose/iterableToArrayLimitLoose.js +10 -10
- package/helpers/babel/jsx/jsx.js +45 -45
- package/helpers/babel/maybeArrayLike/maybeArrayLike.js +10 -10
- package/helpers/babel/newArrowCheck/newArrowCheck.js +5 -5
- package/helpers/babel/nonIterableRest/nonIterableRest.js +5 -5
- package/helpers/babel/nonIterableSpread/nonIterableSpread.js +5 -5
- package/helpers/babel/objectDestructuringEmpty/objectDestructuringEmpty.js +3 -3
- package/helpers/babel/objectSpread/objectSpread.js +23 -23
- package/helpers/babel/objectSpread2/objectSpread2.js +33 -33
- package/helpers/babel/objectWithoutProperties/objectWithoutProperties.js +19 -19
- package/helpers/babel/objectWithoutPropertiesLoose/objectWithoutPropertiesLoose.js +13 -13
- package/helpers/babel/possibleConstructorReturn/possibleConstructorReturn.js +10 -10
- package/helpers/babel/readOnlyError/readOnlyError.js +4 -4
- package/helpers/babel/readme.md +9 -9
- package/helpers/babel/set/set.js +44 -44
- package/helpers/babel/setPrototypeOf/setPrototypeOf.js +6 -6
- package/helpers/babel/skipFirstGeneratorNext/skipFirstGeneratorNext.js +8 -8
- package/helpers/babel/slicedToArray/slicedToArray.js +10 -10
- package/helpers/babel/slicedToArrayLoose/slicedToArrayLoose.js +13 -13
- package/helpers/babel/superPropBase/superPropBase.js +10 -10
- package/helpers/babel/taggedTemplateLiteral/taggedTemplateLiteral.js +10 -10
- package/helpers/babel/taggedTemplateLiteralLoose/taggedTemplateLiteralLoose.js +7 -7
- package/helpers/babel/tdz/tdz.js +4 -4
- package/helpers/babel/temporalRef/temporalRef.js +6 -6
- package/helpers/babel/temporalUndefined/temporalUndefined.js +3 -3
- package/helpers/babel/toArray/toArray.js +10 -10
- package/helpers/babel/toConsumableArray/toConsumableArray.js +10 -10
- package/helpers/babel/toPrimitive/toPrimitive.js +10 -10
- package/helpers/babel/toPropertyKey/toPropertyKey.js +6 -6
- package/helpers/babel/typeof/typeof.js +14 -14
- package/helpers/babel/unsupportedIterableToArray/unsupportedIterableToArray.js +12 -12
- package/helpers/babel/wrapAsyncGenerator/wrapAsyncGenerator.js +8 -8
- package/helpers/babel/wrapNativeSuper/wrapNativeSuper.js +30 -30
- package/helpers/babel/wrapRegExp/wrapRegExp.js +63 -63
- package/helpers/babel/writeOnlyError/writeOnlyError.js +4 -4
- package/helpers/regenerator-runtime/regenerator-runtime.js +748 -748
- package/{LICENSE → license} +21 -21
- package/package.json +2 -2
- package/src/buildProject.js +300 -300
- package/src/execute.js +184 -184
- package/src/internal/browser-launcher/jsenv-browser-system.js +203 -199
- package/src/internal/building/buildUsingRollup.js +2 -10
- package/src/internal/compiling/babel_plugin_import_assertions.js +121 -121
- package/src/internal/compiling/babel_plugin_import_metadata.js +22 -22
- package/src/internal/compiling/babel_plugin_import_visitor.js +84 -84
- package/src/internal/compiling/compile-directory/getOrGenerateCompiledFile.js +268 -268
- package/src/internal/compiling/compile-directory/updateMeta.js +154 -154
- package/src/internal/compiling/compile-directory/validateCache.js +265 -265
- package/src/internal/compiling/compileFile.js +233 -224
- package/src/internal/compiling/compileHtml.js +550 -550
- package/src/internal/compiling/createCompiledFileService.js +291 -291
- package/src/internal/compiling/html_source_file_service.js +403 -404
- package/src/internal/compiling/js-compilation-service/jsenvTransform.js +272 -270
- package/src/internal/compiling/jsenvCompilerForHtml.js +374 -308
- package/src/internal/compiling/jsenvCompilerForJavaScript.js +2 -0
- package/src/internal/compiling/startCompileServer.js +1086 -1048
- package/src/internal/compiling/transformResultToCompilationResult.js +220 -220
- package/src/internal/executing/coverage/babel_plugin_instrument.js +90 -90
- package/src/internal/executing/coverage/reportToCoverage.js +193 -187
- package/src/internal/executing/executePlan.js +183 -183
- package/src/internal/executing/launchAndExecute.js +458 -458
- package/src/internal/generateGroupMap/featuresCompatMap.js +29 -0
- package/src/internal/generateGroupMap/jsenvBabelPluginCompatMap.js +1 -8
- package/src/internal/runtime/createBrowserRuntime/scanBrowserRuntimeFeatures.js +246 -246
- package/src/internal/runtime/createNodeRuntime/scanNodeRuntimeFeatures.js +112 -112
- package/src/internal/runtime/s.js +727 -727
- package/src/internal/toolbar/jsenv-logo.svg +144 -144
- package/src/internal/toolbar/toolbar.main.css +196 -196
- package/src/internal/toolbar/toolbar.main.js +227 -227
- package/src/internal/url_conversion.js +317 -317
- package/src/startExploring.js +309 -309
|
@@ -1,550 +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
|
-
// <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
|
-
}
|
|
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
|
+
}
|