Skip to content

Commit 44c389e

Browse files
authored
Override module._compile for compilation (TypeStrong#180)
* Refactor project to use file caching (used for in-memory compilation from other extension override) * Remove file versioning based on filename and auto-increment instead
1 parent b11180e commit 44c389e

File tree

2 files changed

+61
-83
lines changed

2 files changed

+61
-83
lines changed

src/_bin.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import minimist = require('minimist')
88
import chalk = require('chalk')
99
import { diffLines } from 'diff'
1010
import { createScript } from 'vm'
11-
import { register, VERSION, getFile, getVersion, fileExists, TSError } from './index'
11+
import { register, VERSION, getFile, fileExists, TSError } from './index'
1212

1313
interface Argv {
1414
eval?: string
@@ -156,7 +156,6 @@ const isPrinted = argv.print != null
156156
// Register the TypeScript compiler instance.
157157
const service = register(extend(argv, {
158158
getFile: isEval ? getFileEval : getFile,
159-
getVersion: isEval ? getVersionEval : getVersion,
160159
fileExists: isEval ? fileExistsEval : fileExists,
161160
ignoreWarnings: arrify(argv.ignoreWarnings)
162161
}))
@@ -253,7 +252,7 @@ function _eval (code: string, context: any) {
253252

254253
// Undo on TypeScript compilation errors.
255254
try {
256-
output = service().compile(EVAL_PATH)
255+
output = service().compile(evalFile.input, EVAL_PATH)
257256
} catch (error) {
258257
evalFile.input = undo
259258

@@ -361,13 +360,6 @@ function getFileEval (fileName: string) {
361360
return fileName === EVAL_PATH ? evalFile.input : getFile(fileName)
362361
}
363362

364-
/**
365-
* Get the file version, checking for eval first.
366-
*/
367-
function getVersionEval (fileName: string) {
368-
return fileName === EVAL_PATH ? String(evalFile.version) : getVersion(fileName)
369-
}
370-
371363
/**
372364
* Get whether the file exists.
373365
*/

src/index.ts

Lines changed: 59 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import * as TS from 'typescript'
1111
import { loadSync } from 'tsconfig'
1212

1313
const pkg = require('../package.json')
14-
const oldHandlers: { [key: string]: any } = {}
1514

1615
/**
1716
* Common TypeScript interfaces between versions.
@@ -67,7 +66,6 @@ export interface Options {
6766
ignoreWarnings?: Array<number | string>
6867
disableWarnings?: boolean
6968
getFile?: (fileName: string) => string
70-
getVersion?: (fileName: string) => string
7169
fileExists?: (fileName: string) => boolean
7270
compilerOptions?: any
7371
}
@@ -76,10 +74,9 @@ export interface Options {
7674
* Track the project information.
7775
*/
7876
interface Project {
79-
files: { [fileName: string]: boolean }
80-
versions: { [fileName: string]: string }
81-
maps: { [fileName: string]: string }
82-
version: number
77+
cache: { [fileName: string]: string }
78+
versions: { [fileName: string]: number }
79+
sourceMaps: { [fileName: string]: string }
8380
}
8481

8582
/**
@@ -95,7 +92,6 @@ export interface TypeInfo {
9592
*/
9693
const DEFAULT_OPTIONS: Options = {
9794
getFile,
98-
getVersion,
9995
fileExists,
10096
cache: process.env.TS_NODE_CACHE,
10197
cacheDirectory: process.env.TS_NODE_CACHE_DIRECTORY || join(tmpdir(), 'ts-node'),
@@ -109,7 +105,7 @@ const DEFAULT_OPTIONS: Options = {
109105

110106
export interface Register {
111107
cwd: string
112-
compile (fileName: string): string
108+
compile (code: string, fileName: string): string
113109
getTypeInfo (fileName: string, position: number): TypeInfo
114110
}
115111

@@ -130,16 +126,16 @@ export function register (opts?: Options): () => Register {
130126
options.compilerOptions
131127

132128
function load () {
133-
const project: Project = { version: 0, files: {}, versions: {}, maps: {} }
129+
const project: Project = { cache: {}, versions: {}, sourceMaps: {} }
134130

135131
// Install source map support and read from cache.
136132
sourceMapSupport.install({
137133
environment: 'node',
138134
retrieveSourceMap (fileName: string) {
139-
if (project.maps[fileName]) {
135+
if (project.sourceMaps[fileName]) {
140136
return {
141-
url: project.maps[fileName],
142-
map: options.getFile(project.maps[fileName])
137+
url: project.sourceMaps[fileName],
138+
map: options.getFile(project.sourceMaps[fileName])
143139
}
144140
}
145141
}
@@ -167,14 +163,16 @@ export function register (opts?: Options): () => Register {
167163

168164
// Add all files into the file hash.
169165
for (const fileName of config.fileNames) {
170-
project.files[fileName] = true
166+
if (/\.d\.ts$/.test(fileName)) {
167+
project.versions[fileName] = 1
168+
}
171169
}
172170

173171
/**
174172
* Create the basic required function using transpile mode.
175173
*/
176-
let getOutput = function (fileName: string, contents: string): SourceOutput {
177-
const result = ts.transpileModule(contents, {
174+
let getOutput = function (code: string, fileName: string): SourceOutput {
175+
const result = ts.transpileModule(code, {
178176
fileName,
179177
compilerOptions: config.options,
180178
reportDiagnostics: true
@@ -199,16 +197,33 @@ export function register (opts?: Options): () => Register {
199197

200198
// Use full language services when the fast option is disabled.
201199
if (!options.fast) {
200+
// Add the file to the project.
201+
const addVersion = function (fileName: string) {
202+
if (!project.versions.hasOwnProperty(fileName)) {
203+
project.versions[fileName] = 1
204+
}
205+
}
206+
207+
// Set the file contents into cache.
208+
const addCache = function (code: string, fileName: string) {
209+
project.cache[fileName] = code
210+
project.versions[fileName] += 1
211+
}
212+
213+
// Create the compiler host for type checking.
202214
const serviceHost = {
203-
getScriptFileNames: () => Object.keys(project.files),
204-
getProjectVersion: () => String(project.version),
205-
getScriptVersion: (fileName: string) => versionFile(fileName),
215+
getScriptFileNames: () => Object.keys(project.versions),
216+
getScriptVersion: (fileName: string) => String(project.versions[fileName]),
206217
getScriptSnapshot (fileName: string) {
207-
if (!options.fileExists(fileName)) {
208-
return undefined
218+
if (!project.cache.hasOwnProperty(fileName)) {
219+
if (!options.fileExists(fileName)) {
220+
return undefined
221+
}
222+
223+
project.cache[fileName] = options.getFile(fileName)
209224
}
210225

211-
return ts.ScriptSnapshot.fromString(options.getFile(fileName))
226+
return ts.ScriptSnapshot.fromString(project.cache[fileName])
212227
},
213228
getDirectories: getDirectories,
214229
directoryExists: directoryExists,
@@ -220,28 +235,7 @@ export function register (opts?: Options): () => Register {
220235

221236
const service = ts.createLanguageService(serviceHost)
222237

223-
const addAndVersionFile = function (fileName: string) {
224-
// Add files to the hash before compilation.
225-
project.files[fileName] = true
226-
227-
const currentVersion = project.versions[fileName]
228-
const newVersion = versionFile(fileName)
229-
230-
// Increment the project version for file changes.
231-
if (currentVersion !== newVersion) {
232-
project.version++
233-
}
234-
235-
return newVersion
236-
}
237-
238-
const versionFile = function (fileName: string) {
239-
const version = options.getVersion(fileName)
240-
project.versions[fileName] = version
241-
return version
242-
}
243-
244-
getOutput = function (fileName: string) {
238+
getOutput = function (code: string, fileName: string) {
245239
const output = service.getEmitOutput(fileName)
246240

247241
// Get the relevant diagnostics - this is 3x faster than `getPreEmitDiagnostics`.
@@ -262,14 +256,15 @@ export function register (opts?: Options): () => Register {
262256
return [output.outputFiles[1].text, output.outputFiles[0].text]
263257
}
264258

265-
compile = readThrough(cachedir, options, project, function (fileName: string, contents: string) {
266-
addAndVersionFile(fileName)
259+
compile = readThrough(cachedir, options, project, function (code: string, fileName: string) {
260+
addVersion(fileName)
261+
addCache(code, fileName)
267262

268-
return getOutput(fileName, contents)
263+
return getOutput(code, fileName)
269264
})
270265

271266
getTypeInfo = function (fileName: string, position: number) {
272-
addAndVersionFile(fileName)
267+
addVersion(fileName)
273268

274269
const info = service.getQuickInfoAtPosition(fileName, position)
275270
const name = ts.displayPartsToString(info ? info.displayParts : [])
@@ -286,25 +281,25 @@ export function register (opts?: Options): () => Register {
286281
return result || (result = load())
287282
}
288283

289-
function loader (m: any, fileName: string) {
290-
return m._compile(service().compile(fileName), fileName)
291-
}
292-
293284
function shouldIgnore (filename: string) {
294285
return relative(service().cwd, filename).split(sep).indexOf('node_modules') > -1
295286
}
296287

297288
function registerExtension (ext: string) {
298-
const old = oldHandlers[ext] || oldHandlers['.js'] || require.extensions['.js']
299-
300-
oldHandlers[ext] = require.extensions[ext]
289+
const old = require.extensions[ext] || require.extensions['.js']
301290

302291
require.extensions[ext] = function (m: any, filename: string) {
303292
if (shouldIgnore(filename)) {
304293
return old(m, filename)
305294
}
306295

307-
return loader(m, filename)
296+
const _compile = m._compile
297+
298+
m._compile = function (code: string, fileName: string) {
299+
return _compile.call(this, service().compile(code, fileName), fileName)
300+
}
301+
302+
return old(m, filename)
308303
}
309304
}
310305

@@ -369,16 +364,15 @@ function readThrough (
369364
cachedir: string,
370365
options: Options,
371366
project: Project,
372-
compile: (fileName: string, contents: string) => SourceOutput
367+
compile: (code: string, fileName: string) => SourceOutput
373368
) {
374369
if (options.cache === false) {
375-
return function (fileName: string) {
376-
const contents = options.getFile(fileName)
377-
const cachePath = join(cachedir, getCacheName(contents, fileName))
370+
return function (code: string, fileName: string) {
371+
const cachePath = join(cachedir, getCacheName(code, fileName))
378372
const sourceMapPath = `${cachePath}.js.map`
379-
const out = compile(fileName, contents)
373+
const out = compile(code, fileName)
380374

381-
project.maps[fileName] = sourceMapPath
375+
project.sourceMaps[fileName] = sourceMapPath
382376

383377
const output = updateOutput(out[0], fileName, sourceMapPath)
384378
const sourceMap = updateSourceMap(out[1], fileName)
@@ -389,20 +383,19 @@ function readThrough (
389383
}
390384
}
391385

392-
return function (fileName: string) {
393-
const contents = options.getFile(fileName)
394-
const cachePath = join(cachedir, getCacheName(contents, fileName))
386+
return function (code: string, fileName: string) {
387+
const cachePath = join(cachedir, getCacheName(code, fileName))
395388
const outputPath = `${cachePath}.js`
396389
const sourceMapPath = `${cachePath}.js.map`
397390

398-
project.maps[fileName] = sourceMapPath
391+
project.sourceMaps[fileName] = sourceMapPath
399392

400393
// Use the cache when available.
401394
if (options.fileExists(outputPath)) {
402395
return options.getFile(outputPath)
403396
}
404397

405-
const out = compile(fileName, contents)
398+
const out = compile(code, fileName)
406399

407400
const output = updateOutput(out[0], fileName, sourceMapPath)
408401
const sourceMap = updateSourceMap(out[1], fileName)
@@ -465,13 +458,6 @@ function getCompilerDigest (ts: TSCommon, options: Options, config: any) {
465458
)
466459
}
467460

468-
/**
469-
* Get the file version using the mod time.
470-
*/
471-
export function getVersion (fileName: string): string {
472-
return String(statSync(fileName).mtime.getTime())
473-
}
474-
475461
/**
476462
* Check if the file exists.
477463
*/

0 commit comments

Comments
 (0)