Alan Agius 247b87d40a refactor(@angular-devkit/build-angular): move dev-server webpack config in a separate file
With this change we remove webpack dev-server logic to a seperate file. We also use the webpack-dev-server API to add live-reload and hmr entry-points and settings.
2020-10-16 21:08:18 +02:00

160 lines
6.0 KiB
TypeScript

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { isAbsolute } from 'path';
import { Compiler, compilation } from 'webpack';
import { addWarning } from '../../utils/webpack-diagnostics';
import { isWebpackFiveOrHigher } from '../../utils/webpack-version';
// Webpack doesn't export these so the deep imports can potentially break.
const CommonJsRequireDependency = require('webpack/lib/dependencies/CommonJsRequireDependency');
const AMDDefineDependency = require('webpack/lib/dependencies/AMDDefineDependency');
// The below is used to remain compatible with both Webpack 4 and 5
interface WebpackModule {
name?: string;
rawRequest?: string;
dependencies: WebpackModule[];
issuer: WebpackModule | null;
module: WebpackModule | null;
userRequest?: string;
request: string;
}
export interface CommonJsUsageWarnPluginOptions {
/** A list of CommonJS packages that are allowed to be used without a warning. */
allowedDependencies?: string[];
}
export class CommonJsUsageWarnPlugin {
private shownWarnings = new Set<string>();
// Allow the below dependency for HMR
// tslint:disable-next-line: max-line-length
// https://github.com/angular/angular-cli/blob/1e258317b1f6ec1e957ee3559cc3b28ba602f3ba/packages/angular_devkit/build_angular/src/dev-server/index.ts#L605-L638
private allowedDependencies = new Set<string>([
'webpack/hot/dev-server',
'webpack/hot/only-dev-server',
'@angular-devkit/build-angular',
]);
constructor(private options: CommonJsUsageWarnPluginOptions = {}) {
this.options.allowedDependencies?.forEach(d => this.allowedDependencies.add(d));
}
apply(compiler: Compiler) {
compiler.hooks.compilation.tap('CommonJsUsageWarnPlugin', compilation => {
compilation.hooks.finishModules.tap('CommonJsUsageWarnPlugin', modules => {
for (const module of modules as unknown as WebpackModule[]) {
const {dependencies, rawRequest} = module;
if (
!rawRequest ||
rawRequest.startsWith('.') ||
isAbsolute(rawRequest) ||
this.allowedDependencies.has(rawRequest) ||
this.allowedDependencies.has(this.rawRequestToPackageName(rawRequest)) ||
rawRequest.startsWith('@angular/common/locales/')
) {
/**
* Skip when:
* - module is absolute or relative.
* - module is allowed even if it's a CommonJS.
* - module is a locale imported from '@angular/common'.
*/
continue;
}
if (this.hasCommonJsDependencies(compilation, dependencies)) {
// Dependency is CommonsJS or AMD.
const issuer = getIssuer(compilation, module);
// Check if it's parent issuer is also a CommonJS dependency.
// In case it is skip as an warning will be show for the parent CommonJS dependency.
const parentDependencies = getIssuer(compilation, issuer)?.dependencies;
if (parentDependencies && this.hasCommonJsDependencies(compilation, parentDependencies, true)) {
continue;
}
// Find the main issuer (entry-point).
let mainIssuer = issuer;
let nextIssuer = getIssuer(compilation, mainIssuer);
while (nextIssuer) {
mainIssuer = nextIssuer;
nextIssuer = getIssuer(compilation, mainIssuer);
}
// Only show warnings for modules from main entrypoint.
// And if the issuer request is not from 'webpack-dev-server', as 'webpack-dev-server'
// will require CommonJS libraries for live reloading such as 'sockjs-node'.
if (mainIssuer?.name === 'main' && !issuer?.userRequest?.includes('webpack-dev-server')) {
const warning = `${issuer?.userRequest} depends on '${rawRequest}'. ` +
'CommonJS or AMD dependencies can cause optimization bailouts.\n' +
'For more info see: https://angular.io/guide/build#configuring-commonjs-dependencies';
// Avoid showing the same warning multiple times when in 'watch' mode.
if (!this.shownWarnings.has(warning)) {
addWarning(compilation, warning);
this.shownWarnings.add(warning);
}
}
}
}
});
});
}
private hasCommonJsDependencies(
compilation: compilation.Compilation,
dependencies: WebpackModule[],
checkParentModules = false): boolean {
for (const dep of dependencies) {
if (dep instanceof CommonJsRequireDependency || dep instanceof AMDDefineDependency) {
return true;
}
if (checkParentModules) {
const module = getWebpackModule(compilation, dep);
if (module && this.hasCommonJsDependencies(compilation, module.dependencies)) {
return true;
}
}
}
return false;
}
private rawRequestToPackageName(rawRequest: string): string {
return rawRequest.startsWith('@')
// Scoped request ex: @angular/common/locale/en -> @angular/common
? rawRequest.split('/', 2).join('/')
// Non-scoped request ex: lodash/isEmpty -> lodash
: rawRequest.split('/', 1)[0];
}
}
function getIssuer(compilation: compilation.Compilation, module: WebpackModule | null): WebpackModule | null {
if (!module) {
return null;
}
if (!isWebpackFiveOrHigher()) {
return module?.issuer;
}
return (compilation as unknown as { moduleGraph: { getIssuer(dependency: WebpackModule): WebpackModule; } })
.moduleGraph.getIssuer(module);
}
function getWebpackModule(compilation: compilation.Compilation, dependency: WebpackModule): WebpackModule | null {
if (!isWebpackFiveOrHigher()) {
return dependency.module;
}
return (compilation as unknown as { moduleGraph: { getModule(dependency: WebpackModule): WebpackModule; }})
.moduleGraph.getModule(dependency);
}