UNPKG

@vercel/node

Version:
341 lines (340 loc) 13.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.register = void 0; const build_utils_1 = require("@vercel/build-utils"); const path_1 = require("path"); /* * Fork of TS-Node - https://github.com/TypeStrong/ts-node * Copyright Blake Embrey * MIT License */ /** * Debugging. */ const shouldDebug = false; const debug = shouldDebug ? console.log.bind(console, 'ts-node') : () => undefined; const debugFn = shouldDebug ? (key, fn) => { let i = 0; return (x) => { debug(key, x, ++i); return fn(x); }; } : (_, fn) => fn; /** * Track the project information. */ class MemoryCache { constructor(rootFileNames = []) { this.fileContents = new Map(); this.fileVersions = new Map(); for (const fileName of rootFileNames) this.fileVersions.set(fileName, 1); } } /** * Default register options. */ const DEFAULTS = { files: null, pretty: null, compiler: undefined, compilerOptions: undefined, ignore: undefined, project: undefined, ignoreDiagnostics: undefined, logError: null, }; /** * Default TypeScript compiler options required by `ts-node`. */ const TS_NODE_COMPILER_OPTIONS = { sourceMap: true, inlineSourceMap: false, inlineSources: true, declaration: false, noEmit: false, outDir: '$$ts-node$$', }; /** * Replace backslashes with forward slashes. */ function normalizeSlashes(value) { return value.replace(/\\/g, '/'); } /** * Cached fs operation wrapper. */ function cachedLookup(fn) { const cache = new Map(); return (arg) => { if (!cache.has(arg)) { cache.set(arg, fn(arg)); } return cache.get(arg); }; } /** * Register TypeScript compiler. */ function register(opts = {}) { const options = Object.assign({}, DEFAULTS, opts); const ignoreDiagnostics = [ 6059, 18002, 18003, ...(options.ignoreDiagnostics || []), ].map(Number); // Require the TypeScript compiler and configuration. const cwd = options.basePath || process.cwd(); const nowNodeBase = path_1.resolve(__dirname, '..', '..', '..'); let compiler; const require_ = eval('require'); try { compiler = require_.resolve(options.compiler || 'typescript', { paths: [options.project || cwd, nowNodeBase], }); } catch (e) { compiler = 'typescript'; } //eslint-disable-next-line @typescript-eslint/no-var-requires const ts = require_(compiler); if (compiler.startsWith(nowNodeBase)) { console.log('Using TypeScript ' + ts.version + ' (no local tsconfig.json)'); } else { console.log('Using TypeScript ' + ts.version + ' (local user-provided)'); } const transformers = options.transformers || undefined; const readFile = options.readFile || ts.sys.readFile; const fileExists = options.fileExists || ts.sys.fileExists; const formatDiagnostics = process.stdout.isTTY || options.pretty ? ts.formatDiagnosticsWithColorAndContext : ts.formatDiagnostics; const diagnosticHost = { getNewLine: () => ts.sys.newLine, getCurrentDirectory: () => cwd, getCanonicalFileName: path => path, }; function createTSError(diagnostics) { const message = formatDiagnostics(diagnostics, diagnosticHost); return new build_utils_1.NowBuildError({ code: 'NODE_TYPESCRIPT_ERROR', message }); } function reportTSError(diagnostics, shouldExit) { if (!diagnostics || diagnostics.length === 0) { return; } const error = createTSError(diagnostics); if (shouldExit) { throw error; } else { // Print error in red color and continue execution. console.error('\x1b[31m%s\x1b[0m', error); } } // we create a custom build per tsconfig.json instance const builds = new Map(); function getBuild(configFileName = '') { let build = builds.get(configFileName); if (build) return build; const config = readConfig(configFileName); /** * Create the basic required function using transpile mode. */ const getOutput = function (code, fileName) { const result = ts.transpileModule(code, { fileName, transformers, compilerOptions: config.options, reportDiagnostics: true, }); const diagnosticList = result.diagnostics ? filterDiagnostics(result.diagnostics, ignoreDiagnostics) : []; reportTSError(diagnosticList, config.options.noEmitOnError); return { code: result.outputText, map: result.sourceMapText }; }; // Use full language services when the fast option is disabled. let getOutputTypeCheck; { const memoryCache = new MemoryCache(config.fileNames); const cachedReadFile = cachedLookup(debugFn('readFile', readFile)); // Create the compiler host for type checking. const serviceHost = { getScriptFileNames: () => Array.from(memoryCache.fileVersions.keys()), getScriptVersion: (fileName) => { const version = memoryCache.fileVersions.get(fileName); return version === undefined ? '' : version.toString(); }, getScriptSnapshot(fileName) { let contents = memoryCache.fileContents.get(fileName); // Read contents into TypeScript memory cache. if (contents === undefined) { contents = cachedReadFile(fileName); if (contents === undefined) return; memoryCache.fileVersions.set(fileName, 1); memoryCache.fileContents.set(fileName, contents); } return ts.ScriptSnapshot.fromString(contents); }, readFile: cachedReadFile, readDirectory: cachedLookup(debugFn('readDirectory', ts.sys.readDirectory)), getDirectories: cachedLookup(debugFn('getDirectories', ts.sys.getDirectories)), fileExists: cachedLookup(debugFn('fileExists', fileExists)), directoryExists: cachedLookup(debugFn('directoryExists', ts.sys.directoryExists)), getNewLine: () => ts.sys.newLine, useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames, getCurrentDirectory: () => cwd, getCompilationSettings: () => config.options, getDefaultLibFileName: () => ts.getDefaultLibFilePath(config.options), getCustomTransformers: () => transformers, }; const registry = ts.createDocumentRegistry(ts.sys.useCaseSensitiveFileNames, cwd); const service = ts.createLanguageService(serviceHost, registry); // Set the file contents into cache manually. const updateMemoryCache = function (contents, fileName) { const fileVersion = memoryCache.fileVersions.get(fileName) || 0; // Avoid incrementing cache when nothing has changed. if (memoryCache.fileContents.get(fileName) === contents) return; memoryCache.fileVersions.set(fileName, fileVersion + 1); memoryCache.fileContents.set(fileName, contents); }; getOutputTypeCheck = function (code, fileName) { updateMemoryCache(code, fileName); const output = service.getEmitOutput(fileName); // Get the relevant diagnostics - this is 3x faster than `getPreEmitDiagnostics`. const diagnostics = service .getSemanticDiagnostics(fileName) .concat(service.getSyntacticDiagnostics(fileName)); const diagnosticList = filterDiagnostics(diagnostics, ignoreDiagnostics); reportTSError(diagnosticList, config.options.noEmitOnError); if (output.emitSkipped) { throw new TypeError(`${path_1.relative(cwd, fileName)}: Emit skipped`); } // Throw an error when requiring `.d.ts` files. if (output.outputFiles.length === 0) { throw new TypeError('Unable to require `.d.ts` file.\n' + 'This is usually the result of a faulty configuration or import. ' + 'Make sure there is a `.js`, `.json` or another executable extension and ' + 'loader (attached before `ts-node`) available alongside ' + `\`${path_1.basename(fileName)}\`.`); } return { code: output.outputFiles[1].text, map: output.outputFiles[0].text, }; }; } builds.set(configFileName, (build = { getOutput, getOutputTypeCheck, })); return build; } // determine the tsconfig.json path for a given folder function detectConfig() { let configFileName = undefined; // Read project configuration when available. configFileName = options.project ? ts.findConfigFile(normalizeSlashes(options.project), fileExists) : ts.findConfigFile(normalizeSlashes(cwd), fileExists); if (configFileName) return normalizeSlashes(configFileName); } /** * Load TypeScript configuration. */ function readConfig(configFileName) { let config = { compilerOptions: {} }; const basePath = normalizeSlashes(path_1.dirname(configFileName)); // Read project configuration when available. if (configFileName) { const result = ts.readConfigFile(configFileName, readFile); // Return diagnostics. if (result.error) { const errorResult = { errors: [result.error], fileNames: [], options: {}, }; const configDiagnosticList = filterDiagnostics(errorResult.errors, ignoreDiagnostics); // Render the configuration errors. reportTSError(configDiagnosticList, true); return errorResult; } config = result.config; } // Remove resolution of "files". if (!options.files) { config.files = []; config.include = []; } // Override default configuration options `ts-node` requires. config.compilerOptions = Object.assign({}, config.compilerOptions, options.compilerOptions, TS_NODE_COMPILER_OPTIONS); const configResult = fixConfig(ts, ts.parseJsonConfigFileContent(config, ts.sys, basePath, undefined, configFileName)); if (configFileName) { const configDiagnosticList = filterDiagnostics(configResult.errors, ignoreDiagnostics); // Render the configuration errors. reportTSError(configDiagnosticList, configResult.options.noEmitOnError); } return configResult; } // Create a simple TypeScript compiler proxy. function compile(code, fileName, skipTypeCheck) { const configFileName = detectConfig(); const build = getBuild(configFileName); const { code: value, map: sourceMap } = (skipTypeCheck ? build.getOutput : build.getOutputTypeCheck)(code, fileName); const output = { code: value, map: Object.assign(JSON.parse(sourceMap), { file: path_1.basename(fileName), sources: [fileName], }), }; delete output.map.sourceRoot; return output; } return compile; } exports.register = register; /** * Do post-processing on config options to support `ts-node`. */ function fixConfig(ts, config) { // Delete options that *should not* be passed through. delete config.options.out; delete config.options.outFile; delete config.options.composite; delete config.options.declarationDir; delete config.options.declarationMap; delete config.options.emitDeclarationOnly; delete config.options.tsBuildInfoFile; delete config.options.incremental; // Target esnext output by default (instead of ES3). // This will prevent TS from polyfill/downlevel emit. if (config.options.target === undefined) { config.options.target = ts.ScriptTarget.ESNext; } // When mixing TS with JS, its best to enable this flag. // This is useful when no `tsconfig.json` is supplied. if (config.options.esModuleInterop === undefined) { config.options.esModuleInterop = true; } // Target CommonJS, always! config.options.module = ts.ModuleKind.CommonJS; return config; } /** * Filter diagnostics. */ function filterDiagnostics(diagnostics, ignore) { return diagnostics.filter(x => ignore.indexOf(x.code) === -1); }