UNPKG

11.7 kBJavaScriptView Raw
1var tsNode = require('ts-node')
2
3var fs = require('fs')
4var path = require('path')
5var os = require('os')
6var mkdirp = require('mkdirp')
7var rimraf = require('rimraf')
8var { resolveSync } = require('tsconfig')
9
10var getCompiledPath = require('./get-compiled-path')
11var tmpDir = '.ts-node'
12
13const fixPath = (p) => p.replace(/\\/g, '/').replace(/\$/g, '$$$$')
14
15var sourceMapSupportPath = require.resolve('source-map-support')
16
17var extensions = ['.ts', '.tsx']
18var empty = function () {}
19var cwd = process.cwd()
20var compilationInstanceStamp = Math.random().toString().slice(2)
21
22var originalJsHandler = require.extensions['.js']
23
24var extHandlers = {}
25
26function hasOwnProperty (object, property) {
27 return Object.prototype.hasOwnProperty.call(object, property)
28}
29
30function getCwd(dir, scriptMode, scriptPath) {
31 if (scriptMode) {
32 if (!scriptPath) {
33 throw new TypeError('Script mode must be used with a script name, e.g. `ts-node-dev -s <script.ts>`')
34 }
35
36 if (dir) {
37 throw new TypeError('Script mode cannot be combined with `--dir`')
38 }
39
40 // Use node's own resolution behavior to ensure we follow symlinks.
41 // scriptPath may omit file extension or point to a directory with or without package.json.
42 // This happens before we are registered, so we tell node's resolver to consider ts, tsx, and jsx files.
43 // In extremely rare cases, is is technically possible to resolve the wrong directory,
44 // because we do not yet know preferTsExts, jsx, nor allowJs.
45 // See also, justification why this will not happen in real-world situations:
46 // https://github.com/TypeStrong/ts-node/pull/1009#issuecomment-613017081
47 const exts = ['.js', '.jsx', '.ts', '.tsx']
48 const extsTemporarilyInstalled = []
49 for (const ext of exts) {
50 if (!hasOwnProperty(require.extensions, ext)) { // tslint:disable-line
51 extsTemporarilyInstalled.push(ext)
52 require.extensions[ext] = function() {} // tslint:disable-line
53 }
54 }
55 try {
56 return path.dirname(require.resolve(scriptPath))
57 } finally {
58 for (const ext of extsTemporarilyInstalled) {
59 delete require.extensions[ext] // tslint:disable-line
60 }
61 }
62 }
63
64 return dir
65}
66
67
68var compiler = {
69 allowJs: false,
70 tsConfigPath: '',
71 _errorCompileTimeout: 0,
72 getCompilationId: function () {
73 return compilationInstanceStamp
74 },
75 createCompiledDir: function () {
76 var compiledDir = compiler.getCompiledDir()
77 if (!fs.existsSync(compiledDir)) {
78 mkdirp.sync(compiler.getCompiledDir())
79 }
80 },
81 getCompiledDir: function () {
82 return path.join(tmpDir, 'compiled').replace(/\\/g, '/')
83 },
84 getCompileReqFilePath: function () {
85 return path.join(
86 compiler.getCompiledDir(),
87 compiler.getCompilationId() + '.req'
88 )
89 },
90 getCompilerReadyFilePath: function () {
91 return path
92 .join(os.tmpdir(), 'ts-node-dev-ready-' + compilationInstanceStamp)
93 .replace(/\\/g, '/')
94 },
95 getChildHookPath: function () {
96 return path
97 .join(os.tmpdir(), 'ts-node-dev-hook-' + compilationInstanceStamp + '.js')
98 .replace(/\\/g, '/')
99 },
100 writeReadyFile: function () {
101 var fileData = fs.writeFileSync(compiler.getCompilerReadyFilePath(), '')
102 },
103 writeChildHookFile: function (options) {
104 var fileData = fs.readFileSync(
105 path.join(__dirname, 'child-require-hook.js'),
106 'utf-8'
107 )
108 var compileTimeout = parseInt(options['compile-timeout'])
109 if (compileTimeout) {
110 fileData = fileData.replace('10000', compileTimeout.toString())
111 }
112 if (compiler.allowJs) {
113 fileData = fileData.replace('allowJs = false', 'allowJs = true')
114 }
115 if (options['prefer-ts'] || options['prefer-ts-exts']) {
116 fileData = fileData.replace('preferTs = false', 'preferTs = true')
117 }
118 if (options['exec-check']) {
119 fileData = fileData.replace('execCheck = false', 'execCheck = true')
120 }
121
122 if (options['exit-child']) {
123 fileData = fileData.replace('exitChild = false', 'exitChild = true')
124 }
125 if (options['ignore'] !== undefined) {
126 var ignore = options['ignore']
127 var ignoreVal =
128 !ignore || ignore === 'false'
129 ? 'false'
130 : '[' +
131 (Array.isArray(ignore) ? ignore : ignore.split(/, /))
132 .map((ignore) => 'new RegExp("' + ignore + '")')
133 .join(', ') +
134 ']'
135 fileData = fileData.replace(
136 'var ignore = [/node_modules/]',
137 'var ignore = ' + ignoreVal
138 )
139 }
140 fileData = fileData.replace(
141 'var compilationId',
142 'var compilationId = "' + compiler.getCompilationId() + '"'
143 )
144 fileData = fileData.replace(
145 'var compiledDir',
146 'var compiledDir = "' + compiler.getCompiledDir() + '"'
147 )
148 fileData = fileData.replace(
149 './get-compiled-path',
150 fixPath(path.join(__dirname, 'get-compiled-path'))
151 )
152 fileData = fileData.replace(
153 'var readyFile',
154 'var readyFile = "' + compiler.getCompilerReadyFilePath() + '"'
155 )
156 fileData = fileData.replace(
157 'var sourceMapSupportPath',
158 'var sourceMapSupportPath = "' + fixPath(sourceMapSupportPath) + '"'
159 )
160 fileData = fileData.replace(
161 'var libPath',
162 'var libPath = "' + __dirname.replace(/\\/g, '\\\\') + '"'
163 )
164 fileData = fileData.replace(/__dirname/, '"' + fixPath(__dirname) + '"')
165 fs.writeFileSync(compiler.getChildHookPath(), fileData)
166 },
167 clearErrorCompile: () => {
168 clearTimeout(compiler._errorCompileTimeout)
169 },
170 init: function (options) {
171 compiler.options = options
172 var project = options['project']
173 compiler.log = options.log
174 compiler.tsConfigPath =
175 resolveSync(cwd, typeof project === 'string' ? project : undefined) || ''
176
177 //var originalJsHandler = require.extensions['.js']
178 require.extensions['.ts'] = empty
179 require.extensions['.tsx'] = empty
180 tmpDir = options['cache-directory']
181 ? path.resolve(options['cache-directory'])
182 : fs.mkdtempSync(path.join(os.tmpdir(), '.ts-node'))
183
184 compiler.registerTsNode()
185
186 /* clean up compiled on each new init*/
187 rimraf.sync(compiler.getCompiledDir())
188 compiler.createCompiledDir()
189 /* check if `allowJs` compiler option enable */
190 var allowJsEnabled = require.extensions['.js'] !== originalJsHandler
191 if (allowJsEnabled) {
192 compiler.allowJs = true
193 //require.extensions['.js'] = originalJsHandler
194 extensions.push('.js')
195 }
196
197 compiler.writeChildHookFile(options)
198 },
199 registerTsNode: function () {
200 var options = compiler.options
201 extensions.forEach(function (ext) {
202 require.extensions[ext] = originalJsHandler
203 })
204
205 // var tsNodeOptions = {
206 // //dir: should add
207 // emit: options['emit'],
208 // files: options['files'],
209 // pretty: options['pretty'],
210 // transpileOnly: options['transpile-only'],
211 // ignore: [].concat(options['ignore']),
212 // preferTsExts: options['prefer-ts-exts'] || options['prefer-ts'],
213 // logError: options['log-error'],
214 // project: options['project'],
215 // skipProject: options['skip-project'],
216 // skipIgnore: options['skip-ignore'],
217 // compiler: options['compiler'],
218 // ignoreDiagnostics: options['ignore-diagnostics'],
219 // disableWarnings: options['disableWarnings'],
220 // compilerOptions: options['compiler-options'],
221 // }
222 var compilerOptionsArg =
223 options['compilerOptions'] || options['compiler-options']
224 var compilerOptions
225 if (compilerOptionsArg) {
226 try {
227 compilerOptions = JSON.parse(compilerOptionsArg)
228 } catch (e) {
229 console.log(
230 'Could not parse compilerOptions',
231 options['compilerOptions']
232 )
233 console.log(e)
234 }
235 }
236 let ignore = options['ignore'] || process.env['TS_NODE_IGNORE']
237 if (ignore && typeof ignore === 'string') {
238 ignore = [ignore]
239 }
240
241 const scriptPath = options._.length ? path.resolve(cwd, options._[0]) : undefined
242
243 var DEFAULTS = tsNode.DEFAULTS
244
245 try {
246 compiler.service = tsNode.register({
247 // --dir does not work (it gives a boolean only) so we only check for script-mode
248 dir: getCwd(options['dir'], options['script-mode'], scriptPath),
249 scope: options['dir'] || DEFAULTS.scope,
250 emit: options['emit'] || DEFAULTS.emit,
251 files: options['files'] || DEFAULTS.files,
252 pretty: options['pretty'] || DEFAULTS.pretty,
253 transpileOnly: options['transpile-only'] || DEFAULTS.transpileOnly,
254 ignore: options['ignore']
255 ? tsNode.split(options['ignore']) || options['ignore']
256 : DEFAULTS.ignore,
257 preferTsExts:
258 options['prefer-ts-exts'] ||
259 options['prefer-ts'] ||
260 DEFAULTS.preferTsExts,
261 logError: options['log-error'] || DEFAULTS.logError,
262 project: options['project'],
263 skipProject: options['skip-project'],
264 skipIgnore: options['skip-ignore'],
265 compiler: options['compiler'] || DEFAULTS.compiler,
266 compilerHost: options['compiler-host'] || DEFAULTS.compilerHost,
267 ignoreDiagnostics: options['ignore-diagnostics'],
268 compilerOptions: tsNode.parse(options['compiler-options']),
269 })
270 } catch (e) {
271 console.log(e)
272 return
273 }
274 extensions.forEach(function (ext) {
275 extHandlers[ext] = require.extensions[ext]
276 require.extensions[ext] = originalJsHandler
277 })
278 },
279 compileChanged: function (fileName) {
280 var ext = path.extname(fileName)
281 if (extensions.indexOf(ext) < 0) return
282 try {
283 var code = fs.readFileSync(fileName, 'utf-8')
284 compiler.compile({
285 code: code,
286 compile: fileName,
287 compiledPath: getCompiledPath(
288 code,
289 fileName,
290 compiler.getCompiledDir()
291 ),
292 })
293 } catch (e) {
294 console.error(e)
295 }
296 },
297 compile: function (params) {
298 var fileName = params.compile
299 var code = fs.readFileSync(fileName, 'utf-8')
300 var compiledPath = params.compiledPath
301 function writeCompiled(code, filename) {
302 fs.writeFileSync(compiledPath, code)
303 fs.writeFileSync(compiledPath + '.done', '')
304 }
305 if (fs.existsSync(compiledPath)) {
306 return
307 }
308 var starTime = new Date().getTime()
309 var m = {
310 _compile: writeCompiled,
311 }
312 const _compile = () => {
313 var ext = path.extname(fileName)
314 var extHandler = extHandlers[ext] || require.extensions[ext]
315 extHandler(m, fileName)
316 m._compile(code, fileName)
317 compiler.log.debug(
318 fileName,
319 'compiled in',
320 new Date().getTime() - starTime,
321 'ms'
322 )
323 }
324 try {
325 _compile()
326 } catch (e) {
327 console.log('Compilation error in', fileName)
328 const errorCode =
329 'throw ' + 'new Error(' + JSON.stringify(e.message) + ')' + ';'
330 writeCompiled(errorCode)
331
332 // reinitialize ts-node compilation to clean up state after error
333 // without timeout in causes cases error not be printed out
334 setTimeout(() => {
335 compiler.registerTsNode()
336 })
337
338 if (!compiler.options['error-recompile']) {
339 return
340 }
341 const timeoutMs =
342 parseInt(process.env.TS_NODE_DEV_ERROR_RECOMPILE_TIMEOUT) || 5000
343 const errorHandler = () => {
344 clearTimeout(compiler._errorCompileTimeout)
345 compiler._errorCompileTimeout = setTimeout(() => {
346 try {
347 _compile()
348 compiler.restart(fileName)
349 } catch (e) {
350 compiler.registerTsNode()
351 errorHandler()
352 }
353 }, timeoutMs)
354 }
355
356 errorHandler()
357 }
358 },
359}
360
361module.exports = compiler