@navita/extraction 3.0.0-next.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/LICENSE.md +21 -0
- package/README.md +157 -0
- package/_virtual/_rolldown/runtime.cjs +23 -0
- package/_virtual/_rolldown/runtime.mjs +8 -0
- package/ast.cjs +164 -0
- package/ast.mjs +159 -0
- package/extraction.cjs +23 -0
- package/extraction.mjs +25 -0
- package/index.cjs +3 -0
- package/index.d.ts +24 -0
- package/index.mjs +5 -0
- package/loadOxc.cjs +19 -0
- package/loadOxc.mjs +21 -0
- package/package.json +32 -0
- package/rewrite.cjs +185 -0
- package/rewrite.mjs +185 -0
- package/stripTypes.cjs +12 -0
- package/stripTypes.mjs +14 -0
- package/toAmd.cjs +190 -0
- package/toAmd.mjs +192 -0
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@navita/extraction",
|
|
3
|
+
"version": "3.0.0-next.0",
|
|
4
|
+
"description": "Style extraction transform for Navita (OXC-based)",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"oxc",
|
|
7
|
+
"css-in-js",
|
|
8
|
+
"navita",
|
|
9
|
+
"extraction"
|
|
10
|
+
],
|
|
11
|
+
"private": false,
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./index.mjs",
|
|
16
|
+
"require": "./index.cjs",
|
|
17
|
+
"types": "./index.d.ts"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"magic-string": "^0.30.0",
|
|
22
|
+
"oxc-parser": "^0.137.0",
|
|
23
|
+
"oxc-transform": "^0.137.0"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"author": "Eagerpatch",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/eagerpatch/navita.git",
|
|
30
|
+
"directory": "packages/extraction"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/rewrite.cjs
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_runtime = require("./_virtual/_rolldown/runtime.cjs");
|
|
3
|
+
const require_ast = require("./ast.cjs");
|
|
4
|
+
const require_loadOxc = require("./loadOxc.cjs");
|
|
5
|
+
let magic_string = require("magic-string");
|
|
6
|
+
magic_string = require_runtime.__toESM(magic_string);
|
|
7
|
+
//#region src/rewrite.ts
|
|
8
|
+
const COLLECT_RESULT_NAME = "collectResult";
|
|
9
|
+
const ADAPTER_SOURCE = "@navita/adapter";
|
|
10
|
+
/**
|
|
11
|
+
* The core extraction rewrite. Produces an ES module (still TypeScript, still
|
|
12
|
+
* pre-AMD) where:
|
|
13
|
+
* - every navita style call is wrapped in `collectResult({ ... })`,
|
|
14
|
+
* - the module is pruned to only the imports + statements needed to evaluate
|
|
15
|
+
* those calls (with nested style declarations hoisted to the top level),
|
|
16
|
+
* - unresolved free identifiers referenced inside style calls are synthesized
|
|
17
|
+
* as bare `let x;` declarations.
|
|
18
|
+
*
|
|
19
|
+
* This mirrors the behavior of the original Rust/swc-plugin transform (see the
|
|
20
|
+
* `test!` fixtures in crates/extraction/src/lib.rs).
|
|
21
|
+
*/
|
|
22
|
+
function rewrite(code, { filename, importMap }) {
|
|
23
|
+
const { program } = require_loadOxc.getParser().parseSync(filename, code, {
|
|
24
|
+
lang: "tsx",
|
|
25
|
+
sourceType: "module"
|
|
26
|
+
});
|
|
27
|
+
const body = program.body;
|
|
28
|
+
const importCalls = /* @__PURE__ */ new Set();
|
|
29
|
+
const importBindings = /* @__PURE__ */ new Set();
|
|
30
|
+
for (const stmt of body) {
|
|
31
|
+
if (stmt.type !== "ImportDeclaration" || stmt.importKind === "type") continue;
|
|
32
|
+
const source = stmt.source.value;
|
|
33
|
+
for (const spec of stmt.specifiers || []) {
|
|
34
|
+
importBindings.add(spec.local.name);
|
|
35
|
+
if (spec.type !== "ImportSpecifier" || spec.importKind === "type") continue;
|
|
36
|
+
const imported = require_ast.importedName(spec);
|
|
37
|
+
for (const entry of importMap) if (entry.source === source && entry.callee === imported) importCalls.add(spec.local.name);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const isMatchedCall = (node) => node.type === "CallExpression" && importCalls.has(require_ast.getCalleeName(node) ?? "\0");
|
|
41
|
+
const usedIdents = /* @__PURE__ */ new Set();
|
|
42
|
+
require_ast.walk(program, (node) => {
|
|
43
|
+
if (isMatchedCall(node)) require_ast.collectArgumentIdents(node.arguments, usedIdents);
|
|
44
|
+
});
|
|
45
|
+
const moduleImports = [];
|
|
46
|
+
const keptItems = [];
|
|
47
|
+
const callIdentifier = /* @__PURE__ */ new Map();
|
|
48
|
+
/** Decide whether to keep a VariableDeclaration; records identifier labels. */
|
|
49
|
+
const computeVarDeclKeep = (varDecl) => {
|
|
50
|
+
let keep = false;
|
|
51
|
+
for (const decl of varDecl.declarations) {
|
|
52
|
+
const initCallee = require_ast.getInitCalleeName(decl.init);
|
|
53
|
+
if (initCallee !== null && importCalls.has(initCallee)) {
|
|
54
|
+
keep = true;
|
|
55
|
+
const label = require_ast.patternNames(decl.id)[0] ?? "";
|
|
56
|
+
walkMatched(decl.init, isMatchedCall, (call) => {
|
|
57
|
+
callIdentifier.set(call, label);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
for (const name of require_ast.patternNames(decl.id)) if (usedIdents.has(name)) {
|
|
61
|
+
usedIdents.delete(name);
|
|
62
|
+
keep = true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return keep;
|
|
66
|
+
};
|
|
67
|
+
const visit = (node) => {
|
|
68
|
+
if (!node || typeof node !== "object") return;
|
|
69
|
+
if (Array.isArray(node)) {
|
|
70
|
+
for (const child of node) visit(child);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const n = node;
|
|
74
|
+
switch (n.type) {
|
|
75
|
+
case "ImportDeclaration":
|
|
76
|
+
for (const spec of n.specifiers || []) usedIdents.delete(spec.local.name);
|
|
77
|
+
moduleImports.push(n);
|
|
78
|
+
return;
|
|
79
|
+
case "VariableDeclaration":
|
|
80
|
+
if (computeVarDeclKeep(n)) keptItems.push({
|
|
81
|
+
kind: "statement",
|
|
82
|
+
node: n
|
|
83
|
+
});
|
|
84
|
+
else visitChildren(n);
|
|
85
|
+
return;
|
|
86
|
+
case "ExportNamedDeclaration":
|
|
87
|
+
if (n.declaration && n.declaration.type === "VariableDeclaration") {
|
|
88
|
+
if (computeVarDeclKeep(n.declaration)) keptItems.push({
|
|
89
|
+
kind: "statement",
|
|
90
|
+
node: n
|
|
91
|
+
});
|
|
92
|
+
else visitChildren(n.declaration);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
visitChildren(n);
|
|
96
|
+
return;
|
|
97
|
+
case "FunctionDeclaration":
|
|
98
|
+
visitChildren(n);
|
|
99
|
+
if (n.id && usedIdents.has(n.id.name)) {
|
|
100
|
+
keptItems.push({
|
|
101
|
+
kind: "statement",
|
|
102
|
+
node: n
|
|
103
|
+
});
|
|
104
|
+
usedIdents.delete(n.id.name);
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
case "ClassDeclaration":
|
|
108
|
+
visitChildren(n);
|
|
109
|
+
if (n.id && usedIdents.has(n.id.name)) {
|
|
110
|
+
keptItems.push({
|
|
111
|
+
kind: "statement",
|
|
112
|
+
node: n
|
|
113
|
+
});
|
|
114
|
+
usedIdents.delete(n.id.name);
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
case "CallExpression":
|
|
118
|
+
if (isMatchedCall(n)) {
|
|
119
|
+
keptItems.push({
|
|
120
|
+
kind: "exprCall",
|
|
121
|
+
node: n
|
|
122
|
+
});
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
visitChildren(n);
|
|
126
|
+
return;
|
|
127
|
+
default: visitChildren(n);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const visitChildren = (node) => {
|
|
131
|
+
for (const key in node) {
|
|
132
|
+
if (key === "type" || key === "start" || key === "end") continue;
|
|
133
|
+
const value = node[key];
|
|
134
|
+
if (value && typeof value === "object") visit(value);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
for (const stmt of body) visit(stmt);
|
|
138
|
+
const leftoverLets = [...usedIdents];
|
|
139
|
+
const magic = new magic_string.default(code);
|
|
140
|
+
const lineColumn = require_ast.createLineColumnLookup(code);
|
|
141
|
+
let index = 0;
|
|
142
|
+
const rewriteCall = (call) => {
|
|
143
|
+
const identifier = callIdentifier.get(call) ?? "";
|
|
144
|
+
const start = call.start;
|
|
145
|
+
const end = call.end;
|
|
146
|
+
const { line, column } = lineColumn(start);
|
|
147
|
+
const original = code.slice(start, end);
|
|
148
|
+
const replacement = `${COLLECT_RESULT_NAME}({\n filePath: ${JSON.stringify(filename)},\n index: ${index},\n identifier: ${JSON.stringify(identifier)},\n position: [${start}, ${end}],\n sourceMap: {\n line: ${line},\n column: ${column}\n },\n result: () => ${original}\n})`;
|
|
149
|
+
magic.update(start, end, replacement);
|
|
150
|
+
index += 1;
|
|
151
|
+
};
|
|
152
|
+
for (const item of keptItems) walkMatched(item.node, isMatchedCall, rewriteCall);
|
|
153
|
+
const parts = [];
|
|
154
|
+
parts.push(`import { ${COLLECT_RESULT_NAME} as ${COLLECT_RESULT_NAME} } from "${ADAPTER_SOURCE}";`);
|
|
155
|
+
for (const imp of moduleImports) parts.push(magic.slice(imp.start, imp.end));
|
|
156
|
+
for (const name of leftoverLets) parts.push(`let ${name};`);
|
|
157
|
+
for (const item of keptItems) {
|
|
158
|
+
const sliced = magic.slice(item.node.start, item.node.end);
|
|
159
|
+
parts.push(item.kind === "exprCall" ? `${sliced};` : sliced);
|
|
160
|
+
}
|
|
161
|
+
return parts.join("\n");
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Depth-first visit of matched calls, NOT descending into a matched call's own
|
|
165
|
+
* subtree (mirrors the swc visitor which replaces the node without recursing).
|
|
166
|
+
*/
|
|
167
|
+
function walkMatched(node, isMatched, fn) {
|
|
168
|
+
if (!node || typeof node !== "object") return;
|
|
169
|
+
if (Array.isArray(node)) {
|
|
170
|
+
for (const child of node) walkMatched(child, isMatched, fn);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const n = node;
|
|
174
|
+
if (n.type === "CallExpression" && isMatched(n)) {
|
|
175
|
+
fn(n);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
for (const key in n) {
|
|
179
|
+
if (key === "type" || key === "start" || key === "end") continue;
|
|
180
|
+
const value = n[key];
|
|
181
|
+
if (value && typeof value === "object") walkMatched(value, isMatched, fn);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
//#endregion
|
|
185
|
+
exports.rewrite = rewrite;
|
package/rewrite.mjs
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { collectArgumentIdents, createLineColumnLookup, getCalleeName, getInitCalleeName, importedName, patternNames, walk } from "./ast.mjs";
|
|
5
|
+
import { getParser } from "./loadOxc.mjs";
|
|
6
|
+
import MagicString from "magic-string";
|
|
7
|
+
//#region src/rewrite.ts
|
|
8
|
+
const COLLECT_RESULT_NAME = "collectResult";
|
|
9
|
+
const ADAPTER_SOURCE = "@navita/adapter";
|
|
10
|
+
/**
|
|
11
|
+
* The core extraction rewrite. Produces an ES module (still TypeScript, still
|
|
12
|
+
* pre-AMD) where:
|
|
13
|
+
* - every navita style call is wrapped in `collectResult({ ... })`,
|
|
14
|
+
* - the module is pruned to only the imports + statements needed to evaluate
|
|
15
|
+
* those calls (with nested style declarations hoisted to the top level),
|
|
16
|
+
* - unresolved free identifiers referenced inside style calls are synthesized
|
|
17
|
+
* as bare `let x;` declarations.
|
|
18
|
+
*
|
|
19
|
+
* This mirrors the behavior of the original Rust/swc-plugin transform (see the
|
|
20
|
+
* `test!` fixtures in crates/extraction/src/lib.rs).
|
|
21
|
+
*/
|
|
22
|
+
function rewrite(code, { filename, importMap }) {
|
|
23
|
+
const { program } = getParser().parseSync(filename, code, {
|
|
24
|
+
lang: "tsx",
|
|
25
|
+
sourceType: "module"
|
|
26
|
+
});
|
|
27
|
+
const body = program.body;
|
|
28
|
+
const importCalls = /* @__PURE__ */ new Set();
|
|
29
|
+
const importBindings = /* @__PURE__ */ new Set();
|
|
30
|
+
for (const stmt of body) {
|
|
31
|
+
if (stmt.type !== "ImportDeclaration" || stmt.importKind === "type") continue;
|
|
32
|
+
const source = stmt.source.value;
|
|
33
|
+
for (const spec of stmt.specifiers || []) {
|
|
34
|
+
importBindings.add(spec.local.name);
|
|
35
|
+
if (spec.type !== "ImportSpecifier" || spec.importKind === "type") continue;
|
|
36
|
+
const imported = importedName(spec);
|
|
37
|
+
for (const entry of importMap) if (entry.source === source && entry.callee === imported) importCalls.add(spec.local.name);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const isMatchedCall = (node) => node.type === "CallExpression" && importCalls.has(getCalleeName(node) ?? "\0");
|
|
41
|
+
const usedIdents = /* @__PURE__ */ new Set();
|
|
42
|
+
walk(program, (node) => {
|
|
43
|
+
if (isMatchedCall(node)) collectArgumentIdents(node.arguments, usedIdents);
|
|
44
|
+
});
|
|
45
|
+
const moduleImports = [];
|
|
46
|
+
const keptItems = [];
|
|
47
|
+
const callIdentifier = /* @__PURE__ */ new Map();
|
|
48
|
+
/** Decide whether to keep a VariableDeclaration; records identifier labels. */
|
|
49
|
+
const computeVarDeclKeep = (varDecl) => {
|
|
50
|
+
let keep = false;
|
|
51
|
+
for (const decl of varDecl.declarations) {
|
|
52
|
+
const initCallee = getInitCalleeName(decl.init);
|
|
53
|
+
if (initCallee !== null && importCalls.has(initCallee)) {
|
|
54
|
+
keep = true;
|
|
55
|
+
const label = patternNames(decl.id)[0] ?? "";
|
|
56
|
+
walkMatched(decl.init, isMatchedCall, (call) => {
|
|
57
|
+
callIdentifier.set(call, label);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
for (const name of patternNames(decl.id)) if (usedIdents.has(name)) {
|
|
61
|
+
usedIdents.delete(name);
|
|
62
|
+
keep = true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return keep;
|
|
66
|
+
};
|
|
67
|
+
const visit = (node) => {
|
|
68
|
+
if (!node || typeof node !== "object") return;
|
|
69
|
+
if (Array.isArray(node)) {
|
|
70
|
+
for (const child of node) visit(child);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const n = node;
|
|
74
|
+
switch (n.type) {
|
|
75
|
+
case "ImportDeclaration":
|
|
76
|
+
for (const spec of n.specifiers || []) usedIdents.delete(spec.local.name);
|
|
77
|
+
moduleImports.push(n);
|
|
78
|
+
return;
|
|
79
|
+
case "VariableDeclaration":
|
|
80
|
+
if (computeVarDeclKeep(n)) keptItems.push({
|
|
81
|
+
kind: "statement",
|
|
82
|
+
node: n
|
|
83
|
+
});
|
|
84
|
+
else visitChildren(n);
|
|
85
|
+
return;
|
|
86
|
+
case "ExportNamedDeclaration":
|
|
87
|
+
if (n.declaration && n.declaration.type === "VariableDeclaration") {
|
|
88
|
+
if (computeVarDeclKeep(n.declaration)) keptItems.push({
|
|
89
|
+
kind: "statement",
|
|
90
|
+
node: n
|
|
91
|
+
});
|
|
92
|
+
else visitChildren(n.declaration);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
visitChildren(n);
|
|
96
|
+
return;
|
|
97
|
+
case "FunctionDeclaration":
|
|
98
|
+
visitChildren(n);
|
|
99
|
+
if (n.id && usedIdents.has(n.id.name)) {
|
|
100
|
+
keptItems.push({
|
|
101
|
+
kind: "statement",
|
|
102
|
+
node: n
|
|
103
|
+
});
|
|
104
|
+
usedIdents.delete(n.id.name);
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
case "ClassDeclaration":
|
|
108
|
+
visitChildren(n);
|
|
109
|
+
if (n.id && usedIdents.has(n.id.name)) {
|
|
110
|
+
keptItems.push({
|
|
111
|
+
kind: "statement",
|
|
112
|
+
node: n
|
|
113
|
+
});
|
|
114
|
+
usedIdents.delete(n.id.name);
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
case "CallExpression":
|
|
118
|
+
if (isMatchedCall(n)) {
|
|
119
|
+
keptItems.push({
|
|
120
|
+
kind: "exprCall",
|
|
121
|
+
node: n
|
|
122
|
+
});
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
visitChildren(n);
|
|
126
|
+
return;
|
|
127
|
+
default: visitChildren(n);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const visitChildren = (node) => {
|
|
131
|
+
for (const key in node) {
|
|
132
|
+
if (key === "type" || key === "start" || key === "end") continue;
|
|
133
|
+
const value = node[key];
|
|
134
|
+
if (value && typeof value === "object") visit(value);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
for (const stmt of body) visit(stmt);
|
|
138
|
+
const leftoverLets = [...usedIdents];
|
|
139
|
+
const magic = new MagicString(code);
|
|
140
|
+
const lineColumn = createLineColumnLookup(code);
|
|
141
|
+
let index = 0;
|
|
142
|
+
const rewriteCall = (call) => {
|
|
143
|
+
const identifier = callIdentifier.get(call) ?? "";
|
|
144
|
+
const start = call.start;
|
|
145
|
+
const end = call.end;
|
|
146
|
+
const { line, column } = lineColumn(start);
|
|
147
|
+
const original = code.slice(start, end);
|
|
148
|
+
const replacement = `${COLLECT_RESULT_NAME}({\n filePath: ${JSON.stringify(filename)},\n index: ${index},\n identifier: ${JSON.stringify(identifier)},\n position: [${start}, ${end}],\n sourceMap: {\n line: ${line},\n column: ${column}\n },\n result: () => ${original}\n})`;
|
|
149
|
+
magic.update(start, end, replacement);
|
|
150
|
+
index += 1;
|
|
151
|
+
};
|
|
152
|
+
for (const item of keptItems) walkMatched(item.node, isMatchedCall, rewriteCall);
|
|
153
|
+
const parts = [];
|
|
154
|
+
parts.push(`import { ${COLLECT_RESULT_NAME} as ${COLLECT_RESULT_NAME} } from "${ADAPTER_SOURCE}";`);
|
|
155
|
+
for (const imp of moduleImports) parts.push(magic.slice(imp.start, imp.end));
|
|
156
|
+
for (const name of leftoverLets) parts.push(`let ${name};`);
|
|
157
|
+
for (const item of keptItems) {
|
|
158
|
+
const sliced = magic.slice(item.node.start, item.node.end);
|
|
159
|
+
parts.push(item.kind === "exprCall" ? `${sliced};` : sliced);
|
|
160
|
+
}
|
|
161
|
+
return parts.join("\n");
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Depth-first visit of matched calls, NOT descending into a matched call's own
|
|
165
|
+
* subtree (mirrors the swc visitor which replaces the node without recursing).
|
|
166
|
+
*/
|
|
167
|
+
function walkMatched(node, isMatched, fn) {
|
|
168
|
+
if (!node || typeof node !== "object") return;
|
|
169
|
+
if (Array.isArray(node)) {
|
|
170
|
+
for (const child of node) walkMatched(child, isMatched, fn);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const n = node;
|
|
174
|
+
if (n.type === "CallExpression" && isMatched(n)) {
|
|
175
|
+
fn(n);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
for (const key in n) {
|
|
179
|
+
if (key === "type" || key === "start" || key === "end") continue;
|
|
180
|
+
const value = n[key];
|
|
181
|
+
if (value && typeof value === "object") walkMatched(value, isMatched, fn);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
//#endregion
|
|
185
|
+
export { rewrite };
|
package/stripTypes.cjs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_loadOxc = require("./loadOxc.cjs");
|
|
3
|
+
//#region src/stripTypes.ts
|
|
4
|
+
/**
|
|
5
|
+
* Strip TypeScript types (and transform JSX) from a module, leaving an ES
|
|
6
|
+
* module of plain JavaScript. Module syntax (import/export) is preserved.
|
|
7
|
+
*/
|
|
8
|
+
function stripTypes(filename, code) {
|
|
9
|
+
return require_loadOxc.getTransform().transformSync(filename, code, {}).code;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
exports.stripTypes = stripTypes;
|
package/stripTypes.mjs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { getTransform } from "./loadOxc.mjs";
|
|
5
|
+
//#region src/stripTypes.ts
|
|
6
|
+
/**
|
|
7
|
+
* Strip TypeScript types (and transform JSX) from a module, leaving an ES
|
|
8
|
+
* module of plain JavaScript. Module syntax (import/export) is preserved.
|
|
9
|
+
*/
|
|
10
|
+
function stripTypes(filename, code) {
|
|
11
|
+
return getTransform().transformSync(filename, code, {}).code;
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { stripTypes };
|
package/toAmd.cjs
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_ast = require("./ast.cjs");
|
|
3
|
+
const require_loadOxc = require("./loadOxc.cjs");
|
|
4
|
+
//#region src/toAmd.ts
|
|
5
|
+
/**
|
|
6
|
+
* Convert an ES module (already TypeScript-stripped) into a single AMD
|
|
7
|
+
* `define([...deps], function (require, exports, ...modules) { ... })` call
|
|
8
|
+
* expression — the shape `@navita/core`'s evaluator expects.
|
|
9
|
+
*/
|
|
10
|
+
function esmToAmd(code, { dropUnusedImports }) {
|
|
11
|
+
const { program } = require_loadOxc.getParser().parseSync("module.js", code, {
|
|
12
|
+
lang: "js",
|
|
13
|
+
sourceType: "module"
|
|
14
|
+
});
|
|
15
|
+
const body = program.body;
|
|
16
|
+
const sources = [];
|
|
17
|
+
const sourceIndex = /* @__PURE__ */ new Map();
|
|
18
|
+
const sideEffectSources = [];
|
|
19
|
+
const sourceInfoFor = (source) => {
|
|
20
|
+
let info = sourceIndex.get(source);
|
|
21
|
+
if (!info) {
|
|
22
|
+
info = {
|
|
23
|
+
source,
|
|
24
|
+
specs: [],
|
|
25
|
+
param: ""
|
|
26
|
+
};
|
|
27
|
+
sourceIndex.set(source, info);
|
|
28
|
+
sources.push(info);
|
|
29
|
+
}
|
|
30
|
+
return info;
|
|
31
|
+
};
|
|
32
|
+
for (const stmt of body) {
|
|
33
|
+
if (stmt.type !== "ImportDeclaration") continue;
|
|
34
|
+
const source = stmt.source.value;
|
|
35
|
+
const specifiers = stmt.specifiers || [];
|
|
36
|
+
if (specifiers.length === 0) {
|
|
37
|
+
sideEffectSources.push(source);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const info = sourceInfoFor(source);
|
|
41
|
+
for (const spec of specifiers) if (spec.type === "ImportDefaultSpecifier") info.specs.push({
|
|
42
|
+
kind: "default",
|
|
43
|
+
local: spec.local.name
|
|
44
|
+
});
|
|
45
|
+
else if (spec.type === "ImportNamespaceSpecifier") info.specs.push({
|
|
46
|
+
kind: "namespace",
|
|
47
|
+
local: spec.local.name
|
|
48
|
+
});
|
|
49
|
+
else info.specs.push({
|
|
50
|
+
kind: "named",
|
|
51
|
+
imported: require_ast.importedName(spec),
|
|
52
|
+
local: spec.local.name
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
56
|
+
if (dropUnusedImports) for (const stmt of body) {
|
|
57
|
+
if (stmt.type === "ImportDeclaration") continue;
|
|
58
|
+
collectIdentifierNames(stmt, referenced);
|
|
59
|
+
}
|
|
60
|
+
const keptSources = [];
|
|
61
|
+
for (const info of sources) {
|
|
62
|
+
const specs = dropUnusedImports ? info.specs.filter((s) => referenced.has(s.local)) : info.specs;
|
|
63
|
+
if (specs.length === 0) continue;
|
|
64
|
+
info.specs = specs;
|
|
65
|
+
keptSources.push(info);
|
|
66
|
+
}
|
|
67
|
+
const keptSideEffects = dropUnusedImports ? [] : sideEffectSources;
|
|
68
|
+
keptSources.forEach((info, i) => {
|
|
69
|
+
info.param = `_navita_import_${i}`;
|
|
70
|
+
});
|
|
71
|
+
const sideEffectParams = keptSideEffects.map((_, i) => `_navita_side_${i}`);
|
|
72
|
+
const deps = [
|
|
73
|
+
"\"require\"",
|
|
74
|
+
"\"exports\"",
|
|
75
|
+
...keptSources.map((info) => JSON.stringify(info.source)),
|
|
76
|
+
...keptSideEffects.map((source) => JSON.stringify(source))
|
|
77
|
+
];
|
|
78
|
+
const params = [
|
|
79
|
+
"require",
|
|
80
|
+
"exports",
|
|
81
|
+
...keptSources.map((info) => info.param),
|
|
82
|
+
...sideEffectParams
|
|
83
|
+
];
|
|
84
|
+
const lines = [];
|
|
85
|
+
let reexportCounter = 0;
|
|
86
|
+
const nextReexportNs = () => `_navita_reexport_${reexportCounter++}`;
|
|
87
|
+
for (const info of keptSources) lines.push(...destructure(info));
|
|
88
|
+
for (const stmt of body) {
|
|
89
|
+
if (stmt.type === "ImportDeclaration") continue;
|
|
90
|
+
emitStatement(stmt, code, lines, nextReexportNs);
|
|
91
|
+
}
|
|
92
|
+
const bodyText = lines.join("\n");
|
|
93
|
+
return `define([${deps.join(", ")}], function (${params.join(", ")}) {\n${bodyText}\n})`;
|
|
94
|
+
}
|
|
95
|
+
function destructure(info) {
|
|
96
|
+
const lines = [];
|
|
97
|
+
const named = [];
|
|
98
|
+
for (const spec of info.specs) if (spec.kind === "named") named.push(spec.imported === spec.local ? spec.local : `${spec.imported}: ${spec.local}`);
|
|
99
|
+
else if (spec.kind === "namespace") lines.push(`const ${spec.local} = ${info.param};`);
|
|
100
|
+
else lines.push(`const ${spec.local} = ${info.param} && ${info.param}.__esModule ? ${info.param}.default : (${info.param}.default !== undefined ? ${info.param}.default : ${info.param});`);
|
|
101
|
+
if (named.length > 0) lines.push(`const { ${named.join(", ")} } = ${info.param};`);
|
|
102
|
+
return lines;
|
|
103
|
+
}
|
|
104
|
+
function emitStatement(stmt, code, lines, nextReexportNs) {
|
|
105
|
+
switch (stmt.type) {
|
|
106
|
+
case "ExportNamedDeclaration":
|
|
107
|
+
if (stmt.declaration) {
|
|
108
|
+
const decl = stmt.declaration;
|
|
109
|
+
lines.push(code.slice(decl.start, decl.end));
|
|
110
|
+
for (const name of declaredNames(decl)) lines.push(`exports.${name} = ${name};`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (stmt.source) {
|
|
114
|
+
const ns = nextReexportNs();
|
|
115
|
+
lines.push(`const ${ns} = require(${JSON.stringify(stmt.source.value)});`);
|
|
116
|
+
for (const spec of stmt.specifiers || []) lines.push(`exports.${spec.exported.name} = ${ns}.${spec.local.name};`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
for (const spec of stmt.specifiers || []) lines.push(`exports.${spec.exported.name} = ${spec.local.name};`);
|
|
120
|
+
return;
|
|
121
|
+
case "ExportDefaultDeclaration": {
|
|
122
|
+
const decl = stmt.declaration;
|
|
123
|
+
if (decl.type === "FunctionDeclaration" || decl.type === "ClassDeclaration") if (decl.id) {
|
|
124
|
+
lines.push(code.slice(decl.start, decl.end));
|
|
125
|
+
lines.push(`exports.default = ${decl.id.name};`);
|
|
126
|
+
} else lines.push(`exports.default = ${code.slice(decl.start, decl.end)};`);
|
|
127
|
+
else lines.push(`exports.default = ${code.slice(decl.start, decl.end)};`);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
case "ExportAllDeclaration": {
|
|
131
|
+
const ns = nextReexportNs();
|
|
132
|
+
lines.push(`const ${ns} = require(${JSON.stringify(stmt.source.value)});`);
|
|
133
|
+
if (stmt.exported) lines.push(`exports.${stmt.exported.name} = ${ns};`);
|
|
134
|
+
else lines.push(`for (const _k in ${ns}) { if (_k !== "default") exports[_k] = ${ns}[_k]; }`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
default: lines.push(code.slice(stmt.start, stmt.end));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function declaredNames(decl) {
|
|
141
|
+
if (decl.type === "VariableDeclaration") {
|
|
142
|
+
const names = [];
|
|
143
|
+
for (const d of decl.declarations) require_ast.patternNames(d.id, names);
|
|
144
|
+
return names;
|
|
145
|
+
}
|
|
146
|
+
if (decl.type === "FunctionDeclaration" || decl.type === "ClassDeclaration") return decl.id ? [decl.id.name] : [];
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
const SKIP_KEYS = /* @__PURE__ */ new Set([
|
|
150
|
+
"type",
|
|
151
|
+
"start",
|
|
152
|
+
"end",
|
|
153
|
+
"range",
|
|
154
|
+
"loc",
|
|
155
|
+
"parent"
|
|
156
|
+
]);
|
|
157
|
+
/** Collect identifier names referenced in a subtree (skip static member/key). */
|
|
158
|
+
function collectIdentifierNames(node, out) {
|
|
159
|
+
if (!node || typeof node !== "object") return;
|
|
160
|
+
if (Array.isArray(node)) {
|
|
161
|
+
for (const child of node) collectIdentifierNames(child, out);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const n = node;
|
|
165
|
+
switch (n.type) {
|
|
166
|
+
case "Identifier":
|
|
167
|
+
case "IdentifierReference":
|
|
168
|
+
out.add(n.name);
|
|
169
|
+
return;
|
|
170
|
+
case "MemberExpression":
|
|
171
|
+
case "StaticMemberExpression":
|
|
172
|
+
case "ComputedMemberExpression":
|
|
173
|
+
if (n.computed) collectIdentifierNames(n.property, out);
|
|
174
|
+
collectIdentifierNames(n.object, out);
|
|
175
|
+
return;
|
|
176
|
+
case "Property":
|
|
177
|
+
case "ObjectProperty":
|
|
178
|
+
if (n.computed) collectIdentifierNames(n.key, out);
|
|
179
|
+
collectIdentifierNames(n.value, out);
|
|
180
|
+
return;
|
|
181
|
+
default: break;
|
|
182
|
+
}
|
|
183
|
+
for (const key in n) {
|
|
184
|
+
if (SKIP_KEYS.has(key)) continue;
|
|
185
|
+
const value = n[key];
|
|
186
|
+
if (value && typeof value === "object") collectIdentifierNames(value, out);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
//#endregion
|
|
190
|
+
exports.esmToAmd = esmToAmd;
|