@@ -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,97 @@ 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 } ` ;
96
129
97
- if ( attributes ?. context === 'module' ) {
98
- const componentScript = getComponentScriptContent ( markup ) ;
130
+ if ( sourceMapChain ) {
131
+ const s = new MagicString ( content ) ;
99
132
100
- return `${ content } ${ sep } ${ componentScript } \n${ injectedVars } ` ;
133
+ s . append ( injectedCode ) ;
134
+
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
- }
117
-
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
- }
126
-
127
- let basePath = process . cwd ( ) ;
128
-
129
- const fileDirectory = ( tsOptions . tsconfigDirectory ||
130
- dirname ( filename ) ) as string ;
162
+ if ( ! markup ) return transpiledCode ;
131
163
132
- let tsconfigFile =
133
- tsOptions . tsconfigFile ||
134
- ts . findConfigFile ( fileDirectory , ts . sys . fileExists ) ;
164
+ const injectedCodeStart = transpiledCode . indexOf ( 'const $$$$$$$$ = null;' ) ;
135
165
136
- tsconfigFile = isAbsolute ( tsconfigFile )
137
- ? tsconfigFile
138
- : join ( basePath , tsconfigFile ) ;
166
+ if ( sourceMapChain ) {
167
+ const s = new MagicString ( transpiledCode ) ;
168
+ const st = s . snip ( 0 , injectedCodeStart ) ;
139
169
140
- basePath = dirname ( tsconfigFile ) ;
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
+ } ) ;
141
177
142
- const { error, config } = ts . readConfigFile ( tsconfigFile , ts . sys . readFile ) ;
178
+ sourceMapChain . content [ file ] = code ;
179
+ sourceMapChain . sourcemaps [ file ] = map ;
143
180
144
- if ( error ) {
145
- throw new Error ( formatDiagnostics ( error , basePath ) ) ;
181
+ return code ;
146
182
}
147
183
148
- // Do this so TS will not search for initial files which might take a while
149
- config . include = [ ] ;
184
+ return transpiledCode . slice ( 0 , injectedCodeStart ) ;
185
+ }
150
186
151
- let { errors, options } = ts . parseJsonConfigFileContent (
152
- config ,
153
- ts . sys ,
154
- basePath ,
155
- compilerOptionsJSON ,
156
- tsconfigFile ,
157
- ) ;
187
+ async function concatSourceMaps ( {
188
+ filename,
189
+ sourceMapChain,
190
+ } : {
191
+ filename : string ;
192
+ sourceMapChain ?: SourceMapChain ;
193
+ } ) : Promise < string | object | undefined > {
194
+ if ( ! sourceMapChain ) return ;
158
195
159
- // Filter out "no files found error"
160
- errors = errors . filter ( ( d ) => d . code !== 18003 ) ;
196
+ const chain = await sorcery . load ( `${ filename } .js` , sourceMapChain ) ;
161
197
162
- return { errors , options } ;
198
+ return chain . apply ( ) ;
163
199
}
164
200
165
- const transformer : Transformer < Options . Typescript > = ( {
166
- content,
201
+ function getCompilerOptions ( {
167
202
filename,
168
- markup,
169
- options = { } ,
170
- attributes,
171
- } ) => {
203
+ options,
204
+ basePath,
205
+ } : {
206
+ filename : string ;
207
+ options : Options . Typescript ;
208
+ basePath : string ;
209
+ } ) : CompilerOptions {
172
210
// default options
173
211
const compilerOptionsJSON = {
174
212
moduleResolution : 'node' ,
175
213
target : 'es6' ,
176
214
} ;
177
215
178
- const basePath = process . cwd ( ) ;
179
-
180
216
Object . assign ( compilerOptionsJSON , options . compilerOptions ) ;
181
217
182
218
const { errors, options : convertedCompilerOptions } =
@@ -203,19 +239,38 @@ const transformer: Transformer<Options.Typescript> = ({
203
239
) ;
204
240
}
205
241
242
+ return compilerOptions ;
243
+ }
244
+
245
+ function transpileTs ( {
246
+ code,
247
+ markup,
248
+ filename,
249
+ basePath,
250
+ options,
251
+ compilerOptions,
252
+ sourceMapChain,
253
+ } : {
254
+ code : string ;
255
+ markup : string ;
256
+ filename : string ;
257
+ basePath : string ;
258
+ options : Options . Typescript ;
259
+ compilerOptions : CompilerOptions ;
260
+ sourceMapChain : SourceMapChain ;
261
+ } ) : { transpiledCode : string ; diagnostics : ts . Diagnostic [ ] } {
262
+ const fileName = markup ? `${ filename } .injected.ts` : filename ;
263
+
206
264
const {
207
- outputText : compiledCode ,
208
- sourceMapText : map ,
265
+ outputText : transpiledCode ,
266
+ sourceMapText,
209
267
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
- ) ;
268
+ } = ts . transpileModule ( code , {
269
+ fileName,
270
+ compilerOptions,
271
+ reportDiagnostics : options . reportDiagnostics !== false ,
272
+ transformers : markup ? { } : { before : [ importTransformer ] } ,
273
+ } ) ;
219
274
220
275
if ( diagnostics . length > 0 ) {
221
276
// could this be handled elsewhere?
@@ -232,7 +287,108 @@ const transformer: Transformer<Options.Typescript> = ({
232
287
}
233
288
}
234
289
235
- const code = stripInjectedCode ( { compiledCode, markup } ) ;
290
+ if ( sourceMapChain ) {
291
+ const fname = markup ? `${ filename } .transpiled.js` : `${ filename } .js` ;
292
+
293
+ sourceMapChain . content [ fname ] = transpiledCode ;
294
+ sourceMapChain . sourcemaps [ fname ] = JSON . parse ( sourceMapText ) ;
295
+ }
296
+
297
+ return { transpiledCode, diagnostics } ;
298
+ }
299
+
300
+ export function loadTsconfig (
301
+ compilerOptionsJSON : any ,
302
+ filename : string ,
303
+ tsOptions : Options . Typescript ,
304
+ ) {
305
+ if ( typeof tsOptions . tsconfigFile === 'boolean' ) {
306
+ return { errors : [ ] , options : compilerOptionsJSON } ;
307
+ }
308
+
309
+ let basePath = process . cwd ( ) ;
310
+
311
+ const fileDirectory = ( tsOptions . tsconfigDirectory ||
312
+ dirname ( filename ) ) as string ;
313
+
314
+ let tsconfigFile =
315
+ tsOptions . tsconfigFile ||
316
+ ts . findConfigFile ( fileDirectory , ts . sys . fileExists ) ;
317
+
318
+ tsconfigFile = isAbsolute ( tsconfigFile )
319
+ ? tsconfigFile
320
+ : join ( basePath , tsconfigFile ) ;
321
+
322
+ basePath = dirname ( tsconfigFile ) ;
323
+
324
+ const { error, config } = ts . readConfigFile ( tsconfigFile , ts . sys . readFile ) ;
325
+
326
+ if ( error ) {
327
+ throw new Error ( formatDiagnostics ( error , basePath ) ) ;
328
+ }
329
+
330
+ // Do this so TS will not search for initial files which might take a while
331
+ config . include = [ ] ;
332
+
333
+ let { errors, options } = ts . parseJsonConfigFileContent (
334
+ config ,
335
+ ts . sys ,
336
+ basePath ,
337
+ compilerOptionsJSON ,
338
+ tsconfigFile ,
339
+ ) ;
340
+
341
+ // Filter out "no files found error"
342
+ errors = errors . filter ( ( d ) => d . code !== 18003 ) ;
343
+
344
+ return { errors, options } ;
345
+ }
346
+
347
+ const transformer : Transformer < Options . Typescript > = async ( {
348
+ content,
349
+ filename = 'source.svelte' ,
350
+ markup,
351
+ options = { } ,
352
+ attributes,
353
+ } ) => {
354
+ const basePath = process . cwd ( ) ;
355
+ const compilerOptions = getCompilerOptions ( { filename, options, basePath } ) ;
356
+
357
+ const sourceMapChain = createSourceMapChain ( {
358
+ filename,
359
+ content,
360
+ compilerOptions,
361
+ } ) ;
362
+
363
+ const injectedCode = injectVarsToCode ( {
364
+ content,
365
+ markup,
366
+ filename,
367
+ attributes,
368
+ sourceMapChain,
369
+ } ) ;
370
+
371
+ const { transpiledCode, diagnostics } = transpileTs ( {
372
+ code : injectedCode ,
373
+ markup,
374
+ filename,
375
+ basePath,
376
+ options,
377
+ compilerOptions,
378
+ sourceMapChain,
379
+ } ) ;
380
+
381
+ const code = stripInjectedCode ( {
382
+ transpiledCode,
383
+ markup,
384
+ filename,
385
+ sourceMapChain,
386
+ } ) ;
387
+
388
+ const map = await concatSourceMaps ( {
389
+ filename,
390
+ sourceMapChain,
391
+ } ) ;
236
392
237
393
return {
238
394
code,
0 commit comments