Skip to content

Commit 28e8f06

Browse files
committed
refactor(@ngtools/webpack): cleanup webpack types and use tapPromise
1 parent 4487e9d commit 28e8f06

File tree

3 files changed

+125
-152
lines changed

3 files changed

+125
-152
lines changed

packages/ngtools/webpack/src/angular_compiler_plugin.ts

+103-91
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,12 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
// TODO: fix webpack typings.
9-
// tslint:disable-next-line:no-global-tslint-disable
10-
// tslint:disable:no-any
118
import { dirname, normalize, resolve, virtualFs } from '@angular-devkit/core';
129
import { ChildProcess, ForkOptions, fork } from 'child_process';
1310
import * as fs from 'fs';
1411
import * as path from 'path';
1512
import * as ts from 'typescript';
13+
import { Compiler, compilation } from 'webpack';
1614
import { time, timeEnd } from './benchmark';
1715
import { WebpackCompilerHost, workaroundResolve } from './compiler_host';
1816
import { resolveEntryModuleFromMain } from './entry_resolver';
@@ -53,6 +51,12 @@ import {
5351
VirtualFileSystemDecorator,
5452
VirtualWatchFileSystemDecorator,
5553
} from './virtual_file_system_decorator';
54+
import {
55+
Callback,
56+
InputFileSystem,
57+
NodeWatchFileSystemInterface,
58+
NormalModuleFactoryRequest,
59+
} from './webpack';
5660

5761
const treeKill = require('tree-kill');
5862

@@ -598,18 +602,28 @@ export class AngularCompilerPlugin {
598602
}
599603

