awesome-typescript-loader
Version:
Awesome TS loader for webpack
228 lines (190 loc) • 6.83 kB
text/typescript
import { ICompilerInfo, IFile, TSCONFIG_INFERRED } from './host';
import * as path from 'path';
import { LoaderPlugin, LoaderPluginDef, LoaderConfig } from './instance';
let colors = require('colors/safe');
export enum MessageType {
Init = <any>'init',
Compile = <any>'compile'
}
export interface IMessage {
messageType: MessageType;
payload: any;
}
export interface IInitPayload {
loaderConfig: LoaderConfig;
compilerOptions: ts.CompilerOptions;
compilerInfo: ICompilerInfo;
webpackOptions: any;
defaultLib: string;
plugins: LoaderPluginDef[];
}
export interface ICompilePayload {
files: {[fileName: string]: IFile};
moduleResolutionCache: {[fileName: string]: ts.ResolvedModule};
typeReferenceResolutionCache: {[fileName: string]: ts.ResolvedTypeReferenceDirective};
}
export interface IEnv {
loaderConfig?: LoaderConfig;
compilerOptions?: ts.CompilerOptions;
webpackOptions?: any;
compiler?: typeof ts;
compilerInfo?: ICompilerInfo;
host?: Host;
files?: {[fileName: string]: IFile};
moduleResolutionCache?: {[fileName: string]: ts.ResolvedModule};
typeReferenceResolutionCache?: {[fileName: string]: ts.ResolvedTypeReferenceDirective};
program?: ts.Program;
service?: ts.LanguageService;
plugins?: LoaderPluginDef[];
defaultLib?: string;
initedPlugins?: LoaderPlugin[];
}
export interface SyncResolver {
(context: string, fileName: string): string;
}
let env: IEnv = {};
export class ModuleResolutionHost implements ts.ModuleResolutionHost {
servicesHost: Host;
constructor(servicesHost: Host) {
this.servicesHost = servicesHost;
}
fileExists(fileName: string) {
return this.servicesHost.getScriptSnapshot(fileName) !== undefined;
}
readFile(fileName: string): string {
let snapshot = this.servicesHost.getScriptSnapshot(fileName);
return snapshot && snapshot.getText(0, snapshot.getLength());
}
}
export class Host implements ts.LanguageServiceHost {
moduleResolutionHost: ModuleResolutionHost;
resolver: SyncResolver;
constructor() {
this.moduleResolutionHost = new ModuleResolutionHost(this);
}
getScriptFileNames() {
return Object.keys(env.files);
}
getScriptVersion(fileName: string) {
if (env.files[fileName]) {
return env.files[fileName].version.toString();
}
}
getScriptSnapshot(fileName: string) {
let file = env.files[fileName];
if (!file) {
return null;
}
return env.compiler.ScriptSnapshot.fromString(file.text);
}
getCurrentDirectory() {
return process.cwd();
}
getScriptIsOpen() {
return true;
}
getCompilationSettings() {
return env.compilerOptions;
}
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string) {
if (containingFile.indexOf(TSCONFIG_INFERRED) !== -1) {
containingFile = TSCONFIG_INFERRED;
}
return typeDirectiveNames.map(moduleName => {
return env.typeReferenceResolutionCache[`${containingFile}::${moduleName}`];
});
}
resolveModuleNames(moduleNames: string[], containingFile: string) {
return moduleNames.map(moduleName => {
return env.moduleResolutionCache[`${containingFile}::${moduleName}`];
});
}
getDefaultLibFileName(options: ts.CompilerOptions) {
return env.defaultLib;
}
log(message) {
}
}
function processInit(payload: IInitPayload) {
env.compiler = require(payload.compilerInfo.compilerPath);
env.compilerInfo = payload.compilerInfo;
env.loaderConfig = payload.loaderConfig;
env.compilerOptions = payload.compilerOptions;
env.webpackOptions = payload.webpackOptions;
env.defaultLib = payload.defaultLib;
env.host = new Host();
env.service = env.compiler.createLanguageService(env.host, env.compiler.createDocumentRegistry());
env.plugins = payload.plugins;
env.initedPlugins = env.plugins.map(plugin => {
return require(plugin.file)(plugin.options);
});
}
let DECLARATION_FILE = /\.d\.ts/;
function processCompile(payload: ICompilePayload) {
let instanceName = env.loaderConfig.instanceName || 'default';
let silent = !!env.loaderConfig.forkCheckerSilent;
if (!silent) {
console.log(colors.cyan(`[${ instanceName }] Checking started in a separate process...`));
}
let timeStart = +new Date();
process.send({
messageType: 'progress',
payload: {
inProgress: true
}
});
env.files = payload.files;
env.moduleResolutionCache = payload.moduleResolutionCache;
env.typeReferenceResolutionCache = payload.typeReferenceResolutionCache;
let program = env.program = env.service.getProgram();
let allDiagnostics: ts.Diagnostic[] = [];
if (env.loaderConfig.skipDeclarationFilesCheck) {
let sourceFiles = program.getSourceFiles();
sourceFiles.forEach(sourceFile => {
if (!sourceFile.fileName.match(DECLARATION_FILE)) {
allDiagnostics = allDiagnostics.concat(env.compiler.getPreEmitDiagnostics(program, sourceFile));
}
});
// FIXME internal API
allDiagnostics = (env.compiler as any).sortAndDeduplicateDiagnostics(allDiagnostics);
} else {
allDiagnostics = env.compiler.getPreEmitDiagnostics(program);
}
if (allDiagnostics.length) {
allDiagnostics.forEach(diagnostic => {
let message = env.compiler.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
if (diagnostic.file) {
let { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
console.error(`[${ instanceName }] ${colors.red(path.normalize(diagnostic.file.fileName))}:${line + 1}:${character + 1} \n ${colors.red(message)}`);
} else {
console.error(colors.red(`[${ instanceName }] ${ message }`));
}
});
} else {
if (!silent) {
let timeEnd = +new Date();
console.log(
colors.green(`[${ instanceName }] Ok, ${(timeEnd - timeStart) / 1000} sec.`)
);
}
}
env.initedPlugins.forEach(plugin => {
plugin.processProgram(program);
});
process.send({
messageType: 'progress',
payload: {
inProgress: false
}
});
}
process.on('message', function(msg: IMessage) {
switch (msg.messageType) {
case MessageType.Init:
processInit(msg.payload);
break;
case MessageType.Compile:
processCompile(msg.payload);
break;
}
});