-
Notifications
You must be signed in to change notification settings - Fork 12k
/
Copy pathcommon-js-usage-warn-plugin.ts
153 lines (134 loc) · 5.6 KB
/
common-js-usage-warn-plugin.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/**
* @license
* Copyright Google LLC 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 { Compilation, Compiler, Dependency, Module, NormalModule } from 'webpack';
import { addWarning } from '../../../utils/webpack-diagnostics';
// Webpack doesn't export these so the deep imports can potentially break.
const AMDDefineDependency = require('webpack/lib/dependencies/AMDDefineDependency');
const CommonJsExportsDependency = require('webpack/lib/dependencies/CommonJsExportsDependency');
const CommonJsRequireDependency = require('webpack/lib/dependencies/CommonJsRequireDependency');
const CommonJsSelfReferenceDependency = require('webpack/lib/dependencies/CommonJsSelfReferenceDependency');
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>();
private allowedDependencies: Set<string>;
constructor(private options: CommonJsUsageWarnPluginOptions = {}) {
this.allowedDependencies = new Set(this.options.allowedDependencies);
}
apply(compiler: Compiler) {
compiler.hooks.compilation.tap('CommonJsUsageWarnPlugin', (compilation) => {
compilation.hooks.finishModules.tap('CommonJsUsageWarnPlugin', (modules) => {
const mainEntry = compilation.entries.get('main');
if (!mainEntry) {
return;
}
const mainModules = new Set(
mainEntry.dependencies.map((dep) => compilation.moduleGraph.getModule(dep)),
);
for (const module of modules) {
const { dependencies, rawRequest } = module as NormalModule;
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 && mainModules.has(mainIssuer)) {
const warning =
`${(issuer as NormalModule | null)?.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,
dependencies: Dependency[],
checkParentModules = false,
): boolean {
for (const dep of dependencies) {
if (
dep instanceof CommonJsRequireDependency ||
dep instanceof CommonJsExportsDependency ||
dep instanceof CommonJsSelfReferenceDependency ||
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, module: Module | null): Module | null {
if (!module) {
return null;
}
return compilation.moduleGraph.getIssuer(module);
}
function getWebpackModule(compilation: Compilation, dependency: Dependency | null): Module | null {
if (!dependency) {
return null;
}
return compilation.moduleGraph.getModule(dependency);
}