UNPKG

awesome-typescript-loader

Version:
327 lines (271 loc) 9.29 kB
import * as fs from 'fs'; import * as util from 'util'; import * as path from 'path'; import { FileAnalyzer } from './deps'; import { loadLib } from './helpers'; import { LoaderConfig, TsConfig } from './instance'; let RUNTIME = loadLib('../lib/runtime.d.ts'); export interface IFile { text: string; isDefaultLib: boolean; version: number; } export interface ICompilerInfo { compilerPath: string; tsImpl: typeof ts; } export interface IOutputFile extends ts.OutputFile { sourceName: string; } export interface IEmitOutput extends ts.EmitOutput { outputFiles: IOutputFile[]; } export const TSCONFIG_INFERRED = '__inferred type names__.ts'; export class Host implements ts.LanguageServiceHost { state: State; constructor(state: State) { this.state = state; } getScriptFileNames() { return this.state.allFileNames(); } getScriptVersion(fileName: string) { if (this.state.getFile(fileName)) { return this.state.getFile(fileName).version.toString(); } } getScriptSnapshot(fileName) { let file = this.state.getFile(fileName); if (!file) { return null; } return this.state.ts.ScriptSnapshot.fromString(file.text); } getCurrentDirectory() { return process.cwd(); } getScriptIsOpen() { return true; } getCompilationSettings() { return this.state.compilerConfig.options; } getDefaultLibFileName(options: ts.CompilerOptions) { return this.state.defaultLib; } resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string) { let deps = this.state.fileAnalyzer.dependencies; if (containingFile.indexOf(TSCONFIG_INFERRED) !== -1) { containingFile = TSCONFIG_INFERRED; } return typeDirectiveNames.map(moduleName => { return deps.getTypeReferenceResolution(containingFile, moduleName); }); } resolveModuleNames(moduleNames: string[], containingFile: string) { let deps = this.state.fileAnalyzer.dependencies; return moduleNames.map(moduleName => { return deps.getModuleResolution(containingFile, moduleName); }); } getDefaultLibLocation(): string { return path.dirname(this.state.ts.sys.getExecutingFilePath()); } log(message) { // console.log(message); } } export class State { ts: typeof ts; fs: typeof fs; compilerInfo: ICompilerInfo; host: Host; files: {[fileName: string]: IFile} = {}; services: ts.LanguageService; loaderConfig: LoaderConfig; configFilePath: string; compilerConfig: TsConfig; program: ts.Program; fileAnalyzer: FileAnalyzer; defaultLib: string; constructor( loaderConfig: LoaderConfig, configFilePath: string, compilerConfig: TsConfig, compilerInfo: ICompilerInfo ) { this.ts = compilerInfo.tsImpl; this.compilerInfo = compilerInfo; this.host = new Host(this); this.services = this.ts.createLanguageService(this.host, this.ts.createDocumentRegistry()); this.loaderConfig = loaderConfig; this.configFilePath = configFilePath; this.compilerConfig = compilerConfig; this.fileAnalyzer = new FileAnalyzer(this); if (loaderConfig.emitRequireType) { this.addFile(RUNTIME.fileName, RUNTIME.text); } this.loadDefaultLib(); this.loadTypesFromConfig(); } loadTypesFromConfig() { let { options } = this.compilerConfig; if (options.types) { options.types.forEach(type => { let { resolvedTypeReferenceDirective } = this.ts.resolveTypeReferenceDirective( type, this.configFilePath, options, ts.sys ); if (resolvedTypeReferenceDirective) { this.readFileAndAdd(resolvedTypeReferenceDirective.resolvedFileName); this.fileAnalyzer.dependencies.addTypeReferenceResolution( TSCONFIG_INFERRED, type, resolvedTypeReferenceDirective ); } }); } } loadDefaultLib() { let { options } = this.compilerConfig; if (!options.noLib) { if (options.lib && options.lib.length > 0) { let libraryDir = this.host.getDefaultLibLocation(); options.lib.forEach((libName, i) => { let fileName = path.join(libraryDir, libName); this.fileAnalyzer.checkDependencies(fileName, true); if (i === 0) { this.defaultLib = fileName; } }); } else { let defaultLib = this.ts.getDefaultLibFilePath(options); if (defaultLib) { this.defaultLib = defaultLib; this.fileAnalyzer.checkDependencies(defaultLib, true); } } } } updateProgram() { this.program = this.services.getProgram(); } allFileNames() { return Object.keys(this.files); } getSourceFile(fileName: string): ts.SourceFile { let services: any = this.services; // FIXME @spanferov `getNonBoundSourceFile` is internal API return (services.getSourceFile || services.getNonBoundSourceFile)(fileName); } /** * Returns all the files in this state. * Don't add new files using this value (eg `allFiles()[newFilePath] = ...`), just use it as a * read only reference (as otherwise the paths won't be normalized correctly) */ allFiles() { return this.files; } emit(fileName: string): IEmitOutput { if (!this.program) { this.program = this.services.getProgram(); } let outputFiles: IOutputFile[] = []; function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) { outputFiles.push({ sourceName: fileName, name: fileName, writeByteOrderMark: writeByteOrderMark, text: data }); } let source = this.program.getSourceFile(fileName); if (!source) { this.updateProgram(); source = this.program.getSourceFile(fileName); if (!source) { throw new Error(`File ${fileName} was not found in program`); } } let emitResult = this.program.emit(source, writeFile); let output = { outputFiles: outputFiles, emitSkipped: emitResult.emitSkipped }; if (!output.emitSkipped) { return output; } else { throw new Error("Emit skipped"); } } fastEmit(fileName: string) { let file = this.getFile(fileName); if (!file) { throw new Error(`Unknown file ${ fileName }`); } let transpileResult = this.ts.transpileModule(file.text, { compilerOptions: this.compilerConfig.options, reportDiagnostics: false, fileName }); return { text: transpileResult.outputText, sourceMap: transpileResult.sourceMapText }; } updateFile(fileName: string, text: string, checked: boolean = false): boolean { let prevFile = this.files[fileName]; let version = 0; let changed = true; let isDefaultLib = false; if (prevFile) { isDefaultLib = prevFile.isDefaultLib; if (!checked || (checked && text !== prevFile.text)) { version = prevFile.version + 1; } else { changed = false; } } this.files[fileName] = { text, version, isDefaultLib }; return changed; } addFile(fileName: string, text: string, isDefaultLib = false): IFile { return this.files[fileName] = { text, isDefaultLib, version: 0 }; } getFile(fileName: string): IFile { return this.files[fileName]; } hasFile(fileName: string): boolean { return this.files.hasOwnProperty(fileName); } readFile(fileName: string): string { // Use global fs here, because local doesn't contain `readFileSync` return fs.readFileSync(fileName, {encoding: 'utf-8'}); } readFileAndAdd(fileName: string, isDefaultLib = false) { let text = this.readFile(fileName); this.addFile(fileName, text, isDefaultLib); } readFileAndUpdate(fileName: string, checked: boolean = false): boolean { let text = this.readFile(fileName); return this.updateFile(fileName, text, checked); } } /** * Emit compilation result for a specified fileName. */ export function TypeScriptCompilationError(diagnostics) { this.diagnostics = diagnostics; } util.inherits(TypeScriptCompilationError, Error);