@@ -80,10 +80,130 @@ const importTransformer: ts.TransformerFactory<ts.SourceFile> = context => {
80
80
return node => ts . visitNode ( node , visit ) ;
81
81
} ;
82
82
83
- const TS_TRANSFORMERS = {
84
- before : [ importTransformer ] ,
83
+ function findImportUsages (
84
+ node : ts . Node ,
85
+ context : ts . TransformationContext ,
86
+ ) : { [ name : string ] : number } {
87
+ const usages : { [ name : string ] : number } = { } ;
88
+
89
+ let locals = new Set < string > ( ) ;
90
+
91
+ const enterScope = < T > ( action : ( ) => T ) => {
92
+ const oldLocals = locals ;
93
+ locals = new Set ( [ ...locals ] ) ;
94
+ const result = action ( ) ;
95
+ locals = oldLocals ;
96
+ return result ;
97
+ } ;
98
+
99
+ const findUsages : ts . Visitor = node => {
100
+ if ( ts . isImportClause ( node ) ) {
101
+ const bindings = node . namedBindings ;
102
+ if ( bindings && 'elements' in bindings ) {
103
+ bindings . elements . forEach (
104
+ binding => ( usages [ binding . name . escapedText as string ] = 0 ) ,
105
+ ) ;
106
+ }
107
+ return node ;
108
+ }
109
+
110
+ if ( ts . isFunctionDeclaration ( node ) ) {
111
+ return enterScope ( ( ) => {
112
+ node . parameters
113
+ . map ( p => p . name )
114
+ . filter ( ts . isIdentifier )
115
+ . forEach ( p => locals . add ( p . escapedText as string ) ) ;
116
+ return ts . visitEachChild ( node , child => findUsages ( child ) , context ) ;
117
+ } ) ;
118
+ }
119
+
120
+ if ( ts . isBlock ( node ) ) {
121
+ return enterScope ( ( ) =>
122
+ ts . visitEachChild ( node , child => findUsages ( child ) , context ) ,
123
+ ) ;
124
+ }
125
+
126
+ if ( ts . isVariableDeclaration ( node ) && ts . isIdentifier ( node . name ) ) {
127
+ locals . add ( node . name . escapedText as string ) ;
128
+ } else if ( ts . isIdentifier ( node ) ) {
129
+ const identifier = node . escapedText as string ;
130
+ if ( ! locals . has ( identifier ) && usages [ identifier ] != undefined ) {
131
+ usages [ identifier ] ++ ;
132
+ }
133
+ }
134
+
135
+ return ts . visitEachChild ( node , child => findUsages ( child ) , context ) ;
136
+ } ;
137
+
138
+ ts . visitNode ( node , findUsages ) ;
139
+
140
+ return usages ;
141
+ }
142
+
143
+ const removeNonEmittingImports : (
144
+ mainFile ?: string ,
145
+ ) => ts . TransformerFactory < ts . SourceFile > = mainFile => context => {
146
+ function createVisitor ( usages : { [ name : string ] : number } ) {
147
+ const visit : ts . Visitor = node => {
148
+ if ( ts . isImportDeclaration ( node ) ) {
149
+ let importClause = node . importClause ;
150
+ const bindings = importClause . namedBindings ;
151
+
152
+ if ( bindings && ts . isNamedImports ( bindings ) ) {
153
+ const namedImports = bindings . elements . filter (
154
+ element => usages [ element . name . getText ( ) ] > 0 ,
155
+ ) ;
156
+
157
+ if ( namedImports . length !== bindings . elements . length ) {
158
+ return ts . createImportDeclaration (
159
+ node . decorators ,
160
+ node . modifiers ,
161
+ namedImports . length == 0
162
+ ? undefined
163
+ : ts . createImportClause (
164
+ importClause . name ,
165
+ ts . createNamedImports ( namedImports ) ,
166
+ ) ,
167
+ node . moduleSpecifier ,
168
+ ) ;
169
+ }
170
+ }
171
+
172
+ return node ;
173
+ }
174
+
175
+ if (
176
+ ts . isVariableStatement ( node ) &&
177
+ node . modifiers &&
178
+ node . modifiers [ 0 ] &&
179
+ node . modifiers [ 0 ] . kind == ts . SyntaxKind . ExportKeyword
180
+ ) {
181
+ const name = node . declarationList . declarations [ 0 ] . name ;
182
+
183
+ if ( ts . isIdentifier ( name ) && name . escapedText == '___used_tags__' ) {
184
+ return undefined ;
185
+ }
186
+ }
187
+
188
+ return ts . visitEachChild ( node , child => visit ( child ) , context ) ;
189
+ } ;
190
+
191
+ return visit ;
192
+ }
193
+
194
+ return node =>
195
+ ! mainFile || ts . sys . resolvePath ( node . fileName ) === mainFile
196
+ ? ts . visitNode ( node , createVisitor ( findImportUsages ( node , context ) ) )
197
+ : node ;
85
198
} ;
86
199
200
+ function createTransforms ( mainFile ?: string ) {
201
+ return {
202
+ before : [ importTransformer ] ,
203
+ after : [ removeNonEmittingImports ( mainFile ) ] ,
204
+ } ;
205
+ }
206
+
87
207
const TS2552_REGEX = / C a n n o t f i n d n a m e ' \$ ( [ a - z A - Z 0 - 9 _ ] + ) ' . D i d y o u m e a n ' ( [ a - z A - Z 0 - 9 _ ] + ) ' \? / i;
88
208
function isValidSvelteReactiveValueDiagnostic (
89
209
filename : string ,
@@ -103,6 +223,25 @@ function isValidSvelteReactiveValueDiagnostic(
103
223
return ! ( usedVar && proposedVar && usedVar === proposedVar ) ;
104
224
}
105
225
226
+ function findTagsInMarkup ( markup : string ) {
227
+ if ( ! markup ) {
228
+ return [ ] ;
229
+ }
230
+
231
+ let match : RegExpExecArray ;
232
+ const result : string [ ] = [ ] ;
233
+ const findTag = / < ( [ A - Z ] [ ^ \s \/ > ] * ) ( [ \s \S ] * ?) > / g;
234
+ const template = markup
235
+ . replace ( / < s c r i p t ( [ \s \S ] * ?) (?: > ( [ \s \S ] * ) < \/ s c r i p t > | \/ > ) / g, '' )
236
+ . replace ( / < s t y l e ( [ \s \S ] * ?) (?: > ( [ \s \S ] * ) < \/ s t y l e > | \/ > ) / g, '' ) ;
237
+
238
+ while ( ( match = findTag . exec ( template ) ) !== null ) {
239
+ result . push ( match [ 1 ] ) ;
240
+ }
241
+
242
+ return result ;
243
+ }
244
+
106
245
function compileFileFromMemory (
107
246
compilerOptions : CompilerOptions ,
108
247
{ filename, content } : { filename : string ; content : string } ,
@@ -112,7 +251,7 @@ function compileFileFromMemory(
112
251
113
252
const realHost = ts . createCompilerHost ( compilerOptions , true ) ;
114
253
const dummyFileName = ts . sys . resolvePath ( filename ) ;
115
-
254
+ const dummyBaseName = basename ( dummyFileName ) ;
116
255
const isDummyFile = ( fileName : string ) =>
117
256
ts . sys . resolvePath ( fileName ) === dummyFileName ;
118
257
@@ -142,10 +281,13 @@ function compileFileFromMemory(
142
281
readFile : fileName =>
143
282
isDummyFile ( fileName ) ? content : realHost . readFile ( fileName ) ,
144
283
writeFile : ( fileName , data ) => {
145
- if ( fileName . endsWith ( '.map' ) ) {
146
- map = data ;
147
- } else {
148
- code = data ;
284
+ switch ( basename ( fileName ) ) {
285
+ case dummyBaseName + '.js.map' :
286
+ map = data ;
287
+ break ;
288
+ case dummyBaseName + '.js' :
289
+ code = data . replace ( / \/ \/ # s o u r c e M a p p i n g U R L = .* $ / , '' ) ;
290
+ break ;
149
291
}
150
292
} ,
151
293
directoryExists :
@@ -162,12 +304,13 @@ function compileFileFromMemory(
162
304
} ;
163
305
164
306
const program = ts . createProgram ( [ dummyFileName ] , compilerOptions , host ) ;
307
+
165
308
const emitResult = program . emit (
166
309
undefined ,
167
310
undefined ,
168
311
undefined ,
169
312
undefined ,
170
- TS_TRANSFORMERS ,
313
+ createTransforms ( dummyFileName ) ,
171
314
) ;
172
315
173
316
// collect diagnostics without svelte import errors
@@ -187,6 +330,7 @@ const transformer: Transformer<Options.Typescript> = ({
187
330
content,
188
331
filename,
189
332
options,
333
+ markup,
190
334
} ) => {
191
335
// default options
192
336
const compilerOptionsJSON = {
@@ -242,15 +386,24 @@ const transformer: Transformer<Options.Typescript> = ({
242
386
) ;
243
387
}
244
388
389
+ // Force module kind to es2015, so we keep the correct names.
390
+ compilerOptions . module = ts . ModuleKind . ES2015 ;
391
+
392
+ const tagsInMarkup = findTagsInMarkup ( markup ) ;
393
+
245
394
let code , map , diagnostics ;
395
+
396
+ // Append all used tags
397
+ content += '\nexport const __used_tags__=[' + tagsInMarkup . join ( ',' ) + '];' ;
398
+
246
399
if ( options . transpileOnly || compilerOptions . transpileOnly ) {
247
400
( { outputText : code , sourceMapText : map , diagnostics } = ts . transpileModule (
248
401
content ,
249
402
{
250
403
fileName : filename ,
251
404
compilerOptions : compilerOptions ,
252
405
reportDiagnostics : options . reportDiagnostics !== false ,
253
- transformers : TS_TRANSFORMERS ,
406
+ transformers : createTransforms ( ) ,
254
407
} ,
255
408
) ) ;
256
409
} else {
0 commit comments