UNPKG

@typed/test

Version:
110 lines 4.17 kB
import { createHash } from 'crypto'; import { readFileSync, writeFileSync } from 'fs'; import { basename, extname, join } from 'path'; import { directory } from 'tempy'; import { transpileModule } from 'typescript'; import { diagnosticsToString } from './diagnosticToString'; // tslint:disable-next-line:no-var-requires const sourceMapSupport = require('source-map-support'); const DEFAULT_COMPILER_OPTIONS = { sourceMap: true, inlineSourceMap: false, inlineSources: true, declaration: false, noEmit: false, outDir: '$$ts-transpilation$$', }; export function transpileNode(cwd, compilerOptions) { compilerOptions = Object.assign({}, compilerOptions, DEFAULT_COMPILER_OPTIONS); const cacheDirectory = directory(); const originalJsHandler = require.extensions['.js']; const memoryCache = { contents: Object.create(null), outputs: Object.create(null), }; const extensions = ['.ts', '.tsx']; const compile = (code, fileName) => { const result = transpileModule(code, { fileName, compilerOptions, reportDiagnostics: true, }); const diagnosticList = result.diagnostics; if (diagnosticList && diagnosticList.length) { throw new Error(diagnosticsToString(diagnosticList, cwd)); } return [result.outputText, result.sourceMapText]; }; sourceMapSupport.install({ environment: 'node', retrieveFile(path) { return memoryCache.outputs[path]; }, }); const register = { cwd, cacheDirectory, extensions, compile: readThrough(cacheDirectory, memoryCache, compile, extname), }; extensions.forEach(extension => { registerExtension(extension, register, originalJsHandler); }); } function readThrough(cachedir, memoryCache, compile, getExtension) { // Make sure the cache directory exists before continuing. return (code, fileName) => { const cachePath = join(cachedir, getCacheName(code, fileName)); const extension = getExtension(fileName); const outputPath = `${cachePath}${extension}`; try { const contents = readFileSync(outputPath, 'utf8'); if (isValidCacheContent(contents)) { memoryCache.outputs[fileName] = contents; return contents; } } catch (err) { /* Ignore. */ } const [value, sourceMap] = compile(code, fileName); const output = updateOutput(value, fileName, sourceMap, getExtension); memoryCache.outputs[fileName] = output; writeFileSync(outputPath, output); return output; }; } function updateOutput(outputText, fileName, sourceMap, getExtension) { const base64Map = Buffer.from(updateSourceMap(sourceMap, fileName), 'utf8').toString('base64'); const sourceMapContent = `data:application/json;charset=utf-8;base64,${base64Map}`; const sourceMapLength = `${basename(fileName)}.map`.length + (getExtension(fileName).length - extname(fileName).length); return outputText.slice(0, -sourceMapLength) + sourceMapContent; } function updateSourceMap(sourceMapText, fileName) { const sourceMap = JSON.parse(sourceMapText); sourceMap.file = fileName; sourceMap.sources = [fileName]; delete sourceMap.sourceRoot; return JSON.stringify(sourceMap); } function isValidCacheContent(contents) { return /(?:9|0=|Q==)$/.test(contents.slice(-3)); } function getCacheName(sourceCode, fileName) { return createHash('sha256') .update(extname(fileName), 'utf8') .update('\x00', 'utf8') .update(sourceCode, 'utf8') .digest('hex'); } function registerExtension(ext, register, originalHandler) { const old = require.extensions[ext] || originalHandler; require.extensions[ext] = (m, filename) => { const _compile = m._compile; m._compile = function (code, fileName) { return _compile.call(this, register.compile(code, fileName), fileName); }; return old(m, filename); }; } //# sourceMappingURL=transpileNode.js.map