Skip to content

Commit

Permalink
fix(@ngtools/webpack): decorate file system (#7471)
Browse files Browse the repository at this point in the history
Followup to #7169
  • Loading branch information
filipesilva authored and hansl committed Aug 23, 2017
1 parent d6c07c7 commit b0d1bff
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 78 deletions.
79 changes: 11 additions & 68 deletions packages/@ngtools/webpack/src/compiler_host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,70 +146,6 @@ export class WebpackCompilerHost implements ts.CompilerHost {
this._cache = true;
}

populateWebpackResolver(resolver: any) {
const fs = resolver.fileSystem;
if (!this.dirty) {
return;
}

/**
* storageDataSetter is a temporary hack to address these two issues:
* - https://github.com/angular/angular-cli/issues/7113
* - https://github.com/angular/angular-cli/issues/7136
*
* This way we set values correctly in both a Map (enhanced-resove>=3.4.0) and
* object (enhanced-resolve >= 3.1.0 <3.4.0).
*
* The right solution is to create a virtual filesystem by decorating the filesystem,
* instead of injecting data into the private cache of the filesystem.
*
* Doing it the right way should fix other related bugs, but meanwhile we hack it since:
* - it's affecting a lot of users.
* - the real solution is non-trivial.
*/
function storageDataSetter(data: Map<string, any> | {[k: string]: any}, k: string, v: any) {

if (data instanceof Map) {
data.set(k, v);
} else {
data[k] = v;
}
}



const isWindows = process.platform.startsWith('win');
for (const fileName of this.getChangedFilePaths()) {
const stats = this._files[fileName];
if (stats) {
// If we're on windows, we need to populate with the proper path separator.
const path = isWindows ? fileName.replace(/\//g, '\\') : fileName;
// fs._statStorage.data[path] = [null, stats];
// fs._readFileStorage.data[path] = [null, stats.content];
storageDataSetter(fs._statStorage.data, path, [null, stats]);
storageDataSetter(fs._readFileStorage.data, path, [null, stats.content]);
} else {
// Support removing files as well.
const path = isWindows ? fileName.replace(/\//g, '\\') : fileName;
// fs._statStorage.data[path] = [new Error(), null];
// fs._readFileStorage.data[path] = [new Error(), null];
storageDataSetter(fs._statStorage.data, path, [new Error(), null]);
storageDataSetter(fs._readFileStorage.data, path, [new Error(), null]);
}
}
for (const dirName of Object.keys(this._changedDirs)) {
const stats = this._directories[dirName];
const dirs = this.getDirectories(dirName);
const files = this.getFiles(dirName);
// If we're on windows, we need to populate with the proper path separator.
const path = isWindows ? dirName.replace(/\//g, '\\') : dirName;
// fs._statStorage.data[path] = [null, stats];
// fs._readdirStorage.data[path] = [null, files.concat(dirs)];
storageDataSetter(fs._statStorage.data, path, [null, stats]);
storageDataSetter(fs._readFileStorage.data, path, [null, files.concat(dirs)]);
}
}

resetChangedFileTracker() {
this._changedFiles = Object.create(null);
this._changedDirs = Object.create(null);
Expand All @@ -226,9 +162,9 @@ export class WebpackCompilerHost implements ts.CompilerHost {
}
}

fileExists(fileName: string): boolean {
fileExists(fileName: string, delegate = true): boolean {
fileName = this._resolve(fileName);
return this._files[fileName] != null || this._delegate.fileExists(fileName);
return this._files[fileName] != null || (delegate && this._delegate.fileExists(fileName));
}

readFile(fileName: string): string {
Expand All @@ -247,10 +183,17 @@ export class WebpackCompilerHost implements ts.CompilerHost {
return stats.content;
}

directoryExists(directoryName: string): boolean {
// Does not delegate, use with `fileExists/directoryExists()`.
stat(path: string): VirtualStats {
path = this._resolve(path);
return this._files[path] || this._directories[path];
}

directoryExists(directoryName: string, delegate = true): boolean {
directoryName = this._resolve(directoryName);
return (this._directories[directoryName] != null)
|| (this._delegate.directoryExists != undefined
|| (delegate
&& this._delegate.directoryExists != undefined
&& this._delegate.directoryExists(directoryName));
}

Expand Down
20 changes: 10 additions & 10 deletions packages/@ngtools/webpack/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import * as SourceMap from 'source-map';

const {__NGTOOLS_PRIVATE_API_2, VERSION} = require('@angular/compiler-cli');
const ContextElementDependency = require('webpack/lib/dependencies/ContextElementDependency');
const NodeWatchFileSystem = require('webpack/lib/node/NodeWatchFileSystem');

import {WebpackResourceLoader} from './resource_loader';
import {WebpackCompilerHost} from './compiler_host';
import {resolveEntryModuleFromMain} from './entry_resolver';
import {Tapable} from './webpack';
import {PathsPlugin} from './paths-plugin';
import {findLazyRoutes, LazyRouteMap} from './lazy_routes';
import {VirtualFileSystemDecorator} from './virtual_file_system_decorator';


/**
Expand Down Expand Up @@ -308,16 +310,18 @@ export class AotPlugin implements Tapable {
apply(compiler: any) {
this._compiler = compiler;

// Decorate inputFileSystem to serve contents of CompilerHost.
// Use decorated inputFileSystem in watchFileSystem.
compiler.plugin('environment', () => {
compiler.inputFileSystem = new VirtualFileSystemDecorator(
compiler.inputFileSystem, this._compilerHost);
compiler.watchFileSystem = new NodeWatchFileSystem(compiler.inputFileSystem);
});

compiler.plugin('invalid', () => {
// Turn this off as soon as a file becomes invalid and we're about to start a rebuild.
this._firstRun = false;
this._diagnoseFiles = {};

if (compiler.watchFileSystem.watcher) {
compiler.watchFileSystem.watcher.once('aggregated', (changes: string[]) => {
changes.forEach((fileName: string) => this._compilerHost.invalidate(fileName));
});
}
});

// Add lazy modules to the context module for @angular/core/src/linker
Expand Down Expand Up @@ -543,10 +547,6 @@ export class AotPlugin implements Tapable {
}
}
})
.then(() => {
// Populate the file system cache with the virtual module.
this._compilerHost.populateWebpackResolver(this._compiler.resolvers.normal);
})
.then(() => {
// We need to run the `listLazyRoutes` the first time because it also navigates libraries
// and other things that we might miss using the findLazyRoutesInAst.
Expand Down
107 changes: 107 additions & 0 deletions packages/@ngtools/webpack/src/virtual_file_system_decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { Stats } from 'fs';
import { InputFileSystem, Callback } from './webpack';
import { WebpackCompilerHost } from './compiler_host';


export class VirtualFileSystemDecorator implements InputFileSystem {
constructor(
private _inputFileSystem: InputFileSystem,
private _webpackCompilerHost: WebpackCompilerHost
) { }

private _readFileSync(path: string): string | null {
if (this._webpackCompilerHost.fileExists(path, false)) {
return this._webpackCompilerHost.readFile(path);
}

return null;
}

private _statSync(path: string): Stats | null {
if (this._webpackCompilerHost.fileExists(path, false)
|| this._webpackCompilerHost.directoryExists(path, false)) {
return this._webpackCompilerHost.stat(path);
}

return null;
}

private _readDirSync(path: string): string[] | null {
if (this._webpackCompilerHost.directoryExists(path, false)) {
const dirs = this._webpackCompilerHost.getDirectories(path);
const files = this._webpackCompilerHost.getFiles(path);
return files.concat(dirs);
}

return null;
}

stat(path: string, callback: Callback<any>): void {
const result = this._statSync(path);
if (result) {
callback(null, result);
} else {
this._inputFileSystem.stat(path, callback);
}
}

readdir(path: string, callback: Callback<any>): void {
const result = this._readDirSync(path);
if (result) {
callback(null, result);
} else {
this._inputFileSystem.readdir(path, callback);
}
}

readFile(path: string, callback: Callback<any>): void {
const result = this._readFileSync(path);
if (result) {
callback(null, result);
} else {
this._inputFileSystem.readFile(path, callback);
}
}

readJson(path: string, callback: Callback<any>): void {
this._inputFileSystem.readJson(path, callback);
}

readlink(path: string, callback: Callback<any>): void {
this._inputFileSystem.readlink(path, callback);
}

statSync(path: string): Stats {
const result = this._statSync(path);
return result || this._inputFileSystem.statSync(path);
}

readdirSync(path: string): string[] {
const result = this._readDirSync(path);
return result || this._inputFileSystem.readdirSync(path);
}

readFileSync(path: string): string {
const result = this._readFileSync(path);
return result || this._inputFileSystem.readFileSync(path);
}

readJsonSync(path: string): string {
return this._inputFileSystem.readJsonSync(path);
}

readlinkSync(path: string): string {
return this._inputFileSystem.readlinkSync(path);
}

purge(changes?: string[] | string): void {
if (typeof changes === 'string') {
this._webpackCompilerHost.invalidate(changes);
} else if (Array.isArray(changes)) {
changes.forEach((fileName: string) => this._webpackCompilerHost.invalidate(fileName));
}
if (this._inputFileSystem.purge) {
this._inputFileSystem.purge(changes);
}
}
}
15 changes: 15 additions & 0 deletions packages/@ngtools/webpack/src/webpack.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Stats } from 'fs';

// Declarations for (some) Webpack types. Only what's needed.

export interface Request {
Expand Down Expand Up @@ -60,3 +62,16 @@ export interface LoaderContext {
readonly query: any;
}

export interface InputFileSystem {
stat(path: string, callback: Callback<any>): void;
readdir(path: string, callback: Callback<any>): void;
readFile(path: string, callback: Callback<any>): void;
readJson(path: string, callback: Callback<any>): void;
readlink(path: string, callback: Callback<any>): void;
statSync(path: string): Stats;
readdirSync(path: string): string[];
readFileSync(path: string): string;
readJsonSync(path: string): string;
readlinkSync(path: string): string;
purge(changes?: string[] | string): void;
}

0 comments on commit b0d1bff

Please sign in to comment.