Skip to content

Commit 789e05d

Browse files
clydinfilipesilva
authored andcommitted
feat(@ngtools/webpack): support Webpack 5
The `@ngtools/webpack` package now officially supports Webpack 5. It is also now built against Webpack 5 types. Webpack 4 support is temporarily maintained while the remainder of the tooling is transitioned.
1 parent 73a5610 commit 789e05d

11 files changed

+334
-73
lines changed

packages/ngtools/webpack/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@
2828
"peerDependencies": {
2929
"@angular/compiler-cli": "^12.0.0-next",
3030
"typescript": "~4.0.0 || ~4.1.0",
31-
"webpack": "^4.0.0"
31+
"webpack": "^4.0.0 || ^5.20.0"
3232
},
3333
"devDependencies": {
3434
"@angular/compiler": "12.0.0-next.0",
3535
"@angular/compiler-cli": "12.0.0-next.0",
3636
"typescript": "4.1.5",
37-
"webpack": "4.44.2"
37+
"webpack": "5.21.2"
3838
}
3939
}

packages/ngtools/webpack/src/angular_compiler_plugin.ts

+17-18
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import { ChildProcess, ForkOptions, fork } from 'child_process';
3434
import * as fs from 'fs';
3535
import * as path from 'path';
3636
import * as ts from 'typescript';
37-
import { Compiler, compilation } from 'webpack';
37+
import { Compiler, WebpackFourCompiler, compilation } from 'webpack';
3838
import { time, timeEnd } from './benchmark';
3939
import { WebpackCompilerHost } from './compiler_host';
4040
import { DiagnosticMode, gatherDiagnostics, hasErrors, reportDiagnostics } from './diagnostics';
@@ -75,10 +75,6 @@ import {
7575
VirtualFileSystemDecorator,
7676
VirtualWatchFileSystemDecorator,
7777
} from './virtual_file_system_decorator';
78-
import {
79-
NodeWatchFileSystemInterface,
80-
NormalModuleFactoryRequest,
81-
} from './webpack';
8278
import { addError, addWarning } from './webpack-diagnostics';
8379
import { createWebpackInputHost } from './webpack-input-host';
8480
import { isWebpackFiveOrHigher, mergeResolverMainFields } from './webpack-version';
@@ -686,7 +682,10 @@ export class AngularCompilerPlugin {
686682
};
687683

688684
// Go over all the modules in the webpack compilation and remove them from the sets.
689-
compilation.modules.forEach(m => m.resource ? removeSourceFile(m.resource, true) : null);
685+
// tslint:disable-next-line: no-any
686+
compilation.modules.forEach((m: compilation.Module & { resource?: string }) =>
687+
m.resource ? removeSourceFile(m.resource, true) : null,
688+
);
690689

