refactor dynamic and harmony exports parser state

bailout when using AMD define
This commit is contained in:
Tobias Koppers 2019-12-06 10:19:41 +01:00
parent dd4d68ac8f
commit d9a2b7997a
8 changed files with 161 additions and 55 deletions

View File

@ -12,6 +12,7 @@ const AMDRequireContextDependency = require("./AMDRequireContextDependency");
const AMDRequireItemDependency = require("./AMDRequireItemDependency");
const ConstDependency = require("./ConstDependency");
const ContextDependencyHelpers = require("./ContextDependencyHelpers");
const DynamicExports = require("./DynamicExports");
const LocalModuleDependency = require("./LocalModuleDependency");
const { addLocalModule, getLocalModule } = require("./LocalModulesHelpers");
@ -217,6 +218,7 @@ class AMDDefineDependencyParserPlugin {
default:
return;
}
DynamicExports.bailout(parser.state.module);
let fnParams = null;
let fnParamsOffset = 0;
if (fn) {

View File

@ -9,46 +9,55 @@ const RuntimeGlobals = require("../RuntimeGlobals");
const { evaluateToString } = require("../javascript/JavascriptParserHelpers");
const CommonJsExportsDependency = require("./CommonJsExportsDependency");
const CommonJsSelfReferenceDependency = require("./CommonJsSelfReferenceDependency");
const DynamicExports = require("./DynamicExports");
const HarmonyExports = require("./HarmonyExports");
const ModuleDecoratorDependency = require("./ModuleDecoratorDependency");
/** @typedef {import("../NormalModule")} NormalModule */
/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
class CommonJsExportsDependencyParserPlugin {
/** @type {WeakMap<NormalModule, boolean>} */
const moduleExportsState = new WeakMap();
class CommonJsExportsParserPlugin {
static bailout(module) {
const value = moduleExportsState.get(module);
moduleExportsState.set(module, false);
if (value === true) {
module.buildMeta.exportsType = undefined;
module.buildMeta.defaultObject = false;
}
}
/**
* @param {JavascriptParser} parser the parser
*/
apply(parser) {
const bailedOut = new WeakSet();
const enableModuleExports = () => {
if (!parser.state.module.buildMeta.exportsType) {
if (bailedOut.has(parser.state)) return;
parser.state.module.buildMeta.exportsType = "default";
parser.state.module.buildMeta.defaultObject = "redirect";
}
const enableStructuredExports = () => {
DynamicExports.enable(parser.state.module);
};
const checkNamespace = (members, valueExpr) => {
if (!DynamicExports.isEnabled(parser.state.module)) return;
if (members.length > 0 && members[0] === "__esModule") {
if (
valueExpr &&
valueExpr.type === "Literal" &&
valueExpr.value === true
) {
parser.state.module.buildMeta.exportsType = "flagged";
DynamicExports.setFlagged(parser.state.module);
} else {
bailoutModuleExports();
DynamicExports.bailout(parser.state.module);
}
}
};
const bailoutModuleExports = () => {
bailedOut.add(parser.state);
parser.state.module.buildMeta.exportsType = undefined;
parser.state.module.buildMeta.defaultObject = false;
const bailout = () => {
DynamicExports.bailout(parser.state.module);
};
// metadata //
parser.hooks.evaluateTypeof
.for("module")
.tap("CommonJsExportsDependencyParserPlugin", evaluateToString("object"));
.tap("CommonJsExportsParserPlugin", evaluateToString("object"));
parser.hooks.evaluateTypeof
.for("exports")
.tap("CommonJsPlugin", evaluateToString("object"));
@ -56,9 +65,9 @@ class CommonJsExportsDependencyParserPlugin {
// exporting //
parser.hooks.assignMemberChain
.for("exports")
.tap("CommonJsExportsDependencyParserPlugin", (expr, members) => {
if (parser.state.harmonyModule) return;
enableModuleExports();
.tap("CommonJsExportsParserPlugin", (expr, members) => {
if (HarmonyExports.isEnabled(parser.state.module)) return;
enableStructuredExports();
checkNamespace(members, expr.right);
const dep = new CommonJsExportsDependency(
expr.left.range,
@ -71,10 +80,10 @@ class CommonJsExportsDependencyParserPlugin {
});
parser.hooks.assignMemberChain
.for("this")
.tap("CommonJsExportsDependencyParserPlugin", (expr, members) => {
if (parser.state.harmonyModule) return;
.tap("CommonJsExportsParserPlugin", (expr, members) => {
if (HarmonyExports.isEnabled(parser.state.module)) return;
if (!parser.scope.topLevelScope) return;
enableModuleExports();
enableStructuredExports();
checkNamespace(members, expr.right);
const dep = new CommonJsExportsDependency(
expr.left.range,
@ -87,10 +96,10 @@ class CommonJsExportsDependencyParserPlugin {
});
parser.hooks.assignMemberChain
.for("module")
.tap("CommonJsExportsDependencyParserPlugin", (expr, members) => {
if (parser.state.harmonyModule) return;
.tap("CommonJsExportsParserPlugin", (expr, members) => {
if (HarmonyExports.isEnabled(parser.state.module)) return;
if (members[0] !== "exports" || members.length <= 1) return;
enableModuleExports();
enableStructuredExports();
checkNamespace(members, expr.right);
const dep = new CommonJsExportsDependency(
expr.left.range,
@ -105,9 +114,9 @@ class CommonJsExportsDependencyParserPlugin {
// Self reference //
parser.hooks.expression
.for("exports")
.tap("CommonJsExportsDependencyParserPlugin", expr => {
if (parser.state.harmonyModule) return;
bailoutModuleExports();
.tap("CommonJsExportsParserPlugin", expr => {
if (HarmonyExports.isEnabled(parser.state.module)) return;
bailout();
const dep = new CommonJsSelfReferenceDependency(
expr.range,
"exports",
@ -119,9 +128,9 @@ class CommonJsExportsDependencyParserPlugin {
});
parser.hooks.expression
.for("module.exports")
.tap("CommonJsExportsDependencyParserPlugin", expr => {
if (parser.state.harmonyModule) return;
bailoutModuleExports();
.tap("CommonJsExportsParserPlugin", expr => {
if (HarmonyExports.isEnabled(parser.state.module)) return;
bailout();
const dep = new CommonJsSelfReferenceDependency(
expr.range,
"module.exports",
@ -133,10 +142,10 @@ class CommonJsExportsDependencyParserPlugin {
});
parser.hooks.expression
.for("this")
.tap("CommonJsExportsDependencyParserPlugin", expr => {
if (parser.state.harmonyModule) return;
.tap("CommonJsExportsParserPlugin", expr => {
if (HarmonyExports.isEnabled(parser.state.module)) return;
if (!parser.scope.topLevelScope) return;
bailoutModuleExports();
bailout();
const dep = new CommonJsSelfReferenceDependency(expr.range, "this", []);
dep.loc = expr.loc;
parser.state.module.addDependency(dep);
@ -145,8 +154,8 @@ class CommonJsExportsDependencyParserPlugin {
// Bailouts //
parser.hooks.expression.for("module").tap("CommonJsPlugin", expr => {
bailoutModuleExports();
const isHarmony = parser.state.harmonyModule;
bailout();
const isHarmony = HarmonyExports.isEnabled(parser.state.module);
const dep = new ModuleDecoratorDependency(
isHarmony
? RuntimeGlobals.harmonyModuleDecorator
@ -158,4 +167,4 @@ class CommonJsExportsDependencyParserPlugin {
});
}
}
module.exports = CommonJsExportsDependencyParserPlugin;
module.exports = CommonJsExportsParserPlugin;

View File

@ -0,0 +1,57 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("../NormalModule")} NormalModule */
/** @type {WeakMap<NormalModule, boolean>} */
const moduleExportsState = new WeakMap();
/**
* @param {NormalModule} module the module
* @returns {void}
*/
exports.bailout = module => {
const value = moduleExportsState.get(module);
moduleExportsState.set(module, false);
if (value === true) {
module.buildMeta.exportsType = undefined;
module.buildMeta.defaultObject = false;
}
};
/**
* @param {NormalModule} module the module
* @returns {void}
*/
exports.enable = module => {
const value = moduleExportsState.get(module);
if (value === false) return;
moduleExportsState.set(module, true);
if (value !== true) {
module.buildMeta.exportsType = "default";
module.buildMeta.defaultObject = "redirect";
}
};
/**
* @param {NormalModule} module the module
* @returns {void}
*/
exports.setFlagged = module => {
const value = moduleExportsState.get(module);
if (value !== true) return;
module.buildMeta.exportsType = "flagged";
};
/**
* @param {NormalModule} module the module
* @returns {boolean} true, when enabled
*/
exports.isEnabled = module => {
const value = moduleExportsState.get(module);
return value === true;
};

View File

@ -5,7 +5,9 @@
"use strict";
const DynamicExports = require("./DynamicExports");
const HarmonyCompatibilityDependency = require("./HarmonyCompatibilityDependency");
const HarmonyExports = require("./HarmonyExports");
module.exports = class HarmonyDetectionParserPlugin {
constructor(options) {
@ -48,16 +50,10 @@ module.exports = class HarmonyDetectionParserPlugin {
index: -3
};
module.addPresentationalDependency(compatDep);
parser.state.harmonyModule = true;
DynamicExports.bailout(module);
HarmonyExports.enable(module, isStrictHarmony);
parser.scope.isStrict = true;
module.buildMeta.exportsType = "namespace";
module.buildMeta.async = isAsync;
module.buildInfo.strict = true;
module.buildInfo.exportsArgument = "__webpack_exports__";
if (isStrictHarmony) {
module.buildMeta.strictHarmonyModule = true;
module.buildInfo.moduleArgument = "__webpack_module__";
}
}
});
@ -68,7 +64,7 @@ module.exports = class HarmonyDetectionParserPlugin {
"The top-level-await experiment is not enabled (set experiments.topLevelAwait: true to enabled it)"
);
}
if (!parser.state.harmonyModule) {
if (!HarmonyExports.isEnabled(parser.state.module)) {
throw new Error(
"Top-level-await is only supported in EcmaScript Modules"
);
@ -77,13 +73,13 @@ module.exports = class HarmonyDetectionParserPlugin {
});
const skipInHarmony = () => {
if (parser.state.harmonyModule) {
if (HarmonyExports.isEnabled(parser.state.module)) {
return true;
}
};
const nullInHarmony = () => {
if (parser.state.harmonyModule) {
if (HarmonyExports.isEnabled(parser.state.module)) {
return null;
}
};

View File

@ -0,0 +1,40 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("../NormalModule")} NormalModule */
/** @type {WeakMap<NormalModule, boolean>} */
const moduleExportsState = new WeakMap();
/**
* @param {NormalModule} module the module
* @param {boolean} isStrictHarmony strict harmony mode should be enabled
* @returns {void}
*/
exports.enable = (module, isStrictHarmony) => {
const value = moduleExportsState.get(module);
if (value === false) return;
moduleExportsState.set(module, true);
if (value !== true) {
module.buildMeta.exportsType = "namespace";
module.buildInfo.strict = true;
module.buildInfo.exportsArgument = "__webpack_exports__";
if (isStrictHarmony) {
module.buildMeta.strictHarmonyModule = true;
module.buildInfo.moduleArgument = "__webpack_module__";
}
}
};
/**
* @param {NormalModule} module the module
* @returns {boolean} true, when enabled
*/
exports.isEnabled = module => {
const value = moduleExportsState.get(module);
return value === true;
};

View File

@ -9,6 +9,7 @@ const HotModuleReplacementPlugin = require("../HotModuleReplacementPlugin");
const ConstDependency = require("./ConstDependency");
const HarmonyAcceptDependency = require("./HarmonyAcceptDependency");
const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency");
const HarmonyExports = require("./HarmonyExports");
const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency");
const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency");
@ -168,7 +169,7 @@ module.exports = class HarmonyImportDependencyParserPlugin {
hotAcceptCallback.tap(
"HarmonyImportDependencyParserPlugin",
(expr, requests) => {
if (!parser.state.harmonyModule) {
if (!HarmonyExports.isEnabled(parser.state.module)) {
// This is not a harmony module, skip it
return;
}
@ -192,7 +193,7 @@ module.exports = class HarmonyImportDependencyParserPlugin {
hotAcceptWithoutCallback.tap(
"HarmonyImportDependencyParserPlugin",
(expr, requests) => {
if (!parser.state.harmonyModule) {
if (!HarmonyExports.isEnabled(parser.state.module)) {
// This is not a harmony module, skip it
return;
}

View File

@ -6,6 +6,7 @@
"use strict";
const ConstDependency = require("./ConstDependency");
const HarmonyExports = require("./HarmonyExports");
class HarmonyTopLevelThisParserPlugin {
apply(parser) {
@ -13,8 +14,7 @@ class HarmonyTopLevelThisParserPlugin {
.for("this")
.tap("HarmonyTopLevelThisParserPlugin", node => {
if (!parser.scope.topLevelScope) return;
const isHarmony = parser.state.harmonyModule;
if (isHarmony) {
if (HarmonyExports.isEnabled(parser.state.module)) {
const dep = new ConstDependency("undefined", node.range, null);
dep.loc = node.loc;
parser.state.module.addPresentationalDependency(dep);

View File

@ -7,8 +7,9 @@
const ChunkGraph = require("../ChunkGraph");
const ModuleGraph = require("../ModuleGraph");
const NormalModule = require("../NormalModule");
const { STAGE_DEFAULT } = require("../OptimizationStages");
const HarmonyCompatibilityDependency = require("../dependencies/HarmonyCompatibilityDependency");
const HarmonyExports = require("../dependencies/HarmonyExports");
const HarmonyImportDependency = require("../dependencies/HarmonyImportDependency");
const ModuleHotAcceptDependency = require("../dependencies/ModuleHotAcceptDependency");
const ModuleHotDeclineDependency = require("../dependencies/ModuleHotDeclineDependency");
@ -113,9 +114,9 @@ class ModuleConcatenationPlugin {
if (
!module.buildMeta ||
module.buildMeta.exportsType !== "namespace" ||
module.presentationalDependencies === undefined ||
!module.presentationalDependencies.some(
d => d instanceof HarmonyCompatibilityDependency
!(
module instanceof NormalModule &&
HarmonyExports.isEnabled(module)
)
) {
setBailoutReason(module, "Module is not an ECMAScript module");