Skip to content

Commit b0d1bff

Browse files
filipesilvahansl
authored andcommitted
fix(@ngtools/webpack): decorate file system (#7471)
Followup to #7169
1 parent d6c07c7 commit b0d1bff

File tree

4 files changed

+143
-78
lines changed

4 files changed

+143
-78
lines changed

packages/@ngtools/webpack/src/compiler_host.ts

+11-68
Original file line numberDiff line numberDiff line change
@@ -146,70 +146,6 @@ export class WebpackCompilerHost implements ts.CompilerHost {
146146
this._cache = true;
147147
}
148148

149-
populateWebpackResolver(resolver: any) {
150-
const fs = resolver.fileSystem;
151-
if (!this.dirty) {
152-
return;
153-
}
154-
155-
/**
156-
* storageDataSetter is a temporary hack to address these two issues:
157-
* - https://github.com/angular/angular-cli/issues/7113
158-
* - https://github.com/angular/angular-cli/issues/7136
159-
*
160-
* This way we set values correctly in both a Map (enhanced-resove>=3.4.0) and
161-
* object (enhanced-resolve >= 3.1.0 <3.4.0).
162-
*
163-
* The right solution is to create a virtual filesystem by decorating the filesystem,
164-
* instead of injecting data into the private cache of the filesystem.
165-
*
166-
* Doing it the right way should fix other related bugs, but meanwhile we hack it since:
167-
* - it's affecting a lot of users.
168-
* - the real solution is non-trivial.
169-
*/
170-
function storageDataSetter(data: Map<string, any> | {[k: string]: any}, k: string, v: any) {
171-
172-
if (data instanceof Map) {
173-
data.set(k, v);
174-
} else {
175-
data[k] = v;
176-
}
177-
}
178-
179-
180-
181-
const isWindows = process.platform.startsWith('win');
182-
for (const fileName of this.getChangedFilePaths()) {
183-
const stats = this._files[fileName];
184-
if (stats) {
185-
// If we're on windows, we need to populate with the proper path separator.
186-
const path = isWindows ? fileName.replace(/\//g, '\\') : fileName;
187-
// fs._statStorage.data[path] = [null, stats];
188-
// fs._readFileStorage.data[path] = [null, stats.content];
189-
storageDataSetter(fs._statStorage.data, path, [null, stats]);
190-
storageDataSetter(fs._readFileStorage.data, path, [null, stats.content]);
191-
} else {
192-
// Support removing files as well.
193-
const path = isWindows ? fileName.replace(/\//g, '\\') : fileName;
194-
// fs._statStorage.data[path] = [new Error(), null];
195-
// fs._readFileStorage.data[path] = [new Error(), null];
196-
storageDataSetter(fs._statStorage.data, path, [new Error(), null]);
197-
storageDataSetter(fs._readFileStorage.data, path, [new Error(), null]);
198-
}
199-
}
200-
for (const dirName of Object.keys(this._changedDirs)) {
201-
const stats = this._directories[dirName];
202-
const dirs = this.getDirectories(dirName);
203-
const files = this.getFiles(dirName);
204-
// If we're on windows, we need to populate with the proper path separator.
205-
const path = isWindows ? dirName.replace(/\//g, '\\') : dirName;
206-
// fs._statStorage.data[path] = [null, stats];
207-
// fs._readdirStorage.data[path] = [null, files.concat(dirs)];
208-
storageDataSetter(fs._statStorage.data, path, [null, stats]);
209-
storageDataSetter(fs._readFileStorage.data, path, [null, files.concat(dirs)]);
210-
}
211-
}
212-
213149
resetChangedFileTracker() {
214150
this._changedFiles = Object.create(null);
215151
this._changedDirs = Object.create(null);
@@ -226,9 +162,9 @@ export class WebpackCompilerHost implements ts.CompilerHost {
226162
}
227163
}
228164

229-
fileExists(fileName: string): boolean {
165+
fileExists(fileName: string, delegate = true): boolean {
230166
fileName = this._resolve(fileName);
231-
return this._files[fileName] != null || this._delegate.fileExists(fileName);
167+
return this._files[fileName] != null || (delegate && this._delegate.fileExists(fileName));
232168
}
233169

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

250-
directoryExists(directoryName: string): boolean {
186+
// Does not delegate, use with `fileExists/directoryExists()`.
187+
stat(path: string): VirtualStats {
188+
path = this._resolve(path);
189+
return this._files[path] || this._directories[path];
190+
}
191+
192+
directoryExists(directoryName: string, delegate = true): boolean {
251193
directoryName = this._resolve(directoryName);
252194
return (this._directories[directoryName] != null)
253-
|| (this._delegate.directoryExists != undefined
195+
|| (delegate
196+
&& this._delegate.directoryExists != undefined
254197
&& this._delegate.directoryExists(directoryName));
255198
}
256199

packages/@ngtools/webpack/src/plugin.ts

+10-10
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ import * as SourceMap from 'source-map';
66

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

1011
import {WebpackResourceLoader} from './resource_loader';
1112
import {WebpackCompilerHost} from './compiler_host';
1213
import {resolveEntryModuleFromMain} from './entry_resolver';
1314
import {Tapable} from './webpack';
1415
import {PathsPlugin} from './paths-plugin';
1516
import {findLazyRoutes, LazyRouteMap} from './lazy_routes';
17+
import {VirtualFileSystemDecorator} from './virtual_file_system_decorator';
1618

1719

1820
/**
@@ -308,16 +310,18 @@ export class AotPlugin implements Tapable {
308310
apply(compiler: any) {
309311
this._compiler = compiler;
310312

313+
// Decorate inputFileSystem to serve contents of CompilerHost.
314+
// Use decorated inputFileSystem in watchFileSystem.
315+
compiler.plugin('environment', () => {
316+
compiler.inputFileSystem = new VirtualFileSystemDecorator(
317+
compiler.inputFileSystem, this._compilerHost);
318+
compiler.watchFileSystem = new NodeWatchFileSystem(compiler.inputFileSystem);
319+
});
320+
311321
compiler.plugin('invalid', () => {
312322
// Turn this off as soon as a file becomes invalid and we're about to start a rebuild.
313323
this._firstRun = false;
314324
this._diagnoseFiles = {};
315-
316-
if (compiler.watchFileSystem.watcher) {
317-
compiler.watchFileSystem.watcher.once('aggregated', (changes: string[]) => {
318-
changes.forEach((fileName: string) => this._compilerHost.invalidate(fileName));
319-
});
320-
}
321325
});
322326

323327
// Add lazy modules to the context module for @angular/core/src/linker
@@ -543,10 +547,6 @@ export class AotPlugin implements Tapable {
543547
}
544548
}
545549
})
546-
.then(() => {
547-
// Populate the file system cache with the virtual module.
548-
this._compilerHost.populateWebpackResolver(this._compiler.resolvers.normal);
549-
})
550550
.then(() => {
551551
// We need to run the `listLazyRoutes` the first time because it also navigates libraries
552552
// and other things that we might miss using the findLazyRoutesInAst.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { Stats } from 'fs';
2+
import { InputFileSystem, Callback } from './webpack';
3+
import { WebpackCompilerHost } from './compiler_host';
4+
5+
6+
export class VirtualFileSystemDecorator implements InputFileSystem {
7+
constructor(
8+
private _inputFileSystem: InputFileSystem,
9+
private _webpackCompilerHost: WebpackCompilerHost
10+
) { }
11+
12+
private _readFileSync(path: string): string | null {
13+
if (this._webpackCompilerHost.fileExists(path, false)) {
14+
return this._webpackCompilerHost.readFile(path);
15+
}
16+
17+
return null;
18+
}
19+
20+
private _statSync(path: string): Stats | null {
21+
if (this._webpackCompilerHost.fileExists(path, false)
22+
|| this._webpackCompilerHost.directoryExists(path, false)) {
23+
return this._webpackCompilerHost.stat(path);
24+
}
25+
26+
return null;
27+
}
28+
29+
private _readDirSync(path: string): string[] | null {
30+
if (this._webpackCompilerHost.directoryExists(path, false)) {
31+
const dirs = this._webpackCompilerHost.getDirectories(path);
32+
const files = this._webpackCompilerHost.getFiles(path);
33+
return files.concat(dirs);
34+
}
35+
36+
return null;
37+
}
38+
39+
stat(path: string, callback: Callback<any>): void {
40+
const result = this._statSync(path);
41+
if (result) {
42+
callback(null, result);
43+
} else {
44+
this._inputFileSystem.stat(path, callback);
45+
}
46+
}
47+
48+
readdir(path: string, callback: Callback<any>): void {
49+
const result = this._readDirSync(path);
50+
if (result) {
51+
callback(null, result);
52+
} else {
53+
this._inputFileSystem.readdir(path, callback);
54+
}
55+
}
56+
57+
readFile(path: string, callback: Callback<any>): void {
58+
const result = this._readFileSync(path);
59+
if (result) {
60+
callback(null, result);
61+
} else {
62+
this._inputFileSystem.readFile(path, callback);
63+
}
64+
}
65+
66+
readJson(path: string, callback: Callback<any>): void {
67+
this._inputFileSystem.readJson(path, callback);
68+
}
69+
70+
readlink(path: string, callback: Callback<any>): void {
71+
this._inputFileSystem.readlink(path, callback);
72+
}
73+
74+
statSync(path: string): Stats {
75+
const result = this._statSync(path);
76+
return result || this._inputFileSystem.statSync(path);
77+
}
78+
79+
readdirSync(path: string): string[] {
80+
const result = this._readDirSync(path);
81+
return result || this._inputFileSystem.readdirSync(path);
82+
}
83+
84+
readFileSync(path: string): string {
85+
const result = this._readFileSync(path);
86+
return result || this._inputFileSystem.readFileSync(path);
87+
}
88+
89+
readJsonSync(path: string): string {
90+
return this._inputFileSystem.readJsonSync(path);
91+
}
92+
93+
readlinkSync(path: string): string {
94+
return this._inputFileSystem.readlinkSync(path);
95+
}
96+
97+
purge(changes?: string[] | string): void {
98+
if (typeof changes === 'string') {
99+
this._webpackCompilerHost.invalidate(changes);
100+
} else if (Array.isArray(changes)) {
101+
changes.forEach((fileName: string) => this._webpackCompilerHost.invalidate(fileName));
102+
}
103+
if (this._inputFileSystem.purge) {
104+
this._inputFileSystem.purge(changes);
105+
}
106+
}
107+
}

packages/@ngtools/webpack/src/webpack.ts

+15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Stats } from 'fs';
2+
13
// Declarations for (some) Webpack types. Only what's needed.
24

35
export interface Request {
@@ -60,3 +62,16 @@ export interface LoaderContext {
6062
readonly query: any;
6163
}
6264

65+
export interface InputFileSystem {
66+
stat(path: string, callback: Callback<any>): void;
67+
readdir(path: string, callback: Callback<any>): void;
68+
readFile(path: string, callback: Callback<any>): void;
69+
readJson(path: string, callback: Callback<any>): void;
70+
readlink(path: string, callback: Callback<any>): void;
71+
statSync(path: string): Stats;
72+
readdirSync(path: string): string[];
73+
readFileSync(path: string): string;
74+
readJsonSync(path: string): string;
75+
readlinkSync(path: string): string;
76+
purge(changes?: string[] | string): void;
77+
}

0 commit comments

Comments
 (0)