600604
// Registration hook for webpack plugin.
601-
// tslint:disable-next-line:no-any
602-
apply(compiler: any) {
605+
apply(compiler: Compiler) {
603606
// Decorate inputFileSystem to serve contents of CompilerHost.
604607
// Use decorated inputFileSystem in watchFileSystem.
605608
compiler.hooks.environment.tap('angular-compiler', () => {
606-
compiler.inputFileSystem = new VirtualFileSystemDecorator(
607-
compiler.inputFileSystem, this._compilerHost);
608-
compiler.watchFileSystem = new VirtualWatchFileSystemDecorator(compiler.inputFileSystem);
609+
// The webpack types currently do not include these
610+
const compilerWithFileSystems = compiler as Compiler & {
611+
inputFileSystem: InputFileSystem,
612+
watchFileSystem: NodeWatchFileSystemInterface,
613+
};
614+
615+
const inputDecorator = new VirtualFileSystemDecorator(
616+
compilerWithFileSystems.inputFileSystem,
617+
this._compilerHost,
618+
);
619+
compilerWithFileSystems.inputFileSystem = inputDecorator;
620+
compilerWithFileSystems.watchFileSystem = new VirtualWatchFileSystemDecorator(
621+
inputDecorator,
622+
);
609623
});
610624

611625
// Add lazy modules to the context module for @angular/core
612-
compiler.hooks.contextModuleFactory.tap('angular-compiler', (cmf: any) => {
626+
compiler.hooks.contextModuleFactory.tap('angular-compiler', cmf => {
613627
const angularCorePackagePath = require.resolve('@angular/core/package.json');
614628

615629
// APFv6 does not have single FESM anymore. Instead of verifying if we're pointing to
@@ -621,141 +635,139 @@ export class AngularCompilerPlugin {
621635
// We resolve any symbolic links in order to get the real path that would be used in webpack.
622636
const angularCoreDirname = fs.realpathSync(path.dirname(angularCorePackagePath));
623637

624-
cmf.hooks.afterResolve.tapAsync('angular-compiler',
625-
// tslint:disable-next-line:no-any
626-
(result: any, callback: (err?: Error, request?: any) => void) => {
627-
if (!result) {
628-
return callback();
629-
}
630-
638+
cmf.hooks.afterResolve.tapPromise('angular-compiler', async result => {
631639
// Alter only request from Angular.
632-
if (!result.resource.startsWith(angularCoreDirname)) {
633-
return callback(undefined, result);
634-
}
635-
if (!this.done) {
636-
return callback(undefined, result);
640+
if (!result || !this.done || !result.resource.startsWith(angularCoreDirname)) {
641+
return result;
637642
}
638643

639-
this.done.then(() => {
640-
// This folder does not exist, but we need to give webpack a resource.
641-
// TODO: check if we can't just leave it as is (angularCoreModuleDir).
642-
result.resource = path.join(this._basePath, '$$_lazy_route_resource');
643-
result.dependencies.forEach((d: any) => d.critical = false);
644-
result.resolveDependencies = (_fs: any, resourceOrOptions: any, recursiveOrCallback: any,
645-
_regExp: RegExp, cb: any) => {
646-
const dependencies = Object.keys(this._lazyRoutes)
647-
.map((key) => {
648-
const modulePath = this._lazyRoutes[key];
649-
const importPath = key.split('#')[0];
650-
if (modulePath !== null) {
651-
const name = importPath.replace(/(\.ngfactory)?\.(js|ts)$/, '');
652-
653-
return new this._contextElementDependencyConstructor(modulePath, name);
654-
} else {
655-
return null;
656-
}
657-
})
658-
.filter(x => !!x);
659-
if (typeof cb !== 'function' && typeof recursiveOrCallback === 'function') {
660-
// Webpack 4 only has 3 parameters
661-
cb = recursiveOrCallback;
644+
return this.done.then(
645+
() => {
646+
// This folder does not exist, but we need to give webpack a resource.
647+
// TODO: check if we can't just leave it as is (angularCoreModuleDir).
648+
result.resource = path.join(this._basePath, '$$_lazy_route_resource');
649+
// tslint:disable-next-line:no-any
650+
result.dependencies.forEach((d: any) => d.critical = false);
651+
// tslint:disable-next-line:no-any
652+
result.resolveDependencies = (_fs: any, options: any, callback: Callback) => {
653+
const dependencies = Object.keys(this._lazyRoutes)
654+
.map((key) => {
655+
const modulePath = this._lazyRoutes[key];
656+
const importPath = key.split('#')[0];
657+
if (modulePath !== null) {
658+
const name = importPath.replace(/(\.ngfactory)?\.(js|ts)$/, '');
659+
660+
return new this._contextElementDependencyConstructor(modulePath, name);
661+
} else {
662+
return null;
663+
}
664+
})
665+
.filter(x => !!x);
666+
662667
if (this._options.nameLazyFiles) {
663-
resourceOrOptions.chunkName = '[request]';
668+
options.chunkName = '[request]';
664669
}
665-
}
666-
cb(null, dependencies);
667-
};
668670

669-
return callback(undefined, result);
670-
}, () => callback())
671-
.catch(err => callback(err));
671+
callback(null, dependencies);
672+
};
673+
674+
return result;
675+
},
676+
() => undefined,
677+
);
672678
});
673679
});
674680

675681
// Create and destroy forked type checker on watch mode.
676-
compiler.hooks.watchRun.tapAsync('angular-compiler', (_compiler: any, callback: any) => {
682+
compiler.hooks.watchRun.tap('angular-compiler', () => {
677683
if (this._forkTypeChecker && !this._typeCheckerProcess) {
678684
this._createForkedTypeChecker();
679685
}
680-
callback();
681686
});
682687
compiler.hooks.watchClose.tap('angular-compiler', () => this._killForkedTypeChecker());
683688

684689
// Remake the plugin on each compilation.
685-
compiler.hooks.make.tapAsync(
686-
'angular-compiler',
687-
(compilation: any, cb: any) => this._make(compilation, cb),
688-
);
690+
compiler.hooks.make.tapPromise('angular-compiler', compilation => this._make(compilation));
689691
compiler.hooks.invalid.tap('angular-compiler', () => this._firstRun = false);
690-
compiler.hooks.afterEmit.tapAsync('angular-compiler', (compilation: any, cb: any) => {
691-
compilation._ngToolsWebpackPluginInstance = null;
692-
cb();
692+
compiler.hooks.afterEmit.tap('angular-compiler', compilation => {
693+
// tslint:disable-next-line:no-any
694+
(compilation as any)._ngToolsWebpackPluginInstance = null;
693695
});
694696
compiler.hooks.done.tap('angular-compiler', () => {
695697
this._donePromise = null;
696698
});
697699

698-
compiler.hooks.afterResolvers.tap('angular-compiler', (compiler: any) => {
699-
compiler.hooks.normalModuleFactory.tap('angular-compiler', (nmf: any) => {
700+
compiler.hooks.afterResolvers.tap('angular-compiler', compiler => {
701+
compiler.hooks.normalModuleFactory.tap('angular-compiler', nmf => {
700702
// Virtual file system.
701703
// TODO: consider if it's better to remove this plugin and instead make it wait on the
702704
// VirtualFileSystemDecorator.
703705
// Wait for the plugin to be done when requesting `.ts` files directly (entry points), or
704706
// when the issuer is a `.ts` or `.ngfactory.js` file.
705-
nmf.hooks.beforeResolve.tapAsync('angular-compiler', (request: any, callback: any) => {
706-
if (this.done
707-
&& (request && (request.request.endsWith('.ts') || request.request.endsWith('.tsx'))
708-
|| (request && request.context.issuer
709-
&& /\.ts|ngfactory\.js$/.test(request.context.issuer)))) {
710-
this.done.then(() => callback(null, request), () => callback(null, request));
711-
} else {
712-
callback(null, request);
713-
}
714-
});
715-
});
716-
});
707+
nmf.hooks.beforeResolve.tapPromise(
708+
'angular-compiler',
709+
async (request?: NormalModuleFactoryRequest) => {
710+
if (this.done && request) {
711+
const name = request.request;
712+
const issuer = request.contextInfo.issuer;
713+
if (name.endsWith('.ts') || name.endsWith('.tsx')
714+
|| (issuer && /\.ts|ngfactory\.js$/.test(issuer))) {
715+
try {
716+
await this.done;
717+
} catch {}
718+
}
719+
}
717720

718-
compiler.hooks.normalModuleFactory.tap('angular-compiler', (nmf: any) => {
719-
nmf.hooks.beforeResolve.tapAsync('angular-compiler', (request: any, callback: any) => {
720-
resolveWithPaths(
721-
request,
722-
callback,
723-
this._compilerOptions,
724-
this._compilerHost,
725-
this._moduleResolutionCache,
721+
return request;
722+
},
726723
);
727724
});
728725
});
726+
727+
compiler.hooks.normalModuleFactory.tap('angular-compiler', nmf => {
728+
nmf.hooks.beforeResolve.tapAsync(
729+
'angular-compiler',
730+
(request: NormalModuleFactoryRequest, callback: Callback<NormalModuleFactoryRequest>) => {
731+
resolveWithPaths(
732+
request,
733+
callback,
734+
this._compilerOptions,
735+
this._compilerHost,
736+
this._moduleResolutionCache,
737+
);
738+
},
739+
);
740+
});
729741
}
730742

731-
private _make(compilation: any, cb: (err?: any, request?: any) => void) {
743+
private async _make(compilation: compilation.Compilation) {
732744
time('AngularCompilerPlugin._make');
733745
this._emitSkipped = true;
734-
if (compilation._ngToolsWebpackPluginInstance) {
735-
return cb(new Error('An @ngtools/webpack plugin already exist for this compilation.'));
746+
// tslint:disable-next-line:no-any
747+
if ((compilation as any)._ngToolsWebpackPluginInstance) {
748+
throw new Error('An @ngtools/webpack plugin already exist for this compilation.');
736749
}
737750

738751
// Set a private variable for this plugin instance.
739-
compilation._ngToolsWebpackPluginInstance = this;
752+
// tslint:disable-next-line:no-any
753+
(compilation as any)._ngToolsWebpackPluginInstance = this;
740754

741755
// Update the resource loader with the new webpack compilation.
742756
this._resourceLoader.update(compilation);
743757

744-
this._donePromise = Promise.resolve()
758+
return this._donePromise = Promise.resolve()
745759
.then(() => this._update())
746760
.then(() => {
747761
this.pushCompilationErrors(compilation);
748762
timeEnd('AngularCompilerPlugin._make');
749-
cb();
750-
}, (err: any) => {
763+
}, err => {
751764
compilation.errors.push(err);
752765
this.pushCompilationErrors(compilation);
753766
timeEnd('AngularCompilerPlugin._make');
754-
cb();
755767
});
756768
}
757769

758-
private pushCompilationErrors(compilation: any) {
770+
private pushCompilationErrors(compilation: compilation.Compilation) {
759771
compilation.errors.push(...this._errors);
760772
compilation.warnings.push(...this._warnings);
761773
this._errors = [];

packages/ngtools/webpack/src/ngtools_api.ts

+19-28
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,6 @@
99
// equivalent.
1010
// tslint:disable-next-line:no-global-tslint-disable
1111
// tslint:disable:no-implicit-dependencies
12-
/**
13-
* This is a copy of types in @compiler-cli/src/ngtools_api.d.ts file,
14-
* together with safe imports for private apis for cases where @angular/compiler-cli isn't
15-
* available or is below version 5.
16-
*/
1712
import * as ngc from '@angular/compiler-cli';
1813
import * as ngtools from '@angular/compiler-cli/ngtools2';
1914
import * as path from 'path';
@@ -33,6 +28,14 @@ function _error(api: string, fn: string): never {
3328
throw new Error('Could not find API ' + api + ', function ' + fn);
3429
}
3530

31+
function getApiMember<T, K extends keyof T>(
32+
api: T | null,
33+
func: K,
34+
apiName: string,
35+
): T[K] {
36+
return api && api[func] || _error(apiName, func.toString());
37+
}
38+
3639
// Manually check for Compiler CLI availability and supported version.
3740
// This is needed because @ngtools/webpack does not depend directly on @angular/compiler-cli, since
3841
// it is installed as part of global Angular CLI installs and compiler-cli is not of its
@@ -72,19 +75,13 @@ try {
7275
// plugin cannot be used.
7376
}
7477

75-
export const VERSION: typeof ngc.VERSION =
76-
compilerCli
77-
&& compilerCli.VERSION
78-
|| _error('compiler-cli', 'VERSION');
79-
export const __NGTOOLS_PRIVATE_API_2: typeof ngc.__NGTOOLS_PRIVATE_API_2 =
80-
compilerCli
81-
&& compilerCli.__NGTOOLS_PRIVATE_API_2
82-
|| _error('compiler-cli', '__NGTOOLS_PRIVATE_API_2');
83-
export const readConfiguration: typeof ngc.readConfiguration =
84-
compilerCli
85-
&& compilerCli.readConfiguration
86-
|| _error('compiler-cli', 'readConfiguration');
87-
78+
export const VERSION: typeof ngc.VERSION = getApiMember(compilerCli, 'VERSION', 'compiler-cli');
79+
export const __NGTOOLS_PRIVATE_API_2 = getApiMember(
80+
compilerCli,
81+
'__NGTOOLS_PRIVATE_API_2',
82+
'compiler-cli',
83+
);
84+
export const readConfiguration = getApiMember(compilerCli, 'readConfiguration', 'compiler-cli');
8885

8986
// These imports do not exist on Angular versions lower than 5, so we cannot use a static ES6
9087
// import.
@@ -93,15 +90,9 @@ try {
9390
ngtools2 = require('@angular/compiler-cli/ngtools2');
9491
} catch {
9592
// Don't throw an error if the private API does not exist.
96-
// Instead, the `AngularCompilerPlugin.isSupported` method should return false and indicate the
97-
// plugin cannot be used.
9893
}
9994

100-
export const createProgram: typeof ngtools.createProgram =
101-
ngtools2 && ngtools2.createProgram || _error('ngtools2', 'createProgram');
102-
export const createCompilerHost: typeof ngtools.createCompilerHost =
103-
ngtools2 && ngtools2.createCompilerHost || _error('ngtools2', 'createCompilerHost');
104-
export const formatDiagnostics: typeof ngtools.formatDiagnostics =
105-
ngtools2 && ngtools2.formatDiagnostics || _error('ngtools2', 'formatDiagnostics');
106-
export const EmitFlags: typeof ngtools.EmitFlags =
107-
ngtools2 && ngtools2.EmitFlags || _error('ngtools', 'EmitFlags');
95+
export const createProgram = getApiMember(ngtools2, 'createProgram', 'ngtools2');
96+
export const createCompilerHost = getApiMember(ngtools2, 'createCompilerHost', 'ngtools2');
97+
export const formatDiagnostics = getApiMember(ngtools2, 'formatDiagnostics', 'ngtools2');
98+
export const EmitFlags = getApiMember(ngtools2, 'EmitFlags', 'ngtools2');

0 commit comments

Comments
 (0)