@jsenv/ast 6.1.4 → 6.2.1
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/package.json +2 -2
- package/src/html/html_node.js +224 -52
- package/src/html/html_node_text.js +17 -10
- package/src/main.js +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/ast",
|
|
3
|
-
"version": "6.1
|
|
3
|
+
"version": "6.2.1",
|
|
4
4
|
"description": "Parse and transform HTML, CSS and JS",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"test": "node --conditions=development ./scripts/test.mjs"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@babel/parser": "7.24.
|
|
36
|
+
"@babel/parser": "7.24.8",
|
|
37
37
|
"@jsenv/urls": "2.2.10",
|
|
38
38
|
"acorn": "8.12.1",
|
|
39
39
|
"acorn-import-attributes": "1.9.5",
|
package/src/html/html_node.js
CHANGED
|
@@ -18,10 +18,10 @@ export const removeHtmlNode = (htmlNode) => {
|
|
|
18
18
|
childNodes.splice(childNodes.indexOf(htmlNode), 1);
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
export const createHtmlNode = ({ tagName,
|
|
21
|
+
export const createHtmlNode = ({ tagName, children = "", ...rest }) => {
|
|
22
22
|
const html = `<${tagName} ${stringifyAttributes(
|
|
23
23
|
rest,
|
|
24
|
-
)}>${
|
|
24
|
+
)}>${children}</${tagName}>`;
|
|
25
25
|
const fragment = parseFragment(html);
|
|
26
26
|
return fragment.childNodes[0];
|
|
27
27
|
};
|
|
@@ -46,10 +46,108 @@ export const injectHtmlNode = (htmlAst, node, jsenvPluginName = "jsenv") => {
|
|
|
46
46
|
if (after) {
|
|
47
47
|
insertHtmlNodeAfter(node, after);
|
|
48
48
|
} else {
|
|
49
|
-
|
|
49
|
+
injectWithLineBreakAndIndent(node, bodyNode, 0);
|
|
50
50
|
}
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
+
export const injectJsenvScript = (
|
|
54
|
+
htmlAst,
|
|
55
|
+
{ type, src, content, initCall, pluginName = "jsenv" },
|
|
56
|
+
) => {
|
|
57
|
+
const jsenvScriptsNode = getJsenvScriptsNode(htmlAst);
|
|
58
|
+
if (type === "module") {
|
|
59
|
+
if (src) {
|
|
60
|
+
if (initCall) {
|
|
61
|
+
const paramsSource = stringifyParams(initCall.params, " ");
|
|
62
|
+
const inlineScriptNode = createHtmlNode({
|
|
63
|
+
"tagName": "script",
|
|
64
|
+
"type": "module",
|
|
65
|
+
"jsenv-injected-by": pluginName,
|
|
66
|
+
"children": `import { ${initCall.callee} } from ${src};
|
|
67
|
+
|
|
68
|
+
${initCall.callee}({
|
|
69
|
+
${paramsSource}
|
|
70
|
+
});`,
|
|
71
|
+
});
|
|
72
|
+
insertHtmlNodeInside(inlineScriptNode, jsenvScriptsNode);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const remoteScriptNode = createHtmlNode({
|
|
76
|
+
"tagName": "script",
|
|
77
|
+
"type": "module",
|
|
78
|
+
src,
|
|
79
|
+
"jsenv-injected-by": pluginName,
|
|
80
|
+
});
|
|
81
|
+
insertHtmlNodeInside(remoteScriptNode, jsenvScriptsNode);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const inlineScriptNode = createHtmlNode({
|
|
85
|
+
"tagName": "script",
|
|
86
|
+
"type": "module",
|
|
87
|
+
"jsenv-injected-by": pluginName,
|
|
88
|
+
"children": content,
|
|
89
|
+
});
|
|
90
|
+
insertHtmlNodeInside(jsenvScriptsNode, inlineScriptNode);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (src) {
|
|
94
|
+
const remoteScriptNode = createHtmlNode({
|
|
95
|
+
"tagName": "script",
|
|
96
|
+
src,
|
|
97
|
+
"jsenv-injected-by": pluginName,
|
|
98
|
+
});
|
|
99
|
+
insertHtmlNodeInside(remoteScriptNode, jsenvScriptsNode);
|
|
100
|
+
if (initCall) {
|
|
101
|
+
const paramsSource = stringifyParams(initCall.params, " ");
|
|
102
|
+
const inlineScriptNode = createHtmlNode({
|
|
103
|
+
"tagName": "script",
|
|
104
|
+
"jsenv-injected-by": pluginName,
|
|
105
|
+
"children": `${initCall.callee}({
|
|
106
|
+
${paramsSource}
|
|
107
|
+
});`,
|
|
108
|
+
});
|
|
109
|
+
insertHtmlNodeInside(inlineScriptNode, jsenvScriptsNode);
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const inlineScriptNode = createHtmlNode({
|
|
114
|
+
"tagName": "script",
|
|
115
|
+
"jsenv-injected-by": pluginName,
|
|
116
|
+
"children": content,
|
|
117
|
+
});
|
|
118
|
+
insertHtmlNodeInside(inlineScriptNode, jsenvScriptsNode);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const getJsenvScriptsNode = (htmlAst) => {
|
|
122
|
+
// get or insert <jsenv-scripts>
|
|
123
|
+
let jsenvScripts = findHtmlNode(
|
|
124
|
+
htmlAst,
|
|
125
|
+
(node) => node.nodeName === "jsenv-scripts",
|
|
126
|
+
);
|
|
127
|
+
if (jsenvScripts) {
|
|
128
|
+
return jsenvScripts;
|
|
129
|
+
}
|
|
130
|
+
jsenvScripts = createHtmlNode({
|
|
131
|
+
tagName: "jsenv-scripts",
|
|
132
|
+
});
|
|
133
|
+
getAsFirstJsModuleInjector(htmlAst, {
|
|
134
|
+
anyScript: true,
|
|
135
|
+
})(jsenvScripts);
|
|
136
|
+
return jsenvScripts;
|
|
137
|
+
};
|
|
138
|
+
const stringifyParams = (params, prefix = "") => {
|
|
139
|
+
const source = JSON.stringify(params, null, prefix);
|
|
140
|
+
if (prefix.length) {
|
|
141
|
+
// remove leading "{\n"
|
|
142
|
+
// remove leading prefix
|
|
143
|
+
// remove trailing "\n}"
|
|
144
|
+
return source.slice(2 + prefix.length, -2);
|
|
145
|
+
}
|
|
146
|
+
// remove leading "{"
|
|
147
|
+
// remove trailing "}"
|
|
148
|
+
return source.slice(1, -1);
|
|
149
|
+
};
|
|
150
|
+
|
|
53
151
|
export const injectHtmlNodeAsEarlyAsPossible = (
|
|
54
152
|
htmlAst,
|
|
55
153
|
node,
|
|
@@ -68,6 +166,7 @@ export const injectHtmlNodeAsEarlyAsPossible = (
|
|
|
68
166
|
// - after any <link>
|
|
69
167
|
// - but before first <link rel="modulepreload">
|
|
70
168
|
// - and before <script type="module">
|
|
169
|
+
// - and after any <meta>
|
|
71
170
|
const isImportmap = type === "importmap";
|
|
72
171
|
if (isImportmap) {
|
|
73
172
|
let after = headNode.childNodes[0];
|
|
@@ -85,48 +184,23 @@ export const injectHtmlNodeAsEarlyAsPossible = (
|
|
|
85
184
|
) {
|
|
86
185
|
return insertHtmlNodeBefore(node, child);
|
|
87
186
|
}
|
|
187
|
+
if (child.nodeName === "meta") {
|
|
188
|
+
after = child;
|
|
189
|
+
}
|
|
88
190
|
}
|
|
89
191
|
if (after) {
|
|
90
192
|
return insertHtmlNodeAfter(node, after);
|
|
91
193
|
}
|
|
92
194
|
return injectHtmlNode(htmlAst, node);
|
|
93
195
|
}
|
|
94
|
-
// <script type="module">
|
|
95
|
-
// - after <script type="importmap">
|
|
96
|
-
// - and after any <link>
|
|
97
|
-
// - and before first <script type="module">
|
|
98
196
|
const isJsModule = type === "js_module";
|
|
99
197
|
if (isJsModule) {
|
|
100
|
-
|
|
101
|
-
return (
|
|
102
|
-
node.nodeName === "script" &&
|
|
103
|
-
analyzeScriptNode(node).type === "importmap"
|
|
104
|
-
);
|
|
105
|
-
});
|
|
106
|
-
if (firstImportmapScript) {
|
|
107
|
-
return insertHtmlNodeAfter(node, firstImportmapScript);
|
|
108
|
-
}
|
|
109
|
-
let after = headNode.childNodes[0];
|
|
110
|
-
for (const child of headNode.childNodes) {
|
|
111
|
-
if (child.nodeName === "link") {
|
|
112
|
-
after = child;
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
if (
|
|
116
|
-
child.nodeName === "script" &&
|
|
117
|
-
analyzeScriptNode(child).type === "module"
|
|
118
|
-
) {
|
|
119
|
-
return insertHtmlNodeBefore(node, child);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
if (after) {
|
|
123
|
-
return insertHtmlNodeAfter(node, after);
|
|
124
|
-
}
|
|
125
|
-
return injectHtmlNode(htmlAst, node);
|
|
198
|
+
return getAsFirstJsModuleInjector(htmlAst)(node);
|
|
126
199
|
}
|
|
127
200
|
// <script> or <script type="text/jsx">, ...
|
|
128
201
|
// - after any <link>
|
|
129
202
|
// - before any <script>
|
|
203
|
+
// - after any <meta>
|
|
130
204
|
let after = headNode.childNodes[0];
|
|
131
205
|
for (const child of headNode.childNodes) {
|
|
132
206
|
if (child.nodeName === "link") {
|
|
@@ -136,6 +210,9 @@ export const injectHtmlNodeAsEarlyAsPossible = (
|
|
|
136
210
|
if (child.nodeName === "script") {
|
|
137
211
|
return insertHtmlNodeBefore(node, child);
|
|
138
212
|
}
|
|
213
|
+
if (child.nodeName === "meta") {
|
|
214
|
+
after = child;
|
|
215
|
+
}
|
|
139
216
|
}
|
|
140
217
|
if (after) {
|
|
141
218
|
return insertHtmlNodeAfter(node, after);
|
|
@@ -145,49 +222,139 @@ export const injectHtmlNodeAsEarlyAsPossible = (
|
|
|
145
222
|
return injectHtmlNode(htmlAst, node);
|
|
146
223
|
};
|
|
147
224
|
|
|
225
|
+
// <script type="module">
|
|
226
|
+
// - after <script type="importmap">
|
|
227
|
+
// - and after any <link>
|
|
228
|
+
// - and before first <script type="module">
|
|
229
|
+
// - after any <meta>
|
|
230
|
+
const getAsFirstJsModuleInjector = (htmlAst, { anyScript } = {}) => {
|
|
231
|
+
const headNode = findChild(htmlAst, (node) => node.nodeName === "html")
|
|
232
|
+
.childNodes[0];
|
|
233
|
+
const firstImportmapScript = findHtmlNode(htmlAst, (node) => {
|
|
234
|
+
return (
|
|
235
|
+
node.nodeName === "script" && analyzeScriptNode(node).type === "importmap"
|
|
236
|
+
);
|
|
237
|
+
});
|
|
238
|
+
if (firstImportmapScript) {
|
|
239
|
+
return (node) => insertHtmlNodeAfter(node, firstImportmapScript);
|
|
240
|
+
}
|
|
241
|
+
let after = headNode.childNodes[0];
|
|
242
|
+
for (const child of headNode.childNodes) {
|
|
243
|
+
if (child.nodeName === "link") {
|
|
244
|
+
after = child;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (
|
|
248
|
+
child.nodeName === "script" &&
|
|
249
|
+
(anyScript || analyzeScriptNode(child).type === "module")
|
|
250
|
+
) {
|
|
251
|
+
return (node) => insertHtmlNodeBefore(node, child);
|
|
252
|
+
}
|
|
253
|
+
if (child.nodeName === "meta") {
|
|
254
|
+
after = child;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (after) {
|
|
258
|
+
return (node) => insertHtmlNodeAfter(node, after);
|
|
259
|
+
}
|
|
260
|
+
return (node) => injectHtmlNode(htmlAst, node);
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
export const insertHtmlNodeInside = (nodeToInsert, futureParentNode) => {
|
|
264
|
+
const { childNodes = [] } = futureParentNode;
|
|
265
|
+
return injectWithLineBreakAndIndent(
|
|
266
|
+
nodeToInsert,
|
|
267
|
+
futureParentNode,
|
|
268
|
+
childNodes.length,
|
|
269
|
+
);
|
|
270
|
+
};
|
|
271
|
+
|
|
148
272
|
export const insertHtmlNodeBefore = (nodeToInsert, futureNextSibling) => {
|
|
149
273
|
const futureParentNode = futureNextSibling.parentNode;
|
|
150
274
|
const { childNodes = [] } = futureParentNode;
|
|
151
275
|
const futureIndex = childNodes.indexOf(futureNextSibling);
|
|
152
|
-
|
|
276
|
+
injectWithLineBreakAndIndent(nodeToInsert, futureParentNode, futureIndex);
|
|
153
277
|
};
|
|
154
278
|
|
|
155
279
|
export const insertHtmlNodeAfter = (nodeToInsert, futurePrevSibling) => {
|
|
156
280
|
const futureParentNode = futurePrevSibling.parentNode;
|
|
157
281
|
const { childNodes = [] } = futureParentNode;
|
|
158
282
|
const futureIndex = childNodes.indexOf(futurePrevSibling) + 1;
|
|
159
|
-
|
|
283
|
+
injectWithLineBreakAndIndent(nodeToInsert, futureParentNode, futureIndex);
|
|
160
284
|
};
|
|
161
285
|
|
|
162
|
-
const
|
|
286
|
+
const injectWithLineBreakAndIndent = (
|
|
287
|
+
nodeToInsert,
|
|
288
|
+
futureParentNode,
|
|
289
|
+
futureIndex,
|
|
290
|
+
) => {
|
|
163
291
|
const { childNodes = [] } = futureParentNode;
|
|
164
292
|
const previousSiblings = childNodes.slice(0, futureIndex);
|
|
165
293
|
const nextSiblings = childNodes.slice(futureIndex);
|
|
166
294
|
const futureChildNodes = [];
|
|
167
295
|
|
|
296
|
+
const previousSibling = childNodes[futureIndex - 1];
|
|
297
|
+
const parentIndentation = getIndentation(futureParentNode);
|
|
298
|
+
const nextSibling = childNodes[futureIndex];
|
|
299
|
+
let childIndentation;
|
|
300
|
+
if (previousSibling) {
|
|
301
|
+
childIndentation = getIndentation(previousSibling);
|
|
302
|
+
} else if (nextSibling) {
|
|
303
|
+
childIndentation = getIndentation(nextSibling);
|
|
304
|
+
} else {
|
|
305
|
+
childIndentation = increaseIndentation(parentIndentation, 2);
|
|
306
|
+
}
|
|
168
307
|
if (previousSiblings.length) {
|
|
169
308
|
futureChildNodes.push(...previousSiblings);
|
|
170
309
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
310
|
+
line_break_and_indent_before: {
|
|
311
|
+
if (nodeToInsert.nodeName === "#text") {
|
|
312
|
+
break line_break_and_indent_before;
|
|
313
|
+
}
|
|
314
|
+
if (!previousSibling) {
|
|
315
|
+
futureChildNodes.push({
|
|
316
|
+
nodeName: "#text",
|
|
317
|
+
value: `\n${childIndentation}`,
|
|
318
|
+
parentNode: futureParentNode,
|
|
319
|
+
});
|
|
320
|
+
break line_break_and_indent_before;
|
|
321
|
+
}
|
|
322
|
+
if (isLineBreakAndIndent(previousSibling)) {
|
|
323
|
+
if (!nextSibling) {
|
|
324
|
+
previousSibling.value = `\n${childIndentation}`;
|
|
325
|
+
}
|
|
326
|
+
break line_break_and_indent_before;
|
|
182
327
|
}
|
|
183
328
|
futureChildNodes.push({
|
|
184
329
|
nodeName: "#text",
|
|
185
|
-
value: `\n${
|
|
330
|
+
value: `\n${childIndentation}`,
|
|
186
331
|
parentNode: futureParentNode,
|
|
187
332
|
});
|
|
188
333
|
}
|
|
189
334
|
futureChildNodes.push(nodeToInsert);
|
|
190
335
|
nodeToInsert.parentNode = futureParentNode;
|
|
336
|
+
line_break_and_indent_after: {
|
|
337
|
+
if (nodeToInsert.nodeName === "#text") {
|
|
338
|
+
break line_break_and_indent_after;
|
|
339
|
+
}
|
|
340
|
+
if (!nextSibling) {
|
|
341
|
+
futureChildNodes.push({
|
|
342
|
+
nodeName: "#text",
|
|
343
|
+
value: `\n${parentIndentation}`,
|
|
344
|
+
parentNode: futureParentNode,
|
|
345
|
+
});
|
|
346
|
+
break line_break_and_indent_after;
|
|
347
|
+
}
|
|
348
|
+
if (isLineBreakAndIndent(nextSibling)) {
|
|
349
|
+
// nextSibling.value = `\n${indentation}`;
|
|
350
|
+
break line_break_and_indent_after;
|
|
351
|
+
}
|
|
352
|
+
futureChildNodes.push({
|
|
353
|
+
nodeName: "#text",
|
|
354
|
+
value: `\n${childIndentation}`,
|
|
355
|
+
parentNode: futureParentNode,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
191
358
|
if (nextSiblings.length) {
|
|
192
359
|
futureChildNodes.push(...nextSiblings);
|
|
193
360
|
}
|
|
@@ -200,10 +367,15 @@ const injectWithWhitespaces = (nodeToInsert, futureParentNode, futureIndex) => {
|
|
|
200
367
|
}
|
|
201
368
|
};
|
|
202
369
|
|
|
203
|
-
const
|
|
204
|
-
if (
|
|
205
|
-
|
|
206
|
-
|
|
370
|
+
const isLineBreakAndIndent = (htmlNode) => {
|
|
371
|
+
if (htmlNode.nodeName !== "#text") {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
const { value } = htmlNode;
|
|
375
|
+
if (value[0] !== "\n") {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
return value.slice(1).trim() === "";
|
|
207
379
|
};
|
|
208
380
|
|
|
209
381
|
const findChild = ({ childNodes = [] }, predicate) =>
|
|
@@ -66,26 +66,25 @@ export const getIndentation = (htmlNode) => {
|
|
|
66
66
|
const index = siblings.indexOf(htmlNode);
|
|
67
67
|
if (index === 0) {
|
|
68
68
|
if (htmlNode.nodeName === "#text") {
|
|
69
|
-
|
|
69
|
+
if (htmlNode.value.length) {
|
|
70
|
+
return extractIndentation(htmlNode);
|
|
71
|
+
}
|
|
72
|
+
return increaseIndentation(getIndentation(parentNode), 2);
|
|
70
73
|
}
|
|
71
|
-
return "";
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
let textNodeIndex = 0;
|
|
75
|
-
let textNode;
|
|
76
77
|
while (textNodeIndex !== index) {
|
|
77
78
|
const previousSibling = siblings[textNodeIndex];
|
|
78
79
|
textNodeIndex++;
|
|
79
80
|
if (previousSibling.nodeName === "#text") {
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
if (previousSibling.value.length) {
|
|
82
|
+
return extractIndentation(previousSibling);
|
|
83
|
+
}
|
|
84
|
+
return increaseIndentation(getIndentation(parentNode), 2);
|
|
82
85
|
}
|
|
83
86
|
}
|
|
84
|
-
|
|
85
|
-
return "";
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return extractIndentation(textNode);
|
|
87
|
+
return "";
|
|
89
88
|
};
|
|
90
89
|
|
|
91
90
|
const extractIndentation = (textNode) => {
|
|
@@ -116,3 +115,11 @@ export const increaseIndentation = (indentation, size) => {
|
|
|
116
115
|
const char = indentation[0];
|
|
117
116
|
return char ? `${indentation}${char.repeat(size)}` : " ".repeat(size);
|
|
118
117
|
};
|
|
118
|
+
|
|
119
|
+
export const decreaseIndentation = (indentation, size) => {
|
|
120
|
+
const char = indentation[0];
|
|
121
|
+
if (char) {
|
|
122
|
+
return char.slice(0, -size);
|
|
123
|
+
}
|
|
124
|
+
return "";
|
|
125
|
+
};
|