@@ -2,191 +2,148 @@ import { walk } from "estree-walker";
2
2
import MagicString from "magic-string" ;
3
3
4
4
const whitespace = / \s / ;
5
+ // these will be removed
6
+ const disallowedNodeTypes = [
7
+ "ExpressionStatement" ,
8
+ "DebuggerStatement" ,
9
+ "ImportDeclaration" ,
10
+ "ExportNamedDeclaration" ,
11
+ ] ;
5
12
6
13
export default function afterEffectsJsx ( options = { } ) {
7
14
const exports = [ ] ;
8
15
return {
9
16
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 ) ;
30
33
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
+ }
33
39
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
+ ) ;
38
44
}
39
- }
40
45
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 ;
49
49
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 ) ) {
58
51
remove ( node . start , node . end ) ;
59
- this . skip ( ) ;
52
+ } else {
53
+ magicString . overwrite ( node . start , node . end , "(void 0);" ) ;
60
54
}
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
+ }
72
56
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
79
101
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 (
82
105
( declaration ) => declaration . id . name
83
106
) [ 0 ] ;
84
107
if ( ! exports . includes ( variableName ) ) {
108
+ // Remove variables that aren't exported
85
109
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
+ }
92
129
}
130
+ // don't process child nodes
93
131
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 ) ;
107
136
this . skip ( ) ;
108
137
}
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 ( ) ;
136
146
}
137
147
} ,
138
148
} ;
139
149
}
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