refactor Parser to allow variable tagging

use variable tagging in harmony pluging (instead of magic renaming)
Parser no longer calls hooks.expression for x in `x = ...`, hooks.pattern is called instead
remove rootName argument from hooks.expressionMemberChain and hooks.callMemberChain
This commit is contained in:
Tobias Koppers 2019-08-29 15:28:19 +02:00
parent 4a88ea6de0
commit ab169893a1
18 changed files with 645 additions and 353 deletions

View File

@ -10,6 +10,7 @@ const {
evaluateToString
} = require("./JavascriptParserHelpers");
const RuntimeGlobals = require("./RuntimeGlobals");
const WebpackError = require("./WebpackError");
const ConstDependency = require("./dependencies/ConstDependency");
const ChunkNameRuntimeModule = require("./runtime/ChunkNameRuntimeModule");
const GetFullHashRuntimeModule = require("./runtime/GetFullHashRuntimeModule");
@ -21,52 +22,62 @@ const REPLACEMENTS = {
__webpack_require__: {
expr: RuntimeGlobals.require,
req: [RuntimeGlobals.require],
type: "function"
type: "function",
assign: false
},
__webpack_public_path__: {
expr: RuntimeGlobals.publicPath,
req: [RuntimeGlobals.publicPath],
type: "string"
type: "string",
assign: true
},
__webpack_modules__: {
expr: RuntimeGlobals.moduleFactories,
req: [RuntimeGlobals.moduleFactories],
type: "object"
type: "object",
assign: false
},
__webpack_chunk_load__: {
expr: RuntimeGlobals.ensureChunk,
req: [RuntimeGlobals.ensureChunk],
type: "function"
type: "function",
assign: true
},
__non_webpack_require__: {
expr: "require",
req: null,
type: undefined // type is not know, depends on environment
type: undefined, // type is not know, depends on environment
assign: true
},
__webpack_nonce__: {
expr: RuntimeGlobals.scriptNonce,
req: [RuntimeGlobals.scriptNonce],
type: "string"
type: "string",
assign: true
},
__webpack_hash__: {
expr: `${RuntimeGlobals.getFullHash}()`,
req: [RuntimeGlobals.getFullHash],
type: "string"
type: "string",
assign: false
},
__webpack_chunkname__: {
expr: RuntimeGlobals.chunkName,
req: [RuntimeGlobals.chunkName],
type: "string"
type: "string",
assign: false
},
__webpack_get_script_filename__: {
expr: RuntimeGlobals.getChunkScriptFilename,
req: [RuntimeGlobals.getChunkScriptFilename],
type: "function"
type: "function",
assign: true
},
"require.onError": {
expr: RuntimeGlobals.uncaughtErrorHandler,
req: [RuntimeGlobals.uncaughtErrorHandler],
type: undefined // type is not know, could be function or undefined
type: undefined, // type is not know, could be function or undefined
assign: true // is never a pattern
}
};
/* eslint-enable camelcase */
@ -112,6 +123,13 @@ class APIPlugin {
"APIPlugin",
toConstantDependency(parser, info.expr, info.req)
);
if (info.assign === false) {
parser.hooks.assign.for(key).tap("APIPlugin", expr => {
const err = new WebpackError(`${key} must not be assigned`);
err.loc = expr.loc;
throw err;
});
}
if (info.type) {
parser.hooks.evaluateTypeof
.for(key)

View File

@ -21,22 +21,25 @@ const TypeTemplateString = 11;
class BasicEvaluatedExpression {
constructor() {
this.type = TypeUnknown;
this.range = null;
this.range = undefined;
this.falsy = false;
this.truthy = false;
this.bool = null;
this.number = null;
this.regExp = null;
this.string = null;
this.quasis = null;
this.parts = null;
this.array = null;
this.items = null;
this.options = null;
this.prefix = null;
this.postfix = null;
this.wrappedInnerExpressions = null;
this.expression = null;
this.bool = undefined;
this.number = undefined;
this.regExp = undefined;
this.string = undefined;
this.quasis = undefined;
this.parts = undefined;
this.array = undefined;
this.items = undefined;
this.options = undefined;
this.prefix = undefined;
this.postfix = undefined;
this.wrappedInnerExpressions = undefined;
this.identifier = undefined;
this.rootInfo = undefined;
this.getMembers = undefined;
this.expression = undefined;
}
isNull() {
@ -171,9 +174,11 @@ class BasicEvaluatedExpression {
return this;
}
setIdentifier(identifier) {
setIdentifier(identifier, rootInfo, getMembers) {
this.type = TypeIdentifier;
this.identifier = identifier;
this.rootInfo = rootInfo;
this.getMembers = getMembers;
return this;
}

View File

@ -143,7 +143,7 @@ class CommonJsStuffPlugin {
.for("module.hot")
.tap(
"CommonJsStuffPlugin",
evaluateToIdentifier("module.hot", false)
evaluateToIdentifier("module.hot", "module", () => ["hot"], false)
);
parser.hooks.expression
.for("module")

View File

@ -10,6 +10,8 @@ const ConstDependency = require("./dependencies/ConstDependency");
/** @typedef {import("./Compiler")} Compiler */
/** @typedef {import("./JavascriptParser")} JavascriptParser */
const nestedWebpackRequireTag = Symbol("nested __webpack_require__");
class CompatibilityPlugin {
/**
* Apply the plugin
@ -75,23 +77,17 @@ class CompatibilityPlugin {
const dep = new ConstDependency(newName, pattern.range);
dep.loc = pattern.loc;
parser.state.current.addDependency(dep);
parser.scope.renames.set(
parser.tagVariable(
pattern.name,
"nested __webpack_require__"
);
parser.scope.renames.set(
"nested __webpack_require__ name",
nestedWebpackRequireTag,
newName
);
parser.scope.definitions.delete(pattern.name);
return true;
});
parser.hooks.expression
.for("nested __webpack_require__")
.for(nestedWebpackRequireTag)
.tap("CompatibilityPlugin", expr => {
const newName = parser.scope.renames.get(
"nested __webpack_require__ name"
);
const newName = parser.currentTagData;
const dep = new ConstDependency(newName, expr.range);
dep.loc = expr.loc;
parser.state.current.addDependency(dep);

View File

@ -31,7 +31,7 @@ class ExportsInfoApiPlugin {
const handler = parser => {
parser.hooks.expressionMemberChain
.for("__webpack_exports_info__")
.tap("ExportsInfoApiPlugin", (expr, rootRaw, members) => {
.tap("ExportsInfoApiPlugin", (expr, members) => {
const dep =
members.length >= 2
? new ExportsInfoDependency(

View File

@ -108,7 +108,12 @@ class HotModuleReplacementPlugin {
before: "NodeStuffPlugin"
},
expr => {
return evaluateToIdentifier("module.hot", true)(expr);
return evaluateToIdentifier(
"module.hot",
"module",
() => ["hot"],
true
)(expr);
}
);
parser.hooks.call

File diff suppressed because it is too large Load Diff

View File

@ -32,10 +32,10 @@ exports.evaluateToBoolean = value => {
};
};
exports.evaluateToIdentifier = (identifier, truthy) => {
exports.evaluateToIdentifier = (identifier, rootInfo, getMembers, truthy) => {
return function identifierExpression(expr) {
let evex = new BasicEvaluatedExpression()
.setIdentifier(identifier)
.setIdentifier(identifier, rootInfo, getMembers)
.setRange(expr.range);
if (truthy === true) {
evex = evex.setTruthy();

View File

@ -228,7 +228,7 @@ class AMDDefineDependencyParserPlugin {
}
}
}
let fnRenames = parser.scope.renames.createChild();
let fnRenames = new Map();
if (array) {
const identifiers = {};
const param = parser.evaluateExpression(array);
@ -243,7 +243,7 @@ class AMDDefineDependencyParserPlugin {
if (fnParams) {
fnParams = fnParams.slice(fnParamsOffset).filter((param, idx) => {
if (identifiers[idx]) {
fnRenames.set(param.name, identifiers[idx]);
fnRenames.set(param.name, parser.getVariableInfo(identifiers[idx]));
return false;
}
return true;
@ -254,7 +254,7 @@ class AMDDefineDependencyParserPlugin {
if (fnParams) {
fnParams = fnParams.slice(fnParamsOffset).filter((param, idx) => {
if (identifiers[idx]) {
fnRenames.set(param.name, identifiers[idx]);
fnRenames.set(param.name, parser.getVariableInfo(identifiers[idx]));
return false;
}
return true;
@ -265,7 +265,9 @@ class AMDDefineDependencyParserPlugin {
if (fn && isUnboundFunctionExpression(fn)) {
inTry = parser.scope.inTry;
parser.inScope(fnParams, () => {
parser.scope.renames = fnRenames;
for (const [name, varInfo] of fnRenames) {
parser.setVariable(name, varInfo);
}
parser.scope.inTry = inTry;
if (fn.body.type === "BlockStatement") {
parser.walkStatement(fn.body);
@ -280,7 +282,9 @@ class AMDDefineDependencyParserPlugin {
i => !["require", "module", "exports"].includes(i.name)
),
() => {
parser.scope.renames = fnRenames;
for (const [name, varInfo] of fnRenames) {
parser.setVariable(name, varInfo);
}
parser.scope.inTry = inTry;
if (fn.callee.object.body.type === "BlockStatement") {
parser.walkStatement(fn.callee.object.body);

View File

@ -124,7 +124,7 @@ class AMDPlugin {
const handler = (parser, parserOptions) => {
if (parserOptions.amd !== undefined && !parserOptions.amd) return;
const tapOptionsHooks = optionExpr => {
const tapOptionsHooks = (optionExpr, rootName, getMembers) => {
parser.hooks.expression
.for(optionExpr)
.tap(
@ -135,7 +135,10 @@ class AMDPlugin {
);
parser.hooks.evaluateIdentifier
.for(optionExpr)
.tap("AMDPlugin", evaluateToIdentifier(optionExpr, true));
.tap(
"AMDPlugin",
evaluateToIdentifier(optionExpr, rootName, getMembers, true)
);
parser.hooks.evaluateTypeof
.for(optionExpr)
.tap("AMDPlugin", evaluateToString("object"));
@ -150,9 +153,13 @@ class AMDPlugin {
new AMDRequireDependenciesBlockParserPlugin(options).apply(parser);
new AMDDefineDependencyParserPlugin(options).apply(parser);
tapOptionsHooks("define.amd");
tapOptionsHooks("require.amd");
tapOptionsHooks("__webpack_amd_options__");
tapOptionsHooks("define.amd", "define", () => "amd");
tapOptionsHooks("require.amd", "require", () => ["amd"]);
tapOptionsHooks(
"__webpack_amd_options__",
"__webpack_amd_options__",
() => []
);
parser.hooks.expression.for("define").tap("AMDPlugin", expr => {
const dep = new ConstDependency(

View File

@ -84,12 +84,7 @@ class CommonJsPlugin {
if (parserOptions.commonjs !== undefined && !parserOptions.commonjs)
return;
const requireExpressions = [
"require",
"require.resolve",
"require.resolveWeak"
];
for (let expression of requireExpressions) {
const tapRequireExpression = (expression, getMembers) => {
parser.hooks.typeof
.for(expression)
.tap(
@ -101,8 +96,14 @@ class CommonJsPlugin {
.tap("CommonJsPlugin", evaluateToString("function"));
parser.hooks.evaluateIdentifier
.for(expression)
.tap("CommonJsPlugin", evaluateToIdentifier(expression, true));
}
.tap(
"CommonJsPlugin",
evaluateToIdentifier(expression, "require", getMembers, true)
);
};
tapRequireExpression("require", () => []);
tapRequireExpression("require.resolve", () => ["resolve"]);
tapRequireExpression("require.resolveWeak", () => ["resolveWeak"]);
parser.hooks.evaluateTypeof
.for("module")
@ -121,15 +122,14 @@ class CommonJsPlugin {
const dep = new ConstDependency("var require;", 0);
dep.loc = expr.loc;
parser.state.current.addDependency(dep);
parser.scope.definitions.add("require");
return true;
});
parser.hooks.canRename
.for("require")
.tap("CommonJsPlugin", () => true);
parser.hooks.rename.for("require").tap("CommonJsPlugin", expr => {
// define the require variable. It's still undefined, but not "not defined".
const dep = new ConstDependency("var require;", 0);
// To avoid "not defined" error, replace the value with undefined
const dep = new ConstDependency("undefined", expr.range);
dep.loc = expr.loc;
parser.state.current.addDependency(dep);
return false;

View File

@ -11,6 +11,8 @@ const HarmonyExportHeaderDependency = require("./HarmonyExportHeaderDependency")
const HarmonyExportImportedSpecifierDependency = require("./HarmonyExportImportedSpecifierDependency");
const HarmonyExportSpecifierDependency = require("./HarmonyExportSpecifierDependency");
const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency");
const harmonySpecifierTag = require("./HarmonyImportDependencyParserPlugin")
.harmonySpecifierTag;
module.exports = class HarmonyExportDependencyParserPlugin {
constructor(options) {
@ -93,13 +95,12 @@ module.exports = class HarmonyExportDependencyParserPlugin {
parser.hooks.exportSpecifier.tap(
"HarmonyExportDependencyParserPlugin",
(statement, id, name, idx) => {
const rename = parser.scope.renames.get(id);
const settings = parser.getTagData(id, harmonySpecifierTag);
let dep;
const harmonyNamedExports = (parser.state.harmonyNamedExports =
parser.state.harmonyNamedExports || new Set());
harmonyNamedExports.add(name);
if (rename === "imported var") {
const settings = parser.state.harmonySpecifier.get(id);
if (settings) {
dep = new HarmonyExportImportedSpecifierDependency(
settings.source,
settings.sourceOrder,

View File

@ -12,6 +12,8 @@ const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency")
const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency");
const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency");
const harmonySpecifierTag = Symbol("harmony import");
module.exports = class HarmonyImportDependencyParserPlugin {
constructor(options) {
const { module: moduleOptions } = options;
@ -47,13 +49,9 @@ module.exports = class HarmonyImportDependencyParserPlugin {
parser.hooks.importSpecifier.tap(
"HarmonyImportDependencyParserPlugin",
(statement, source, id, name) => {
parser.scope.definitions.delete(name);
parser.scope.renames.set(name, "imported var");
if (!parser.state.harmonySpecifier) {
parser.state.harmonySpecifier = new Map();
}
const ids = id === null ? [] : [id];
parser.state.harmonySpecifier.set(name, {
parser.tagVariable(name, harmonySpecifierTag, {
name,
source,
ids,
sourceOrder: parser.state.lastHarmonyImportOrder,
@ -63,15 +61,14 @@ module.exports = class HarmonyImportDependencyParserPlugin {
}
);
parser.hooks.expression
.for("imported var")
.for(harmonySpecifierTag)
.tap("HarmonyImportDependencyParserPlugin", expr => {
const name = expr.name;
const settings = parser.state.harmonySpecifier.get(name);
const settings = parser.currentTagData;
const dep = new HarmonyImportSpecifierDependency(
settings.source,
settings.sourceOrder,
settings.ids,
name,
settings.name,
expr.range,
this.strictExportPresence
);
@ -84,15 +81,15 @@ module.exports = class HarmonyImportDependencyParserPlugin {
return true;
});
parser.hooks.expressionMemberChain
.for("imported var")
.tap("HarmonyImportDependencyParserPlugin", (expr, name, members) => {
const settings = parser.state.harmonySpecifier.get(name);
.for(harmonySpecifierTag)
.tap("HarmonyImportDependencyParserPlugin", (expr, members) => {
const settings = parser.currentTagData;
const ids = settings.ids.concat(members);
const dep = new HarmonyImportSpecifierDependency(
settings.source,
settings.sourceOrder,
ids,
name,
settings.name,
expr.range,
this.strictExportPresence
);
@ -103,17 +100,17 @@ module.exports = class HarmonyImportDependencyParserPlugin {
return true;
});
parser.hooks.callMemberChain
.for("imported var")
.tap("HarmonyImportDependencyParserPlugin", (expr, name, members) => {
.for(harmonySpecifierTag)
.tap("HarmonyImportDependencyParserPlugin", (expr, members) => {
const args = expr.arguments;
expr = expr.callee;
const settings = parser.state.harmonySpecifier.get(name);
const settings = parser.currentTagData;
const ids = settings.ids.concat(members);
const dep = new HarmonyImportSpecifierDependency(
settings.source,
settings.sourceOrder,
ids,
name,
settings.name,
expr.range,
this.strictExportPresence
);
@ -183,3 +180,5 @@ module.exports = class HarmonyImportDependencyParserPlugin {
);
}
};
module.exports.harmonySpecifierTag = harmonySpecifierTag;

View File

@ -203,14 +203,12 @@ HarmonyImportSpecifierDependency.Template = class HarmonyImportSpecifierDependen
apply(dependency, source, templateContext) {
super.apply(dependency, source, templateContext);
const dep = /** @type {HarmonyImportSpecifierDependency} */ (dependency);
const content = this.getContent(dep, templateContext);
source.replace(dep.range[0], dep.range[1] - 1, content);
}
getContent(
dep,
{ runtimeTemplate, module, moduleGraph, runtimeRequirements }
) {
const {
runtimeTemplate,
module,
moduleGraph,
runtimeRequirements
} = templateContext;
const ids = dep.getIds(moduleGraph);
const exportExpr = runtimeTemplate.exportFromImport({
moduleGraph,
@ -224,7 +222,11 @@ HarmonyImportSpecifierDependency.Template = class HarmonyImportSpecifierDependen
importVar: dep.getImportVar(moduleGraph),
runtimeRequirements
});
return dep.shorthand ? `${dep.name}: ${exportExpr}` : exportExpr;
if (dep.shorthand) {
source.insert(dep.range[1], `: ${exportExpr}`);
} else {
source.replace(dep.range[0], dep.range[1] - 1, exportExpr);
}
}
};

View File

@ -1392,9 +1392,10 @@ class HarmonyImportSpecifierDependencyConcatenatedTemplate extends DependencyTem
});
}
if (dep.shorthand) {
content = dep.name + ": " + content;
source.insert(dep.range[1], ": " + content);
} else {
source.replace(dep.range[0], dep.range[1] - 1, content);
}
source.replace(dep.range[0], dep.range[1] - 1, content);
}
}

