Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(@ngtools/webpack): decorate file system #7471

Merged
merged 1 commit into from
Aug 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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;
}