Skip to content

Commit a08bb7a

Browse files
committed
rewrite to output js object looking syntax
1 parent 4e64bbd commit a08bb7a

File tree

1 file changed

+119
-162
lines changed

1 file changed

+119
-162
lines changed

src/index.js

Lines changed: 119 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -2,191 +2,148 @@ import { walk } from "estree-walker";
22
import MagicString from "magic-string";
33

44
const whitespace = /\s/;
5+
// these will be removed
6+
const disallowedNodeTypes = [
7+
"ExpressionStatement",
8+
"DebuggerStatement",
9+
"ImportDeclaration",
10+
"ExportNamedDeclaration",
11+
];
512

613
export default function afterEffectsJsx(options = {}) {
714
const exports = [];
815
return {
916
name: "after-effects-jsx", // this name will show up in warnings and errors
10-
transform(code, id) {
11-
let ast;
12-
try {
13-
ast = this.parse(code);
14-
} catch (err) {
15-
err.message += ` in ${id}`;
16-
throw err;
17-
}
18-
const magicString = new MagicString(code);
19-
20-
function remove(start, end) {
21-
while (whitespace.test(code[start - 1])) start -= 1;
22-
magicString.remove(start, end);
23-
}
24-
25-
function isBlock(node) {
26-
return (
27-
node && (node.type === "BlockStatement" || node.type === "Program")
28-
);
29-
}
17+
generateBundle(options = {}, bundle, isWrite) {
18+
// format each file
19+
// to be ae-jsx
20+
for (const file in bundle) {
21+
// Get the string code of the file
22+
let code = bundle[file].code;
23+
// generate AST to walk through
24+
let ast;
25+
try {
26+
ast = this.parse(code);
27+
} catch (err) {
28+
err.message += ` in ${file}`;
29+
throw err;
30+
}
31+
// create magic string to perform operations on
32+
const magicString = new MagicString(code);
3033

31-
function removeStatement(node) {
32-
const { parent } = node;
34+
// removes characters from the magicString
35+
function remove(start, end) {
36+
while (whitespace.test(code[start - 1])) start -= 1;
37+
magicString.remove(start, end);
38+
}
3339

34-
if (isBlock(parent)) {
35-
remove(node.start, node.end);
36-
} else {
37-
magicString.overwrite(node.start, node.end, "(void 0);");
40+
function isBlock(node) {
41+
return (
42+
node && (node.type === "BlockStatement" || node.type === "Program")
43+
);
3844
}
39-
}
4045

41-
// Find exports
42-
walk(ast, {
43-
enter(node, parent) {
44-
Object.defineProperty(node, "parent", {
45-
value: parent,
46-
enumerable: false,
47-
configurable: true,
48-
});
46+
// removes entire statements
47+
function removeStatement(node) {
48+
const { parent } = node;
4949

50-
if (
51-
// is un-named export
52-
node.type === "ExportNamedDeclaration" &&
53-
node.declaration === null
54-
) {
55-
node.specifiers.forEach((specifier) =>
56-
exports.push(specifier.local.name)
57-
);
50+
if (isBlock(parent)) {
5851
remove(node.start, node.end);
59-
this.skip();
52+
} else {
53+
magicString.overwrite(node.start, node.end, "(void 0);");
6054
}
61-
},
62-
});
63-
64-
// Remove nodes
65-
walk(ast, {
66-
enter(node, parent) {
67-
Object.defineProperty(node, "parent", {
68-
value: parent,
69-
enumerable: false,
70-
configurable: true,
71-
});
55+
}
7256

73-
if (node.type === "ImportDeclaration") {
74-
remove(node.start, node.end);
75-
this.skip();
76-
} else if (node.type === "ExportNamedDeclaration") {
77-
const declaration = node.declaration;
78-
if (declaration == null) {
57+
// Find exports by looking for expressions
58+
// that are exports.[exportName] = [exportName];
59+
walk(ast, {
60+
enter(node, parent) {
61+
Object.defineProperty(node, "parent", {
62+
value: parent,
63+
enumerable: false,
64+
configurable: true,
65+
});
66+
67+
if (
68+
// it's an export expression statement
69+
node.type === "ExportNamedDeclaration"
70+
) {
71+
exports.push(
72+
...node.specifiers.map((exportNode) => exportNode.local.name)
73+
);
74+
}
75+
},
76+
});
77+
78+
// Remove non exported nodes and convert
79+
// to object property style compatible syntax
80+
walk(ast, {
81+
enter(node, parent) {
82+
Object.defineProperty(node, "parent", {
83+
value: parent,
84+
enumerable: false,
85+
configurable: true,
86+
});
87+
88+
if (node.type === "FunctionDeclaration") {
89+
// Deal with functions
90+
const functionName = node.id.name;
91+
if (!exports.includes(functionName)) {
92+
// Remove non-exported functions
93+
remove(node.start, node.end);
94+
} else {
95+
// remove the function keyword
96+
magicString.remove(node.start, node.id.start);
97+
// add a trailing comma
98+
magicString.appendLeft(node.end, ",");
99+
}
100+
// don't process child nodes
79101
this.skip();
80-
} else if (declaration.type === "VariableDeclaration") {
81-
const variableName = declaration.declarations.map(
102+
} else if (node.type === "VariableDeclaration") {
103+
// deal with variables
104+
const variableName = node.declarations.map(
82105
(declaration) => declaration.id.name
83106
)[0];
84107
if (!exports.includes(variableName)) {
108+
// Remove variables that aren't exported
85109
remove(node.start, node.end);
86-
this.skip();
87-
}
88-
} else if (declaration.type === "FunctionDeclaration") {
89-
const functionName = declaration.id.name;
90-
if (!exports.includes(functionName)) {
91-
remove(node.start, node.end);
110+
} else {
111+
const valueStart = node.declarations[0].init.start;
112+
const variableName = node.declarations[0].id.name;
113+
// remove anything before the variable name
114+
// e.g. const, var, let
115+
magicString.overwrite(
116+
node.start,
117+
valueStart - 1,
118+
`${variableName}:`
119+
);
120+
const endsInSemiColon =
121+
magicString.slice(node.end - 1, node.end) === ";";
122+
if (endsInSemiColon) {
123+
// replace ; with ,
124+
magicString.overwrite(node.end - 1, node.end, ",");
125+
} else {
126+
// or add trailing comma
127+
magicString.appendLeft(node.end, ",");
128+
}
92129
}
130+
// don't process child nodes
93131
this.skip();
94-
}
95-
} else if (node.type === "FunctionDeclaration") {
96-
const functionName = node.id.name;
97-
if (!exports.includes(functionName)) {
98-
remove(node.start, node.end);
99-
}
100-
this.skip();
101-
} else if (node.type === "VariableDeclaration") {
102-
const variableName = node.declarations.map(
103-
(declaration) => declaration.id.name
104-
)[0];
105-
if (!exports.includes(variableName)) {
106-
remove(node.start, node.end);
132+
} else if (disallowedNodeTypes.includes(node.type)) {
133+
// Remove every top level node that isn't
134+
// a function or variable, as they're not allowed
135+
removeStatement(node);
107136
this.skip();
108137
}
109-
} else if (node.type === "DebuggerStatement") {
110-
removeStatement(node);
111-
this.skip();
112-
}
113-
},
114-
});
115-
code = magicString.toString();
116-
console.log(`Exported JSX: ${exports}`);
117-
return { code, map: null, moduleSideEffects: "no-treeshake" };
118-
},
119-
generateBundle(options = {}, bundle, isWrite) {
120-
// format each file
121-
// to be ae-jsx
122-
for (const file in bundle) {
123-
// Get the string code of the file
124-
let fixedCode = bundle[file].code;
125-
// Modify code
126-
fixedCode = removeBundlerCode(fixedCode);
127-
fixedCode = removeComments(fixedCode);
128-
// fixedCode = removeDeclaration(fixedCode, 'PropertyGroupBase');
129-
fixedCode = wrapExportsInQuotes(exports, fixedCode);
130-
fixedCode = separateExportsWithCommas(exports, fixedCode);
131-
fixedCode = indentAllLines(fixedCode);
132-
fixedCode = wrapInBrackets(fixedCode);
133-
134-
// Add replace code of file with modified
135-
bundle[file].code = fixedCode;
138+
},
139+
});
140+
// Log exports to the terminal
141+
console.log(`Exported JSX:`, exports);
142+
// Sanitize output and wrap in braces
143+
magicString.trim().indent().prepend("{\n").append("\n}");
144+
// Replace the files code with modified
145+
bundle[file].code = magicString.toString();
136146
}
137147
},
138148
};
139149
}
140-
141-
function removeBundlerCode(code) {
142-
return code.replace("'use strict';", "");
143-
}
144-
145-
function wrapExportsInQuotes(exports, code) {
146-
let newCode = code;
147-
exports.forEach((name) => {
148-
newCode = newCode
149-
.replace(`function ${name}`, `"${name}": function`)
150-
.replace(`const ${name} =`, `"${name}": `)
151-
.replace(`let ${name} =`, `"${name}": `)
152-
.replace(`var ${name} =`, `"${name}": `)
153-
.replace(`}\n"${name}"`, `}\n"${name}"`)
154-
.replace(`;\n"${name}"`, `,\n"${name}"`);
155-
});
156-
return newCode;
157-
}
158-
159-
function separateExportsWithCommas(exports, code) {
160-
let codeLines = code.split("\n");
161-
const fixedLines = codeLines.map((line, lineIndex) => {
162-
let newLine = line;
163-
exports.forEach((name, exportIndex) => {
164-
if (line.startsWith(`"${name}"`)) {
165-
newLine = newLine.replace(
166-
";",
167-
exportIndex === exports.length ? "," : ""
168-
);
169-
}
170-
});
171-
if (newLine === "}" && lineIndex !== codeLines.length) {
172-
newLine = "},";
173-
}
174-
return newLine;
175-
});
176-
return fixedLines.join("\n");
177-
}
178-
179-
function indentAllLines(code) {
180-
return code
181-
.split("\n")
182-
.map((line) => ` ${line}`)
183-
.join("\n");
184-
}
185-
186-
function wrapInBrackets(code) {
187-
return `{\n ${code.trim()}\n}`;
188-
}
189-
190-
function removeComments(code) {
191-
return code.replace(/^\/\/.+/gm, "");
192-
}

0 commit comments

Comments
 (0)