View File

@ -40,7 +40,7 @@ const extractPair = pair => {
*/
class StackedSetMap {
/**
* @param {Map<K, V>[]=} parentStack an optional parent
* @param {Map<K, InternalCell<V>>[]=} parentStack an optional parent
*/
constructor(parentStack) {
/** @type {Map<K, InternalCell<V>>} */

View File

@ -1,11 +1,11 @@
it("should handle the css loader correctly", function() {
expect(
(require("!css-loader!../_css/stylesheet.css") + "").indexOf(".rule-direct")
).not.toEqual(-1);
expect(
(require("!css-loader!../_css/stylesheet.css") + "").indexOf(".rule-import1")
).not.toEqual(-1);
expect(
(require("!css-loader!../_css/stylesheet.css") + "").indexOf(".rule-import2")
).not.toEqual(-1);
expect(require("!css-loader!../_css/stylesheet.css") + "").toContain(
".rule-direct"
);
expect(require("!css-loader!../_css/stylesheet.css") + "").toContain(
".rule-import1"
);
expect(require("!css-loader!../_css/stylesheet.css") + "").toContain(
".rule-import2"
);
});

View File

@ -5,7 +5,7 @@ it("should be able to load a file with the require.context method", function() {
expect(require.context("./loaders/queryloader?dog=bark!./templates?cat=meow")("./tmpl")).toEqual({
resourceQuery: "?cat=meow",
query: "?dog=bark",
prev: "module.exports = \"test template\";"
prev: 'module.exports = "test template";'
});
expect(require . context ( "." + "/." + "/" + "templ" + "ates" ) ( "./subdir/tmpl.js" )).toBe("subdir test template");
expect(require.context("./templates", true, /./)("xyz")).toBe("xyz");
@ -22,13 +22,13 @@ it("should automatically create contexts", function() {
it("should be able to require.resolve with automatical context", function() {
var template = "tmpl";
expect(require.resolve("./templates/" + template)).toBe(require.resolve("./templates/tmpl"));
expect(require.resolve("./templates/" + template)).toBe(
require.resolve("./templates/tmpl")
);
});
it("should be able to use renaming combined with a context", function() {
var renamedRequire = require;
require = function () {};
require("fail");
var template = "tmpl";
expect(renamedRequire("./templates/" + template)).toBe("test template");
});