source: imaps-frontend/node_modules/webpack/lib/dependencies/CommonJsExportsParserPlugin.js@ 79a0317

main
Last change on this file since 79a0317 was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 3 days ago

F4 Finalna Verzija

  • Property mode set to 100644
File size: 13.9 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const RuntimeGlobals = require("../RuntimeGlobals");
9const formatLocation = require("../formatLocation");
10const { evaluateToString } = require("../javascript/JavascriptParserHelpers");
11const propertyAccess = require("../util/propertyAccess");
12const CommonJsExportRequireDependency = require("./CommonJsExportRequireDependency");
13const CommonJsExportsDependency = require("./CommonJsExportsDependency");
14const CommonJsSelfReferenceDependency = require("./CommonJsSelfReferenceDependency");
15const DynamicExports = require("./DynamicExports");
16const HarmonyExports = require("./HarmonyExports");
17const ModuleDecoratorDependency = require("./ModuleDecoratorDependency");
18
19/** @typedef {import("estree").AssignmentExpression} AssignmentExpression */
20/** @typedef {import("estree").CallExpression} CallExpression */
21/** @typedef {import("estree").Expression} Expression */
22/** @typedef {import("estree").Super} Super */
23/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
24/** @typedef {import("../ModuleGraph")} ModuleGraph */
25/** @typedef {import("../NormalModule")} NormalModule */
26/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */
27/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
28/** @typedef {import("../javascript/JavascriptParser").Range} Range */
29/** @typedef {import("../javascript/JavascriptParser").StatementPath} StatementPath */
30/** @typedef {import("./CommonJsDependencyHelpers").CommonJSDependencyBaseKeywords} CommonJSDependencyBaseKeywords */
31
32/**
33 * This function takes a generic expression and detects whether it is an ObjectExpression.
34 * This is used in the context of parsing CommonJS exports to get the value of the property descriptor
35 * when the `exports` object is assigned to `Object.defineProperty`.
36 *
37 * In CommonJS modules, the `exports` object can be assigned to `Object.defineProperty` and therefore
38 * webpack has to detect this case and get the value key of the property descriptor. See the following example
39 * for more information: https://astexplorer.net/#/gist/83ce51a4e96e59d777df315a6d111da6/8058ead48a1bb53c097738225db0967ef7f70e57
40 *
41 * This would be an example of a CommonJS module that exports an object with a property descriptor:
42 * ```js
43 * Object.defineProperty(exports, "__esModule", { value: true });
44 * exports.foo = void 0;
45 * exports.foo = "bar";
46 * ```
47 * @param {TODO} expr expression
48 * @returns {Expression | undefined} returns the value of property descriptor
49 */
50const getValueOfPropertyDescription = expr => {
51 if (expr.type !== "ObjectExpression") return;
52 for (const property of expr.properties) {
53 if (property.computed) continue;
54 const key = property.key;
55 if (key.type !== "Identifier" || key.name !== "value") continue;
56 return property.value;
57 }
58};
59
60/**
61 * The purpose of this function is to check whether an expression is a truthy literal or not. This is
62 * useful when parsing CommonJS exports, because CommonJS modules can export any value, including falsy
63 * values like `null` and `false`. However, exports should only be created if the exported value is truthy.
64 * @param {Expression} expr expression being checked
65 * @returns {boolean} true, when the expression is a truthy literal
66 */
67const isTruthyLiteral = expr => {
68 switch (expr.type) {
69 case "Literal":
70 return Boolean(expr.value);
71 case "UnaryExpression":
72 if (expr.operator === "!") return isFalsyLiteral(expr.argument);
73 }
74 return false;
75};
76
77/**
78 * The purpose of this function is to check whether an expression is a falsy literal or not. This is
79 * useful when parsing CommonJS exports, because CommonJS modules can export any value, including falsy
80 * values like `null` and `false`. However, exports should only be created if the exported value is truthy.
81 * @param {Expression} expr expression being checked
82 * @returns {boolean} true, when the expression is a falsy literal
83 */
84const isFalsyLiteral = expr => {
85 switch (expr.type) {
86 case "Literal":
87 return !expr.value;
88 case "UnaryExpression":
89 if (expr.operator === "!") return isTruthyLiteral(expr.argument);
90 }
91 return false;
92};
93
94/**
95 * @param {JavascriptParser} parser the parser
96 * @param {Expression} expr expression
97 * @returns {{ argument: BasicEvaluatedExpression, ids: string[] } | undefined} parsed call
98 */
99const parseRequireCall = (parser, expr) => {
100 const ids = [];
101 while (expr.type === "MemberExpression") {
102 if (expr.object.type === "Super") return;
103 if (!expr.property) return;
104 const prop = expr.property;
105 if (expr.computed) {
106 if (prop.type !== "Literal") return;
107 ids.push(`${prop.value}`);
108 } else {
109 if (prop.type !== "Identifier") return;
110 ids.push(prop.name);
111 }
112 expr = expr.object;
113 }
114 if (expr.type !== "CallExpression" || expr.arguments.length !== 1) return;
115 const callee = expr.callee;
116 if (
117 callee.type !== "Identifier" ||
118 parser.getVariableInfo(callee.name) !== "require"
119 ) {
120 return;
121 }
122 const arg = expr.arguments[0];
123 if (arg.type === "SpreadElement") return;
124 const argValue = parser.evaluateExpression(arg);
125 return { argument: argValue, ids: ids.reverse() };
126};
127
128class CommonJsExportsParserPlugin {
129 /**
130 * @param {ModuleGraph} moduleGraph module graph
131 */
132 constructor(moduleGraph) {
133 this.moduleGraph = moduleGraph;
134 }
135
136 /**
137 * @param {JavascriptParser} parser the parser
138 * @returns {void}
139 */
140 apply(parser) {
141 const enableStructuredExports = () => {
142 DynamicExports.enable(parser.state);
143 };
144
145 /**
146 * @param {boolean} topLevel true, when the export is on top level
147 * @param {string[]} members members of the export
148 * @param {Expression | undefined} valueExpr expression for the value
149 * @returns {void}
150 */
151 const checkNamespace = (topLevel, members, valueExpr) => {
152 if (!DynamicExports.isEnabled(parser.state)) return;
153 if (members.length > 0 && members[0] === "__esModule") {
154 if (valueExpr && isTruthyLiteral(valueExpr) && topLevel) {
155 DynamicExports.setFlagged(parser.state);
156 } else {
157 DynamicExports.setDynamic(parser.state);
158 }
159 }
160 };
161 /**
162 * @param {string=} reason reason
163 */
164 const bailout = reason => {
165 DynamicExports.bailout(parser.state);
166 if (reason) bailoutHint(reason);
167 };
168 /**
169 * @param {string} reason reason
170 */
171 const bailoutHint = reason => {
172 this.moduleGraph
173 .getOptimizationBailout(parser.state.module)
174 .push(`CommonJS bailout: ${reason}`);
175 };
176
177 // metadata //
178 parser.hooks.evaluateTypeof
179 .for("module")
180 .tap("CommonJsExportsParserPlugin", evaluateToString("object"));
181 parser.hooks.evaluateTypeof
182 .for("exports")
183 .tap("CommonJsPlugin", evaluateToString("object"));
184
185 // exporting //
186
187 /**
188 * @param {AssignmentExpression} expr expression
189 * @param {CommonJSDependencyBaseKeywords} base commonjs base keywords
190 * @param {string[]} members members of the export
191 * @returns {boolean | undefined} true, when the expression was handled
192 */
193 const handleAssignExport = (expr, base, members) => {
194 if (HarmonyExports.isEnabled(parser.state)) return;
195 // Handle reexporting
196 const requireCall = parseRequireCall(parser, expr.right);
197 if (
198 requireCall &&
199 requireCall.argument.isString() &&
200 (members.length === 0 || members[0] !== "__esModule")
201 ) {
202 enableStructuredExports();
203 // It's possible to reexport __esModule, so we must convert to a dynamic module
204 if (members.length === 0) DynamicExports.setDynamic(parser.state);
205 const dep = new CommonJsExportRequireDependency(
206 /** @type {Range} */ (expr.range),
207 null,
208 base,
209 members,
210 /** @type {string} */ (requireCall.argument.string),
211 requireCall.ids,
212 !parser.isStatementLevelExpression(expr)
213 );
214 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
215 dep.optional = Boolean(parser.scope.inTry);
216 parser.state.module.addDependency(dep);
217 return true;
218 }
219 if (members.length === 0) return;
220 enableStructuredExports();
221 const remainingMembers = members;
222 checkNamespace(
223 /** @type {StatementPath} */
224 (parser.statementPath).length === 1 &&
225 parser.isStatementLevelExpression(expr),
226 remainingMembers,
227 expr.right
228 );
229 const dep = new CommonJsExportsDependency(
230 /** @type {Range} */ (expr.left.range),
231 null,
232 base,
233 remainingMembers
234 );
235 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
236 parser.state.module.addDependency(dep);
237 parser.walkExpression(expr.right);
238 return true;
239 };
240 parser.hooks.assignMemberChain
241 .for("exports")
242 .tap("CommonJsExportsParserPlugin", (expr, members) =>
243 handleAssignExport(expr, "exports", members)
244 );
245 parser.hooks.assignMemberChain
246 .for("this")
247 .tap("CommonJsExportsParserPlugin", (expr, members) => {
248 if (!parser.scope.topLevelScope) return;
249 return handleAssignExport(expr, "this", members);
250 });
251 parser.hooks.assignMemberChain
252 .for("module")
253 .tap("CommonJsExportsParserPlugin", (expr, members) => {
254 if (members[0] !== "exports") return;
255 return handleAssignExport(expr, "module.exports", members.slice(1));
256 });
257 parser.hooks.call
258 .for("Object.defineProperty")
259 .tap("CommonJsExportsParserPlugin", expression => {
260 const expr = /** @type {CallExpression} */ (expression);
261 if (!parser.isStatementLevelExpression(expr)) return;
262 if (expr.arguments.length !== 3) return;
263 if (expr.arguments[0].type === "SpreadElement") return;
264 if (expr.arguments[1].type === "SpreadElement") return;
265 if (expr.arguments[2].type === "SpreadElement") return;
266 const exportsArg = parser.evaluateExpression(expr.arguments[0]);
267 if (!exportsArg.isIdentifier()) return;
268 if (
269 exportsArg.identifier !== "exports" &&
270 exportsArg.identifier !== "module.exports" &&
271 (exportsArg.identifier !== "this" || !parser.scope.topLevelScope)
272 ) {
273 return;
274 }
275 const propertyArg = parser.evaluateExpression(expr.arguments[1]);
276 const property = propertyArg.asString();
277 if (typeof property !== "string") return;
278 enableStructuredExports();
279 const descArg = expr.arguments[2];
280 checkNamespace(
281 /** @type {StatementPath} */
282 (parser.statementPath).length === 1,
283 [property],
284 getValueOfPropertyDescription(descArg)
285 );
286 const dep = new CommonJsExportsDependency(
287 /** @type {Range} */ (expr.range),
288 /** @type {Range} */ (expr.arguments[2].range),
289 `Object.defineProperty(${exportsArg.identifier})`,
290 [property]
291 );
292 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
293 parser.state.module.addDependency(dep);
294
295 parser.walkExpression(expr.arguments[2]);
296 return true;
297 });
298
299 // Self reference //
300
301 /**
302 * @param {Expression | Super} expr expression
303 * @param {CommonJSDependencyBaseKeywords} base commonjs base keywords
304 * @param {string[]} members members of the export
305 * @param {CallExpression=} call call expression
306 * @returns {boolean | void} true, when the expression was handled
307 */
308 const handleAccessExport = (expr, base, members, call) => {
309 if (HarmonyExports.isEnabled(parser.state)) return;
310 if (members.length === 0) {
311 bailout(
312 `${base} is used directly at ${formatLocation(
313 /** @type {DependencyLocation} */ (expr.loc)
314 )}`
315 );
316 }
317 if (call && members.length === 1) {
318 bailoutHint(
319 `${base}${propertyAccess(
320 members
321 )}(...) prevents optimization as ${base} is passed as call context at ${formatLocation(
322 /** @type {DependencyLocation} */ (expr.loc)
323 )}`
324 );
325 }
326 const dep = new CommonJsSelfReferenceDependency(
327 /** @type {Range} */ (expr.range),
328 base,
329 members,
330 Boolean(call)
331 );
332 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
333 parser.state.module.addDependency(dep);
334 if (call) {
335 parser.walkExpressions(call.arguments);
336 }
337 return true;
338 };
339 parser.hooks.callMemberChain
340 .for("exports")
341 .tap("CommonJsExportsParserPlugin", (expr, members) =>
342 handleAccessExport(expr.callee, "exports", members, expr)
343 );
344 parser.hooks.expressionMemberChain
345 .for("exports")
346 .tap("CommonJsExportsParserPlugin", (expr, members) =>
347 handleAccessExport(expr, "exports", members)
348 );
349 parser.hooks.expression
350 .for("exports")
351 .tap("CommonJsExportsParserPlugin", expr =>
352 handleAccessExport(expr, "exports", [])
353 );
354 parser.hooks.callMemberChain
355 .for("module")
356 .tap("CommonJsExportsParserPlugin", (expr, members) => {
357 if (members[0] !== "exports") return;
358 return handleAccessExport(
359 expr.callee,
360 "module.exports",
361 members.slice(1),
362 expr
363 );
364 });
365 parser.hooks.expressionMemberChain
366 .for("module")
367 .tap("CommonJsExportsParserPlugin", (expr, members) => {
368 if (members[0] !== "exports") return;
369 return handleAccessExport(expr, "module.exports", members.slice(1));
370 });
371 parser.hooks.expression
372 .for("module.exports")
373 .tap("CommonJsExportsParserPlugin", expr =>
374 handleAccessExport(expr, "module.exports", [])
375 );
376 parser.hooks.callMemberChain
377 .for("this")
378 .tap("CommonJsExportsParserPlugin", (expr, members) => {
379 if (!parser.scope.topLevelScope) return;
380 return handleAccessExport(expr.callee, "this", members, expr);
381 });
382 parser.hooks.expressionMemberChain
383 .for("this")
384 .tap("CommonJsExportsParserPlugin", (expr, members) => {
385 if (!parser.scope.topLevelScope) return;
386 return handleAccessExport(expr, "this", members);
387 });
388 parser.hooks.expression
389 .for("this")
390 .tap("CommonJsExportsParserPlugin", expr => {
391 if (!parser.scope.topLevelScope) return;
392 return handleAccessExport(expr, "this", []);
393 });
394
395 // Bailouts //
396 parser.hooks.expression.for("module").tap("CommonJsPlugin", expr => {
397 bailout();
398 const isHarmony = HarmonyExports.isEnabled(parser.state);
399 const dep = new ModuleDecoratorDependency(
400 isHarmony
401 ? RuntimeGlobals.harmonyModuleDecorator
402 : RuntimeGlobals.nodeModuleDecorator,
403 !isHarmony
404 );
405 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
406 parser.state.module.addDependency(dep);
407 return true;
408 });
409 }
410}
411module.exports = CommonJsExportsParserPlugin;
Note: See TracBrowser for help on using the repository browser.