UNPKG

11.9 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const CommentCompilationWarning = require("../CommentCompilationWarning");
9const RuntimeGlobals = require("../RuntimeGlobals");
10const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning");
11const {
12 evaluateToIdentifier,
13 evaluateToString,
14 expressionIsUnsupported,
15 toConstantDependency
16} = require("../javascript/JavascriptParserHelpers");
17const CommonJsFullRequireDependency = require("./CommonJsFullRequireDependency");
18const CommonJsRequireContextDependency = require("./CommonJsRequireContextDependency");
19const CommonJsRequireDependency = require("./CommonJsRequireDependency");
20const ConstDependency = require("./ConstDependency");
21const ContextDependencyHelpers = require("./ContextDependencyHelpers");
22const LocalModuleDependency = require("./LocalModuleDependency");
23const { getLocalModule } = require("./LocalModulesHelpers");
24const RequireHeaderDependency = require("./RequireHeaderDependency");
25const RequireResolveContextDependency = require("./RequireResolveContextDependency");
26const RequireResolveDependency = require("./RequireResolveDependency");
27const RequireResolveHeaderDependency = require("./RequireResolveHeaderDependency");
28
29/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
30
31class CommonJsImportsParserPlugin {
32 /**
33 * @param {JavascriptParserOptions} options parser options
34 */
35 constructor(options) {
36 this.options = options;
37 }
38
39 apply(parser) {
40 const options = this.options;
41
42 // metadata //
43 const tapRequireExpression = (expression, getMembers) => {
44 parser.hooks.typeof
45 .for(expression)
46 .tap(
47 "CommonJsPlugin",
48 toConstantDependency(parser, JSON.stringify("function"))
49 );
50 parser.hooks.evaluateTypeof
51 .for(expression)
52 .tap("CommonJsPlugin", evaluateToString("function"));
53 parser.hooks.evaluateIdentifier
54 .for(expression)
55 .tap(
56 "CommonJsPlugin",
57 evaluateToIdentifier(expression, "require", getMembers, true)
58 );
59 };
60 tapRequireExpression("require", () => []);
61 tapRequireExpression("require.resolve", () => ["resolve"]);
62 tapRequireExpression("require.resolveWeak", () => ["resolveWeak"]);
63
64 // Weird stuff //
65 parser.hooks.assign.for("require").tap("CommonJsPlugin", expr => {
66 // to not leak to global "require", we need to define a local require here.
67 const dep = new ConstDependency("var require;", 0);
68 dep.loc = expr.loc;
69 parser.state.module.addPresentationalDependency(dep);
70 return true;
71 });
72
73 // Unsupported //
74 parser.hooks.expression
75 .for("require.main.require")
76 .tap(
77 "CommonJsPlugin",
78 expressionIsUnsupported(
79 parser,
80 "require.main.require is not supported by webpack."
81 )
82 );
83 parser.hooks.call
84 .for("require.main.require")
85 .tap(
86 "CommonJsPlugin",
87 expressionIsUnsupported(
88 parser,
89 "require.main.require is not supported by webpack."
90 )
91 );
92 parser.hooks.expression
93 .for("module.parent.require")
94 .tap(
95 "CommonJsPlugin",
96 expressionIsUnsupported(
97 parser,
98 "module.parent.require is not supported by webpack."
99 )
100 );
101 parser.hooks.call
102 .for("module.parent.require")
103 .tap(
104 "CommonJsPlugin",
105 expressionIsUnsupported(
106 parser,
107 "module.parent.require is not supported by webpack."
108 )
109 );
110
111 // renaming //
112 parser.hooks.canRename.for("require").tap("CommonJsPlugin", () => true);
113 parser.hooks.rename.for("require").tap("CommonJsPlugin", expr => {
114 // To avoid "not defined" error, replace the value with undefined
115 const dep = new ConstDependency("undefined", expr.range);
116 dep.loc = expr.loc;
117 parser.state.module.addPresentationalDependency(dep);
118 return false;
119 });
120
121 // inspection //
122 parser.hooks.expression
123 .for("require.cache")
124 .tap(
125 "CommonJsImportsParserPlugin",
126 toConstantDependency(parser, RuntimeGlobals.moduleCache, [
127 RuntimeGlobals.moduleCache,
128 RuntimeGlobals.moduleId,
129 RuntimeGlobals.moduleLoaded
130 ])
131 );
132
133 // require as expression //
134 parser.hooks.expression
135 .for("require")
136 .tap("CommonJsImportsParserPlugin", expr => {
137 const dep = new CommonJsRequireContextDependency(
138 {
139 request: options.unknownContextRequest,
140 recursive: options.unknownContextRecursive,
141 regExp: options.unknownContextRegExp,
142 mode: "sync"
143 },
144 expr.range,
145 undefined,
146 parser.scope.inShorthand
147 );
148 dep.critical =
149 options.unknownContextCritical &&
150 "require function is used in a way in which dependencies cannot be statically extracted";
151 dep.loc = expr.loc;
152 dep.optional = !!parser.scope.inTry;
153 parser.state.current.addDependency(dep);
154 return true;
155 });
156
157 // require //
158 const processRequireItem = (expr, param) => {
159 if (param.isString()) {
160 const dep = new CommonJsRequireDependency(param.string, param.range);
161 dep.loc = expr.loc;
162 dep.optional = !!parser.scope.inTry;
163 parser.state.current.addDependency(dep);
164 return true;
165 }
166 };
167 const processRequireContext = (expr, param) => {
168 const dep = ContextDependencyHelpers.create(
169 CommonJsRequireContextDependency,
170 expr.range,
171 param,
172 expr,
173 options,
174 {
175 category: "commonjs"
176 },
177 parser
178 );
179 if (!dep) return;
180 dep.loc = expr.loc;
181 dep.optional = !!parser.scope.inTry;
182 parser.state.current.addDependency(dep);
183 return true;
184 };
185 const createRequireHandler = callNew => expr => {
186 if (options.commonjsMagicComments) {
187 const { options: requireOptions, errors: commentErrors } =
188 parser.parseCommentOptions(expr.range);
189
190 if (commentErrors) {
191 for (const e of commentErrors) {
192 const { comment } = e;
193 parser.state.module.addWarning(
194 new CommentCompilationWarning(
195 `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
196 comment.loc
197 )
198 );
199 }
200 }
201 if (requireOptions) {
202 if (requireOptions.webpackIgnore !== undefined) {
203 if (typeof requireOptions.webpackIgnore !== "boolean") {
204 parser.state.module.addWarning(
205 new UnsupportedFeatureWarning(
206 `\`webpackIgnore\` expected a boolean, but received: ${requireOptions.webpackIgnore}.`,
207 expr.loc
208 )
209 );
210 } else {
211 // Do not instrument `require()` if `webpackIgnore` is `true`
212 if (requireOptions.webpackIgnore) {
213 return true;
214 }
215 }
216 }
217 }
218 }
219
220 if (expr.arguments.length !== 1) return;
221 let localModule;
222 const param = parser.evaluateExpression(expr.arguments[0]);
223 if (param.isConditional()) {
224 let isExpression = false;
225 for (const p of param.options) {
226 const result = processRequireItem(expr, p);
227 if (result === undefined) {
228 isExpression = true;
229 }
230 }
231 if (!isExpression) {
232 const dep = new RequireHeaderDependency(expr.callee.range);
233 dep.loc = expr.loc;
234 parser.state.module.addPresentationalDependency(dep);
235 return true;
236 }
237 }
238 if (
239 param.isString() &&
240 (localModule = getLocalModule(parser.state, param.string))
241 ) {
242 localModule.flagUsed();
243 const dep = new LocalModuleDependency(localModule, expr.range, callNew);
244 dep.loc = expr.loc;
245 parser.state.module.addPresentationalDependency(dep);
246 return true;
247 } else {
248 const result = processRequireItem(expr, param);
249 if (result === undefined) {
250 processRequireContext(expr, param);
251 } else {
252 const dep = new RequireHeaderDependency(expr.callee.range);
253 dep.loc = expr.loc;
254 parser.state.module.addPresentationalDependency(dep);
255 }
256 return true;
257 }
258 };
259 parser.hooks.call
260 .for("require")
261 .tap("CommonJsImportsParserPlugin", createRequireHandler(false));
262 parser.hooks.new
263 .for("require")
264 .tap("CommonJsImportsParserPlugin", createRequireHandler(true));
265 parser.hooks.call
266 .for("module.require")
267 .tap("CommonJsImportsParserPlugin", createRequireHandler(false));
268 parser.hooks.new
269 .for("module.require")
270 .tap("CommonJsImportsParserPlugin", createRequireHandler(true));
271
272 // require with property access //
273 const chainHandler = (expr, calleeMembers, callExpr, members) => {
274 if (callExpr.arguments.length !== 1) return;
275 const param = parser.evaluateExpression(callExpr.arguments[0]);
276 if (param.isString() && !getLocalModule(parser.state, param.string)) {
277 const dep = new CommonJsFullRequireDependency(
278 param.string,
279 expr.range,
280 members
281 );
282 dep.asiSafe = !parser.isAsiPosition(expr.range[0]);
283 dep.optional = !!parser.scope.inTry;
284 dep.loc = expr.loc;
285 parser.state.current.addDependency(dep);
286 return true;
287 }
288 };
289 const callChainHandler = (expr, calleeMembers, callExpr, members) => {
290 if (callExpr.arguments.length !== 1) return;
291 const param = parser.evaluateExpression(callExpr.arguments[0]);
292 if (param.isString() && !getLocalModule(parser.state, param.string)) {
293 const dep = new CommonJsFullRequireDependency(
294 param.string,
295 expr.callee.range,
296 members
297 );
298 dep.call = true;
299 dep.asiSafe = !parser.isAsiPosition(expr.range[0]);
300 dep.optional = !!parser.scope.inTry;
301 dep.loc = expr.callee.loc;
302 parser.state.current.addDependency(dep);
303 parser.walkExpressions(expr.arguments);
304 return true;
305 }
306 };
307 parser.hooks.memberChainOfCallMemberChain
308 .for("require")
309 .tap("CommonJsImportsParserPlugin", chainHandler);
310 parser.hooks.memberChainOfCallMemberChain
311 .for("module.require")
312 .tap("CommonJsImportsParserPlugin", chainHandler);
313 parser.hooks.callMemberChainOfCallMemberChain
314 .for("require")
315 .tap("CommonJsImportsParserPlugin", callChainHandler);
316 parser.hooks.callMemberChainOfCallMemberChain
317 .for("module.require")
318 .tap("CommonJsImportsParserPlugin", callChainHandler);
319
320 // require.resolve //
321 const processResolve = (expr, weak) => {
322 if (expr.arguments.length !== 1) return;
323 const param = parser.evaluateExpression(expr.arguments[0]);
324 if (param.isConditional()) {
325 for (const option of param.options) {
326 const result = processResolveItem(expr, option, weak);
327 if (result === undefined) {
328 processResolveContext(expr, option, weak);
329 }
330 }
331 const dep = new RequireResolveHeaderDependency(expr.callee.range);
332 dep.loc = expr.loc;
333 parser.state.module.addPresentationalDependency(dep);
334 return true;
335 } else {
336 const result = processResolveItem(expr, param, weak);
337 if (result === undefined) {
338 processResolveContext(expr, param, weak);
339 }
340 const dep = new RequireResolveHeaderDependency(expr.callee.range);
341 dep.loc = expr.loc;
342 parser.state.module.addPresentationalDependency(dep);
343 return true;
344 }
345 };
346 const processResolveItem = (expr, param, weak) => {
347 if (param.isString()) {
348 const dep = new RequireResolveDependency(param.string, param.range);
349 dep.loc = expr.loc;
350 dep.optional = !!parser.scope.inTry;
351 dep.weak = weak;
352 parser.state.current.addDependency(dep);
353 return true;
354 }
355 };
356 const processResolveContext = (expr, param, weak) => {
357 const dep = ContextDependencyHelpers.create(
358 RequireResolveContextDependency,
359 param.range,
360 param,
361 expr,
362 options,
363 {
364 category: "commonjs",
365 mode: weak ? "weak" : "sync"
366 },
367 parser
368 );
369 if (!dep) return;
370 dep.loc = expr.loc;
371 dep.optional = !!parser.scope.inTry;
372 parser.state.current.addDependency(dep);
373 return true;
374 };
375
376 parser.hooks.call
377 .for("require.resolve")
378 .tap("RequireResolveDependencyParserPlugin", expr => {
379 return processResolve(expr, false);
380 });
381 parser.hooks.call
382 .for("require.resolveWeak")
383 .tap("RequireResolveDependencyParserPlugin", expr => {
384 return processResolve(expr, true);
385 });
386 }
387}
388module.exports = CommonJsImportsParserPlugin;