diff --git a/src/cache.ts b/src/cache.ts index 13f5501..b2bccf3 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -1,8 +1,8 @@ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import * as zlib from 'zlib'; -import { createHash } from 'crypto'; +import * as fs from "fs"; +import * as os from "os"; +import * as path from "path"; +import * as zlib from "zlib"; +import { createHash } from "crypto"; export interface CompiledModule { fileName: string; @@ -12,7 +12,7 @@ export interface CompiledModule { } export function findCompiledModule(fileName: string): CompiledModule { - let baseFileName = fileName.replace(/(\.ts|\.tsx)$/, ''); + let baseFileName = fileName.replace(/(\.ts|\.tsx)$/, ""); let compiledFileName = `${baseFileName}.js`; if (fs.existsSync(compiledFileName)) { @@ -64,7 +64,7 @@ function write(filename: string, result: any) { * @return {String} */ function filename(source: string, identifier, options) { - let hash = createHash('sha512') as any; + let hash = createHash("sha512") as any; let contents = JSON.stringify({ identifier: identifier, options: options, @@ -73,7 +73,7 @@ function filename(source: string, identifier, options) { hash.end(contents); - return hash.read().toString('hex') + '.json.gzip'; + return hash.read().toString("hex") + ".json.gzip"; } export interface CacheParams { @@ -94,7 +94,7 @@ export function cache(params: CacheParams): Promise<{cached: boolean, resu let options = params.options || {}; let transform = params.transform; let identifier = params.identifier; - let directory = (typeof params.directory === 'string') ? + let directory = (typeof params.directory === "string") ? params.directory : os.tmpdir(); diff --git a/src/checker/checker.ts b/src/checker/checker.ts index 1a82d77..7b3b746 100644 --- a/src/checker/checker.ts +++ b/src/checker/checker.ts @@ -1,7 +1,7 @@ -import * as _ from 'lodash'; -import * as childProcess from 'child_process'; -import * as path from 'path'; -import { QueuedSender, createQueuedSender } from './send'; +import * as _ from "lodash"; +import * as childProcess from "child_process"; +import * as path from "path"; +import { QueuedSender, createQueuedSender } from "./send"; import { CompilerInfo, @@ -15,7 +15,7 @@ import { UpdateFile, TsConfig, RemoveFile -} from './protocol'; +} from "./protocol"; export interface Resolve { resolve: (...args: any[]) => void; @@ -44,8 +44,8 @@ export class Checker { ) { const execArgv = getExecArgv(); const checker: childProcess.ChildProcess = fork - ? childProcess.fork(path.join(__dirname, 'runtime.js'), [], { execArgv }) - : require('./runtime').run(); + ? childProcess.fork(path.join(__dirname, "runtime.js"), [], { execArgv }) + : require("./runtime").run(); this.sender = fork ? createQueuedSender(checker) @@ -57,11 +57,11 @@ export class Checker { this.compilerConfig = compilerConfig; this.webpackOptions = webpackOptions; - checker.on('error', (e) => { - console.error('Typescript checker error:', e); + checker.on("error", (e) => { + console.error("Typescript checker error:", e); }); - checker.on('message', (res: Res) => { + checker.on("message", (res: Res) => { const {seq, success, payload} = res; if (seq && this.pending.has(seq)) { const resolver = this.pending.get(seq); @@ -73,14 +73,14 @@ export class Checker { this.pending.delete(seq); } else { - console.warn('Unknown message: ', payload); + console.warn("Unknown message: ", payload); } }); this.req({ - type: 'Init', + type: "Init", payload: { - compilerInfo: _.omit(compilerInfo, 'tsImpl'), + compilerInfo: _.omit(compilerInfo, "tsImpl"), loaderConfig, compilerConfig, webpackOptions, @@ -104,7 +104,7 @@ export class Checker { emitFile(fileName: string, text: string): Promise { return this.req({ - type: 'EmitFile', + type: "EmitFile", payload: { fileName, text @@ -114,7 +114,7 @@ export class Checker { updateFile(fileName: string, text: string, ifExist = false) { return this.req({ - type: 'UpdateFile', + type: "UpdateFile", payload: { fileName, text, @@ -125,7 +125,7 @@ export class Checker { removeFile(fileName: string) { return this.req({ - type: 'RemoveFile', + type: "RemoveFile", payload: { fileName, } @@ -134,18 +134,18 @@ export class Checker { getDiagnostics(): Promise { return this.req({ - type: 'Diagnostics' + type: "Diagnostics" } as Diagnostics.Request); } getFiles(): any { return this.req({ - type: 'Files' + type: "Files" } as Files.Request); } kill() { - this.checker.kill('SIGKILL'); + this.checker.kill("SIGKILL"); } } diff --git a/src/checker/index.ts b/src/checker/index.ts index 3130b9c..8180a8b 100644 --- a/src/checker/index.ts +++ b/src/checker/index.ts @@ -1 +1 @@ -export { Checker } from './checker'; +export { Checker } from "./checker"; diff --git a/src/checker/protocol.ts b/src/checker/protocol.ts index ff8a08e..5148794 100644 --- a/src/checker/protocol.ts +++ b/src/checker/protocol.ts @@ -1,14 +1,14 @@ -import { LoaderConfig, CompilerInfo, TsConfig } from '../interfaces'; +import { LoaderConfig, CompilerInfo, TsConfig } from "../interfaces"; export { CompilerInfo, LoaderConfig, TsConfig } -export type MessageType = 'Init' | 'UpdateFile' | 'Diagnostics' | 'EmitFile' | 'Files' | 'RemoveFile' +export type MessageType = "Init" | "UpdateFile" | "Diagnostics" | "EmitFile" | "Files" | "RemoveFile" export const MessageType = { - Init: 'Init' as 'Init', - Files: 'Files' as 'Files', - UpdateFile: 'UpdateFile' as 'UpdateFile', - RemoveFile: 'RemoveFile' as 'RemoveFile', - Diagnostics: 'Diagnostics' as 'Diagnostics', - EmitFile: 'EmitFile' as 'EmitFile', + Init: "Init" as "Init", + Files: "Files" as "Files", + UpdateFile: "UpdateFile" as "UpdateFile", + RemoveFile: "RemoveFile" as "RemoveFile", + Diagnostics: "Diagnostics" as "Diagnostics", + EmitFile: "EmitFile" as "EmitFile", }; export interface ReqBase { @@ -44,7 +44,7 @@ export namespace Init { } export interface Request extends ReqBase { - type: 'Init'; + type: "Init"; payload: Payload; } @@ -59,7 +59,7 @@ export namespace UpdateFile { } export interface Request extends ReqBase { - type: 'UpdateFile'; + type: "UpdateFile"; payload: Payload; } @@ -77,7 +77,7 @@ export namespace RemoveFile { } export interface Request extends ReqBase { - type: 'RemoveFile'; + type: "RemoveFile"; payload: Payload; } @@ -94,7 +94,7 @@ export namespace EmitFile { } export interface Request extends ReqBase { - type: 'EmitFile'; + type: "EmitFile"; payload: ReqPayload; } @@ -114,7 +114,7 @@ export namespace EmitFile { export namespace Files { export interface Request extends ReqBase { - type: 'Files'; + type: "Files"; } export interface Response { @@ -126,7 +126,7 @@ export namespace Files { export namespace Diagnostics { export interface Request extends ReqBase { - type: 'Diagnostics'; + type: "Diagnostics"; } export interface Response { diff --git a/src/checker/runtime.ts b/src/checker/runtime.ts index b00bbe5..403b295 100644 --- a/src/checker/runtime.ts +++ b/src/checker/runtime.ts @@ -1,551 +1,539 @@ -import * as ts from 'typescript'; -import * as path from 'path'; -import * as micromatch from 'micromatch'; -import * as colors from 'colors'; -import { findResultFor, toUnix } from '../helpers'; -import { - Req, - Res, - LoaderConfig, - CompilerInfo, - Init, - EmitFile, - UpdateFile, - Diagnostics, - RemoveFile, - Files, - MessageType, - TsConfig -} from './protocol'; - -import { CaseInsensitiveMap } from './fs'; -import { isCaseInsensitive } from '../helpers'; - -const caseInsensitive = isCaseInsensitive(); - -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: () => { } - }; - }; -} - -interface File { - fileName: string; - text: string; - version: number; - snapshot: ts.IScriptSnapshot; -} - -type Filter = (file: ts.SourceFile) => boolean; - -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 = new CaseInsensitiveMap(); - let host: ts.LanguageServiceHost; - let service: ts.LanguageService; - let ignoreDiagnostics: { [id: number]: boolean } = {}; - let instanceName: string; - let context: string; - - function ensureFile(fileName: string) { - const file = files.get(fileName); - if (!file) { - const text = compiler.sys.readFile(fileName); - if (text) { - files.set(fileName, { - fileName: fileName, - text, - version: 0, - snapshot: compiler.ScriptSnapshot.fromString(text) - }); - } - } else { - if (file.fileName !== fileName) { - if (caseInsensitive) { - file.fileName = fileName; // use most recent name for case-sensitive file systems - file.version++; - projectVersion++; - } else { - removeFile(file.fileName); - projectVersion++; - - const text = compiler.sys.readFile(fileName); - files.set(fileName, { - fileName, - text, - version: 0, - snapshot: compiler.ScriptSnapshot.fromString(text) - }); - return; - } - } - } - } - - 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; - getCustomTransformers = loaderConfig.getCustomTransformers; - - constructor(filesRegex: RegExp) { - this.filesRegex = filesRegex; - } - - getProjectVersion() { return projectVersion.toString(); } - - getScriptFileNames() { - const names = files.map(file => file.fileName) - .filter(fileName => this.filesRegex.test(fileName)); - return names; - } - - getScriptVersion(fileName: string) { - ensureFile(fileName); - const file = files.get(fileName); - if (file) { - return file.version.toString(); - } - } - - getScriptSnapshot(fileName: string) { - ensureFile(fileName); - const file = files.get(fileName); - if (file) { - return file.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); - if (!text) { return; } - files.set(fileName, { - fileName, - text, - version: 0, - snapshot: compiler.ScriptSnapshot.fromString(text) - }); - }); - - const program = service.getProgram(); - program.getSourceFiles().forEach(file => { - files.set(file.fileName, { - fileName: file.fileName, - text: file.text, - version: 0, - snapshot: compiler.ScriptSnapshot.fromString(file.text) - }); - }); - - if (loaderConfig.debug) { - console.log(`[${instanceName}] @DEBUG Initial files`, Object.keys(files)); - } - - if (loaderConfig.ignoreDiagnostics) { - loaderConfig.ignoreDiagnostics.forEach(diag => { - ignoreDiagnostics[diag] = true; - }); - } - - replyOk(seq, null); - } - - function updateFile(fileName: string, text: string, ifExist = false) { - const file = files.get(fileName); - if (file) { - let updated = false; - if (file.fileName !== fileName) { - if (caseInsensitive) { - file.fileName = fileName; // use most recent name for case-sensitive file systems - } else { - removeFile(file.fileName); - projectVersion++; - files.set(fileName, { - fileName, - text, - version: 0, - snapshot: compiler.ScriptSnapshot.fromString(text) - }); - return; - } - } - if (file.text !== text) { updated = updated || true; } - if (!updated) { - return; - } - projectVersion++; - file.version++; - file.text = text; - file.snapshot = compiler.ScriptSnapshot.fromString(text); - } else if (!ifExist) { - projectVersion++; - files.set(fileName, { - fileName, - text, - version: 0, - snapshot: compiler.ScriptSnapshot.fromString(text) - }); - } - } - - function removeFile(fileName: string) { - if (files.has(fileName)) { - files.delete(fileName); - projectVersion++; - } - } - - 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.get(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, payload.ifExist); - 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; - - if (!silent) { - console.log(colors.cyan(`\n[${instanceName}] Checking started in a separate process...`)); - } - - const program = service.getProgram(); - - const allDiagnostics = program - .getOptionsDiagnostics() - .concat(program.getGlobalDiagnostics()); - - const filters: Filter[] = []; - - if (compilerConfig.options.skipLibCheck) { - filters.push(file => { - return !file.isDeclarationFile; - }); - } - - if (loaderConfig.reportFiles) { - filters.push(file => { - const fileName = path.relative(context, file.fileName); - return micromatch(fileName, loaderConfig.reportFiles).length > 0; - }); - } - - let nativeGetter: () => ts.SourceFile[]; - if (filters.length > 0) { - nativeGetter = program.getSourceFiles; - program.getSourceFiles = () => nativeGetter().filter(file => { - return filters.every(f => f(file)); - }); - } - - allDiagnostics.push(...program.getSyntacticDiagnostics()); - allDiagnostics.push(...program.getSemanticDiagnostics()); - - if (loaderConfig.debug) { - console.log(`[${instanceName}] @DEBUG Typechecked files`, program.getSourceFiles()); - } - - if (nativeGetter) { - program.getSourceFiles = nativeGetter; - } - - const processedDiagnostics = allDiagnostics - .filter(diag => !ignoreDiagnostics[diag.code]) - .map(diagnostic => { - const message = compiler.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); - let fileName = diagnostic.file && path.relative(context, diagnostic.file.fileName); - - if (fileName && fileName[0] !== '.') { - fileName = './' + toUnix(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.RemoveFile: - processRemove(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; - - } - } catch (e) { - console.error(`[${instanceName}]: Child process failed to process the request: `, e); - replyErr(req.seq, null); - } - }); -} +import * as ts from 'typescript'; +import * as path from 'path'; +import * as micromatch from 'micromatch'; +import * as colors from 'colors'; +import { findResultFor, toUnix, unorderedRemoveItem } from '../helpers'; +import { + Req, + Res, + LoaderConfig, + Init, + EmitFile, + UpdateFile, + Diagnostics, + RemoveFile, + Files, + MessageType, + TsConfig +} from './protocol'; + +import { CaseInsensitiveMap } from './fs'; +import { isCaseInsensitive } from '../helpers'; + +const caseInsensitive = isCaseInsensitive(); + +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: () => { } + }; + }; +} + +interface File { + fileName: string; + text: string; +} + +type Filter = (file: ts.SourceFile) => boolean; + +function createChecker(receive: (cb: (msg: Req) => void) => void, send: (msg: Res) => void) { + let loaderConfig: LoaderConfig; + let compilerConfig: TsConfig; + let compilerOptions: ts.CompilerOptions; + let compiler: typeof ts; + let files = new CaseInsensitiveMap(); + let ignoreDiagnostics: { [id: number]: boolean } = {}; + let instanceName: string; + let context: string; + let rootFilesChanged = false; + + let filesRegex: RegExp; + type WatchCallbacks = Map; + const watchedFiles: WatchCallbacks = new Map(); + const watchedDirectories: WatchCallbacks = new Map(); + const watchedDirectoriesRecursive: WatchCallbacks = new Map(); + const useCaseSensitiveFileNames = () => !caseInsensitive; + const getCanonicalFileName: (fileName: string) => string = caseInsensitive ? + fileName => fileName.toLowerCase() : + (fileName => fileName); + + let watchHost: ts.WatchCompilerHostOfFilesAndCompilerOptions; + let watch: ts.WatchOfFilesAndCompilerOptions; + + function createWatchHost(): ts.WatchCompilerHostOfFilesAndCompilerOptions & ts.BuilderProgramHost { + return { + rootFiles: getRootFiles(), + options: compilerOptions, + + useCaseSensitiveFileNames, + getNewLine: () => compiler.sys.newLine, + getCurrentDirectory: () => context, + getDefaultLibFileName, + fileExists: (...args) => compiler.sys.fileExists.apply(compiler.sys, args), + readFile, + directoryExists: (...args) => compiler.sys.directoryExists.apply(compiler.sys, args), + getDirectories: (...args) => compiler.sys.getDirectories.apply(compiler.sys, args), + readDirectory: (...args) => compiler.sys.readDirectory.apply(compiler.sys, args), + realpath: (...args) => compiler.sys.resolvePath.apply(compiler.sys, args), + + watchFile, + watchDirectory, + + createProgram: compiler.createSemanticDiagnosticsBuilderProgram, + + createHash: (...args) => compiler.sys.createHash.apply(compiler.sys, args) + }; + + function readFile(fileName: string) { + ensureFile(fileName); + const file = files.get(fileName); + if (file) { + return file.text; + } + } + } + + function createWatch(): ts.WatchOfFilesAndCompilerOptions { + watchHost = createWatchHost(); + return compiler.createWatchProgram(watchHost); + } + + function getProgram(): ts.SemanticDiagnosticsBuilderProgram { + if (rootFilesChanged) { + rootFilesChanged = false; + watch.updateRootFileNames(getRootFiles()); + } + return watch.getProgram(); + } + + function getRootFiles() { + const names = files.map(file => file.fileName) + .filter(fileName => filesRegex.test(fileName)); + return names; + } + + function getDefaultLibFileName(options: ts.CompilerOptions) { + return path.join(path.dirname(compiler.sys.getExecutingFilePath()), compiler.getDefaultLibFileName(options)); + } + function invokeWatcherCallbacks(callbacks: ts.FileWatcherCallback[] | undefined, fileName: string, eventKind: ts.FileWatcherEventKind): void; + function invokeWatcherCallbacks(callbacks: ts.DirectoryWatcherCallback[] | undefined, fileName: string): void; + function invokeWatcherCallbacks(callbacks: ts.FileWatcherCallback[] | ts.DirectoryWatcherCallback[] | undefined, fileName: string, eventKind?: ts.FileWatcherEventKind) { + if (callbacks) { + // The array copy is made to ensure that even if one of the callback removes the callbacks, + // we dont miss any callbacks following it + const cbs = callbacks.slice(); + for (const cb of cbs) { + cb(fileName, eventKind as ts.FileWatcherEventKind); + } + } + } + + function invokeFileWatcher(fileName: string, eventKind: ts.FileWatcherEventKind) { + fileName = getCanonicalFileName(fileName); + invokeWatcherCallbacks(watchedFiles.get(fileName), fileName, eventKind); + } + + function invokeDirectoryWatcher(directory: string, fileAddedOrRemoved: string) { + directory = getCanonicalFileName(directory); + invokeWatcherCallbacks(watchedDirectories.get(directory), fileAddedOrRemoved); + invokeRecursiveDirectoryWatcher(directory, fileAddedOrRemoved); + } + + function invokeRecursiveDirectoryWatcher(directory: string, fileAddedOrRemoved: string) { + invokeWatcherCallbacks(watchedDirectoriesRecursive.get(directory), fileAddedOrRemoved); + const basePath = path.dirname(directory); + if (directory !== basePath) { + invokeRecursiveDirectoryWatcher(basePath, fileAddedOrRemoved); + } + } + + function createWatcher(file: string, callbacks: WatchCallbacks, callback: T): ts.FileWatcher { + file = getCanonicalFileName(file); + const existing = callbacks.get(file); + if (existing) { + existing.push(callback); + } + else { + callbacks.set(file, [callback]); + } + return { + close: () => { + const existing = callbacks.get(file); + if (existing) { + unorderedRemoveItem(existing, callback); + } + } + }; + } + + function watchFile(fileName: string, callback: ts.FileWatcherCallback, _pollingInterval?: number) { + return createWatcher(fileName, watchedFiles, callback); + } + + function watchDirectory(fileName: string, callback: ts.DirectoryWatcherCallback, recursive?: boolean) { + return createWatcher(fileName, recursive ? watchedDirectoriesRecursive : watchedDirectories, callback); + } + + function onFileCreated(fileName: string) { + rootFilesChanged = true; + invokeFileWatcher(fileName, compiler.FileWatcherEventKind.Created); + invokeDirectoryWatcher(path.dirname(fileName), fileName); + } + + function onFileRemoved(fileName: string) { + rootFilesChanged = true; + invokeFileWatcher(fileName, compiler.FileWatcherEventKind.Deleted); + invokeDirectoryWatcher(path.dirname(fileName), fileName); + } + + function onFileChanged(fileName: string) { + invokeFileWatcher(fileName, compiler.FileWatcherEventKind.Changed); + } + + function ensureFile(fileName: string) { + const file = files.get(fileName); + if (!file) { + const text = compiler.sys.readFile(fileName); + if (text) { + files.set(fileName, { + fileName: fileName, + text, + }); + onFileCreated(fileName); + } + } else { + if (file.fileName !== fileName) { + if (caseInsensitive) { + file.fileName = fileName; // use most recent name for case-sensitive file systems + onFileChanged(fileName); + } else { + removeFile(file.fileName); + + const text = compiler.sys.readFile(fileName); + files.set(fileName, { + fileName, + text, + }); + onFileCreated(fileName); + } + } + } + } + + const TS_AND_JS_FILES = /\.tsx?$|\.jsx?$/i; + const TS_FILES = /\.tsx?$/i; + + function processInit({ seq, payload }: Init.Request) { + compiler = require(payload.compilerInfo.compilerPath); + loaderConfig = payload.loaderConfig; + compilerConfig = payload.compilerConfig; + compilerOptions = compilerConfig.options; + context = payload.context; + filesRegex = compilerOptions.allowJs ? TS_AND_JS_FILES : TS_FILES; + + instanceName = loaderConfig.instance || 'at-loader'; + + compilerConfig.fileNames.forEach(fileName => ensureFile(fileName)); + watch = createWatch(); + + if (loaderConfig.debug) { + console.log(`[${instanceName}] @DEBUG Initial files`, Object.keys(files)); + } + + if (loaderConfig.ignoreDiagnostics) { + loaderConfig.ignoreDiagnostics.forEach(diag => { + ignoreDiagnostics[diag] = true; + }); + } + + replyOk(seq, null); + } + + function updateFile(fileName: string, text: string, ifExist = false) { + const file = files.get(fileName); + if (file) { + let updated = false; + if (file.fileName !== fileName) { + if (caseInsensitive) { + file.fileName = fileName; // use most recent name for case-sensitive file systems + } else { + removeFile(file.fileName); + + files.set(fileName, { + fileName, + text, + }); + onFileCreated(fileName); + } + } + if (file.text !== text) { updated = updated || true; } + if (!updated) { + return; + } + file.text = text; + onFileChanged(fileName); + } else if (!ifExist) { + files.set(fileName, { + fileName, + text + }); + onFileCreated(fileName); + } + } + + function removeFile(fileName: string) { + if (files.has(fileName)) { + files.delete(fileName); + onFileRemoved(fileName); + } + } + + function getEmitOutput(fileName: string) { + const program = getProgram(); + const outputFiles: ts.OutputFile[] = []; + const writeFile = (fileName: string, text: string, writeByteOrderMark: boolean) => + outputFiles.push({ name: fileName, writeByteOrderMark, text }); + const sourceFile = program.getSourceFile(fileName); + program.emit(sourceFile, writeFile, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ false, loaderConfig.getCustomTransformers && loaderConfig.getCustomTransformers()); + return outputFiles; + } + + function emit(fileName: string) { + if (loaderConfig.useTranspileModule || loaderConfig.transpileOnly) { + return fastEmit(fileName); + } else { + const outputFiles = getEmitOutput(fileName); + if (outputFiles.length > 0) { + return findResultFor(fileName, outputFiles); + } else { + // Use fast emit in case of errors + return fastEmit(fileName); + } + } + } + + function fastEmit(fileName: string) { + const trans = compiler.transpileModule(files.get(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, payload.ifExist); + 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 program = getProgram(); + const sourceFile = program.getSourceFile(payload.fileName); + const deps = program.getAllDependencies(sourceFile); + + replyOk(seq, { emitResult, deps }); + } + + function processFiles({ seq }: Files.Request) { + replyOk(seq, { + files: getProgram().getSourceFiles().map(f => f.fileName) + }); + } + + function isAffectedSourceFile(affected: ts.SourceFile | ts.Program): affected is ts.SourceFile { + return (affected as ts.SourceFile).kind === compiler.SyntaxKind.SourceFile; + } + + function processDiagnostics({ seq }: Diagnostics.Request) { + let silent = !!loaderConfig.silent; + + if (!silent) { + console.log(colors.cyan(`\n[${instanceName}] Checking started in a separate process...`)); + } + + const program = getProgram(); + const sourceFiles = program.getSourceFiles(); + + const allDiagnostics = program + .getOptionsDiagnostics() + .concat(program.getGlobalDiagnostics()); + + const filters: Filter[] = []; + + if (compilerConfig.options.skipLibCheck) { + filters.push(file => { + return !file.isDeclarationFile; + }); + } + + if (loaderConfig.reportFiles) { + filters.push(file => { + const fileName = path.relative(context, file.fileName); + return micromatch(fileName, loaderConfig.reportFiles).length > 0; + }); + } + const diagnosticsCollected: boolean[] = new Array(sourceFiles.length); + const ignoreSouceFile: (sourceFile: ts.SourceFile) => boolean = file => { + return filters.length && filters.some(f => !f(file)); + }; + + let result: ts.AffectedFileResult>; + while (result = program.getSemanticDiagnosticsOfNextAffectedFile(/*cancellationToken*/ undefined, ignoreSouceFile)) { + // If whole program is affected, just get those diagnostics from cache again in later pass + // But if its single file, set the results push the results + if (isAffectedSourceFile(result.affected)) { + const file = result.affected as ts.SourceFile; + allDiagnostics.push(...program.getSyntacticDiagnostics(file)); + // Semantic diagnostics + allDiagnostics.push(...result.result); + diagnosticsCollected[sourceFiles.indexOf(file)] = true; + } + } + + sourceFiles.forEach(file => { + if (diagnosticsCollected[sourceFiles.indexOf(file)] || ignoreSouceFile(file)) { + // Skip the file + return; + } + + allDiagnostics.push(...program.getSyntacticDiagnostics(file)); + allDiagnostics.push(...program.getSemanticDiagnostics(file)); + }); + + if (loaderConfig.debug) { + console.log(`[${instanceName}] @DEBUG Typechecked files`, program.getSourceFiles()); + } + + const processedDiagnostics = allDiagnostics + .filter(diag => !ignoreDiagnostics[diag.code]) + .map(diagnostic => { + const message = compiler.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); + let fileName = diagnostic.file && path.relative(context, diagnostic.file.fileName); + + if (fileName && fileName[0] !== '.') { + fileName = './' + toUnix(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.RemoveFile: + processRemove(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; + + } + } catch (e) { + console.error(`[${instanceName}]: Child process failed to process the request: `, e); + replyErr(req.seq, null); + } + }); +} diff --git a/src/checker/send.ts b/src/checker/send.ts index 8e5387e..3b8b43f 100644 --- a/src/checker/send.ts +++ b/src/checker/send.ts @@ -1,4 +1,4 @@ -import { ChildProcess } from 'child_process'; +import { ChildProcess } from "child_process"; export interface QueuedSender { send: (msg: any) => void; @@ -12,7 +12,7 @@ const logOnError = error => { if (error) { console.error(error); } }; // queue is free again to consume messages. // On Windows we always wait for the send() method to return before sending the next message // to workaround https://github.com/nodejs/node/issues/7657 (IPC can freeze process) -export function createQueuedSender(childProcess: ChildProcess | NodeJS.Process): QueuedSender { +export function createQueuedSender(childProcess: ChildProcess): QueuedSender { if (isWindows) { let msgQueue = []; let isSending = false; diff --git a/src/entry.ts b/src/entry.ts index c523550..254b394 100644 --- a/src/entry.ts +++ b/src/entry.ts @@ -1,2 +1,2 @@ -require('source-map-support').install(); -module.exports = require('./index'); +require("source-map-support").install(); +module.exports = require("./index"); diff --git a/src/helpers.ts b/src/helpers.ts index 2b1633a..c34305c 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,163 +1,175 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as ts from 'typescript'; -import { OutputFile } from './interfaces'; - -const double = /\/\//; -export function toUnix(fileName: string): string { - let res: string = fileName.replace(/\\/g, '/'); - while (res.match(double)) { - res = res.replace(double, '/'); - } - - return res; -} - -let caseInsensitiveFs: boolean | undefined; -export function isCaseInsensitive() { - if (typeof caseInsensitiveFs !== 'undefined') { - return caseInsensitiveFs; - } - - const lowerCaseStat = statSyncNoException(process.execPath.toLowerCase()); - const upperCaseStat = statSyncNoException(process.execPath.toUpperCase()); - - if (lowerCaseStat && upperCaseStat) { - caseInsensitiveFs = lowerCaseStat.dev === upperCaseStat.dev && lowerCaseStat.ino === upperCaseStat.ino; - } else { - caseInsensitiveFs = false; - } - - return caseInsensitiveFs; -} - -function statSyncNoException(path: string) { - try { - return fs.statSync(path); - } catch (e) { - return undefined; - } -} - -function withoutExt(fileName: string): string { - return path.basename(fileName).split('.')[0]; -} - -function compareFileName(first: string, second: string) { - if (isCaseInsensitive()) { - return first.toLowerCase() === second.toLowerCase(); - } else { - return first === second; - } -} - -function isFileEmit(fileName, outputFileName, sourceFileName) { - return compareFileName(sourceFileName, fileName) - // typescript now emits .jsx files for .tsx files. - && ( - outputFileName.substr(-3).toLowerCase() === '.js' || - outputFileName.substr(-4).toLowerCase() === '.jsx' - ); -} - -function isSourceMapEmit(fileName, outputFileName, sourceFileName) { - return compareFileName(sourceFileName, fileName) - // typescript now emits .jsx files for .tsx files. - && ( - outputFileName.substr(-7).toLowerCase() === '.js.map' || - outputFileName.substr(-8).toLowerCase() === '.jsx.map' - ); -} - -function isDeclarationEmit(fileName, outputFileName, sourceFileName) { - return compareFileName(sourceFileName, fileName) - && (outputFileName.substr(-5).toLowerCase() === '.d.ts'); -} - -export function findResultFor(fileName: string, output: ts.EmitOutput): OutputFile { - let text; - let sourceMap; - let declaration: ts.OutputFile; - fileName = withoutExt(fileName); - - for (let i = 0; i < output.outputFiles.length; i++) { - let o = output.outputFiles[i]; - let outputFileName = o.name; - let sourceFileName = withoutExt(o.name); - if (isFileEmit(fileName, outputFileName, sourceFileName)) { - text = o.text; - } - if (isSourceMapEmit(fileName, outputFileName, sourceFileName)) { - sourceMap = o.text; - } - if (isDeclarationEmit(fileName, outputFileName, sourceFileName)) { - declaration = o; - } - } - - return { - text: text, - sourceMap: sourceMap, - declaration - }; -} - -export function codegenErrorReport(errors) { - return errors - .map(function (error) { - return 'console.error(' + JSON.stringify(error) + ');'; - }) - .join('\n'); -} - -export function formatError(diagnostic) { - let lineChar; - if (diagnostic.file) { - lineChar = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); - } - return ( - (diagnostic.file ? path.normalize(diagnostic.file.fileName) : '') - + (lineChar ? formatLineChar(lineChar) + ' ' : '') + "\n" - + (typeof diagnostic.messageText == "string" ? - diagnostic.messageText : - formatMessageChain(diagnostic.messageText)) - ); -} - -export function formatMessageChain(chain: ts.DiagnosticMessageChain) { - let result = ""; - let separator = "\n "; - let current = chain; - - while (current) { - result += current.messageText; - - if (!!current.next) { - result += separator; - separator += " "; - } - - current = current.next; - } - - return result; -} - -export function formatLineChar(lineChar) { - return ':' + (lineChar.line + 1) + ':' + lineChar.character; -} - -export function loadLib(moduleId) { - let fileName = require.resolve(moduleId); - let text = fs.readFileSync(fileName, 'utf8'); - return { - fileName: fileName, - text: text - }; -} - -const TYPESCRIPT_EXTENSION = /\.(d\.)?(t|j)s$/; - -export function withoutTypeScriptExtension(fileName: string): string { - return fileName.replace(TYPESCRIPT_EXTENSION, ''); -} +import * as fs from "fs"; +import * as path from "path"; +import * as ts from "typescript"; +import { OutputFile } from "./interfaces"; + +const double = /\/\//; +export function toUnix(fileName: string): string { + let res: string = fileName.replace(/\\/g, "/"); + while (res.match(double)) { + res = res.replace(double, "/"); + } + + return res; +} + +let caseInsensitiveFs: boolean | undefined; +export function isCaseInsensitive() { + if (typeof caseInsensitiveFs !== "undefined") { + return caseInsensitiveFs; + } + + const lowerCaseStat = statSyncNoException(process.execPath.toLowerCase()); + const upperCaseStat = statSyncNoException(process.execPath.toUpperCase()); + + if (lowerCaseStat && upperCaseStat) { + caseInsensitiveFs = lowerCaseStat.dev === upperCaseStat.dev && lowerCaseStat.ino === upperCaseStat.ino; + } else { + caseInsensitiveFs = false; + } + + return caseInsensitiveFs; +} + +function statSyncNoException(path: string) { + try { + return fs.statSync(path); + } catch (e) { + return undefined; + } +} + +function withoutExt(fileName: string): string { + return path.basename(fileName).split(".")[0]; +} + +function compareFileName(first: string, second: string) { + if (isCaseInsensitive()) { + return first.toLowerCase() === second.toLowerCase(); + } else { + return first === second; + } +} + +function isFileEmit(fileName, outputFileName, sourceFileName) { + return compareFileName(sourceFileName, fileName) + // typescript now emits .jsx files for .tsx files. + && ( + outputFileName.substr(-3).toLowerCase() === ".js" || + outputFileName.substr(-4).toLowerCase() === ".jsx" + ); +} + +function isSourceMapEmit(fileName, outputFileName, sourceFileName) { + return compareFileName(sourceFileName, fileName) + // typescript now emits .jsx files for .tsx files. + && ( + outputFileName.substr(-7).toLowerCase() === ".js.map" || + outputFileName.substr(-8).toLowerCase() === ".jsx.map" + ); +} + +function isDeclarationEmit(fileName, outputFileName, sourceFileName) { + return compareFileName(sourceFileName, fileName) + && (outputFileName.substr(-5).toLowerCase() === ".d.ts"); +} + +export function findResultFor(fileName: string, outputFiles: ts.OutputFile[]): OutputFile { + let text; + let sourceMap; + let declaration: ts.OutputFile; + fileName = withoutExt(fileName); + + for (let i = 0; i < outputFiles.length; i++) { + let o = outputFiles[i]; + let outputFileName = o.name; + let sourceFileName = withoutExt(o.name); + if (isFileEmit(fileName, outputFileName, sourceFileName)) { + text = o.text; + } + if (isSourceMapEmit(fileName, outputFileName, sourceFileName)) { + sourceMap = o.text; + } + if (isDeclarationEmit(fileName, outputFileName, sourceFileName)) { + declaration = o; + } + } + + return { + text: text, + sourceMap: sourceMap, + declaration + }; +} + +export function codegenErrorReport(errors) { + return errors + .map(function (error) { + return "console.error(" + JSON.stringify(error) + ");"; + }) + .join("\n"); +} + +export function formatError(diagnostic) { + let lineChar; + if (diagnostic.file) { + lineChar = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); + } + return ( + (diagnostic.file ? path.normalize(diagnostic.file.fileName) : "") + + (lineChar ? formatLineChar(lineChar) + " " : "") + "\n" + + (typeof diagnostic.messageText == "string" ? + diagnostic.messageText : + formatMessageChain(diagnostic.messageText)) + ); +} + +export function formatMessageChain(chain: ts.DiagnosticMessageChain) { + let result = ""; + let separator = "\n "; + let current = chain; + + while (current) { + result += current.messageText; + + if (!!current.next) { + result += separator; + separator += " "; + } + + current = current.next; + } + + return result; +} + +export function formatLineChar(lineChar) { + return ":" + (lineChar.line + 1) + ":" + lineChar.character; +} + +export function loadLib(moduleId) { + let fileName = require.resolve(moduleId); + let text = fs.readFileSync(fileName, "utf8"); + return { + fileName: fileName, + text: text + }; +} + +const TYPESCRIPT_EXTENSION = /\.(d\.)?(t|j)s$/; + +export function withoutTypeScriptExtension(fileName: string): string { + return fileName.replace(TYPESCRIPT_EXTENSION, ""); +} + +export function unorderedRemoveItem(array: T[], item: T): boolean { + for (let i = 0; i < array.length; i++) { + if (array[i] === item) { + // Fill in the "hole" left at `index`. + array[i] = array[array.length - 1]; + array.pop(); + return true; + } + } + return false; +} diff --git a/src/index.ts b/src/index.ts index cf7548a..c373af3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,15 +1,15 @@ -import * as _ from 'lodash'; -import * as path from 'path'; -import * as fs from 'fs'; +import * as _ from "lodash"; +import * as path from "path"; +import * as fs from "fs"; -import { findCompiledModule, cache } from './cache'; -import * as helpers from './helpers'; -import { QueryOptions, Loader, ensureInstance, Instance, getRootCompiler } from './instance'; -import { PathPlugin } from './paths-plugin'; -import { CheckerPlugin as _CheckerPlugin } from './watch-mode'; +import { findCompiledModule, cache } from "./cache"; +import * as helpers from "./helpers"; +import { QueryOptions, Loader, ensureInstance, Instance, getRootCompiler } from "./instance"; +import { PathPlugin } from "./paths-plugin"; +import { CheckerPlugin as _CheckerPlugin } from "./watch-mode"; -const loaderUtils = require('loader-utils'); -const mkdirp = require('mkdirp'); +const loaderUtils = require("loader-utils"); +const mkdirp = require("mkdirp"); function loader(text) { try { @@ -43,7 +43,7 @@ function compiler(loader: Loader, text: string): void { const query = (loaderUtils.getOptions(loader) || {}); const options = (loader.options && loader.options.ts) || {}; - const instanceName = query.instance || 'at-loader'; + const instanceName = query.instance || "at-loader"; const instance = ensureInstance(loader, query, options, instanceName, rootCompiler); const callback = loader.async(); @@ -52,7 +52,7 @@ function compiler(loader: Loader, text: string): void { if (DECLARATION.test(fileName)) { loader.emitWarning(`[${instanceName}] TypeScript declaration files should never be required`); - return callback(null, ''); + return callback(null, ""); } let compiledModule; @@ -98,7 +98,7 @@ function compiler(loader: Loader, text: string): void { instance.compilerConfig.options.isolatedModules; if (!isolated && result.deps) { - // If our modules are isolated we don't need to recompile all the deps + // If our modules are isolated we don"t need to recompile all the deps result.deps.forEach(dep => loader.addDependency(path.normalize(dep))); } if (cached) { @@ -113,8 +113,8 @@ function compiler(loader: Loader, text: string): void { }) .catch(callback) .catch(e => { - console.error('Error in bail mode:', e, e.stack.join - ? e.stack.join ('\n') + console.error("Error in bail mode:", e, e.stack.join + ? e.stack.join ("\n") : e.stack ); process.exit(1); @@ -134,14 +134,14 @@ function transform( resultSourceMap = emitResult.sourceMap; resultText = emitResult.text; - let sourceFileName = fileName.replace(instance.context + '/', ''); + let sourceFileName = fileName.replace(instance.context + "/", ""); if (resultSourceMap) { resultSourceMap = JSON.parse(resultSourceMap); resultSourceMap.sources = [ sourceFileName ]; resultSourceMap.file = sourceFileName; resultSourceMap.sourcesContent = [ text ]; - resultText = resultText.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, ''); + resultText = resultText.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, ""); } if (instance.loaderConfig.useBabel) { diff --git a/src/instance.ts b/src/instance.ts index 5d8ebbf..cf1bc29 100644 --- a/src/instance.ts +++ b/src/instance.ts @@ -1,17 +1,17 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as _ from 'lodash'; -import * as ts from 'typescript'; -import { toUnix } from './helpers'; -import { Checker } from './checker'; -import { CompilerInfo, LoaderConfig, TsConfig } from './interfaces'; -import { WatchModeSymbol } from './watch-mode'; -import { createHash } from 'crypto'; - -let colors = require('colors/safe'); -let pkg = require('../package.json'); -let mkdirp = require('mkdirp'); -let enhancedResolve = require('enhanced-resolve'); +import * as fs from "fs"; +import * as path from "path"; +import * as _ from "lodash"; +import * as ts from "typescript"; +import { toUnix } from "./helpers"; +import { Checker } from "./checker"; +import { CompilerInfo, LoaderConfig, TsConfig } from "./interfaces"; +import { WatchModeSymbol } from "./watch-mode"; +import { createHash } from "crypto"; + +let colors = require("colors/safe"); +let pkg = require("../package.json"); +let mkdirp = require("mkdirp"); +let enhancedResolve = require("enhanced-resolve"); export interface Instance { id: number; @@ -117,7 +117,7 @@ export function ensureInstance( ); if (!loaderConfig.silent) { - const sync = watching === WatchMode.Enabled ? ' (in a forked process)' : ''; + const sync = watching === WatchMode.Enabled ? " (in a forked process)" : ""; console.log(`\n[${instanceName}] Using typescript@${compilerInfo.compilerVersion} from ${compilerInfo.compilerPath} and ` + `"tsconfig.json" from ${configFilePath}${sync}.\n`); } @@ -136,7 +136,7 @@ export function ensureInstance( setupWatchRun(compiler, instanceName); setupAfterCompile(compiler, instanceName); - const webpackOptions = _.pick(webpack._compiler.options, 'resolve'); + const webpackOptions = _.pick(webpack._compiler.options, "resolve"); const checker = new Checker( compilerInfo, loaderConfig, @@ -162,15 +162,15 @@ export function ensureInstance( function findTsImplPackage(inputPath: string) { let pkgDir = path.dirname(inputPath); - if (fs.readdirSync(pkgDir).find((value) => value === 'package.json')) { - return path.join(pkgDir, 'package.json'); + if (fs.readdirSync(pkgDir).find((value) => value === "package.json")) { + return path.join(pkgDir, "package.json"); } else { return findTsImplPackage(pkgDir); } } export function setupTs(compiler: string): CompilerInfo { - let compilerPath = compiler || 'typescript'; + let compilerPath = compiler || "typescript"; let tsImpl: typeof ts; let tsImplPath: string; @@ -205,26 +205,26 @@ function setupCache( ): string { if (loaderConfig.useCache) { if (!loaderConfig.cacheDirectory) { - loaderConfig.cacheDirectory = path.join(context, '.awcache'); + loaderConfig.cacheDirectory = path.join(context, ".awcache"); } if (!fs.existsSync(loaderConfig.cacheDirectory)) { mkdirp.sync(loaderConfig.cacheDirectory); } - let hash = createHash('sha512') as any; + let hash = createHash("sha512") as any; let contents = JSON.stringify({ typescript: tsImpl.version, - 'awesome-typescript-loader': pkg.version, - 'babel-core': babelImpl ? babelImpl.version : null, + "awesome-typescript-loader": pkg.version, + "babel-core": babelImpl ? babelImpl.version : null, babelPkg: pkg.babel, // TODO: babelrc.json/babelrc.js compilerConfig, - env: process.env.BABEL_ENV || process.env.NODE_ENV || 'development' + env: process.env.BABEL_ENV || process.env.NODE_ENV || "development" }); hash.end(contents); - return hash.read().toString('hex'); + return hash.read().toString("hex"); } } @@ -234,7 +234,7 @@ function setupBabel(loaderConfig: LoaderConfig, context: string): any { let babelImpl: any; if (loaderConfig.useBabel) { try { - let babelPath = loaderConfig.babelCore || resolver(context, 'babel-core'); + let babelPath = loaderConfig.babelCore || resolver(context, "babel-core"); babelImpl = require(babelPath); } catch (e) { console.error(BABEL_ERROR, e); @@ -310,11 +310,11 @@ export function readConfigFile( configFilePath = tsImpl.findConfigFile(context, tsImpl.sys.fileExists); } - let existingOptions = tsImpl.convertCompilerOptionsFromJson(query, context, 'atl.query'); + let existingOptions = tsImpl.convertCompilerOptionsFromJson(query, context, "atl.query"); if (!configFilePath || query.configFileContent) { return { - configFilePath: configFilePath || path.join(context, 'tsconfig.json'), + configFilePath: configFilePath || path.join(context, "tsconfig.json"), compilerConfig: tsImpl.parseJsonConfigFileContent( query.configFileContent || {}, tsImpl.sys, @@ -361,7 +361,7 @@ const filterMtimes = (mtimes: any) => { }; function setupWatchRun(compiler, instanceName: string) { - compiler.plugin('watch-run', function (watching, callback) { + compiler.plugin("watch-run", function (watching, callback) { const instance = resolveInstance(watching.compiler, instanceName); const checker = instance.checker; const watcher = watching.compiler.watchFileSystem.watcher @@ -433,8 +433,8 @@ function isWatching(compiler: any): WatchMode { } function setupAfterCompile(compiler, instanceName, forkChecker = false) { - compiler.plugin('after-compile', function (compilation, callback) { - // Don't add errors for child compilations + compiler.plugin("after-compile", function (compilation, callback) { + // Don"t add errors for child compilations if (compilation.compiler.isChild()) { callback(); return; @@ -447,7 +447,7 @@ function setupAfterCompile(compiler, instanceName, forkChecker = false) { let emitError = (msg) => { if (asyncErrors) { - console.log(msg, '\n'); + console.log(msg, "\n"); } else { if (!instance.loaderConfig.errorsAsWarnings) { compilation.errors.push(new Error(msg)); @@ -489,7 +489,7 @@ function setupAfterCompile(compiler, instanceName, forkChecker = false) { files .then(() => { if (asyncErrors) { - diag(); // Don't wait for diags in watch mode + diag(); // Don"t wait for diags in watch mode return; } else { return diag(); diff --git a/src/interfaces.ts b/src/interfaces.ts index 91ff2fe..e4f59fd 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -1,4 +1,4 @@ -import * as ts from 'typescript'; +import * as ts from "typescript"; export interface CompilerInfo { compilerPath: string; diff --git a/src/paths-plugin.ts b/src/paths-plugin.ts index 35980d8..632fa6e 100644 --- a/src/paths-plugin.ts +++ b/src/paths-plugin.ts @@ -1,14 +1,14 @@ -import { setupTs, readConfigFile } from './instance'; -import { LoaderConfig } from './interfaces'; -import * as path from 'path'; -import * as _ from 'lodash'; -import * as ts from 'typescript'; +import { setupTs, readConfigFile } from "./instance"; +import { LoaderConfig } from "./interfaces"; +import * as path from "path"; +import * as _ from "lodash"; +import * as ts from "typescript"; const ModulesInRootPlugin: new (a: string, b: string, c: string) => ResolverPlugin - = require('enhanced-resolve/lib/ModulesInRootPlugin'); + = require("enhanced-resolve/lib/ModulesInRootPlugin"); -const createInnerCallback: CreateInnerCallback = require('enhanced-resolve/lib/createInnerCallback'); -const getInnerRequest: getInnerRequest = require('enhanced-resolve/lib/getInnerRequest'); +const createInnerCallback: CreateInnerCallback = require("enhanced-resolve/lib/createInnerCallback"); +const getInnerRequest: getInnerRequest = require("enhanced-resolve/lib/getInnerRequest"); type CreateInnerCallback = (callback: Callback, options: Callback, message?: string, messageOptional?: string) => Callback; type getInnerRequest = (resolver: Resolver, request: Request) => string; @@ -66,8 +66,8 @@ export class PathPlugin implements ResolverPlugin { absoluteBaseUrl: string; constructor(config: LoaderConfig & ts.CompilerOptions & PathPluginOptions = {} as any) { - this.source = 'described-resolve'; - this.target = 'resolve'; + this.source = "described-resolve"; + this.target = "resolve"; this.ts = setupTs(config.compiler).tsImpl; @@ -80,13 +80,13 @@ export class PathPlugin implements ResolverPlugin { this.baseUrl = this.options.baseUrl; this.absoluteBaseUrl = path.resolve( path.dirname(this.configFilePath), - this.baseUrl || '.' + this.baseUrl || "." ); this.mappings = []; let paths = this.options.paths || {}; Object.keys(paths).forEach(alias => { - let onlyModule = alias.indexOf('*') === -1; + let onlyModule = alias.indexOf("*") === -1; let excapedAlias = escapeRegExp(alias); let targets = paths[alias]; targets.forEach(target => { @@ -94,7 +94,7 @@ export class PathPlugin implements ResolverPlugin { if (onlyModule) { aliasPattern = new RegExp(`^${excapedAlias}$`); } else { - let withStarCapturing = excapedAlias.replace('\\*', '(.*)'); + let withStarCapturing = excapedAlias.replace("\\*", "(.*)"); aliasPattern = new RegExp(`^${withStarCapturing}`); } @@ -124,7 +124,7 @@ export class PathPlugin implements ResolverPlugin { } isTyping(target: string) { - return target.indexOf('@types') !== -1 || target.indexOf('.d.ts') !== -1; + return target.indexOf("@types") !== -1 || target.indexOf(".d.ts") !== -1; } createPlugin(resolver: Resolver, mapping: Mapping) { @@ -141,10 +141,10 @@ export class PathPlugin implements ResolverPlugin { let newRequestStr = mapping.target; if (!mapping.onlyModule) { - newRequestStr = newRequestStr.replace('*', match[1]); + newRequestStr = newRequestStr.replace("*", match[1]); } - if (newRequestStr[0] === '.') { + if (newRequestStr[0] === ".") { newRequestStr = path.resolve(this.absoluteBaseUrl, newRequestStr); } diff --git a/src/watch-mode.ts b/src/watch-mode.ts index fa5858a..40fadac 100644 --- a/src/watch-mode.ts +++ b/src/watch-mode.ts @@ -1,4 +1,4 @@ -export const WatchModeSymbol = Symbol('WatchMode'); +export const WatchModeSymbol = Symbol("WatchMode"); export class CheckerPlugin { apply(compiler) {