Skip to content

Commit

Permalink
Improve performance for after-compile plugin. (#187)
Browse files Browse the repository at this point in the history
* Improve performance for `after-compile` plugin.

This fix makes optimization into `after-compile` plugin, which increases performance of watch mode.
Instead of iterate over all files in instance, iterate only over modified files.

* Make `modifiedFiles` optional

* Push file into `modifiedFiles` on each loader call, proper handle dependencies between files.

* Change `=== null` check into boolean cast

* Add a missed clear for `modifiedFiles`

* Do not hide files with errors on rebuilds caused by modified other files.

* Implemented reverseDependencyGraph to get list of files, which are possible affected by changes

* On initial build check all files for errors.
On each watch-run put affected file to `instance.modifiedFiles`

This commit fix issue, when errors in declaration files swallowed by the loader.
  • Loading branch information
Strate authored and johnnyreilly committed Oct 18, 2016
1 parent 3f5f19e commit 8b62b3d
Showing 1 changed file with 83 additions and 12 deletions.
95 changes: 83 additions & 12 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ interface TSInstance {
languageService?: typescript.LanguageService;
version?: number;
dependencyGraph: any;
reverseDependencyGraph: any;
modifiedFiles?: TSFiles;
filesWithErrors?: TSFiles;
}

interface TSInstances {
Expand Down Expand Up @@ -225,7 +228,9 @@ function ensureTypeScriptInstance(loaderOptions: LoaderOptions, loader: any): {
files,
languageService: null,
version: 0,
dependencyGraph: {}
dependencyGraph: {},
reverseDependencyGraph: {},
modifiedFiles: null
};

var compilerOptions: typescript.CompilerOptions = {
Expand Down Expand Up @@ -332,7 +337,7 @@ function ensureTypeScriptInstance(loaderOptions: LoaderOptions, loader: any): {
loader._module.errors,
formatErrors(diagnostics, instance, {file: configFilePath || 'tsconfig.json'}));

return { instance: instances[loaderOptions.instance] = { compiler, compilerOptions, loaderOptions, files, dependencyGraph: {} }};
return { instance: instances[loaderOptions.instance] = { compiler, compilerOptions, loaderOptions, files, dependencyGraph: {}, reverseDependencyGraph: {} }};
}

// Load initial files (core lib files, any files specified in tsconfig.json)
Expand Down Expand Up @@ -445,25 +450,32 @@ function ensureTypeScriptInstance(loaderOptions: LoaderOptions, loader: any): {

resolvedModules.push(resolutionResult);
}

instance.dependencyGraph[path.normalize(containingFile)] = resolvedModules.filter(m => m != null).map(m => m.resolvedFileName);


let importedFiles = resolvedModules.filter(m => m != null).map(m => m.resolvedFileName);
instance.dependencyGraph[path.normalize(containingFile)] = importedFiles;
importedFiles.forEach(importedFileName => {
if (!instance.reverseDependencyGraph[importedFileName]) {
instance.reverseDependencyGraph[importedFileName] = {}
}
instance.reverseDependencyGraph[importedFileName][path.normalize(containingFile)] = true
})


return resolvedModules;
}
};

var languageService = instance.languageService = compiler.createLanguageService(servicesHost, compiler.createDocumentRegistry());

var getCompilerOptionDiagnostics = true;
var checkAllFilesForErrors = true;

loader._compiler.plugin("after-compile", (compilation, callback) => {
// Don't add errors for child compilations
if (compilation.compiler.isChild()) {
callback();
return;
}

let stats = compilation.stats;

// handle all other errors. The basic approach here to get accurate error
// reporting is to start with a "blank slate" each compilation and gather
Expand All @@ -481,6 +493,23 @@ function ensureTypeScriptInstance(loaderOptions: LoaderOptions, loader: any): {
}
}

/**
* Recursive collect all possible dependats of passed file
*/
function collectAllDependants(fileName: string, collected: any = {}): string[] {
let result = {}
result[fileName] = true
collected[fileName] = true
if (instance.reverseDependencyGraph[fileName]) {
Object.keys(instance.reverseDependencyGraph[fileName]).forEach(dependantFileName => {
if (!collected[dependantFileName]) {
collectAllDependants(dependantFileName, collected).forEach(fName => result[fName] = true)
}
})
}
return Object.keys(result)
}

removeTSLoaderErrors(compilation.errors);

// handle compiler option errors after the first compile
Expand Down Expand Up @@ -511,10 +540,39 @@ function ensureTypeScriptInstance(loaderOptions: LoaderOptions, loader: any): {
})

