@jsenv/ast 4.0.2 → 4.1.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.
- package/package.json +4 -4
- package/src/html/html_node.js +67 -31
- package/src/js/apply_babel_plugins.js +17 -25
- package/src/js/parse_js_urls.js +10 -7
- package/src/js/parse_js_with_acorn.js +7 -16
- package/src/js/visit_js_ast_until.js +30 -0
- package/src/main.js +1 -0
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/ast",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Parse and transform HTML,CSS and JS",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/jsenv/core",
|
|
9
|
-
"directory": "packages/
|
|
9
|
+
"directory": "packages/internal/ast"
|
|
10
10
|
},
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
"test": "node --conditions=development ./scripts/test.mjs"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@babel/parser": "7.
|
|
36
|
+
"@babel/parser": "7.22.5",
|
|
37
37
|
"acorn": "8.8.2",
|
|
38
38
|
"acorn-import-assertions": "1.9.0",
|
|
39
39
|
"acorn-walk": "8.2.0",
|
|
40
40
|
"parse5": "7.1.2",
|
|
41
|
-
"postcss": "8.4.
|
|
41
|
+
"postcss": "8.4.24",
|
|
42
42
|
"postcss-value-parser": "4.2.0"
|
|
43
43
|
}
|
|
44
44
|
}
|
package/src/html/html_node.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { parseFragment } from "parse5";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
getHtmlNodeAttribute,
|
|
5
|
+
setHtmlNodeAttributes,
|
|
6
|
+
} from "./html_node_attributes.js";
|
|
4
7
|
import { findHtmlNode } from "./html_search.js";
|
|
5
8
|
import { analyzeScriptNode } from "./html_analysis.js";
|
|
6
9
|
import {
|
|
@@ -56,9 +59,43 @@ export const injectHtmlNodeAsEarlyAsPossible = (
|
|
|
56
59
|
"jsenv-injected-by": jsenvPluginName,
|
|
57
60
|
});
|
|
58
61
|
const isScript = node.nodeName === "script";
|
|
59
|
-
|
|
60
62
|
if (isScript) {
|
|
61
|
-
const
|
|
63
|
+
const { type } = analyzeScriptNode(node);
|
|
64
|
+
const headNode = findChild(htmlAst, (node) => node.nodeName === "html")
|
|
65
|
+
.childNodes[0];
|
|
66
|
+
|
|
67
|
+
// <script type="importmap">
|
|
68
|
+
// - after any <link>
|
|
69
|
+
// - but before first <link rel="modulepreload">
|
|
70
|
+
// - and before <script type="module">
|
|
71
|
+
const isImportmap = type === "importmap";
|
|
72
|
+
if (isImportmap) {
|
|
73
|
+
let after = headNode.childNodes[0];
|
|
74
|
+
for (const child of headNode.childNodes) {
|
|
75
|
+
if (child.nodeName === "link") {
|
|
76
|
+
if (getHtmlNodeAttribute(child, "rel") === "modulepreload") {
|
|
77
|
+
return insertHtmlNodeBefore(node, child);
|
|
78
|
+
}
|
|
79
|
+
after = child;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (
|
|
83
|
+
child.nodeName === "script" &&
|
|
84
|
+
analyzeScriptNode(child).type === "module"
|
|
85
|
+
) {
|
|
86
|
+
return insertHtmlNodeBefore(node, child);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (after) {
|
|
90
|
+
return insertHtmlNodeAfter(node, after);
|
|
91
|
+
}
|
|
92
|
+
return injectHtmlNode(htmlAst, node);
|
|
93
|
+
}
|
|
94
|
+
// <script type="module">
|
|
95
|
+
// - after <script type="importmap">
|
|
96
|
+
// - and after any <link>
|
|
97
|
+
// - and before first <script type="module">
|
|
98
|
+
const isJsModule = type === "js_module";
|
|
62
99
|
if (isJsModule) {
|
|
63
100
|
const firstImportmapScript = findHtmlNode(htmlAst, (node) => {
|
|
64
101
|
return (
|
|
@@ -66,47 +103,46 @@ export const injectHtmlNodeAsEarlyAsPossible = (
|
|
|
66
103
|
analyzeScriptNode(node).type === "importmap"
|
|
67
104
|
);
|
|
68
105
|
});
|
|
69
|
-
|
|
70
106
|
if (firstImportmapScript) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (nextSibling.nodeName === "script") {
|
|
79
|
-
insertHtmlNodeBefore(node, nextSibling);
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
if (nextSibling.nodeName === "link") {
|
|
83
|
-
after = nextSibling;
|
|
84
|
-
}
|
|
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;
|
|
85
114
|
}
|
|
86
|
-
|
|
87
|
-
|
|
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);
|
|
88
124
|
}
|
|
125
|
+
return injectHtmlNode(htmlAst, node);
|
|
89
126
|
}
|
|
90
|
-
|
|
91
|
-
|
|
127
|
+
// <script> or <script type="text/jsx">, ...
|
|
128
|
+
// - after any <link>
|
|
129
|
+
// - before any <script>
|
|
92
130
|
let after = headNode.childNodes[0];
|
|
93
131
|
for (const child of headNode.childNodes) {
|
|
94
|
-
if (child.nodeName === "script") {
|
|
95
|
-
insertHtmlNodeBefore(node, child);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
132
|
if (child.nodeName === "link") {
|
|
99
133
|
after = child;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (child.nodeName === "script") {
|
|
137
|
+
return insertHtmlNodeBefore(node, child);
|
|
100
138
|
}
|
|
101
139
|
}
|
|
102
140
|
if (after) {
|
|
103
|
-
insertHtmlNodeAfter(node, after);
|
|
141
|
+
return insertHtmlNodeAfter(node, after);
|
|
104
142
|
}
|
|
105
|
-
return;
|
|
143
|
+
return injectHtmlNode(htmlAst, node);
|
|
106
144
|
}
|
|
107
|
-
|
|
108
|
-
injectHtmlNode(htmlAst, node);
|
|
109
|
-
return;
|
|
145
|
+
return injectHtmlNode(htmlAst, node);
|
|
110
146
|
};
|
|
111
147
|
|
|
112
148
|
export const insertHtmlNodeBefore = (nodeToInsert, futureNextSibling) => {
|
|
@@ -9,34 +9,28 @@ import { createJsParseError } from "./js_parse_error.js";
|
|
|
9
9
|
|
|
10
10
|
export const applyBabelPlugins = async ({
|
|
11
11
|
babelPlugins,
|
|
12
|
-
|
|
12
|
+
input,
|
|
13
|
+
inputIsJsModule,
|
|
14
|
+
inputUrl,
|
|
15
|
+
outputUrl,
|
|
13
16
|
ast,
|
|
14
17
|
options = {},
|
|
15
18
|
}) => {
|
|
16
|
-
const sourceType = {
|
|
17
|
-
[urlInfo.type]: undefined,
|
|
18
|
-
js_module: "module",
|
|
19
|
-
js_classic: "classic",
|
|
20
|
-
}[urlInfo.type];
|
|
21
|
-
const url = urlInfo.originalUrl;
|
|
22
|
-
const generatedUrl = urlInfo.generatedUrl;
|
|
23
|
-
const content = urlInfo.content;
|
|
24
|
-
|
|
25
19
|
if (babelPlugins.length === 0) {
|
|
26
|
-
return { code:
|
|
20
|
+
return { code: input };
|
|
27
21
|
}
|
|
28
22
|
const { transformAsync, transformFromAstAsync } = await import("@babel/core");
|
|
29
|
-
const sourceFileName =
|
|
30
|
-
? fileURLToPath(
|
|
23
|
+
const sourceFileName = inputUrl.startsWith("file:")
|
|
24
|
+
? fileURLToPath(inputUrl)
|
|
31
25
|
: undefined;
|
|
32
26
|
options = {
|
|
33
27
|
ast: false,
|
|
34
28
|
// https://babeljs.io/docs/en/options#source-map-options
|
|
35
29
|
sourceMaps: true,
|
|
36
30
|
sourceFileName,
|
|
37
|
-
filename:
|
|
38
|
-
?
|
|
39
|
-
? fileURLToPath(
|
|
31
|
+
filename: outputUrl
|
|
32
|
+
? outputUrl.startsWith("file:")
|
|
33
|
+
? fileURLToPath(inputUrl)
|
|
40
34
|
: undefined
|
|
41
35
|
: sourceFileName,
|
|
42
36
|
configFile: false,
|
|
@@ -45,19 +39,17 @@ export const applyBabelPlugins = async ({
|
|
|
45
39
|
// consider using startColumn and startLine for inline scripts?
|
|
46
40
|
// see https://github.com/babel/babel/blob/3ee9db7afe741f4d2f7933c519d8e7672fccb08d/packages/babel-parser/src/options.js#L36-L39
|
|
47
41
|
parserOpts: {
|
|
48
|
-
sourceType,
|
|
42
|
+
sourceType: inputIsJsModule ? "module" : "classic",
|
|
49
43
|
// allowAwaitOutsideFunction: true,
|
|
50
44
|
plugins: [
|
|
51
45
|
// "importMeta",
|
|
52
46
|
// "topLevelAwait",
|
|
53
|
-
...(
|
|
54
|
-
? ["dynamicImport", "importAssertions"]
|
|
55
|
-
: []),
|
|
47
|
+
...(inputIsJsModule ? ["dynamicImport", "importAssertions"] : []),
|
|
56
48
|
"jsx",
|
|
57
49
|
"classProperties",
|
|
58
50
|
"classPrivateProperties",
|
|
59
51
|
"classPrivateMethods",
|
|
60
|
-
...(useTypeScriptExtension(
|
|
52
|
+
...(useTypeScriptExtension(inputUrl) ? ["typescript"] : []),
|
|
61
53
|
...(options.parserPlugins || []),
|
|
62
54
|
].filter(Boolean),
|
|
63
55
|
},
|
|
@@ -70,18 +62,18 @@ export const applyBabelPlugins = async ({
|
|
|
70
62
|
};
|
|
71
63
|
try {
|
|
72
64
|
if (ast) {
|
|
73
|
-
const result = await transformFromAstAsync(ast,
|
|
65
|
+
const result = await transformFromAstAsync(ast, input, options);
|
|
74
66
|
return result;
|
|
75
67
|
}
|
|
76
|
-
const result = await transformAsync(
|
|
68
|
+
const result = await transformAsync(input, options);
|
|
77
69
|
return result;
|
|
78
70
|
} catch (error) {
|
|
79
71
|
if (error && error.code === "BABEL_PARSE_ERROR") {
|
|
80
72
|
throw createJsParseError({
|
|
81
73
|
message: error.message,
|
|
82
74
|
reasonCode: error.reasonCode,
|
|
83
|
-
content,
|
|
84
|
-
url,
|
|
75
|
+
content: input,
|
|
76
|
+
url: inputUrl,
|
|
85
77
|
line: error.loc.line,
|
|
86
78
|
column: error.loc.column,
|
|
87
79
|
});
|
package/src/js/parse_js_urls.js
CHANGED
|
@@ -48,19 +48,22 @@ import {
|
|
|
48
48
|
analyzeJSONParseCall,
|
|
49
49
|
} from "./js_static_analysis/json_parse.js";
|
|
50
50
|
|
|
51
|
-
export const parseJsUrls =
|
|
51
|
+
export const parseJsUrls = ({
|
|
52
52
|
js,
|
|
53
53
|
url,
|
|
54
|
+
ast,
|
|
54
55
|
isJsModule = false,
|
|
55
56
|
isWebWorker = false,
|
|
56
57
|
inlineContent = true,
|
|
57
58
|
} = {}) => {
|
|
58
59
|
const jsUrls = [];
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
if (ast === undefined) {
|
|
61
|
+
ast = parseJsWithAcorn({
|
|
62
|
+
js,
|
|
63
|
+
url,
|
|
64
|
+
isJsModule,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
64
67
|
const onUrl = (jsUrl) => {
|
|
65
68
|
jsUrls.push(jsUrl);
|
|
66
69
|
};
|
|
@@ -70,7 +73,7 @@ export const parseJsUrls = async ({
|
|
|
70
73
|
...inlineContentInfo,
|
|
71
74
|
});
|
|
72
75
|
};
|
|
73
|
-
ancestor(
|
|
76
|
+
ancestor(ast, {
|
|
74
77
|
ImportDeclaration: (node) => {
|
|
75
78
|
analyzeImportDeclaration(node, { onUrl });
|
|
76
79
|
},
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { Parser, getLineInfo } from "acorn";
|
|
2
|
+
import { importAssertions } from "acorn-import-assertions";
|
|
3
|
+
|
|
1
4
|
import { createJsParseError } from "./js_parse_error.js";
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
|
|
6
|
+
export const parseJsWithAcorn = ({ js, url, isJsModule }) => {
|
|
7
|
+
const AcornParser = Parser.extend(importAssertions);
|
|
5
8
|
|
|
6
|
-
export const parseJsWithAcorn = async ({ js, url, isJsModule }) => {
|
|
7
|
-
await initAcornParser();
|
|
8
9
|
try {
|
|
9
10
|
// https://github.com/acornjs/acorn/tree/master/acorn#interface
|
|
10
11
|
const jsAst = AcornParser.parse(js, {
|
|
@@ -16,10 +17,11 @@ export const parseJsWithAcorn = async ({ js, url, isJsModule }) => {
|
|
|
16
17
|
return jsAst;
|
|
17
18
|
} catch (e) {
|
|
18
19
|
if (e && e.name === "SyntaxError") {
|
|
19
|
-
const { line, column } =
|
|
20
|
+
const { line, column } = getLineInfo(js, e.raisedAt);
|
|
20
21
|
throw createJsParseError({
|
|
21
22
|
message: e.message,
|
|
22
23
|
reasonCode: e.message,
|
|
24
|
+
content: js,
|
|
23
25
|
url,
|
|
24
26
|
line,
|
|
25
27
|
column,
|
|
@@ -28,14 +30,3 @@ export const parseJsWithAcorn = async ({ js, url, isJsModule }) => {
|
|
|
28
30
|
throw e;
|
|
29
31
|
}
|
|
30
32
|
};
|
|
31
|
-
|
|
32
|
-
const initAcornParser = async () => {
|
|
33
|
-
if (AcornParser) {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
const { Parser, getLineInfo } = await import("acorn");
|
|
37
|
-
const { importAssertions } = await import("acorn-import-assertions");
|
|
38
|
-
|
|
39
|
-
AcornParser = Parser.extend(importAssertions);
|
|
40
|
-
_getLineInfo = getLineInfo;
|
|
41
|
-
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { simple } from "acorn-walk";
|
|
2
|
+
|
|
3
|
+
const stop = {};
|
|
4
|
+
|
|
5
|
+
export const visitJsAstUntil = (ast, visitors) => {
|
|
6
|
+
const visitorsMapped = {};
|
|
7
|
+
let returnValue = null;
|
|
8
|
+
Object.keys(visitors).forEach((key) => {
|
|
9
|
+
visitorsMapped[key] = (node, ...args) => {
|
|
10
|
+
const visitorReturnValue = visitors[key](node, ...args);
|
|
11
|
+
if (visitorReturnValue === true) {
|
|
12
|
+
returnValue = node;
|
|
13
|
+
throw stop;
|
|
14
|
+
}
|
|
15
|
+
if (typeof visitorReturnValue === "object" && returnValue !== null) {
|
|
16
|
+
returnValue = visitorReturnValue;
|
|
17
|
+
throw stop;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
try {
|
|
22
|
+
simple(ast, visitorsMapped);
|
|
23
|
+
return returnValue;
|
|
24
|
+
} catch (e) {
|
|
25
|
+
if (e === stop) {
|
|
26
|
+
return returnValue;
|
|
27
|
+
}
|
|
28
|
+
throw e;
|
|
29
|
+
}
|
|
30
|
+
};
|
package/src/main.js
CHANGED
|
@@ -42,3 +42,4 @@ export { applyBabelPlugins } from "./js/apply_babel_plugins.js";
|
|
|
42
42
|
export { injectJsImport } from "./js/babel_utils.js";
|
|
43
43
|
export { parseJsWithAcorn } from "./js/parse_js_with_acorn.js";
|
|
44
44
|
export { parseJsUrls } from "./js/parse_js_urls.js";
|
|
45
|
+
export { visitJsAstUntil } from "./js/visit_js_ast_until.js";
|