@@ -2,16 +2,24 @@ import { dirname, isAbsolute, join } from 'path';
2
2
3
3
import ts from 'typescript' ;
4
4
import { compile } from 'svelte/compiler' ;
5
+ import MagicString from 'magic-string' ;
6
+ import sorcery from 'sorcery' ;
5
7
6
8
import { throwTypescriptError } from '../modules/errors' ;
7
9
import { createTagRegex , parseAttributes , stripTags } from '../modules/markup' ;
8
10
import type { Transformer , Options } from '../types' ;
9
11
10
- type CompilerOptions = Options . Typescript [ 'compilerOptions' ] ;
12
+ type CompilerOptions = ts . CompilerOptions ;
13
+
14
+ type SourceMapChain = {
15
+ content : Record < string , string > ;
16
+ sourcemaps : Record < string , object > ;
17
+ } ;
11
18
12
19
function createFormatDiagnosticsHost ( cwd : string ) : ts . FormatDiagnosticsHost {
13
20
return {
14
- getCanonicalFileName : ( fileName : string ) => fileName ,
21
+ getCanonicalFileName : ( fileName : string ) =>
22
+ fileName . replace ( '.injected.ts' , '' ) ,
15
23
getCurrentDirectory : ( ) => cwd ,
16
24
getNewLine : ( ) => ts . sys . newLine ,
17
25
} ;
@@ -70,16 +78,37 @@ function getComponentScriptContent(markup: string): string {
70
78
return '' ;
71
79
}
72
80
81
+ function createSourceMapChain ( {
82
+ filename,
83
+ content,
84
+ compilerOptions,
85
+ } : {
86
+ filename : string ;
87
+ content : string ;
88
+ compilerOptions : CompilerOptions ;
89
+ } ) : SourceMapChain | undefined {
90
+ if ( compilerOptions . sourceMap ) {
91
+ return {
92
+ content : {
93
+ [ filename ] : content ,
94
+ } ,
95
+ sourcemaps : { } ,
96
+ } ;
97
+ }
98
+ }
99
+
73
100
function injectVarsToCode ( {
74
101
content,
75
102
markup,
76
103
filename,
77
104
attributes,
105
+ sourceMapChain,
78
106
} : {
79
107
content : string ;
80
108
markup ?: string ;
81
- filename ? : string ;
109
+ filename : string ;
82
110
attributes ?: Record < string , any > ;
111
+ sourceMapChain ?: SourceMapChain ;
83
112
} ) : string {
84
113
if ( ! markup ) return content ;
85
114
@@ -93,90 +122,103 @@ function injectVarsToCode({
93
122
const sep = '\nconst $$$$$$$$ = null;\n' ;
94
123
const varsValues = vars . map ( ( v ) => v . name ) . join ( ',' ) ;
95
124
const injectedVars = `const $$vars$$ = [${ varsValues } ];` ;
125
+ const injectedCode =
126
+ attributes ?. context === 'module'
127
+ ? `${ sep } ${ getComponentScriptContent ( markup ) } \n${ injectedVars } `
128
+ : `${ sep } ${ injectedVars } ` ;
129
+
130
+ if ( sourceMapChain ) {
131
+ const s = new MagicString ( content ) ;
96
132
97
- if ( attributes ?. context === 'module' ) {
98
- const componentScript = getComponentScriptContent ( markup ) ;
133
+ s . append ( injectedCode ) ;
99
134
100
- return `${ content } ${ sep } ${ componentScript } \n${ injectedVars } ` ;
135
+ const fname = `${ filename } .injected.ts` ;
136
+ const code = s . toString ( ) ;
137
+ const map = s . generateMap ( {
138
+ source : filename ,
139
+ file : fname ,
140
+ } ) ;
141
+
142
+ sourceMapChain . content [ fname ] = code ;
143
+ sourceMapChain . sourcemaps [ fname ] = map ;
144
+
145
+ return code ;
101
146
}
102
147
103
- return `${ content } ${ sep } ${ injectedVars } ` ;
148
+ return `${ content } ${ injectedCode } ` ;
104
149
}
105
150
106
151
function stripInjectedCode ( {
107
- compiledCode ,
152
+ transpiledCode ,
108
153
markup,
154
+ filename,
155
+ sourceMapChain,
109
156
} : {
110
- compiledCode : string ;
157
+ transpiledCode : string ;
111
158
markup ?: string ;
159
+ filename : string ;
160
+ sourceMapChain ?: SourceMapChain ;
112
161
} ) : string {
113
- return markup
114
- ? compiledCode . slice ( 0 , compiledCode . indexOf ( 'const $$$$$$$$ = null;' ) )
115
- : compiledCode ;
116
- }
162
+ if ( ! markup ) return transpiledCode ;
117
163
118
- export function loadTsconfig (
119
- compilerOptionsJSON : any ,
120
- filename : string ,
121
- tsOptions : Options . Typescript ,
122
- ) {
123
- if ( typeof tsOptions . tsconfigFile === 'boolean' ) {
124
- return { errors : [ ] , options : compilerOptionsJSON } ;
125
- }
164
+ const injectedCodeStart = transpiledCode . indexOf ( 'const $$$$$$$$ = null;' ) ;
126
165
127
- let basePath = process . cwd ( ) ;
166
+ if ( sourceMapChain ) {
167
+ const s = new MagicString ( transpiledCode ) ;
168
+ const st = s . snip ( 0 , injectedCodeStart ) ;
128
169
129
- const fileDirectory = ( tsOptions . tsconfigDirectory ||
130
- dirname ( filename ) ) as string ;
170
+ const source = `${ filename } .transpiled.js` ;
171
+ const file = `${ filename } .js` ;
172
+ const code = st . toString ( ) ;
173
+ const map = st . generateMap ( {
174
+ source,
175
+ file,
176
+ } ) ;
131
177
132
- let tsconfigFile =
133
- tsOptions . tsconfigFile ||
134
- ts . findConfigFile ( fileDirectory , ts . sys . fileExists ) ;
178
+ sourceMapChain . content [ file ] = code ;
179
+ sourceMapChain . sourcemaps [ file ] = map ;
135
180
136
- tsconfigFile = isAbsolute ( tsconfigFile )
137
- ? tsconfigFile
138
- : join ( basePath , tsconfigFile ) ;
181
+ return code ;
182
+ }
139
183
140
- basePath = dirname ( tsconfigFile ) ;
184
+ return transpiledCode . slice ( 0 , injectedCodeStart ) ;
185
+ }
141
186
142
- const { error, config } = ts . readConfigFile ( tsconfigFile , ts . sys . readFile ) ;
187
+ async function concatSourceMaps ( {
188
+ filename,
189
+ markup,
190
+ sourceMapChain,
191
+ } : {
192
+ filename : string ;
193
+ markup ?: string ;
194
+ sourceMapChain ?: SourceMapChain ;
195
+ } ) : Promise < string | object | undefined > {
196
+ if ( ! sourceMapChain ) return ;
143
197
144
- if ( error ) {
145
- throw new Error ( formatDiagnostics ( error , basePath ) ) ;
198
+ if ( ! markup ) {
199
+ return sourceMapChain . sourcemaps [ ` ${ filename } .js` ] ;
146
200
}
147
201
148
- // Do this so TS will not search for initial files which might take a while
149
- config . include = [ ] ;
150
-
151
- let { errors, options } = ts . parseJsonConfigFileContent (
152
- config ,
153
- ts . sys ,
154
- basePath ,
155
- compilerOptionsJSON ,
156
- tsconfigFile ,
157
- ) ;
158
-
159
- // Filter out "no files found error"
160
- errors = errors . filter ( ( d ) => d . code !== 18003 ) ;
202
+ const chain = await sorcery . load ( `${ filename } .js` , sourceMapChain ) ;
161
203
162
- return { errors , options } ;
204
+ return chain . apply ( ) ;
163
205
}
164
206
165
- const transformer : Transformer < Options . Typescript > = ( {
166
- content,
207
+ function getCompilerOptions ( {
167
208
filename,
168
- markup,
169
- options = { } ,
170
- attributes,
171
- } ) => {
209
+ options,
210
+ basePath,
211
+ } : {
212
+ filename : string ;
213
+ options : Options . Typescript ;
214
+ basePath : string ;
215
+ } ) : CompilerOptions {
172
216
// default options
173
217
const compilerOptionsJSON = {
174
218
moduleResolution : 'node' ,
175
219
target : 'es6' ,
176
220
} ;
177
221
178
- const basePath = process . cwd ( ) ;
179
-
180
222
Object . assign ( compilerOptionsJSON , options . compilerOptions ) ;
181
223
182
224
const { errors, options : convertedCompilerOptions } =
@@ -203,19 +245,38 @@ const transformer: Transformer<Options.Typescript> = ({
203
245
) ;
204
246
}
205
247
248
+ return compilerOptions ;
249
+ }
250
+
251
+ function transpileTs ( {
252
+ code,
253
+ markup,
254
+ filename,
255
+ basePath,
256
+ options,
257
+ compilerOptions,
258
+ sourceMapChain,
259
+ } : {
260
+ code : string ;
261
+ markup : string ;
262
+ filename : string ;
263
+ basePath : string ;
264
+ options : Options . Typescript ;
265
+ compilerOptions : CompilerOptions ;
266
+ sourceMapChain : SourceMapChain ;
267
+ } ) : { transpiledCode : string ; diagnostics : ts . Diagnostic [ ] } {
268
+ const fileName = markup ? `${ filename } .injected.ts` : filename ;
269
+
206
270
const {
207
- outputText : compiledCode ,
208
- sourceMapText : map ,
271
+ outputText : transpiledCode ,
272
+ sourceMapText,
209
273
diagnostics,
210
- } = ts . transpileModule (
211
- injectVarsToCode ( { content, markup, filename, attributes } ) ,
212
- {
213
- fileName : filename ,
214
- compilerOptions,
215
- reportDiagnostics : options . reportDiagnostics !== false ,
216
- transformers : markup ? { } : { before : [ importTransformer ] } ,
217
- } ,
218
- ) ;
274
+ } = ts . transpileModule ( code , {
275
+ fileName,
276
+ compilerOptions,
277
+ reportDiagnostics : options . reportDiagnostics !== false ,
278
+ transformers : markup ? { } : { before : [ importTransformer ] } ,
279
+ } ) ;
219
280
220
281
if ( diagnostics . length > 0 ) {
221
282
// could this be handled elsewhere?
@@ -232,7 +293,109 @@ const transformer: Transformer<Options.Typescript> = ({
232
293
}
233
294
}
234
295
235
- const code = stripInjectedCode ( { compiledCode, markup } ) ;
296
+ if ( sourceMapChain ) {
297
+ const fname = markup ? `${ filename } .transpiled.js` : `${ filename } .js` ;
298
+
299
+ sourceMapChain . content [ fname ] = transpiledCode ;
300
+ sourceMapChain . sourcemaps [ fname ] = JSON . parse ( sourceMapText ) ;
301
+ }
302
+
303
+ return { transpiledCode, diagnostics } ;
304
+ }
305
+
306
+ export function loadTsconfig (
307
+ compilerOptionsJSON : any ,
308
+ filename : string ,
309
+ tsOptions : Options . Typescript ,
310
+ ) {
311
+ if ( typeof tsOptions . tsconfigFile === 'boolean' ) {
312
+ return { errors : [ ] , options : compilerOptionsJSON } ;
313
+ }
314
+
315
+ let basePath = process . cwd ( ) ;
316
+
317
+ const fileDirectory = ( tsOptions . tsconfigDirectory ||
318
+ dirname ( filename ) ) as string ;
319
+
320
+ let tsconfigFile =
321
+ tsOptions . tsconfigFile ||
322
+ ts . findConfigFile ( fileDirectory , ts . sys . fileExists ) ;
323
+
324
+ tsconfigFile = isAbsolute ( tsconfigFile )
325
+ ? tsconfigFile
326
+ : join ( basePath , tsconfigFile ) ;
327
+
328
+ basePath = dirname ( tsconfigFile ) ;
329
+
330
+ const { error, config } = ts . readConfigFile ( tsconfigFile , ts . sys . readFile ) ;
331
+
332
+ if ( error ) {
333
+ throw new Error ( formatDiagnostics ( error , basePath ) ) ;
334
+ }
335
+
336
+ // Do this so TS will not search for initial files which might take a while
337
+ config . include = [ ] ;
338
+
339
+ let { errors, options } = ts . parseJsonConfigFileContent (
340
+ config ,
341
+ ts . sys ,
342
+ basePath ,
343
+ compilerOptionsJSON ,
344
+ tsconfigFile ,
345
+ ) ;
346
+
347
+ // Filter out "no files found error"
348
+ errors = errors . filter ( ( d ) => d . code !== 18003 ) ;
349
+
350
+ return { errors, options } ;
351
+ }
352
+
353
+ const transformer : Transformer < Options . Typescript > = async ( {
354
+ content,
355
+ filename = 'source.svelte' ,
356
+ markup,
357
+ options = { } ,
358
+ attributes,
359
+ } ) => {
360
+ const basePath = process . cwd ( ) ;
361
+ const compilerOptions = getCompilerOptions ( { filename, options, basePath } ) ;
362
+
363
+ const sourceMapChain = createSourceMapChain ( {
364
+ filename,
365
+ content,
366
+ compilerOptions,
367
+ } ) ;
368
+
369
+ const injectedCode = injectVarsToCode ( {
370
+ content,
371
+ markup,
372
+ filename,
373
+ attributes,
374
+ sourceMapChain,
375
+ } ) ;
376
+
377
+ const { transpiledCode, diagnostics } = transpileTs ( {
378
+ code : injectedCode ,
379
+ markup,
380
+ filename,
381
+ basePath,
382
+ options,
383
+ compilerOptions,
384
+ sourceMapChain,
385
+ } ) ;
386
+
387
+ const code = stripInjectedCode ( {
388
+ transpiledCode,
389
+ markup,
390
+ filename,
391
+ sourceMapChain,
392
+ } ) ;
393
+
394
+ const map = await concatSourceMaps ( {
395
+ filename,
396
+ markup,
397
+ sourceMapChain,
398
+ } ) ;
236
399
237
400
return {
238
401
code,
0 commit comments