UNPKG

webpack

Version:

Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.

255 lines (241 loc) 8.35 kB
/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ "use strict"; const { JAVASCRIPT_MODULE_TYPE_AUTO, JAVASCRIPT_MODULE_TYPE_DYNAMIC, JAVASCRIPT_MODULE_TYPE_ESM } = require("./ModuleTypeConstants"); const RuntimeGlobals = require("./RuntimeGlobals"); const ConstDependency = require("./dependencies/ConstDependency"); /** @typedef {import("estree").CallExpression} CallExpression */ /** @typedef {import("./Compiler")} Compiler */ /** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ /** @typedef {import("./dependencies/ContextDependency")} ContextDependency */ /** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */ /** @typedef {import("./javascript/JavascriptParser").Range} Range */ /** * Captures the source range of a renamed compatibility binding so it can be * rewritten exactly once. * @typedef {object} CompatibilitySettingsDeclaration * @property {boolean} updated * @property {DependencyLocation} loc * @property {Range} range */ /** * Stores the replacement variable name and the declaration metadata tracked * for a compatibility rewrite. * @typedef {object} CompatibilitySettings * @property {string} name * @property {CompatibilitySettingsDeclaration} declaration */ const nestedWebpackIdentifierTag = Symbol("nested webpack identifier"); const PLUGIN_NAME = "CompatibilityPlugin"; /** * Adds parser-time compatibility rewrites for legacy runtime patterns that * webpack still needs to recognize in user and generated code. */ class CompatibilityPlugin { /** * Installs parser hooks that preserve compatibility with legacy patterns * such as nested `__webpack_require__` bindings and hashbang handling. * @param {Compiler} compiler the compiler instance * @returns {void} */ apply(compiler) { compiler.hooks.compilation.tap( PLUGIN_NAME, (compilation, { normalModuleFactory }) => { compilation.dependencyTemplates.set( ConstDependency, new ConstDependency.Template() ); normalModuleFactory.hooks.parser .for(JAVASCRIPT_MODULE_TYPE_AUTO) .tap(PLUGIN_NAME, (parser, parserOptions) => { if ( parserOptions.browserify !== undefined && !parserOptions.browserify ) { return; } parser.hooks.call.for("require").tap( PLUGIN_NAME, /** * Rewrites browserify-style delegated `require` calls into a * plain webpack require reference and removes the synthetic * context dependency created for the delegator pattern. * @param {CallExpression} expr call expression * @returns {boolean | void} true when need to handle */ (expr) => { // support for browserify style require delegator: "require(o, !0)" if (expr.arguments.length !== 2) return; const second = parser.evaluateExpression(expr.arguments[1]); if (!second.isBoolean()) return; if (second.asBool() !== true) return; const dep = new ConstDependency( "require", /** @type {Range} */ (expr.callee.range) ); dep.loc = /** @type {DependencyLocation} */ (expr.loc); if (parser.state.current.dependencies.length > 0) { const last = /** @type {ContextDependency} */ ( parser.state.current.dependencies[ parser.state.current.dependencies.length - 1 ] ); if ( last.critical && last.options && last.options.request === "." && last.userRequest === "." && last.options.recursive ) { parser.state.current.dependencies.pop(); } } parser.state.module.addPresentationalDependency(dep); return true; } ); }); /** * Attaches the compatibility rewrites for a JavaScript parser * instance. * @param {JavascriptParser} parser the parser * @returns {void} */ const handler = (parser) => { // Handle nested requires parser.hooks.preStatement.tap(PLUGIN_NAME, (statement) => { if ( statement.type === "FunctionDeclaration" && statement.id && statement.id.name === RuntimeGlobals.require ) { const newName = `__nested_webpack_require_${ /** @type {Range} */ (statement.range)[0] }__`; parser.tagVariable( statement.id.name, nestedWebpackIdentifierTag, { name: newName, declaration: { updated: false, loc: /** @type {DependencyLocation} */ (statement.id.loc), range: /** @type {Range} */ (statement.id.range) } } ); return true; } }); parser.hooks.pattern .for(RuntimeGlobals.require) .tap(PLUGIN_NAME, (pattern) => { const newName = `__nested_webpack_require_${ /** @type {Range} */ (pattern.range)[0] }__`; parser.tagVariable(pattern.name, nestedWebpackIdentifierTag, { name: newName, declaration: { updated: false, loc: /** @type {DependencyLocation} */ (pattern.loc), range: /** @type {Range} */ (pattern.range) } }); if (parser.scope.topLevelScope !== true) { return true; } }); parser.hooks.pattern .for(RuntimeGlobals.exports) .tap(PLUGIN_NAME, (pattern) => { const newName = "__nested_webpack_exports__"; parser.tagVariable(pattern.name, nestedWebpackIdentifierTag, { name: newName, declaration: { updated: false, loc: /** @type {DependencyLocation} */ (pattern.loc), range: /** @type {Range} */ (pattern.range) } }); return true; }); // Update single `var __webpack_require__ = {};` and `var __webpack_exports__ = {};` without expression parser.hooks.declarator.tap(PLUGIN_NAME, (declarator) => { if ( declarator.id.type === "Identifier" && (declarator.id.name === RuntimeGlobals.exports || declarator.id.name === RuntimeGlobals.require) ) { const { name, declaration } = /** @type {CompatibilitySettings} */ ( parser.getTagData( declarator.id.name, nestedWebpackIdentifierTag ) ); if (!declaration.updated) { const dep = new ConstDependency(name, declaration.range); dep.loc = declaration.loc; parser.state.module.addPresentationalDependency(dep); declaration.updated = true; } } }); parser.hooks.expression .for(nestedWebpackIdentifierTag) .tap(PLUGIN_NAME, (expr) => { const { name, declaration } = /** @type {CompatibilitySettings} */ (parser.currentTagData); if (!declaration.updated) { const dep = new ConstDependency(name, declaration.range); dep.loc = declaration.loc; parser.state.module.addPresentationalDependency(dep); declaration.updated = true; } const dep = new ConstDependency( name, /** @type {Range} */ (expr.range) ); dep.loc = /** @type {DependencyLocation} */ (expr.loc); parser.state.module.addPresentationalDependency(dep); return true; }); // Handle hashbang parser.hooks.program.tap(PLUGIN_NAME, (program, comments) => { if (comments.length === 0) return; const c = comments[0]; if (c.type === "Line" && /** @type {Range} */ (c.range)[0] === 0) { if (parser.state.source.slice(0, 2).toString() !== "#!") return; // this is a hashbang comment const dep = new ConstDependency("//", 0); dep.loc = /** @type {DependencyLocation} */ (c.loc); parser.state.module.addPresentationalDependency(dep); } }); }; normalModuleFactory.hooks.parser .for(JAVASCRIPT_MODULE_TYPE_AUTO) .tap(PLUGIN_NAME, handler); normalModuleFactory.hooks.parser .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC) .tap(PLUGIN_NAME, handler); normalModuleFactory.hooks.parser .for(JAVASCRIPT_MODULE_TYPE_ESM) .tap(PLUGIN_NAME, handler); } ); } } module.exports = CompatibilityPlugin; module.exports.nestedWebpackIdentifierTag = nestedWebpackIdentifierTag;