@@ -2,191 +2,148 @@ import { walk } from "estree-walker";
22import MagicString from "magic-string" ;
33
44const whitespace = / \s / ;
5+ // these will be removed
6+ const disallowedNodeTypes = [
7+ "ExpressionStatement" ,
8+ "DebuggerStatement" ,
9+ "ImportDeclaration" ,
10+ "ExportNamedDeclaration" ,
11+ ] ;
512
613export 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