// gather all errors from TypeScript and output them to webpack
Object.keys(instance.files)
let filesWithErrors: TSFiles = {}
// calculate array of files to check
let filesToCheckForErrors: TSFiles = null
if (checkAllFilesForErrors) {
// check all files on initial run
filesToCheckForErrors = instance.files
checkAllFilesForErrors = false
} else {
filesToCheckForErrors = {}
// check all modified files, and all dependants
Object.keys(instance.modifiedFiles).forEach(modifiedFileName => {
collectAllDependants(modifiedFileName).forEach(fName => {
filesToCheckForErrors[fName] = instance.files[fName]
})
})
}
// re-check files with errors from previous build
if (instance.filesWithErrors) {
Object.keys(instance.filesWithErrors).forEach(fileWithErrorName =>
filesToCheckForErrors[fileWithErrorName] = instance.filesWithErrors[fileWithErrorName]
)
}

Object.keys(filesToCheckForErrors)
.filter(filePath => !!filePath.match(/(\.d)?\.ts(x?)$/))
.forEach(filePath => {
let errors = languageService.getSyntacticDiagnostics(filePath).concat(languageService.getSemanticDiagnostics(filePath));
if (errors.length > 0) {
if (null === filesWithErrors) {
filesWithErrors = {}
}
filesWithErrors[filePath] = instance.files[filePath]
}

// if we have access to a webpack module, use that
if (hasOwnProperty(modules, filePath)) {
Expand All @@ -538,7 +596,7 @@ function ensureTypeScriptInstance(loaderOptions: LoaderOptions, loader: any): {


// gather all declaration files from TypeScript and output them to webpack
Object.keys(instance.files)
Object.keys(filesToCheckForErrors)
.filter(filePath => !!filePath.match(/\.ts(x?)$/))
.forEach(filePath => {
let output = languageService.getEmitOutput(filePath);
Expand All @@ -552,12 +610,18 @@ function ensureTypeScriptInstance(loaderOptions: LoaderOptions, loader: any): {
}
});

instance.filesWithErrors = filesWithErrors;
instance.modifiedFiles = null;
callback();
});

// manually update changed files
loader._compiler.plugin("watch-run", (watching, cb) => {
var mtimes = watching.compiler.watchFileSystem.watcher.mtimes;
if (null === instance.modifiedFiles) {
instance.modifiedFiles = {}
}

Object.keys(mtimes)
.filter(filePath => !!filePath.match(/\.tsx?$|\.jsx?$/))
.forEach(filePath => {
Expand All @@ -567,6 +631,7 @@ function ensureTypeScriptInstance(loaderOptions: LoaderOptions, loader: any): {
file.text = fs.readFileSync(filePath, {encoding: 'utf8'});
file.version++;
instance.version++;
instance.modifiedFiles[filePath] = file;
}
});
cb()
Expand Down Expand Up @@ -622,6 +687,12 @@ function loader(contents) {
instance.version++;
}

// push this file to modified files hash.
if (!instance.modifiedFiles) {
instance.modifiedFiles = {}
}
instance.modifiedFiles[filePath] = file;

var outputText: string, sourceMapText: string, diagnostics: typescript.Diagnostic[] = [];

if (options.transpileOnly) {
Expand All @@ -641,7 +712,7 @@ function loader(contents) {

// Emit Javascript
var output = langService.getEmitOutput(filePath);

// Make this file dependent on *all* definition files in the program
this.clearDependencies();
this.addDependency(filePath);
Expand All @@ -652,9 +723,9 @@ function loader(contents) {
// Additionally make this file dependent on all imported files
let additionalDependencies = instance.dependencyGraph[filePath];
if (additionalDependencies) {
additionalDependencies.forEach(this.addDependency.bind(this))
additionalDependencies.forEach(this.addDependency.bind(this))
}

this._module.meta.tsLoaderDefinitionFileVersions = allDefinitionFiles
.concat(additionalDependencies)
.map(filePath => filePath+'@'+(instance.files[filePath] || {version: '?'}).version);
Expand Down

0 comments on commit 8b62b3d

Please sign in to comment.