| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Haijie Xie @hai-x
- */
- "use strict";
- const { UsageState } = require("../ExportsInfo");
- const {
- JAVASCRIPT_MODULE_TYPE_AUTO,
- JAVASCRIPT_MODULE_TYPE_ESM
- } = require("../ModuleTypeConstants");
- const BasicEvaluatedExpression = require("../javascript/BasicEvaluatedExpression");
- const { VariableInfoFlags } = require("../javascript/JavascriptParser");
- const {
- InlinedUsedName,
- enableInlineExports,
- toInlinedValue
- } = require("./InlineExports");
- /** @import { InlinedValue } from "./InlineExports" */
- /** @import Compiler from "../Compiler" */
- /** @import ExportsInfo from "../ExportsInfo" */
- /** @import JavascriptParser from "../javascript/JavascriptParser" */
- /**
- * @import {
- * JavascriptModuleBuildInfo
- * } from "../javascript/JavascriptModule"
- */
- /** @typedef {{ inlineExports: boolean }} ConstValueParserPluginOptions */
- const PLUGIN_NAME = "ConstExportsPlugin";
- // Parser tag for top-level `const X = <primitive>` bindings
- const CONST_BINDING_TAG = Symbol("const binding");
- class ConstExportsPlugin {
- /**
- * @param {ConstValueParserPluginOptions} options plugin options
- */
- constructor(options) {
- /** @type {ConstValueParserPluginOptions} */
- this.options = options;
- }
- /**
- * @param {Compiler} compiler the compiler instance
- * @returns {void}
- */
- apply(compiler) {
- compiler.hooks.compilation.tap(
- PLUGIN_NAME,
- (compilation, { normalModuleFactory }) => {
- /**
- * @param {JavascriptParser} parser the parser
- * @returns {void}
- */
- const handleInlineExports = (parser) => {
- parser.hooks.program.tap(PLUGIN_NAME, () => {
- const buildInfo =
- /** @type {JavascriptModuleBuildInfo | undefined} */
- (parser.state.module.buildInfo);
- if (buildInfo) buildInfo.inlineExports = true;
- });
- // Propagate inlined constant through evaluator so chained constants and uses see the literal
- parser.hooks.evaluateIdentifier
- .for(CONST_BINDING_TAG)
- .tap(PLUGIN_NAME, (expr) => {
- const tagData =
- /** @type {{ value?: InlinedValue } | undefined} */
- (parser.currentTagData);
- if (!tagData || !tagData.value) return;
- const { value } = tagData;
- const eval_ = new BasicEvaluatedExpression().setRange(
- /** @type {[number, number]} */ (expr.range)
- );
- switch (value.kind) {
- case "null":
- return eval_.setNull();
- case "undefined":
- return eval_.setUndefined();
- case "boolean":
- return eval_.setBoolean(/** @type {boolean} */ (value.value));
- case "number":
- return eval_.setNumber(/** @type {number} */ (value.value));
- case "string":
- return eval_.setString(/** @type {string} */ (value.value));
- }
- });
- };
- /**
- * @param {JavascriptParser} parser the parser
- * @returns {void}
- */
- const handleConstValue = (parser) => {
- // Only const is tracked; function/class names can be reassigned in sloppy mode.
- // Re-exports always use getters: cross-module bindings may be mutable,
- // and SideEffectsFlagPlugin can rewire connections skipping the template.
- parser.hooks.preDeclarator.tap(
- PLUGIN_NAME,
- (declarator, statement) => {
- // Detect top-level `const` declarations:
- // - Tag ALL const bindings with CONST_BINDING_TAG (for const detection via tag system)
- // - Carry inlined primitive value in tag data when eligible (for inline optimization)
- if (statement.kind !== "const") return;
- if (parser.scope.topLevelScope !== true) return;
- if (declarator.id.type === "Identifier") {
- let inlinedValue;
- if (this.options.inlineExports && declarator.init) {
- const evaluated = parser.evaluateExpression(declarator.init);
- inlinedValue = toInlinedValue(evaluated);
- }
- parser.tagVariable(
- declarator.id.name,
- CONST_BINDING_TAG,
- inlinedValue ? { value: inlinedValue } : {},
- VariableInfoFlags.Normal
- );
- } else {
- // Handle destructuring patterns (ObjectPattern, ArrayPattern, etc.)
- parser.enterPattern(declarator.id, (name) => {
- parser.tagVariable(
- name,
- CONST_BINDING_TAG,
- {},
- VariableInfoFlags.Normal
- );
- });
- }
- }
- );
- };
- /**
- * @param {JavascriptParser} parser the parser
- * @returns {void}
- */
- const handler = (parser) => {
- handleConstValue(parser);
- if (this.options.inlineExports) {
- handleInlineExports(parser);
- }
- };
- normalModuleFactory.hooks.parser
- .for(JAVASCRIPT_MODULE_TYPE_AUTO)
- .tap(PLUGIN_NAME, handler);
- normalModuleFactory.hooks.parser
- .for(JAVASCRIPT_MODULE_TYPE_ESM)
- .tap(PLUGIN_NAME, handler);
- if (!this.options.inlineExports) return;
- // Lets HarmonyImportSpecifierDependency.getCondition skip the inline check
- // (and stay unconditional) when this plugin is not active
- enableInlineExports(compilation.moduleGraph);
- const moduleGraph = compilation.moduleGraph;
- compilation.hooks.optimizeDependencies.tap(PLUGIN_NAME, (modules) => {
- /** @type {Set<ExportsInfo>} */
- const visited = new Set();
- /** @type {ExportsInfo[]} */
- let queue = [];
- for (const module of modules) {
- queue.push(moduleGraph.getExportsInfo(module));
- }
- while (queue.length > 0) {
- const items = queue;
- queue = [];
- for (const exportsInfo of items) {
- if (visited.has(exportsInfo)) continue;
- visited.add(exportsInfo);
- // Other-export usage means we can't safely inline (some non-statically-known consumer)
- if (
- exportsInfo.otherExportsInfo.getUsed(undefined) !==
- UsageState.Unused
- ) {
- continue;
- }
- for (const exportInfo of exportsInfo.ownedExports) {
- const inlined = exportInfo.canInline();
- const doInline =
- !exportInfo.hasUsedName() &&
- inlined !== undefined &&
- exportInfo.provided === true;
- if (doInline) {
- exportInfo.setUsedName(new InlinedUsedName(inlined));
- exportsInfo.markInlinedExports();
- }
- if (exportInfo.exportsInfoOwned && exportInfo.exportsInfo) {
- const used = exportInfo.getUsed(undefined);
- if (
- used === UsageState.OnlyPropertiesUsed ||
- used === UsageState.Unused
- ) {
- queue.push(exportInfo.exportsInfo);
- }
- }
- }
- }
- }
- });
- }
- );
- }
- }
- ConstExportsPlugin.CONST_BINDING_TAG = CONST_BINDING_TAG;
- module.exports = ConstExportsPlugin;
|