Skip to content

Commit

Permalink
Merge pull request #685 from sheetalkamat/builderApi
Browse files Browse the repository at this point in the history
Using the new watch api of compiler
  • Loading branch information
johnnyreilly committed Jan 20, 2018
2 parents 3015608 + 42e04a5 commit 7add160
Show file tree
Hide file tree
Showing 7 changed files with 292 additions and 28 deletions.
36 changes: 23 additions & 13 deletions src/after-compile.ts
@@ -1,5 +1,4 @@
import * as path from 'path';
import * as typescript from 'typescript';

import { collectAllDependants, formatErrors, hasOwnProperty, registerWebpackErrors } from './utils';
import * as constants from './constants';
Expand All @@ -10,6 +9,7 @@ import {
WebpackError,
WebpackModule
} from './interfaces';
import { getEmitOutput } from './instances';

export function makeAfterCompile(
instance: TSInstance,
Expand Down Expand Up @@ -38,7 +38,7 @@ export function makeAfterCompile(
const filesWithErrors: TSFiles = {};
provideErrorsToWebpack(filesToCheckForErrors, filesWithErrors, compilation, modules, instance);

provideDeclarationFilesToWebpack(filesToCheckForErrors, instance.languageService!, compilation);
provideDeclarationFilesToWebpack(filesToCheckForErrors, instance, compilation);

instance.filesWithErrors = filesWithErrors;
instance.modifiedFiles = null;
Expand All @@ -60,11 +60,13 @@ function provideCompilerOptionDiagnosticErrorsToWebpack(
configFilePath: string | undefined
) {
if (getCompilerOptionDiagnostics) {
const { languageService, loaderOptions, compiler } = instance;
const { languageService, loaderOptions, compiler, program } = instance;
registerWebpackErrors(
compilation.errors,
formatErrors(
languageService!.getCompilerOptionsDiagnostics(),
program ?
program.getOptionsDiagnostics() :
languageService!.getCompilerOptionsDiagnostics(),
loaderOptions, instance.colors, compiler,
{ file: configFilePath || 'tsconfig.json' }));
}
Expand Down Expand Up @@ -99,18 +101,23 @@ function determineFilesToCheckForErrors(
checkAllFilesForErrors: boolean,
instance: TSInstance
) {
const { files, modifiedFiles, filesWithErrors } = instance
const { files, modifiedFiles, filesWithErrors, otherFiles } = instance
// calculate array of files to check
let filesToCheckForErrors: TSFiles = {};
if (checkAllFilesForErrors) {
// check all files on initial run
filesToCheckForErrors = files;
Object.keys(files).forEach(fileName => {
filesToCheckForErrors[fileName] = files[fileName];
});
Object.keys(otherFiles).forEach(fileName => {
filesToCheckForErrors[fileName] = otherFiles[fileName];
});
} else if (modifiedFiles !== null && modifiedFiles !== undefined) {
// check all modified files, and all dependants
Object.keys(modifiedFiles).forEach(modifiedFileName => {
collectAllDependants(instance.reverseDependencyGraph, modifiedFileName)
.forEach(fileName => {
filesToCheckForErrors[fileName] = files[fileName];
filesToCheckForErrors[fileName] = files[fileName] || otherFiles[fileName];
});
});
}
Expand All @@ -131,16 +138,19 @@ function provideErrorsToWebpack(
modules: Modules,
instance: TSInstance
) {
const { compiler, languageService, files, loaderOptions, compilerOptions } = instance;
const { compiler, program, languageService, files, loaderOptions, compilerOptions, otherFiles } = instance;

let filePathRegex = !!compilerOptions.checkJs ? constants.dtsTsTsxJsJsxRegex : constants.dtsTsTsxRegex;

Object.keys(filesToCheckForErrors)
.filter(filePath => filePath.match(filePathRegex))
.forEach(filePath => {
const errors = languageService!.getSyntacticDiagnostics(filePath).concat(languageService!.getSemanticDiagnostics(filePath));
const sourceFile = program && program.getSourceFile(filePath);
const errors = program ?
program.getSyntacticDiagnostics(sourceFile).concat(program.getSemanticDiagnostics(sourceFile)) :
languageService!.getSyntacticDiagnostics(filePath).concat(languageService!.getSemanticDiagnostics(filePath));
if (errors.length > 0) {
filesWithErrors[filePath] = files[filePath];
filesWithErrors[filePath] = files[filePath] || otherFiles[filePath];
}

// if we have access to a webpack module, use that
Expand Down Expand Up @@ -168,14 +178,14 @@ function provideErrorsToWebpack(
*/
function provideDeclarationFilesToWebpack(
filesToCheckForErrors: TSFiles,
languageService: typescript.LanguageService,
instance: TSInstance,
compilation: WebpackCompilation
) {
Object.keys(filesToCheckForErrors)
.filter(filePath => filePath.match(constants.tsTsxRegex))
.forEach(filePath => {
const output = languageService.getEmitOutput(filePath);
const declarationFile = output.outputFiles.filter(outputFile => outputFile.name.match(constants.dtsDtsxRegex)).pop();
const outputFiles = getEmitOutput(instance, filePath);
const declarationFile = outputFiles.filter(outputFile => outputFile.name.match(constants.dtsDtsxRegex)).pop();
if (declarationFile !== undefined) {
const assetPath = path.relative(compilation.compiler.context, declarationFile.name);
compilation.assets[assetPath] = {
Expand Down
34 changes: 28 additions & 6 deletions src/index.ts
@@ -1,7 +1,8 @@
import * as path from 'path';
import * as loaderUtils from 'loader-utils';
import * as typescript from 'typescript';

import { getTypeScriptInstance } from './instances';
import { getTypeScriptInstance, getEmitOutput } from './instances';
import { appendSuffixesIfMatch, arrify, formatErrors, hasOwnProperty, registerWebpackErrors } from './utils';
import * as constants from './constants';
import {
Expand Down Expand Up @@ -166,16 +167,38 @@ function makeLoaderOptions(instanceName: string, configFileOptions: Partial<Load
* Also add the file to the modified files
*/
function updateFileInCache(filePath: string, contents: string, instance: TSInstance) {
let fileWatcherEventKind: typescript.FileWatcherEventKind | undefined;
// Update file contents
let file = instance.files[filePath];
if (file === undefined) {
file = instance.files[filePath] = <TSFile>{ version: 0 };
file = instance.otherFiles[filePath];
if (file !== undefined) {
delete instance.otherFiles[filePath];
instance.files[filePath] = file;
}
else {
fileWatcherEventKind = instance.compiler.FileWatcherEventKind.Created;
file = instance.files[filePath] = <TSFile>{ version: 0 };
}
instance.changedFilesList = true;
}

if (contents === undefined) {
fileWatcherEventKind === instance.compiler.FileWatcherEventKind.Deleted;
}

if (file.text !== contents) {
file.version++;
file.text = contents;
instance.version!++;
if (instance.watchHost && fileWatcherEventKind === undefined) {
instance.watchHost.invokeFileWatcher(filePath, instance.compiler.FileWatcherEventKind.Changed);
}
}

if (instance.watchHost && fileWatcherEventKind !== undefined) {
instance.watchHost.invokeFileWatcher(filePath, fileWatcherEventKind);
instance.watchHost.invokeDirectoryWatcher(path.dirname(filePath), filePath);
}

// push this file to modified files hash.
Expand All @@ -192,8 +215,7 @@ function getEmit(
instance: TSInstance,
loader: Webpack
) {
// Emit Javascript
const output = instance.languageService!.getEmitOutput(filePath);
const outputFiles = getEmitOutput(instance, filePath);

loader.clearDependencies();
loader.addDependency(rawFilePath);
Expand All @@ -217,10 +239,10 @@ function getEmit(
.concat(additionalDependencies)
.map(defFilePath => defFilePath + '@' + (instance.files[defFilePath] || { version: '?' }).version);

const outputFile = output.outputFiles.filter(outputFile => outputFile.name.match(constants.jsJsx)).pop();
const outputFile = outputFiles.filter(outputFile => outputFile.name.match(constants.jsJsx)).pop();
const outputText = (outputFile) ? outputFile.text : undefined;

const sourceMapFile = output.outputFiles.filter(outputFile => outputFile.name.match(constants.jsJsxMap)).pop();
const sourceMapFile = outputFiles.filter(outputFile => outputFile.name.match(constants.jsJsxMap)).pop();
const sourceMapText = (sourceMapFile) ? sourceMapFile.text : undefined;

return { outputText, sourceMapText };
Expand Down
48 changes: 42 additions & 6 deletions src/instances.ts
Expand Up @@ -9,7 +9,7 @@ import { EOL, dtsDtsxRegex } from './constants';
import { getCompilerOptions, getCompiler } from './compilerSetup';
import { hasOwnProperty, makeError, formatErrors, registerWebpackErrors } from './utils';
import * as logger from './logger';
import { makeServicesHost } from './servicesHost';
import { makeServicesHost, makeWatchHost } from './servicesHost';
import { makeWatchRun } from './watch-run';
import {
LoaderOptions,
Expand All @@ -34,6 +34,15 @@ export function getTypeScriptInstance(
loader: Webpack
): { instance?: TSInstance, error?: WebpackError } {
if (hasOwnProperty(instances, loaderOptions.instance)) {
const instance = instances[loaderOptions.instance];
if (instance && instance.watchHost) {
if (instance.changedFilesList) {
instance.watchHost.updateRootFileNames();
}
if (instance.watchOfFilesAndCompilerOptions) {
instance.program = instance.watchOfFilesAndCompilerOptions.getProgram().getProgram();
}
}
return { instance: instances[loaderOptions.instance] };
}

Expand Down Expand Up @@ -87,27 +96,29 @@ function successfulTypeScriptInstance(

const compilerOptions = getCompilerOptions(configParseResult);
const files: TSFiles = {};
const otherFiles: TSFiles = {};

const getCustomTransformers = loaderOptions.getCustomTransformers || Function.prototype;

if (loaderOptions.transpileOnly) {
// quick return for transpiling
// we do need to check for any issues with TS options though
const program = compiler!.createProgram([], compilerOptions);
const diagnostics = program.getOptionsDiagnostics();

// happypack does not have _module.errors - see https://github.com/TypeStrong/ts-loader/issues/336
if (!loaderOptions.happyPackMode) {
const diagnostics = program.getOptionsDiagnostics();
registerWebpackErrors(
loader._module.errors,
formatErrors(diagnostics, loaderOptions, colors, compiler!, {file: configFilePath || 'tsconfig.json'}));
}

const instance = {
const instance: TSInstance = {
compiler,
compilerOptions,
loaderOptions,
files,
files,
otherFiles,
dependencyGraph: {},
reverseDependencyGraph: {},
transformers: getCustomTransformers(),
Expand Down Expand Up @@ -146,6 +157,7 @@ function successfulTypeScriptInstance(
compilerOptions,
loaderOptions,
files,
otherFiles,
languageService: null,
version: 0,
transformers: getCustomTransformers(),
Expand All @@ -155,11 +167,35 @@ function successfulTypeScriptInstance(
colors
};

const servicesHost = makeServicesHost(scriptRegex, log, loader, instance, loaderOptions.appendTsSuffixTo, loaderOptions.appendTsxSuffixTo);
instance.languageService = compiler.createLanguageService(servicesHost, compiler.createDocumentRegistry());
if (compiler.createWatchProgram) {
console.log("Using watch api");
// If there is api available for watch, use it instead of language service
const watchHost = makeWatchHost(scriptRegex, log, loader, instance, loaderOptions.appendTsSuffixTo, loaderOptions.appendTsxSuffixTo);
instance.watchOfFilesAndCompilerOptions = compiler.createWatchProgram(watchHost);
instance.program = instance.watchOfFilesAndCompilerOptions.getProgram().getProgram();
}
else {
const servicesHost = makeServicesHost(scriptRegex, log, loader, instance, loaderOptions.appendTsSuffixTo, loaderOptions.appendTsxSuffixTo);
instance.languageService = compiler.createLanguageService(servicesHost, compiler.createDocumentRegistry());
}

loader._compiler.plugin("after-compile", makeAfterCompile(instance, configFilePath));
loader._compiler.plugin("watch-run", makeWatchRun(instance));

return { instance };
}

export function getEmitOutput(instance: TSInstance, filePath: string) {
if (instance.program) {
const outputFiles: typescript.OutputFile[] = [];
const writeFile = (fileName: string, text: string, writeByteOrderMark: boolean) =>
outputFiles.push({ name: fileName, writeByteOrderMark, text });
const sourceFile = instance.program.getSourceFile(filePath);
instance.program.emit(sourceFile, writeFile, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ false, instance.transformers);
return outputFiles;
}
else {
// Emit Javascript
return instance.languageService!.getEmitOutput(filePath).outputFiles;
}
}
12 changes: 12 additions & 0 deletions src/interfaces.ts
Expand Up @@ -214,6 +214,12 @@ export interface ModuleResolutionHost {
readFile(fileName: string, encoding?: string | undefined): string | undefined;
}

export interface WatchHost extends typescript.WatchCompilerHostOfFilesAndCompilerOptions<typescript.BuilderProgram> {
invokeFileWatcher(fileName: string, eventKind: typescript.FileWatcherEventKind): void;
invokeDirectoryWatcher(directory: string, fileAddedOrRemoved: string): void;
updateRootFileNames(): void;
}

export interface TSInstance {
compiler: typeof typescript;
compilerOptions: typescript.CompilerOptions;
Expand All @@ -233,6 +239,12 @@ export interface TSInstance {
filesWithErrors?: TSFiles;
transformers: typescript.CustomTransformers;
colors: Chalk;

otherFiles: TSFiles;
watchHost?: WatchHost;
watchOfFilesAndCompilerOptions?: typescript.WatchOfFilesAndCompilerOptions<typescript.BuilderProgram>;
program?: typescript.Program;
changedFilesList?: boolean;
}

export interface LoaderOptionsCache {
Expand Down

0 comments on commit 7add160

Please sign in to comment.