UNPKG

8.09 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5"use strict";
6
7const ConstDependency = require("./dependencies/ConstDependency");
8const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
9const ParserHelpers = require("./ParserHelpers");
10const NullFactory = require("./NullFactory");
11
12/** @typedef {import("./Compiler")} Compiler */
13/** @typedef {import("./Parser")} Parser */
14/** @typedef {null|undefined|RegExp|Function|string|number} CodeValuePrimitive */
15/** @typedef {CodeValuePrimitive|Record<string, CodeValuePrimitive>|RuntimeValue} CodeValue */
16
17class RuntimeValue {
18 constructor(fn, fileDependencies) {
19 this.fn = fn;
20 this.fileDependencies = fileDependencies || [];
21 }
22
23 exec(parser) {
24 if (this.fileDependencies === true) {
25 parser.state.module.buildInfo.cacheable = false;
26 } else {
27 for (const fileDependency of this.fileDependencies) {
28 parser.state.module.buildInfo.fileDependencies.add(fileDependency);
29 }
30 }
31
32 return this.fn({ module: parser.state.module });
33 }
34}
35
36const stringifyObj = (obj, parser) => {
37 return (
38 "Object({" +
39 Object.keys(obj)
40 .map(key => {
41 const code = obj[key];
42 return JSON.stringify(key) + ":" + toCode(code, parser);
43 })
44 .join(",") +
45 "})"
46 );
47};
48
49/**
50 * Convert code to a string that evaluates
51 * @param {CodeValue} code Code to evaluate
52 * @param {Parser} parser Parser
53 * @returns {string} code converted to string that evaluates
54 */
55const toCode = (code, parser) => {
56 if (code === null) {
57 return "null";
58 }
59 if (code === undefined) {
60 return "undefined";
61 }
62 if (code instanceof RuntimeValue) {
63 return toCode(code.exec(parser), parser);
64 }
65 if (code instanceof RegExp && code.toString) {
66 return code.toString();
67 }
68 if (typeof code === "function" && code.toString) {
69 return "(" + code.toString() + ")";
70 }
71 if (typeof code === "object") {
72 return stringifyObj(code, parser);
73 }
74 return code + "";
75};
76
77class DefinePlugin {
78 /**
79 * Create a new define plugin
80 * @param {Record<string, CodeValue>} definitions A map of global object definitions
81 */
82 constructor(definitions) {
83 this.definitions = definitions;
84 }
85
86 static runtimeValue(fn, fileDependencies) {
87 return new RuntimeValue(fn, fileDependencies);
88 }
89
90 /**
91 * Apply the plugin
92 * @param {Compiler} compiler Webpack compiler
93 * @returns {void}
94 */
95 apply(compiler) {
96 const definitions = this.definitions;
97 compiler.hooks.compilation.tap(
98 "DefinePlugin",
99 (compilation, { normalModuleFactory }) => {
100 compilation.dependencyFactories.set(ConstDependency, new NullFactory());
101 compilation.dependencyTemplates.set(
102 ConstDependency,
103 new ConstDependency.Template()
104 );
105
106 /**
107 * Handler
108 * @param {Parser} parser Parser
109 * @returns {void}
110 */
111 const handler = parser => {
112 /**
113 * Walk definitions
114 * @param {Object} definitions Definitions map
115 * @param {string} prefix Prefix string
116 * @returns {void}
117 */
118 const walkDefinitions = (definitions, prefix) => {
119 Object.keys(definitions).forEach(key => {
120 const code = definitions[key];
121 if (
122 code &&
123 typeof code === "object" &&
124 !(code instanceof RuntimeValue) &&
125 !(code instanceof RegExp)
126 ) {
127 walkDefinitions(code, prefix + key + ".");
128 applyObjectDefine(prefix + key, code);
129 return;
130 }
131 applyDefineKey(prefix, key);
132 applyDefine(prefix + key, code);
133 });
134 };
135
136 /**
137 * Apply define key
138 * @param {string} prefix Prefix
139 * @param {string} key Key
140 * @returns {void}
141 */
142 const applyDefineKey = (prefix, key) => {
143 const splittedKey = key.split(".");
144 splittedKey.slice(1).forEach((_, i) => {
145 const fullKey = prefix + splittedKey.slice(0, i + 1).join(".");
146 parser.hooks.canRename
147 .for(fullKey)
148 .tap("DefinePlugin", ParserHelpers.approve);
149 });
150 };
151
152 /**
153 * Apply Code
154 * @param {string} key Key
155 * @param {CodeValue} code Code
156 * @returns {void}
157 */
158 const applyDefine = (key, code) => {
159 const isTypeof = /^typeof\s+/.test(key);
160 if (isTypeof) key = key.replace(/^typeof\s+/, "");
161 let recurse = false;
162 let recurseTypeof = false;
163 if (!isTypeof) {
164 parser.hooks.canRename
165 .for(key)
166 .tap("DefinePlugin", ParserHelpers.approve);
167 parser.hooks.evaluateIdentifier
168 .for(key)
169 .tap("DefinePlugin", expr => {
170 /**
171 * this is needed in case there is a recursion in the DefinePlugin
172 * to prevent an endless recursion
173 * e.g.: new DefinePlugin({
174 * "a": "b",
175 * "b": "a"
176 * });
177 */
178 if (recurse) return;
179 recurse = true;
180 const res = parser.evaluate(toCode(code, parser));
181 recurse = false;
182 res.setRange(expr.range);
183 return res;
184 });
185 parser.hooks.expression.for(key).tap("DefinePlugin", expr => {
186 const strCode = toCode(code, parser);
187 if (/__webpack_require__/.test(strCode)) {
188 return ParserHelpers.toConstantDependencyWithWebpackRequire(
189 parser,
190 strCode
191 )(expr);
192 } else {
193 return ParserHelpers.toConstantDependency(parser, strCode)(
194 expr
195 );
196 }
197 });
198 }
199 parser.hooks.evaluateTypeof.for(key).tap("DefinePlugin", expr => {
200 /**
201 * this is needed in case there is a recursion in the DefinePlugin
202 * to prevent an endless recursion
203 * e.g.: new DefinePlugin({
204 * "typeof a": "typeof b",
205 * "typeof b": "typeof a"
206 * });
207 */
208 if (recurseTypeof) return;
209 recurseTypeof = true;
210 const typeofCode = isTypeof
211 ? toCode(code, parser)
212 : "typeof (" + toCode(code, parser) + ")";
213 const res = parser.evaluate(typeofCode);
214 recurseTypeof = false;
215 res.setRange(expr.range);
216 return res;
217 });
218 parser.hooks.typeof.for(key).tap("DefinePlugin", expr => {
219 const typeofCode = isTypeof
220 ? toCode(code, parser)
221 : "typeof (" + toCode(code, parser) + ")";
222 const res = parser.evaluate(typeofCode);
223 if (!res.isString()) return;
224 return ParserHelpers.toConstantDependency(
225 parser,
226 JSON.stringify(res.string)
227 ).bind(parser)(expr);
228 });
229 };
230
231 /**
232 * Apply Object
233 * @param {string} key Key
234 * @param {Object} obj Object
235 * @returns {void}
236 */
237 const applyObjectDefine = (key, obj) => {
238 parser.hooks.canRename
239 .for(key)
240 .tap("DefinePlugin", ParserHelpers.approve);
241 parser.hooks.evaluateIdentifier
242 .for(key)
243 .tap("DefinePlugin", expr =>
244 new BasicEvaluatedExpression().setTruthy().setRange(expr.range)
245 );
246 parser.hooks.evaluateTypeof.for(key).tap("DefinePlugin", expr => {
247 return ParserHelpers.evaluateToString("object")(expr);
248 });
249 parser.hooks.expression.for(key).tap("DefinePlugin", expr => {
250 const strCode = stringifyObj(obj, parser);
251
252 if (/__webpack_require__/.test(strCode)) {
253 return ParserHelpers.toConstantDependencyWithWebpackRequire(
254 parser,
255 strCode
256 )(expr);
257 } else {
258 return ParserHelpers.toConstantDependency(parser, strCode)(
259 expr
260 );
261 }
262 });
263 parser.hooks.typeof.for(key).tap("DefinePlugin", expr => {
264 return ParserHelpers.toConstantDependency(
265 parser,
266 JSON.stringify("object")
267 )(expr);
268 });
269 };
270
271 walkDefinitions(definitions, "");
272 };
273
274 normalModuleFactory.hooks.parser
275 .for("javascript/auto")
276 .tap("DefinePlugin", handler);
277 normalModuleFactory.hooks.parser
278 .for("javascript/dynamic")
279 .tap("DefinePlugin", handler);
280 normalModuleFactory.hooks.parser
281 .for("javascript/esm")
282 .tap("DefinePlugin", handler);
283 }
284 );
285 }
286}
287module.exports = DefinePlugin;