| 1 | /*
|
|---|
| 2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
|---|
| 3 | Author Tobias Koppers @sokra
|
|---|
| 4 | */
|
|---|
| 5 |
|
|---|
| 6 | "use strict";
|
|---|
| 7 |
|
|---|
| 8 | const {
|
|---|
| 9 | JAVASCRIPT_MODULE_TYPE_AUTO,
|
|---|
| 10 | JAVASCRIPT_MODULE_TYPE_DYNAMIC,
|
|---|
| 11 | JAVASCRIPT_MODULE_TYPE_ESM
|
|---|
| 12 | } = require("./ModuleTypeConstants");
|
|---|
| 13 | const RuntimeGlobals = require("./RuntimeGlobals");
|
|---|
| 14 | const ConstDependency = require("./dependencies/ConstDependency");
|
|---|
| 15 |
|
|---|
| 16 | /** @typedef {import("estree").CallExpression} CallExpression */
|
|---|
| 17 | /** @typedef {import("./Compiler")} Compiler */
|
|---|
| 18 | /** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
|
|---|
| 19 | /** @typedef {import("./dependencies/ContextDependency")} ContextDependency */
|
|---|
| 20 | /** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */
|
|---|
| 21 | /** @typedef {import("./javascript/JavascriptParser").Range} Range */
|
|---|
| 22 |
|
|---|
| 23 | /**
|
|---|
| 24 | * Captures the source range of a renamed compatibility binding so it can be
|
|---|
| 25 | * rewritten exactly once.
|
|---|
| 26 | * @typedef {object} CompatibilitySettingsDeclaration
|
|---|
| 27 | * @property {boolean} updated
|
|---|
| 28 | * @property {DependencyLocation} loc
|
|---|
| 29 | * @property {Range} range
|
|---|
| 30 | */
|
|---|
| 31 |
|
|---|
| 32 | /**
|
|---|
| 33 | * Stores the replacement variable name and the declaration metadata tracked
|
|---|
| 34 | * for a compatibility rewrite.
|
|---|
| 35 | * @typedef {object} CompatibilitySettings
|
|---|
| 36 | * @property {string} name
|
|---|
| 37 | * @property {CompatibilitySettingsDeclaration} declaration
|
|---|
| 38 | */
|
|---|
| 39 |
|
|---|
| 40 | const nestedWebpackIdentifierTag = Symbol("nested webpack identifier");
|
|---|
| 41 | const PLUGIN_NAME = "CompatibilityPlugin";
|
|---|
| 42 |
|
|---|
| 43 | /**
|
|---|
| 44 | * Adds parser-time compatibility rewrites for legacy runtime patterns that
|
|---|
| 45 | * webpack still needs to recognize in user and generated code.
|
|---|
| 46 | */
|
|---|
| 47 | class CompatibilityPlugin {
|
|---|
| 48 | /**
|
|---|
| 49 | * Installs parser hooks that preserve compatibility with legacy patterns
|
|---|
| 50 | * such as nested `__webpack_require__` bindings and hashbang handling.
|
|---|
| 51 | * @param {Compiler} compiler the compiler instance
|
|---|
| 52 | * @returns {void}
|
|---|
| 53 | */
|
|---|
| 54 | apply(compiler) {
|
|---|
| 55 | compiler.hooks.compilation.tap(
|
|---|
| 56 | PLUGIN_NAME,
|
|---|
| 57 | (compilation, { normalModuleFactory }) => {
|
|---|
| 58 | compilation.dependencyTemplates.set(
|
|---|
| 59 | ConstDependency,
|
|---|
| 60 | new ConstDependency.Template()
|
|---|
| 61 | );
|
|---|
| 62 |
|
|---|
| 63 | normalModuleFactory.hooks.parser
|
|---|
| 64 | .for(JAVASCRIPT_MODULE_TYPE_AUTO)
|
|---|
| 65 | .tap(PLUGIN_NAME, (parser, parserOptions) => {
|
|---|
| 66 | if (
|
|---|
| 67 | parserOptions.browserify !== undefined &&
|
|---|
| 68 | !parserOptions.browserify
|
|---|
| 69 | ) {
|
|---|
| 70 | return;
|
|---|
| 71 | }
|
|---|
| 72 |
|
|---|
| 73 | parser.hooks.call.for("require").tap(
|
|---|
| 74 | PLUGIN_NAME,
|
|---|
| 75 | /**
|
|---|
| 76 | * Rewrites browserify-style delegated `require` calls into a
|
|---|
| 77 | * plain webpack require reference and removes the synthetic
|
|---|
| 78 | * context dependency created for the delegator pattern.
|
|---|
| 79 | * @param {CallExpression} expr call expression
|
|---|
| 80 | * @returns {boolean | void} true when need to handle
|
|---|
| 81 | */
|
|---|
| 82 | (expr) => {
|
|---|
| 83 | // support for browserify style require delegator: "require(o, !0)"
|
|---|
| 84 | if (expr.arguments.length !== 2) return;
|
|---|
| 85 | const second = parser.evaluateExpression(expr.arguments[1]);
|
|---|
| 86 | if (!second.isBoolean()) return;
|
|---|
| 87 | if (second.asBool() !== true) return;
|
|---|
| 88 | const dep = new ConstDependency(
|
|---|
| 89 | "require",
|
|---|
| 90 | /** @type {Range} */ (expr.callee.range)
|
|---|
| 91 | );
|
|---|
| 92 | dep.loc = /** @type {DependencyLocation} */ (expr.loc);
|
|---|
| 93 | if (parser.state.current.dependencies.length > 0) {
|
|---|
| 94 | const last =
|
|---|
| 95 | /** @type {ContextDependency} */
|
|---|
| 96 | (
|
|---|
| 97 | parser.state.current.dependencies[
|
|---|
| 98 | parser.state.current.dependencies.length - 1
|
|---|
| 99 | ]
|
|---|
| 100 | );
|
|---|
| 101 | if (
|
|---|
| 102 | last.critical &&
|
|---|
| 103 | last.options &&
|
|---|
| 104 | last.options.request === "." &&
|
|---|
| 105 | last.userRequest === "." &&
|
|---|
| 106 | last.options.recursive
|
|---|
| 107 | ) {
|
|---|
| 108 | parser.state.current.dependencies.pop();
|
|---|
| 109 | }
|
|---|
| 110 | }
|
|---|
| 111 | parser.state.module.addPresentationalDependency(dep);
|
|---|
| 112 | return true;
|
|---|
| 113 | }
|
|---|
| 114 | );
|
|---|
| 115 | });
|
|---|
| 116 |
|
|---|
| 117 | /**
|
|---|
| 118 | * Attaches the compatibility rewrites for a JavaScript parser
|
|---|
| 119 | * instance.
|
|---|
| 120 | * @param {JavascriptParser} parser the parser
|
|---|
| 121 | * @returns {void}
|
|---|
| 122 | */
|
|---|
| 123 | const handler = (parser) => {
|
|---|
| 124 | // Handle nested requires
|
|---|
| 125 | parser.hooks.preStatement.tap(PLUGIN_NAME, (statement) => {
|
|---|
| 126 | if (
|
|---|
| 127 | statement.type === "FunctionDeclaration" &&
|
|---|
| 128 | statement.id &&
|
|---|
| 129 | statement.id.name === RuntimeGlobals.require
|
|---|
| 130 | ) {
|
|---|
| 131 | const newName = `__nested_webpack_require_${
|
|---|
| 132 | /** @type {Range} */
|
|---|
| 133 | (statement.range)[0]
|
|---|
| 134 | }__`;
|
|---|
| 135 | parser.tagVariable(
|
|---|
| 136 | statement.id.name,
|
|---|
| 137 | nestedWebpackIdentifierTag,
|
|---|
| 138 | {
|
|---|
| 139 | name: newName,
|
|---|
| 140 | declaration: {
|
|---|
| 141 | updated: false,
|
|---|
| 142 | loc: /** @type {DependencyLocation} */ (statement.id.loc),
|
|---|
| 143 | range: /** @type {Range} */ (statement.id.range)
|
|---|
| 144 | }
|
|---|
| 145 | }
|
|---|
| 146 | );
|
|---|
| 147 | return true;
|
|---|
| 148 | }
|
|---|
| 149 | });
|
|---|
| 150 | parser.hooks.pattern
|
|---|
| 151 | .for(RuntimeGlobals.require)
|
|---|
| 152 | .tap(PLUGIN_NAME, (pattern) => {
|
|---|
| 153 | const newName = `__nested_webpack_require_${
|
|---|
| 154 | /** @type {Range} */ (pattern.range)[0]
|
|---|
| 155 | }__`;
|
|---|
| 156 | parser.tagVariable(pattern.name, nestedWebpackIdentifierTag, {
|
|---|
| 157 | name: newName,
|
|---|
| 158 | declaration: {
|
|---|
| 159 | updated: false,
|
|---|
| 160 | loc: /** @type {DependencyLocation} */ (pattern.loc),
|
|---|
| 161 | range: /** @type {Range} */ (pattern.range)
|
|---|
| 162 | }
|
|---|
| 163 | });
|
|---|
| 164 | if (parser.scope.topLevelScope !== true) {
|
|---|
| 165 | return true;
|
|---|
| 166 | }
|
|---|
| 167 | });
|
|---|
| 168 | parser.hooks.pattern
|
|---|
| 169 | .for(RuntimeGlobals.exports)
|
|---|
| 170 | .tap(PLUGIN_NAME, (pattern) => {
|
|---|
| 171 | const newName = "__nested_webpack_exports__";
|
|---|
| 172 | parser.tagVariable(pattern.name, nestedWebpackIdentifierTag, {
|
|---|
| 173 | name: newName,
|
|---|
| 174 | declaration: {
|
|---|
| 175 | updated: false,
|
|---|
| 176 | loc: /** @type {DependencyLocation} */ (pattern.loc),
|
|---|
| 177 | range: /** @type {Range} */ (pattern.range)
|
|---|
| 178 | }
|
|---|
| 179 | });
|
|---|
| 180 | return true;
|
|---|
| 181 | });
|
|---|
| 182 | // Update single `var __webpack_require__ = {};` and `var __webpack_exports__ = {};` without expression
|
|---|
| 183 | parser.hooks.declarator.tap(PLUGIN_NAME, (declarator) => {
|
|---|
| 184 | if (
|
|---|
| 185 | declarator.id.type === "Identifier" &&
|
|---|
| 186 | (declarator.id.name === RuntimeGlobals.exports ||
|
|---|
| 187 | declarator.id.name === RuntimeGlobals.require)
|
|---|
| 188 | ) {
|
|---|
| 189 | const tagData = /** @type {CompatibilitySettings | undefined} */ (
|
|---|
| 190 | parser.getTagData(
|
|---|
| 191 | declarator.id.name,
|
|---|
| 192 | nestedWebpackIdentifierTag
|
|---|
| 193 | )
|
|---|
| 194 | );
|
|---|
| 195 | if (!tagData) return;
|
|---|
| 196 | const { name, declaration } = tagData;
|
|---|
| 197 | if (!declaration.updated) {
|
|---|
| 198 | const dep = new ConstDependency(name, declaration.range);
|
|---|
| 199 | dep.loc = declaration.loc;
|
|---|
| 200 | parser.state.module.addPresentationalDependency(dep);
|
|---|
| 201 | declaration.updated = true;
|
|---|
| 202 | }
|
|---|
| 203 | }
|
|---|
| 204 | });
|
|---|
| 205 | parser.hooks.expression
|
|---|
| 206 | .for(nestedWebpackIdentifierTag)
|
|---|
| 207 | .tap(PLUGIN_NAME, (expr) => {
|
|---|
| 208 | const { name, declaration } =
|
|---|
| 209 | /** @type {CompatibilitySettings} */
|
|---|
| 210 | (parser.currentTagData);
|
|---|
| 211 | if (!declaration.updated) {
|
|---|
| 212 | const dep = new ConstDependency(name, declaration.range);
|
|---|
| 213 | dep.loc = declaration.loc;
|
|---|
| 214 | parser.state.module.addPresentationalDependency(dep);
|
|---|
| 215 | declaration.updated = true;
|
|---|
| 216 | }
|
|---|
| 217 | const dep = new ConstDependency(
|
|---|
| 218 | name,
|
|---|
| 219 | /** @type {Range} */ (expr.range)
|
|---|
| 220 | );
|
|---|
| 221 | dep.loc = /** @type {DependencyLocation} */ (expr.loc);
|
|---|
| 222 | parser.state.module.addPresentationalDependency(dep);
|
|---|
| 223 | return true;
|
|---|
| 224 | });
|
|---|
| 225 |
|
|---|
| 226 | // Handle hashbang
|
|---|
| 227 | parser.hooks.program.tap(PLUGIN_NAME, (program, comments) => {
|
|---|
| 228 | if (comments.length === 0) return;
|
|---|
| 229 | const c = comments[0];
|
|---|
| 230 | if (c.type === "Line" && /** @type {Range} */ (c.range)[0] === 0) {
|
|---|
| 231 | if (parser.state.source.slice(0, 2).toString() !== "#!") return;
|
|---|
| 232 | // this is a hashbang comment
|
|---|
| 233 | const dep = new ConstDependency("//", 0);
|
|---|
| 234 | dep.loc = /** @type {DependencyLocation} */ (c.loc);
|
|---|
| 235 | parser.state.module.addPresentationalDependency(dep);
|
|---|
| 236 | }
|
|---|
| 237 | });
|
|---|
| 238 | };
|
|---|
| 239 |
|
|---|
| 240 | normalModuleFactory.hooks.parser
|
|---|
| 241 | .for(JAVASCRIPT_MODULE_TYPE_AUTO)
|
|---|
| 242 | .tap(PLUGIN_NAME, handler);
|
|---|
| 243 | normalModuleFactory.hooks.parser
|
|---|
| 244 | .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
|
|---|
| 245 | .tap(PLUGIN_NAME, handler);
|
|---|
| 246 | normalModuleFactory.hooks.parser
|
|---|
| 247 | .for(JAVASCRIPT_MODULE_TYPE_ESM)
|
|---|
| 248 | .tap(PLUGIN_NAME, handler);
|
|---|
| 249 | }
|
|---|
| 250 | );
|
|---|
| 251 | }
|
|---|
| 252 | }
|
|---|
| 253 |
|
|---|
| 254 | module.exports = CompatibilityPlugin;
|
|---|
| 255 | module.exports.nestedWebpackIdentifierTag = nestedWebpackIdentifierTag;
|
|---|