source: frontend/node_modules/webpack/lib/dependencies/CreateRequireParserPlugin.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 10.7 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 { fileURLToPath } = require("url");
9const WebpackError = require("../errors/WebpackError");
10const BasicEvaluatedExpression = require("../javascript/BasicEvaluatedExpression");
11const { VariableInfo } = require("../javascript/JavascriptParser");
12const {
13 evaluateToString,
14 expressionIsUnsupported,
15 toConstantDependency
16} = require("../javascript/JavascriptParserHelpers");
17const CommonJsImportsParserPlugin = require("./CommonJsImportsParserPlugin");
18const ConstDependency = require("./ConstDependency");
19
20/** @typedef {import("estree").CallExpression} CallExpression */
21/** @typedef {import("estree").Expression} Expression */
22/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
23/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
24/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
25/** @typedef {import("../javascript/JavascriptParser").ImportSource} ImportSource */
26/** @typedef {import("../javascript/JavascriptParser").Range} Range */
27
28/**
29 * Defines the common js import settings type used by this module.
30 * @typedef {object} CommonJsImportSettings
31 * @property {string=} name
32 * @property {string} context
33 */
34
35const createRequireSpecifierTag = Symbol("createRequire");
36const createdRequireIdentifierTag = Symbol("createRequire()");
37
38const PLUGIN_NAME = "CreateRequireParserPlugin";
39
40const {
41 createProcessResolveHandler,
42 createRequireAsExpressionHandler,
43 createRequireCacheDependency,
44 createRequireHandler
45} = CommonJsImportsParserPlugin;
46
47class CreateRequireParserPlugin {
48 /**
49 * Creates an instance of CreateRequireParserPlugin.
50 * @param {JavascriptParserOptions} options parser options
51 */
52 constructor(options) {
53 this.options = options;
54 }
55
56 /**
57 * Applies the plugin by registering its hooks on the compiler.
58 * @param {JavascriptParser} parser the parser
59 * @returns {void}
60 */
61 apply(parser) {
62 const options = this.options;
63 if (!options.createRequire) return;
64
65 const getContext = () => {
66 if (parser.currentTagData) {
67 const { context } =
68 /** @type {CommonJsImportSettings} */
69 (parser.currentTagData);
70 return context;
71 }
72 };
73
74 /**
75 * Tap require expression tag.
76 * @param {string | symbol} tag tag
77 */
78 const tapRequireExpressionTag = (tag) => {
79 parser.hooks.typeof
80 .for(tag)
81 .tap(
82 PLUGIN_NAME,
83 toConstantDependency(parser, JSON.stringify("function"))
84 );
85 parser.hooks.evaluateTypeof
86 .for(tag)
87 .tap(PLUGIN_NAME, evaluateToString("function"));
88 };
89
90 /**
91 * Returns true when set undefined.
92 * @param {Expression} expr expression
93 * @returns {boolean} true when set undefined
94 */
95 const defineUndefined = (expr) => {
96 const dep = new ConstDependency(
97 "undefined",
98 /** @type {Range} */ (expr.range)
99 );
100 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
101 parser.state.module.addPresentationalDependency(dep);
102 return false;
103 };
104
105 const requireCache = createRequireCacheDependency(parser);
106 const requireAsExpressionHandler = createRequireAsExpressionHandler(
107 parser,
108 options,
109 getContext
110 );
111 const createRequireCallHandler = createRequireHandler(
112 parser,
113 options,
114 getContext
115 );
116 const processResolve = createProcessResolveHandler(
117 parser,
118 options,
119 getContext
120 );
121
122 /** @type {ImportSource[]} */
123 let moduleNames = [];
124 /** @type {string | undefined} */
125 let specifierName;
126
127 if (options.createRequire === true) {
128 moduleNames = ["module", "node:module"];
129 specifierName = "createRequire";
130 } else if (typeof options.createRequire === "string") {
131 /** @type {undefined | string} */
132 let parsedModuleName;
133 const match = /^(.*) from (.*)$/.exec(options.createRequire);
134 if (match) {
135 [, specifierName, parsedModuleName] = match;
136 }
137 if (!specifierName || !parsedModuleName) {
138 const err = new WebpackError(
139 `Parsing javascript parser option "createRequire" failed, got ${JSON.stringify(
140 options.createRequire
141 )}`
142 );
143 err.details =
144 'Expected string in format "createRequire from module", where "createRequire" is specifier name and "module" name of the module';
145 throw err;
146 }
147 moduleNames = [parsedModuleName];
148 } else {
149 return;
150 }
151
152 /**
153 * Parses create require arguments.
154 * @param {CallExpression} expr call expression
155 * @returns {string | void} context
156 */
157 const parseCreateRequireArguments = (expr) => {
158 const args = expr.arguments;
159 if (args.length !== 1) {
160 const err = new WebpackError(
161 "module.createRequire supports only one argument."
162 );
163 err.loc = /** @type {DependencyLocation} */ (expr.loc);
164 parser.state.module.addWarning(err);
165 return;
166 }
167 const arg = args[0];
168 const evaluated = parser.evaluateExpression(arg);
169 if (!evaluated.isString()) {
170 const err = new WebpackError(
171 "module.createRequire failed parsing argument."
172 );
173 err.loc = /** @type {DependencyLocation} */ (arg.loc);
174 parser.state.module.addWarning(err);
175 return;
176 }
177 const ctx = /** @type {string} */ (evaluated.string).startsWith("file://")
178 ? fileURLToPath(/** @type {string} */ (evaluated.string))
179 : /** @type {string} */ (evaluated.string);
180 // argument always should be a filename
181 return ctx.slice(0, ctx.lastIndexOf(ctx.startsWith("/") ? "/" : "\\"));
182 };
183
184 tapRequireExpressionTag(createdRequireIdentifierTag);
185 tapRequireExpressionTag(createRequireSpecifierTag);
186
187 parser.hooks.evaluateCallExpression
188 .for(createRequireSpecifierTag)
189 .tap(PLUGIN_NAME, (expr) => {
190 const context = parseCreateRequireArguments(expr);
191 if (context === undefined) return;
192 const ident = parser.evaluatedVariable({
193 tag: createdRequireIdentifierTag,
194 data: { context },
195 next: undefined
196 });
197
198 return new BasicEvaluatedExpression()
199 .setIdentifier(ident, ident, () => [])
200 .setSideEffects(false)
201 .setRange(/** @type {Range} */ (expr.range));
202 });
203
204 parser.hooks.unhandledExpressionMemberChain
205 .for(createdRequireIdentifierTag)
206 .tap(PLUGIN_NAME, (expr, members) =>
207 expressionIsUnsupported(
208 parser,
209 `createRequire().${members.join(".")} is not supported by webpack.`
210 )(expr)
211 );
212 parser.hooks.canRename
213 .for(createdRequireIdentifierTag)
214 .tap(PLUGIN_NAME, () => true);
215 parser.hooks.canRename
216 .for(createRequireSpecifierTag)
217 .tap(PLUGIN_NAME, () => true);
218 parser.hooks.rename
219 .for(createRequireSpecifierTag)
220 .tap(PLUGIN_NAME, defineUndefined);
221 parser.hooks.expression
222 .for(createdRequireIdentifierTag)
223 .tap(PLUGIN_NAME, requireAsExpressionHandler);
224 parser.hooks.call
225 .for(createdRequireIdentifierTag)
226 .tap(PLUGIN_NAME, createRequireCallHandler(false));
227
228 parser.hooks.import.tap(
229 {
230 name: PLUGIN_NAME,
231 stage: -10
232 },
233 (statement, source) => {
234 if (
235 !moduleNames.includes(source) ||
236 statement.specifiers.length !== 1 ||
237 statement.specifiers[0].type !== "ImportSpecifier" ||
238 statement.specifiers[0].imported.type !== "Identifier" ||
239 statement.specifiers[0].imported.name !== specifierName
240 ) {
241 return;
242 }
243 // clear for 'import { createRequire as x } from "module"'
244 // if any other specifier was used import module
245 const clearDep = new ConstDependency(
246 parser.isAsiPosition(/** @type {Range} */ (statement.range)[0])
247 ? ";"
248 : "",
249 /** @type {Range} */ (statement.range)
250 );
251 clearDep.loc = /** @type {DependencyLocation} */ (statement.loc);
252 parser.state.module.addPresentationalDependency(clearDep);
253 parser.unsetAsiPosition(/** @type {Range} */ (statement.range)[1]);
254 return true;
255 }
256 );
257 parser.hooks.importSpecifier.tap(
258 {
259 name: PLUGIN_NAME,
260 stage: -10
261 },
262 (statement, source, id, name) => {
263 if (!moduleNames.includes(source) || id !== specifierName) return;
264 parser.tagVariable(name, createRequireSpecifierTag);
265 return true;
266 }
267 );
268 parser.hooks.preDeclarator.tap(PLUGIN_NAME, (declarator) => {
269 if (
270 declarator.id.type !== "Identifier" ||
271 !declarator.init ||
272 declarator.init.type !== "CallExpression" ||
273 declarator.init.callee.type !== "Identifier"
274 ) {
275 return;
276 }
277 const variableInfo = parser.getVariableInfo(declarator.init.callee.name);
278 if (
279 variableInfo instanceof VariableInfo &&
280 variableInfo.tagInfo &&
281 variableInfo.tagInfo.tag === createRequireSpecifierTag
282 ) {
283 const context = parseCreateRequireArguments(declarator.init);
284 if (context === undefined) return;
285 parser.tagVariable(declarator.id.name, createdRequireIdentifierTag, {
286 name: declarator.id.name,
287 context
288 });
289 return true;
290 }
291 });
292
293 parser.hooks.memberChainOfCallMemberChain
294 .for(createRequireSpecifierTag)
295 .tap(PLUGIN_NAME, (expr, calleeMembers, callExpr, members) => {
296 if (
297 calleeMembers.length !== 0 ||
298 members.length !== 1 ||
299 members[0] !== "cache"
300 ) {
301 return;
302 }
303 // createRequire().cache
304 const context = parseCreateRequireArguments(callExpr);
305 if (context === undefined) return;
306 return requireCache(expr);
307 });
308 parser.hooks.callMemberChainOfCallMemberChain
309 .for(createRequireSpecifierTag)
310 .tap(PLUGIN_NAME, (expr, calleeMembers, innerCallExpression, members) => {
311 if (
312 calleeMembers.length !== 0 ||
313 members.length !== 1 ||
314 members[0] !== "resolve"
315 ) {
316 return;
317 }
318 // createRequire().resolve()
319 return processResolve(expr, false);
320 });
321 parser.hooks.expressionMemberChain
322 .for(createdRequireIdentifierTag)
323 .tap(PLUGIN_NAME, (expr, members) => {
324 // require.cache
325 if (members.length === 1 && members[0] === "cache") {
326 return requireCache(expr);
327 }
328 });
329 parser.hooks.callMemberChain
330 .for(createdRequireIdentifierTag)
331 .tap(PLUGIN_NAME, (expr, members) => {
332 // require.resolve()
333 if (members.length === 1 && members[0] === "resolve") {
334 return processResolve(expr, false);
335 }
336 });
337 parser.hooks.expression
338 .for(createRequireSpecifierTag)
339 .tap(PLUGIN_NAME, (expr) => {
340 const clearDep = new ConstDependency(
341 "/* createRequire */ undefined",
342 /** @type {Range} */ (expr.range)
343 );
344 clearDep.loc = /** @type {DependencyLocation} */ (expr.loc);
345 parser.state.module.addPresentationalDependency(clearDep);
346 return true;
347 });
348 parser.hooks.call
349 .for(createRequireSpecifierTag)
350 .tap(PLUGIN_NAME, (expr) => {
351 const clearDep = new ConstDependency(
352 "/* createRequire() */ undefined",
353 /** @type {Range} */ (expr.range)
354 );
355 clearDep.loc = /** @type {DependencyLocation} */ (expr.loc);
356 parser.state.module.addPresentationalDependency(clearDep);
357 return true;
358 });
359 }
360}
361
362module.exports = CreateRequireParserPlugin;
Note: See TracBrowser for help on using the repository browser.