@jsenv/ast 4.0.3 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/ast",
3
- "version": "4.0.3",
3
+ "version": "4.1.0",
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.22.4",
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",
@@ -1,6 +1,9 @@
1
1
  import { parseFragment } from "parse5";
2
2
 
3
- import { setHtmlNodeAttributes } from "./html_node_attributes.js";
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 isJsModule = analyzeScriptNode(node).type === "js_module";
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
- const importmapParent = firstImportmapScript.parentNode;
72
- const importmapSiblings = importmapParent.childNodes;
73
- const nextSiblings = importmapSiblings.slice(
74
- importmapSiblings.indexOf(firstImportmapScript) + 1,
75
- );
76
- let after = firstImportmapScript;
77
- for (const nextSibling of nextSiblings) {
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
- insertHtmlNodeAfter(node, after);
87
- return;
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
- const headNode = findChild(htmlAst, (node) => node.nodeName === "html")
91
- .childNodes[0];
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) => {
@@ -48,19 +48,22 @@ import {
48
48
  analyzeJSONParseCall,
49
49
  } from "./js_static_analysis/json_parse.js";
50
50
 
51
- export const parseJsUrls = async ({
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
- const jsAst = await parseJsWithAcorn({
60
- js,
61
- url,
62
- isJsModule,
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(jsAst, {
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
- let AcornParser;
4
- let _getLineInfo;
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 } = _getLineInfo(js, e.raisedAt);
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";