691690
// Anything that remains is unused, because it wasn't referenced directly or transitively
692691
// on the files in the compilation.
@@ -712,7 +711,12 @@ export class AngularCompilerPlugin {
712711

713712
// Registration hook for webpack plugin.
714713
// tslint:disable-next-line:no-big-function
715-
apply(compiler: Compiler & { watchMode?: boolean, parentCompilation?: compilation.Compilation }) {
714+
apply(webpackCompiler: Compiler | WebpackFourCompiler) {
715+
const compiler = webpackCompiler as Compiler & {
716+
watchMode?: boolean;
717+
parentCompilation?: compilation.Compilation;
718+
watchFileSystem?: unknown;
719+
};
716720
// The below is require by NGCC processor
717721
// since we need to know which fields we need to process
718722
compiler.hooks.environment.tap('angular-compiler', () => {
@@ -748,13 +752,8 @@ export class AngularCompilerPlugin {
748752
// Decorate inputFileSystem to serve contents of CompilerHost.
749753
// Use decorated inputFileSystem in watchFileSystem.
750754
compiler.hooks.environment.tap('angular-compiler', () => {
751-
// The webpack types currently do not include these
752-
const compilerWithFileSystems = compiler as Compiler & {
753-
watchFileSystem: NodeWatchFileSystemInterface,
754-
};
755-
756755
let host: virtualFs.Host<fs.Stats> = this._options.host || createWebpackInputHost(
757-
compilerWithFileSystems.inputFileSystem,
756+
compiler.inputFileSystem,
758757
);
759758

760759
let replacements: Map<Path, Path> | ((path: Path) => Path) | undefined;
@@ -791,7 +790,7 @@ export class AngularCompilerPlugin {
791790
this._errors,
792791
this._basePath,
793792
this._tsConfigPath,
794-
compilerWithFileSystems.inputFileSystem,
793+
compiler.inputFileSystem,
795794
compiler.options.resolve?.symlinks,
796795
);
797796

@@ -830,11 +829,11 @@ export class AngularCompilerPlugin {
830829
}
831830

832831
const inputDecorator = new VirtualFileSystemDecorator(
833-
compilerWithFileSystems.inputFileSystem,
832+
compiler.inputFileSystem,
834833
this._compilerHost,
835834
);
836-
compilerWithFileSystems.inputFileSystem = inputDecorator;
837-
compilerWithFileSystems.watchFileSystem = new VirtualWatchFileSystemDecorator(
835+
compiler.inputFileSystem = inputDecorator;
836+
compiler.watchFileSystem = new VirtualWatchFileSystemDecorator(
838837
inputDecorator,
839838
replacements,
840839
);
@@ -956,7 +955,7 @@ export class AngularCompilerPlugin {
956955
// when the issuer is a `.ts` or `.ngfactory.js` file.
957956
nmf.hooks.beforeResolve.tapPromise(
958957
'angular-compiler',
959-
async (request?: NormalModuleFactoryRequest) => {
958+
async (request) => {
960959
if (this.done && request) {
961960
const name = request.request;
962961
const issuer = request.contextInfo.issuer;

packages/ngtools/webpack/src/ivy/cache.ts

+13-4
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,23 @@ import { normalizePath } from './paths';
1010

1111
export class SourceFileCache extends Map<string, ts.SourceFile> {
1212
invalidate(
13-
fileTimestamps: Map<string, number | { timestamp: number } | null>,
13+
fileTimestamps: Map<string, 'ignore' | number | { safeTime: number } | null>,
1414
buildTimestamp: number,
1515
): Set<string> {
1616
const changedFiles = new Set<string>();
1717
for (const [file, timeOrEntry] of fileTimestamps) {
18-
const time =
19-
timeOrEntry && (typeof timeOrEntry === 'number' ? timeOrEntry : timeOrEntry.timestamp);
20-
if (time === null || buildTimestamp < time) {
18+
if (timeOrEntry === 'ignore') {
19+
continue;
20+
}
21+
22+
let time;
23+
if (typeof timeOrEntry === 'number') {
24+
time = timeOrEntry;
25+
} else if (timeOrEntry) {
26+
time = timeOrEntry.safeTime;
27+
}
28+
29+
if (!time || time >= buildTimestamp) {
2130
// Cache stores paths using the POSIX directory separator
2231
const normalizedFile = normalizePath(file);
2332
this.delete(normalizedFile);

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

+13-6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
Compiler,
1515
ContextReplacementPlugin,
1616
NormalModuleReplacementPlugin,
17+
WebpackFourCompiler,
1718
compilation,
1819
} from 'webpack';
1920
import { NgccProcessor } from '../ngcc_processor';
@@ -33,7 +34,7 @@ import {
3334
} from './host';
3435
import { externalizePath, normalizePath } from './paths';
3536
import { AngularPluginSymbol, EmitFileResult, FileEmitter } from './symbol';
36-
import { createWebpackSystem } from './system';
37+
import { InputFileSystemSync, createWebpackSystem } from './system';
3738
import { createAotTransformers, createJitTransformers, mergeTransformers } from './transformation';
3839

3940
export interface AngularPluginOptions {
@@ -50,7 +51,8 @@ export interface AngularPluginOptions {
5051

5152
// Add support for missing properties in Webpack types as well as the loader's file emitter
5253
interface WebpackCompilation extends compilation.Compilation {
53-
compilationDependencies: Set<string>;
54+
// tslint:disable-next-line: no-any
55+
compilationDependencies: { add(item: string): any };
5456
rebuildModule(module: compilation.Module, callback: () => void): void;
5557
[AngularPluginSymbol]: FileEmitter;
5658
}
@@ -113,7 +115,8 @@ export class AngularWebpackPlugin {
113115
return this.pluginOptions;
114116
}
115117

116-
apply(compiler: Compiler & { watchMode?: boolean }): void {
118+
apply(webpackCompiler: Compiler | WebpackFourCompiler): void {
119+
const compiler = webpackCompiler as Compiler & { watchMode?: boolean };
117120
// Setup file replacements with webpack
118121
for (const [key, value] of Object.entries(this.pluginOptions.fileReplacements)) {
119122
new NormalModuleReplacementPlugin(
@@ -188,7 +191,11 @@ export class AngularWebpackPlugin {
188191
pathsPlugin.update(compilerOptions);
189192

190193
// Create a Webpack-based TypeScript compiler host
191-
const system = createWebpackSystem(compiler.inputFileSystem, normalizePath(compiler.context));
194+
const system = createWebpackSystem(
195+
// Webpack lacks an InputFileSytem type definition with sync functions
196+
compiler.inputFileSystem as InputFileSystemSync,
197+
normalizePath(compiler.context),
198+
);
192199
const host = ts.createIncrementalCompilerHost(compilerOptions, system);
193200

194201
// Setup source file caching and reuse cache from previous compilation if present
@@ -267,7 +274,7 @@ export class AngularWebpackPlugin {
267274
.filter((sourceFile) => !sourceFile.isDeclarationFile)
268275
.map((sourceFile) => sourceFile.fileName),
269276
);
270-
modules.forEach(({ resource }: compilation.Module & { resource?: string }) => {
277+
Array.from(modules).forEach(({ resource }: compilation.Module & { resource?: string }) => {
271278
const sourceFile = resource && builder.getSourceFile(resource);
272279
if (!sourceFile) {
273280
return;
@@ -303,7 +310,7 @@ export class AngularWebpackPlugin {
303310
}
304311

305312
const rebuild = (webpackModule: compilation.Module) =>
306-
new Promise<void>((resolve) => compilation.rebuildModule(webpackModule, resolve));
313+
new Promise<void>((resolve) => compilation.rebuildModule(webpackModule, () => resolve()));
307314

308315
const filesToRebuild = new Set<string>();
309316
for (const requiredFile of this.requiredFilesToEmit) {

packages/ngtools/webpack/src/ivy/system.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,19 @@ import * as ts from 'typescript';
99
import { InputFileSystem } from 'webpack';
1010
import { externalizePath } from './paths';
1111

12+
export interface InputFileSystemSync extends InputFileSystem {
13+
readFileSync(path: string): Buffer;
14+
statSync(path: string): { size: number; mtime: Date; isDirectory(): boolean; isFile(): boolean };
15+
}
16+
1217
function shouldNotWrite(): never {
1318
throw new Error('Webpack TypeScript System should not write.');
1419
}
1520

16-
export function createWebpackSystem(input: InputFileSystem, currentDirectory: string): ts.System {
21+
export function createWebpackSystem(
22+
input: InputFileSystemSync,
23+
currentDirectory: string,
24+
): ts.System {
1725
// Webpack's CachedInputFileSystem uses the default directory separator in the paths it uses
1826
// for keys to its cache. If the keys do not match then the file watcher will not purge outdated
1927
// files and cause stale data to be used in the next rebuild. TypeScript always uses a `/` (POSIX)

packages/ngtools/webpack/src/paths-plugin.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@
77
*/
88
import * as path from 'path';
99
import { CompilerOptions, MapLike } from 'typescript';
10-
import { NormalModuleFactoryRequest } from './webpack';
1110

1211
const getInnerRequest = require('enhanced-resolve/lib/getInnerRequest');
1312

13+
interface NormalModuleFactoryRequest {
14+
request: string;
15+
context: { issuer: string };
16+
contextInfo: { issuer: string };
17+
typescriptPathMapped?: boolean;
18+
}
19+
1420
export interface TypeScriptPathsPluginOptions extends Pick<CompilerOptions, 'paths' | 'baseUrl'> {
1521

1622
}

packages/ngtools/webpack/src/virtual_file_system_decorator.ts

+18-3
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,17 @@ import { FileDoesNotExistException, Path, getSystemPath, normalize } from '@angu
99
import { Stats } from 'fs';
1010
import { InputFileSystem } from 'webpack';
1111
import { WebpackCompilerHost } from './compiler_host';
12-
import { NodeWatchFileSystemInterface } from './webpack';
1312
import { isWebpackFiveOrHigher } from './webpack-version';
1413

14+
interface NodeWatchFileSystemInterface {
15+
inputFileSystem: InputFileSystem;
16+
new(inputFileSystem: InputFileSystem): NodeWatchFileSystemInterface;
17+
// tslint:disable-next-line:no-any
18+
watch(files: any, dirs: any, missing: any, startTime: any, options: any, callback: any,
19+
// tslint:disable-next-line:no-any
20+
callbackUndelayed: any): any;
21+
}
22+
1523
export const NodeWatchFileSystem: NodeWatchFileSystemInterface = require(
1624
'webpack/lib/node/NodeWatchFileSystem');
1725

@@ -61,7 +69,12 @@ export class VirtualFileSystemDecorator implements InputFileSystem {
6169
(this._inputFileSystem as any).readJson(path, callback);
6270
}
6371

64-
readlink(path: string, callback: (err: Error | null | undefined, linkString: string) => void): void {
72+
readlink(
73+
path: string,
74+
// Callback types differ between Webpack 4 and 5
75+
// tslint:disable-next-line: no-any
76+
callback: (err: any, linkString: any) => void,
77+
): void {
6578
this._inputFileSystem.readlink(path, callback);
6679
}
6780

@@ -89,7 +102,9 @@ export class VirtualFileSystemDecorator implements InputFileSystem {
89102
}
90103

91104
readlinkSync(path: string): string {
92-
return this._inputFileSystem.readlinkSync(path);
105+
// Synchronous functions are missing from the Webpack typings
106+
// tslint:disable-next-line: no-any
107+
return (this._inputFileSystem as any).readlinkSync(path);
93108
}
94109

95110
purge(changes?: string[] | string): void {

packages/ngtools/webpack/src/webpack-input-host.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ export function createWebpackInputHost(inputFileSystem: InputFileSystem) {
2525
},
2626

2727
read(path): virtualFs.FileBuffer {
28-
const data = inputFileSystem.readFileSync(getSystemPath(path));
28+
// Synchronous functions are missing from the Webpack typings
29+
// tslint:disable-next-line: no-any
30+
const data = (inputFileSystem as any).readFileSync(getSystemPath(path));
2931

3032
return new Uint8Array(data).buffer as ArrayBuffer;
3133
},
@@ -53,7 +55,9 @@ export function createWebpackInputHost(inputFileSystem: InputFileSystem) {
5355

5456
stat(path): Stats | null {
5557
try {
56-
return inputFileSystem.statSync(getSystemPath(path));
58+
// Synchronous functions are missing from the Webpack typings
59+
// tslint:disable-next-line: no-any
60+
return (inputFileSystem as any).statSync(getSystemPath(path));
5761
} catch (e) {
5862
if (e.code === 'ENOENT') {
5963
return null;
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import * as webpack from 'webpack';
9+
import { Compiler as webpack4Compiler, loader as webpack4Loader } from '@types/webpack';
10+
11+
// Webpack 5 transition support types
12+
declare module 'webpack' {
13+
export type WebpackFourCompiler = webpack4Compiler;
14+
15+
export type InputFileSystem = webpack.Compiler['inputFileSystem'];
16+
17+
export namespace compilation {
18+
export type Compilation = webpack.Compilation;
19+
export type Module = webpack.Module;
20+
}
21+
22+
export namespace loader {
23+
export type LoaderContext = webpack4Loader.LoaderContext;
24+
}
25+
}

packages/ngtools/webpack/src/webpack.ts

-26
This file was deleted.

0 commit comments

Comments
 (0)