UNPKG

awesome-typescript-loader

Version:
475 lines (399 loc) 14.2 kB
if (!module.parent) { process.on('uncaughtException', function (err) { console.log("UNCAUGHT EXCEPTION in awesome-typescript-loader"); console.log("[Inside 'uncaughtException' event] ", err.message, err.stack); }); process.on('disconnect', function() { process.exit(); }); process.on('exit', () => { console.log('EXIT RUNTIME'); }); createChecker( process.on.bind(process, 'message'), process.send.bind(process) ); } else { module.exports.run = function run() { let send: (msg: Req, cb: (err?: Error) => void) => void; let receive = (msg) => {}; createChecker( (receive: (msg: Req) => void) => { send = (msg: Req, cb: (err?: Error) => void) => { receive(msg); if (cb) { cb(); } }; }, (msg) => receive(msg) ); return { on: (type: string, cb) => { if (type === 'message') { receive = cb; } }, send, kill: () => {} }; }; } import * as ts from 'typescript'; import * as path from 'path'; import * as colors from 'colors'; import { findResultFor } from '../helpers'; import { Req, Res, LoaderConfig, CompilerInfo, Init, EmitFile, UpdateFile, Diagnostics, RemoveFile, Files, MessageType, TsConfig } from './protocol'; interface File { text: string; version: number; snapshot: ts.IScriptSnapshot; } function createChecker(receive: (cb: (msg: Req) => void) => void, send: (msg: Res) => void) { let projectVersion = 0; let loaderConfig: LoaderConfig; let compilerConfig: TsConfig; let compilerOptions: ts.CompilerOptions; let webpackOptions: any; let compiler: typeof ts; let compilerInfo: CompilerInfo; let files: {[fileName: string]: File} = {}; let host: ts.LanguageServiceHost; let service: ts.LanguageService; let ignoreDiagnostics: {[id: number]: boolean} = {}; let instanceName: string; let context: string; function ensureFile(fileName: string) { if (!files[fileName]) { const text = compiler.sys.readFile(fileName); if (text) { files[fileName] = { text, version: 0, snapshot: compiler.ScriptSnapshot.fromString(text) }; } } } class FileDeps { files: {[fileName: string]: string[]} = {}; add(containingFile: string, ...dep: string[]) { if (!this.files[containingFile]) { this.files[containingFile] = Array.from(dep); } else { const deps = this.files[containingFile]; deps.push.apply(deps, dep); } } getDeps(containingFile: string): string[] { return this.files[containingFile] || []; } getAllDeps(containingFile: string, allDeps: {[key: string]: boolean} = {}, initial = true): string[] { const deps = this.getDeps(containingFile); deps.forEach(dep => { if (!allDeps[dep]) { allDeps[dep] = true; this.getAllDeps(dep, allDeps, false); } }); if (initial) { return Object.keys(allDeps); } else { return []; } } } const fileDeps = new FileDeps(); const TS_AND_JS_FILES = /\.tsx?$|\.jsx?$/i; const TS_FILES = /\.tsx?$/i; class Host implements ts.LanguageServiceHost { filesRegex: RegExp; constructor(filesRegex: RegExp) { this.filesRegex = filesRegex; } getProjectVersion() { return projectVersion.toString(); } getScriptFileNames() { return Object.keys(files).filter(filePath => this.filesRegex.test(filePath)); } getScriptVersion(fileName: string) { ensureFile(fileName); if (files[fileName]) { return files[fileName].version.toString(); } } getScriptSnapshot(fileName: string) { ensureFile(fileName); if (files[fileName]) { return files[fileName].snapshot; } } getCurrentDirectory() { return context; } getScriptIsOpen() { return true; } getCompilationSettings() { return compilerOptions; } resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string) { const resolved = typeDirectiveNames.map(directive => compiler.resolveTypeReferenceDirective(directive, containingFile, compilerOptions, compiler.sys) .resolvedTypeReferenceDirective); resolved.forEach(res => { if (res && res.resolvedFileName) { fileDeps.add(containingFile, res.resolvedFileName); } }); return resolved; } resolveModuleNames(moduleNames: string[], containingFile: string) { const resolved = moduleNames.map(module => compiler.resolveModuleName(module, containingFile, compilerOptions, compiler.sys).resolvedModule); resolved.forEach(res => { if (res && res.resolvedFileName) { fileDeps.add(containingFile, res.resolvedFileName); } }); return resolved; } log(message) { console.log(message); } fileExists(...args) { return compiler.sys.fileExists.apply(compiler.sys, args); } readFile(...args) { return compiler.sys.readFile.apply(compiler.sys, args); } readDirectory(...args) { return compiler.sys.readDirectory.apply(compiler.sys, args); } getDefaultLibFileName(options: ts.CompilerOptions) { return compiler.getDefaultLibFilePath(options); } useCaseSensitiveFileNames() { return compiler.sys.useCaseSensitiveFileNames; } getDirectories(...args) { return compiler.sys.getDirectories.apply(compiler.sys, args); } directoryExists(path: string) { return compiler.sys.directoryExists(path); } } function processInit({seq, payload}: Init.Request) { compiler = require(payload.compilerInfo.compilerPath); compilerInfo = payload.compilerInfo; loaderConfig = payload.loaderConfig; compilerConfig = payload.compilerConfig; compilerOptions = compilerConfig.options; webpackOptions = payload.webpackOptions; context = payload.context; instanceName = loaderConfig.instance || 'at-loader'; host = new Host(compilerOptions.allowJs ? TS_AND_JS_FILES : TS_FILES ); service = compiler.createLanguageService(host); compilerConfig.fileNames.forEach(fileName => { const text = compiler.sys.readFile(fileName); files[fileName] = { text, version: 0, snapshot: compiler.ScriptSnapshot.fromString(text) }; }); const program = service.getProgram(); program.getSourceFiles().forEach(file => { files[file.fileName] = { text: file.text, version: 0, snapshot: compiler.ScriptSnapshot.fromString(file.text) }; }); if (loaderConfig.ignoreDiagnostics) { loaderConfig.ignoreDiagnostics.forEach(diag => { ignoreDiagnostics[diag] = true; }); } replyOk(seq, null); } function updateFile(fileName: string, text: string) { const file = files[fileName]; if (file) { projectVersion++; file.version++; file.text = text; file.snapshot = compiler.ScriptSnapshot.fromString(text); } else { projectVersion++; files[fileName] = { text, version: 0, snapshot: compiler.ScriptSnapshot.fromString(text) }; } } function removeFile(fileName: string) { const file = files[fileName]; if (file) { delete files[fileName]; } } function emit(fileName: string) { if (loaderConfig.useTranspileModule || loaderConfig.transpileOnly) { return fastEmit(fileName); } else { const output = service.getEmitOutput(fileName, false); if (output.outputFiles.length > 0) { return findResultFor(fileName, output); } else { // Use fast emit in case of errors return fastEmit(fileName); } } } function fastEmit(fileName: string) { const trans = compiler.transpileModule(files[fileName].text, { compilerOptions: compilerOptions, fileName, reportDiagnostics: false }); return { text: trans.outputText, sourceMap: trans.sourceMapText }; } function processUpdate({seq, payload}: UpdateFile.Request) { updateFile(payload.fileName, payload.text); replyOk(seq, null); } function processRemove({seq, payload}: RemoveFile.Request) { removeFile(payload.fileName); replyOk(seq, null); } function processEmit({seq, payload}: EmitFile.Request) { updateFile(payload.fileName, payload.text); const emitResult = emit(payload.fileName); const deps = fileDeps.getAllDeps(payload.fileName); replyOk(seq, {emitResult, deps}); } function processFiles({seq}: Files.Request) { replyOk(seq, { files: service.getProgram().getSourceFiles().map(f => f.fileName) }); } function processDiagnostics({seq}: Diagnostics.Request) { let silent = !!loaderConfig.silent; const timeStart = +new Date(); if (!silent) { console.log(colors.cyan(`\n[${ instanceName }] Checking started in a separate process...`)); } const program = service.getProgram(); const allDiagnostics = program .getOptionsDiagnostics().concat( service.getProgram().getGlobalDiagnostics() ); const nativeGetter = program.getSourceFiles; if (compilerConfig.options.skipLibCheck) { program.getSourceFiles = () => nativeGetter().filter(file => { return !file.isDeclarationFile; }); } allDiagnostics.push(...service.getProgram().getSyntacticDiagnostics()); allDiagnostics.push(...service.getProgram().getSemanticDiagnostics()); program.getSourceFiles = nativeGetter; if (allDiagnostics.length) { console.error(colors.red(`\n[${ instanceName }] Checking finished with ${ allDiagnostics.length } errors`)); } else { if (!silent) { let timeEnd = +new Date(); console.log( colors.green(`\n[${ instanceName }] Ok, ${(timeEnd - timeStart) / 1000} sec.`) ); } } const processedDiagnostics = allDiagnostics .filter(diag => !ignoreDiagnostics[diag.code]) .map(diagnostic => { const message = compiler.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); const fileName = diagnostic.file && path.relative(context, diagnostic.file.fileName); let pretty = ''; let line = 0; let character = 0; let code = diagnostic.code; if (diagnostic.file) { const pos = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); line = pos.line; character = pos.character; pretty = (`[${ instanceName }] ${colors.red(fileName)}:${line + 1}:${character + 1} \n TS${code}: ${colors.red(message)}`); } else { pretty = (colors.red(`[${ instanceName }] TS${code}: ${ message }`)); } return { category: diagnostic.category, code: diagnostic.code, fileName, start: diagnostic.start, message, pretty, line, character }; }); replyOk(seq, processedDiagnostics); } function replyOk(seq: number, payload: any) { send({ seq, success: true, payload } as Res); } function replyErr(seq: number, payload: any) { send({ seq, success: false, payload } as Res); } receive(function(req: Req) { try { switch (req.type) { case MessageType.Init: processInit(req); break; case MessageType.UpdateFile: processUpdate(req); break; case MessageType.EmitFile: processEmit(req); break; case MessageType.Diagnostics: processDiagnostics(req); break; case MessageType.Files: processFiles(req); break; case MessageType.RemoveFile: processRemove(req); break; } } catch (e) { console.error(`[${instanceName}]: Child process failed to process the request: `, e); replyErr(req.seq, null); } }); }