Merge pull request #11075 from vankop/import-meta

add import.meta support
This commit is contained in:
Tobias Koppers 2020-07-06 20:19:23 +02:00 committed by GitHub
commit d9435e0e3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 939 additions and 410 deletions

View File

@ -4,6 +4,7 @@
"words": [
"webpack",
"webpack's",
"endregion",
"entrypoint",
"entrypoints",
"splitted",

View File

@ -12,9 +12,11 @@ const Compilation = require("./Compilation");
const HotUpdateChunk = require("./HotUpdateChunk");
const NormalModule = require("./NormalModule");
const RuntimeGlobals = require("./RuntimeGlobals");
const ConstDependency = require("./dependencies/ConstDependency");
const ImportMetaHotAcceptDependency = require("./dependencies/ImportMetaHotAcceptDependency");
const ImportMetaHotDeclineDependency = require("./dependencies/ImportMetaHotDeclineDependency");
const ModuleHotAcceptDependency = require("./dependencies/ModuleHotAcceptDependency");
const ModuleHotDeclineDependency = require("./dependencies/ModuleHotDeclineDependency");
const ModuleHotDependency = require("./dependencies/ModuleHotDependency");
const HotModuleReplacementRuntimeModule = require("./hmr/HotModuleReplacementRuntimeModule");
const JavascriptParser = require("./javascript/JavascriptParser");
const {
@ -81,24 +83,98 @@ class HotModuleReplacementPlugin {
return callback();
}
);
const runtimeRequirements = [RuntimeGlobals.module];
const addParserPlugins = (parser, parserOptions) => {
const createAcceptHandler = (parser, ParamDependency) => {
const {
hotAcceptCallback,
hotAcceptWithoutCallback
} = HotModuleReplacementPlugin.getParserHooks(parser);
parser.hooks.expression
.for("__webpack_hash__")
.tap(
"HotModuleReplacementPlugin",
toConstantDependency(parser, `${RuntimeGlobals.getFullHash}()`, [
RuntimeGlobals.getFullHash
])
return expr => {
const module = parser.state.module;
const dep = new ConstDependency(
`${module.moduleArgument}.hot.accept`,
expr.callee.range,
runtimeRequirements
);
parser.hooks.evaluateTypeof
.for("__webpack_hash__")
.tap("HotModuleReplacementPlugin", evaluateToString("string"));
dep.loc = expr.loc;
module.addPresentationalDependency(dep);
if (expr.arguments.length >= 1) {
const arg = parser.evaluateExpression(expr.arguments[0]);
let params = [];
let requests = [];
if (arg.isString()) {
params = [arg];
} else if (arg.isArray()) {
params = arg.items.filter(param => param.isString());
}
if (params.length > 0) {
params.forEach((param, idx) => {
const request = param.string;
const dep = new ParamDependency(request, param.range);
dep.optional = true;
dep.loc = Object.create(expr.loc);
dep.loc.index = idx;
module.addDependency(dep);
requests.push(request);
});
if (expr.arguments.length > 1) {
hotAcceptCallback.call(expr.arguments[1], requests);
parser.walkExpression(expr.arguments[1]); // other args are ignored
return true;
} else {
hotAcceptWithoutCallback.call(expr, requests);
return true;
}
}
}
parser.walkExpressions(expr.arguments);
return true;
};
};
const createDeclineHandler = (parser, ParamDependency) => expr => {
const module = parser.state.module;
const dep = new ConstDependency(
`${module.moduleArgument}.hot.decline`,
expr.callee.range,
runtimeRequirements
);
dep.loc = expr.loc;
module.addPresentationalDependency(dep);
if (expr.arguments.length === 1) {
const arg = parser.evaluateExpression(expr.arguments[0]);
let params = [];
if (arg.isString()) {
params = [arg];
} else if (arg.isArray()) {
params = arg.items.filter(param => param.isString());
}
params.forEach((param, idx) => {
const dep = new ParamDependency(param.string, param.range);
dep.optional = true;
dep.loc = Object.create(expr.loc);
dep.loc.index = idx;
module.addDependency(dep);
});
}
return true;
};
const createHMRExpressionHandler = parser => expr => {
const module = parser.state.module;
const dep = new ConstDependency(
`${module.moduleArgument}.hot`,
expr.range,
runtimeRequirements
);
dep.loc = expr.loc;
module.addPresentationalDependency(dep);
return true;
};
const applyModuleHot = parser => {
parser.hooks.evaluateIdentifier.for("module.hot").tap(
{
name: "HotModuleReplacementPlugin",
@ -115,77 +191,61 @@ class HotModuleReplacementPlugin {
);
parser.hooks.call
.for("module.hot.accept")
.tap("HotModuleReplacementPlugin", expr => {
const dep = new ModuleHotDependency(expr.callee.range, "accept");
dep.loc = expr.loc;
parser.state.module.addDependency(dep);
if (expr.arguments.length >= 1) {
const arg = parser.evaluateExpression(expr.arguments[0]);
let params = [];
let requests = [];
if (arg.isString()) {
params = [arg];
} else if (arg.isArray()) {
params = arg.items.filter(param => param.isString());
}
if (params.length > 0) {
params.forEach((param, idx) => {
const request = param.string;
const dep = new ModuleHotAcceptDependency(request, param.range);
dep.optional = true;
dep.loc = Object.create(expr.loc);
dep.loc.index = idx;
parser.state.module.addDependency(dep);
requests.push(request);
});
if (expr.arguments.length > 1) {
hotAcceptCallback.call(expr.arguments[1], requests);
parser.walkExpression(expr.arguments[1]); // other args are ignored
return true;
} else {
hotAcceptWithoutCallback.call(expr, requests);
return true;
}
}
}
parser.walkExpressions(expr.arguments);
return true;
});
.tap(
"HotModuleReplacementPlugin",
createAcceptHandler(parser, ModuleHotAcceptDependency)
);
parser.hooks.call
.for("module.hot.decline")
.tap("HotModuleReplacementPlugin", expr => {
const dep = new ModuleHotDependency(expr.callee.range, "decline");
dep.loc = expr.loc;
parser.state.module.addDependency(dep);
if (expr.arguments.length === 1) {
const arg = parser.evaluateExpression(expr.arguments[0]);
let params = [];
if (arg.isString()) {
params = [arg];
} else if (arg.isArray()) {
params = arg.items.filter(param => param.isString());
}
params.forEach((param, idx) => {
const dep = new ModuleHotDeclineDependency(
param.string,
param.range
);
dep.optional = true;
dep.loc = Object.create(expr.loc);
dep.loc.index = idx;
parser.state.module.addDependency(dep);
});
}
return true;
});
.tap(
"HotModuleReplacementPlugin",
createDeclineHandler(parser, ModuleHotDeclineDependency)
);
parser.hooks.expression
.for("module.hot")
.tap("HotModuleReplacementPlugin", createHMRExpressionHandler(parser));
};
const applyImportMetaHot = parser => {
parser.hooks.evaluateIdentifier
.for("import.meta.webpackHot")
.tap("HotModuleReplacementPlugin", expr => {
const dep = new ModuleHotDependency(expr.range);
dep.loc = expr.loc;
parser.state.module.addDependency(dep);
return true;
return evaluateToIdentifier(
"import.meta.webpackHot",
"import.meta",
() => ["webpackHot"],
true
)(expr);
});
parser.hooks.call
.for("import.meta.webpackHot.accept")
.tap(
"HotModuleReplacementPlugin",
createAcceptHandler(parser, ImportMetaHotAcceptDependency)
);
parser.hooks.call
.for("import.meta.webpackHot.decline")
.tap(
"HotModuleReplacementPlugin",
createDeclineHandler(parser, ImportMetaHotDeclineDependency)
);
parser.hooks.expression
.for("import.meta.webpackHot")
.tap("HotModuleReplacementPlugin", createHMRExpressionHandler(parser));
};
const applyWebpackHash = parser => {
parser.hooks.expression
.for("__webpack_hash__")
.tap(
"HotModuleReplacementPlugin",
toConstantDependency(parser, `${RuntimeGlobals.getFullHash}()`, [
RuntimeGlobals.getFullHash
])
);
parser.hooks.evaluateTypeof
.for("__webpack_hash__")
.tap("HotModuleReplacementPlugin", evaluateToString("string"));
};
compiler.hooks.compilation.tap(
@ -195,6 +255,7 @@ class HotModuleReplacementPlugin {
// It should not affect child compilations
if (compilation.compiler !== compiler) return;
//#region module.hot.* API
compilation.dependencyFactories.set(
ModuleHotAcceptDependency,
normalModuleFactory
@ -203,7 +264,6 @@ class HotModuleReplacementPlugin {
ModuleHotAcceptDependency,
new ModuleHotAcceptDependency.Template()
);
compilation.dependencyFactories.set(
ModuleHotDeclineDependency,
normalModuleFactory
@ -212,11 +272,26 @@ class HotModuleReplacementPlugin {
ModuleHotDeclineDependency,
new ModuleHotDeclineDependency.Template()
);
//#endregion
compilation.dependencyTemplates.set(
ModuleHotDependency,
new ModuleHotDependency.Template()
//#region import.meta.webpackHot.* API
compilation.dependencyFactories.set(
ImportMetaHotAcceptDependency,
normalModuleFactory
);
compilation.dependencyTemplates.set(
ImportMetaHotAcceptDependency,
new ImportMetaHotAcceptDependency.Template()
);
compilation.dependencyFactories.set(
ImportMetaHotDeclineDependency,
normalModuleFactory
);
compilation.dependencyTemplates.set(
ImportMetaHotDeclineDependency,
new ImportMetaHotDeclineDependency.Template()
);
//#endregion
compilation.hooks.record.tap(
"HotModuleReplacementPlugin",
@ -428,13 +503,25 @@ class HotModuleReplacementPlugin {
}
);
// TODO add HMR support for javascript/esm
normalModuleFactory.hooks.parser
.for("javascript/auto")
.tap("HotModuleReplacementPlugin", addParserPlugins);
.tap("HotModuleReplacementPlugin", parser => {
applyWebpackHash(parser);
applyModuleHot(parser);
applyImportMetaHot(parser);
});
normalModuleFactory.hooks.parser
.for("javascript/dynamic")
.tap("HotModuleReplacementPlugin", addParserPlugins);
.tap("HotModuleReplacementPlugin", parser => {
applyWebpackHash(parser);
applyModuleHot(parser);
});
normalModuleFactory.hooks.parser
.for("javascript/esm")
.tap("HotModuleReplacementPlugin", parser => {
applyWebpackHash(parser);
applyImportMetaHot(parser);
});
NormalModule.getCompilationHooks(compilation).loader.tap(
"HotModuleReplacementPlugin",

View File

@ -33,6 +33,7 @@ const ResolverCachePlugin = require("./cache/ResolverCachePlugin");
const CommonJsPlugin = require("./dependencies/CommonJsPlugin");
const HarmonyModulesPlugin = require("./dependencies/HarmonyModulesPlugin");
const ImportMetaPlugin = require("./dependencies/ImportMetaPlugin");
const ImportPlugin = require("./dependencies/ImportPlugin");
const LoaderPlugin = require("./dependencies/LoaderPlugin");
const RequireContextPlugin = require("./dependencies/RequireContextPlugin");
@ -346,6 +347,7 @@ class WebpackOptionsApply extends OptionsApply {
).apply(compiler);
new ImportPlugin(options.module).apply(compiler);
new SystemPlugin(options.module).apply(compiler);
new ImportMetaPlugin().apply(compiler);
new DefaultStatsFactoryPlugin().apply(compiler);
new DefaultStatsPresetPlugin().apply(compiler);

View File

@ -8,12 +8,16 @@
const Dependency = require("../Dependency");
const DependencyTemplate = require("../DependencyTemplate");
const makeSerializable = require("../util/makeSerializable");
const CriticalDependencyWarning = require("./CriticalDependencyWarning");
const memorize = require("../util/memorize");
/** @typedef {import("../ContextModule").ContextOptions} ContextOptions */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
/** @typedef {import("../WebpackError")} WebpackError */
const getCriticalDependencyWarning = memorize(() =>
require("./CriticalDependencyWarning")
);
/** @typedef {ContextOptions & { request: string }} ContextDependencyOptions */
const regExpToString = r => (r ? r + "" : "");
@ -74,11 +78,13 @@ class ContextDependency extends Dependency {
if (this.critical) {
if (!warnings) warnings = [];
const CriticalDependencyWarning = getCriticalDependencyWarning();
warnings.push(new CriticalDependencyWarning(this.critical));
}
if (this.hadGlobalOrStickyRegExp) {
if (!warnings) warnings = [];
const CriticalDependencyWarning = getCriticalDependencyWarning();
warnings.push(
new CriticalDependencyWarning(
"Contexts can't use RegExps with the 'g' or 'y' flags."

View File

@ -0,0 +1,35 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Ivan Kopeykin @vankop
*/
"use strict";
const makeSerializable = require("../util/makeSerializable");
const ModuleDependency = require("./ModuleDependency");
const ModuleDependencyTemplateAsId = require("./ModuleDependencyTemplateAsId");
class ImportMetaHotAcceptDependency extends ModuleDependency {
constructor(request, range) {
super(request);
this.range = range;
this.weak = true;
}
get type() {
return "import.meta.webpackHot.accept";
}
get category() {
return "esm";
}
}
makeSerializable(
ImportMetaHotAcceptDependency,
"webpack/lib/dependencies/ImportMetaHotAcceptDependency"
);
ImportMetaHotAcceptDependency.Template = ModuleDependencyTemplateAsId;
module.exports = ImportMetaHotAcceptDependency;

View File

@ -0,0 +1,36 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Ivan Kopeykin @vankop
*/
"use strict";
const makeSerializable = require("../util/makeSerializable");
const ModuleDependency = require("./ModuleDependency");
const ModuleDependencyTemplateAsId = require("./ModuleDependencyTemplateAsId");
class ImportMetaHotDeclineDependency extends ModuleDependency {
constructor(request, range) {
super(request);
this.range = range;
this.weak = true;
}
get type() {
return "import.meta.webpackHot.decline";
}
get category() {
return "esm";
}
}
makeSerializable(
ImportMetaHotDeclineDependency,
"webpack/lib/dependencies/ImportMetaHotDeclineDependency"
);
ImportMetaHotDeclineDependency.Template = ModuleDependencyTemplateAsId;
module.exports = ImportMetaHotDeclineDependency;

View File

@ -0,0 +1,181 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Ivan Kopeykin @vankop
*/
"use strict";
const { pathToFileURL } = require("url");
const ModuleDependencyWarning = require("../ModuleDependencyWarning");
const Template = require("../Template");
const BasicEvaluatedExpression = require("../javascript/BasicEvaluatedExpression");
const {
evaluateToIdentifier,
toConstantDependency,
evaluateToString,
evaluateToNumber
} = require("../javascript/JavascriptParserHelpers");
const memorize = require("../util/memorize");
const propertyAccess = require("../util/propertyAccess");
const ConstDependency = require("./ConstDependency");
/** @typedef {import("estree").MemberExpression} MemberExpression */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../NormalModule")} NormalModule */
/** @typedef {import("../javascript/JavascriptParser")} Parser */
const getCriticalDependencyWarning = memorize(() =>
require("./CriticalDependencyWarning")
);
class ImportMetaPlugin {
/**
* @param {Compiler} compiler compiler
*/
apply(compiler) {
compiler.hooks.compilation.tap(
"ImportMetaPlugin",
(compilation, { normalModuleFactory }) => {
/**
* @param {NormalModule} module module
* @returns {string} file url
*/
const getUrl = module => {
return pathToFileURL(module.resource).toString();
};
/**
* @param {Parser} parser parser
* @param {Object} parserOptions parserOptions
* @returns {void}
*/
const parserHandler = (parser, parserOptions) => {
/// import.meta direct ///
parser.hooks.typeof
.for("import.meta")
.tap(
"ImportMetaPlugin",
toConstantDependency(parser, JSON.stringify("object"))
);
parser.hooks.metaProperty.tap(
"ImportMetaPlugin",
toConstantDependency(parser, "Object()")
);
parser.hooks.metaProperty.tap("ImportMetaPlugin", metaProperty => {
const CriticalDependencyWarning = getCriticalDependencyWarning();
parser.state.module.addWarning(
new ModuleDependencyWarning(
parser.state.module,
new CriticalDependencyWarning(
"Accessing import.meta directly is unsupported (only property access is supported)"
),
metaProperty.loc
)
);
const dep = new ConstDependency("Object()", metaProperty.range);
dep.loc = metaProperty.loc;
parser.state.module.addPresentationalDependency(dep);
return true;
});
parser.hooks.evaluateTypeof
.for("import.meta")
.tap("ImportMetaPlugin", evaluateToString("object"));
parser.hooks.evaluateIdentifier.for("import.meta").tap(
"ImportMetaPlugin",
evaluateToIdentifier("import.meta", "import.meta", () => [], true)
);
/// import.meta.url ///
parser.hooks.typeof
.for("import.meta.url")
.tap(
"ImportMetaPlugin",
toConstantDependency(parser, JSON.stringify("string"))
);
parser.hooks.expression
.for("import.meta.url")
.tap("ImportMetaPlugin", expr => {
const dep = new ConstDependency(
JSON.stringify(getUrl(parser.state.module)),
expr.range
);
dep.loc = expr.loc;
parser.state.module.addPresentationalDependency(dep);
return true;
});
parser.hooks.evaluateTypeof
.for("import.meta.url")
.tap("ImportMetaPlugin", evaluateToString("string"));
parser.hooks.evaluateIdentifier
.for("import.meta.url")
.tap("ImportMetaPlugin", expr => {
return new BasicEvaluatedExpression()
.setString(getUrl(parser.state.module))
.setRange(expr.range);
});
/// import.meta.webpack ///
const webpackVersion = parseInt(
require("../../package.json").version,
10
);
parser.hooks.typeof
.for("import.meta.webpack")
.tap(
"ImportMetaPlugin",
toConstantDependency(parser, JSON.stringify("number"))
);
parser.hooks.expression
.for("import.meta.webpack")
.tap(
"ImportMetaPlugin",
toConstantDependency(parser, JSON.stringify(webpackVersion))
);
parser.hooks.evaluateTypeof
.for("import.meta.webpack")
.tap("ImportMetaPlugin", evaluateToString("number"));
parser.hooks.evaluateIdentifier
.for("import.meta.webpack")
.tap("ImportMetaPlugin", evaluateToNumber(webpackVersion));
/// Unknown properties ///
parser.hooks.unhandledExpressionMemberChain
.for("import.meta")
.tap("ImportMetaPlugin", (expr, members) => {
const dep = new ConstDependency(
`${Template.toNormalComment(
"unsupported import.meta." + members.join(".")
)} undefined${propertyAccess(members, 1)}`,
expr.range
);
dep.loc = expr.loc;
parser.state.module.addPresentationalDependency(dep);
return true;
});
parser.hooks.evaluate
.for("MemberExpression")
.tap("ImportMetaPlugin", expression => {
const expr = /** @type {MemberExpression} */ (expression);
if (
expr.object.type === "MetaProperty" &&
expr.property.type ===
(expr.computed ? "Literal" : "Identifier")
) {
return new BasicEvaluatedExpression()
.setUndefined()
.setRange(expr.range);
}
});
};
normalModuleFactory.hooks.parser
.for("javascript/auto")
.tap("ImportMetaPlugin", parserHandler);
normalModuleFactory.hooks.parser
.for("javascript/esm")
.tap("ImportMetaPlugin", parserHandler);
}
);
}
}
module.exports = ImportMetaPlugin;

View File

@ -1,70 +0,0 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const RuntimeGlobals = require("../RuntimeGlobals");
const makeSerializable = require("../util/makeSerializable");
const NullDependency = require("./NullDependency");
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
/** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
class ModuleHotDependency extends NullDependency {
constructor(range, apiMethod) {
super();
this.range = range;
this.apiMethod = apiMethod;
}
get type() {
return "module.hot";
}
serialize(context) {
const { write } = context;
write(this.range);
write(this.apiMethod);
super.serialize(context);
}
deserialize(context) {
const { read } = context;
this.range = read();
this.apiMethod = read();
super.deserialize(context);
}
}
makeSerializable(
ModuleHotDependency,
"webpack/lib/dependencies/ModuleHotDependency"
);
ModuleHotDependency.Template = class ModuleHotDependencyTemplate extends NullDependency.Template {
/**
* @param {Dependency} dependency the dependency for which the template should be applied
* @param {ReplaceSource} source the current replace source which can be modified
* @param {DependencyTemplateContext} templateContext the context object
* @returns {void}
*/
apply(dependency, source, { module, runtimeRequirements }) {
const dep = /** @type {ModuleHotDependency} */ (dependency);
runtimeRequirements.add(RuntimeGlobals.module);
source.replace(
dep.range[0],
dep.range[1] - 1,
`${module.moduleArgument}.hot${dep.apiMethod ? `.${dep.apiMethod}` : ""}`
);
}
};
module.exports = ModuleHotDependency;

View File

@ -6,18 +6,19 @@
"use strict";
const TypeUnknown = 0;
const TypeNull = 1;
const TypeString = 2;
const TypeNumber = 3;
const TypeBoolean = 4;
const TypeRegExp = 5;
const TypeConditional = 6;
const TypeArray = 7;
const TypeConstArray = 8;
const TypeIdentifier = 9;
const TypeWrapped = 10;
const TypeTemplateString = 11;
const TypeBigInt = 12;
const TypeUndefined = 1;
const TypeNull = 2;
const TypeString = 3;
const TypeNumber = 4;
const TypeBoolean = 5;
const TypeRegExp = 6;
const TypeConditional = 7;
const TypeArray = 8;
const TypeConstArray = 9;
const TypeIdentifier = 10;
const TypeWrapped = 11;
const TypeTemplateString = 12;
const TypeBigInt = 13;
class BasicEvaluatedExpression {
constructor() {
@ -48,6 +49,10 @@ class BasicEvaluatedExpression {
return this.type === TypeNull;
}
isUndefined() {
return this.type === TypeUndefined;
}
isString() {
return this.type === TypeString;
}
@ -105,6 +110,7 @@ class BasicEvaluatedExpression {
if (this.falsy) return false;
if (this.isBoolean()) return this.bool;
if (this.isNull()) return false;
if (this.isUndefined()) return false;
if (this.isString()) return this.string !== "";
if (this.isNumber()) return this.number !== 0;
if (this.isBigInt()) return this.bigint !== BigInt(0);
@ -127,6 +133,7 @@ class BasicEvaluatedExpression {
asString() {
if (this.isBoolean()) return `${this.bool}`;
if (this.isNull()) return "null";
if (this.isUndefined()) return "undefined";
if (this.isString()) return this.string;
if (this.isNumber()) return `${this.number}`;
if (this.isBigInt()) return `${this.bigint}`;
@ -159,6 +166,11 @@ class BasicEvaluatedExpression {
return this;
}
setUndefined() {
this.type = TypeUndefined;
return this;
}
setNull() {
this.type = TypeNull;
return this;

View File

@ -30,6 +30,7 @@ const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
/** @typedef {import("estree").Literal} LiteralNode */
/** @typedef {import("estree").LogicalExpression} LogicalExpressionNode */
/** @typedef {import("estree").MemberExpression} MemberExpressionNode */
/** @typedef {import("estree").MetaProperty} MetaPropertyNode */
/** @typedef {import("estree").MethodDefinition} MethodDefinitionNode */
/** @typedef {import("estree").ModuleDeclaration} ModuleDeclarationNode */
/** @typedef {import("estree").Node} AnyNode */
@ -105,6 +106,8 @@ const getRootName = expression => {
return expression.name;
case "ThisExpression":
return "this";
case "MetaProperty":
return "import.meta";
default:
return undefined;
}
@ -140,7 +143,7 @@ class JavascriptParser extends Parser {
evaluateTypeof: new HookMap(() => new SyncBailHook(["expression"])),
/** @type {HookMap<SyncBailHook<[ExpressionNode], BasicEvaluatedExpression | undefined | null>>} */
evaluate: new HookMap(() => new SyncBailHook(["expression"])),
/** @type {HookMap<SyncBailHook<[IdentifierNode | ThisExpressionNode | MemberExpressionNode], BasicEvaluatedExpression | undefined | null>>} */
/** @type {HookMap<SyncBailHook<[IdentifierNode | ThisExpressionNode | MemberExpressionNode | MetaPropertyNode], BasicEvaluatedExpression | undefined | null>>} */
evaluateIdentifier: new HookMap(() => new SyncBailHook(["expression"])),
/** @type {HookMap<SyncBailHook<[IdentifierNode | ThisExpressionNode | MemberExpressionNode], BasicEvaluatedExpression | undefined | null>>} */
evaluateDefinedIdentifier: new HookMap(
@ -257,12 +260,18 @@ class JavascriptParser extends Parser {
),
/** @type {HookMap<SyncBailHook<[ExpressionNode], boolean | void>>} */
new: new HookMap(() => new SyncBailHook(["expression"])),
/** @type {SyncBailHook<[MetaPropertyNode], boolean | void>} */
metaProperty: new SyncBailHook(["metaProperty"]),
/** @type {HookMap<SyncBailHook<[ExpressionNode], boolean | void>>} */
expression: new HookMap(() => new SyncBailHook(["expression"])),
/** @type {HookMap<SyncBailHook<[ExpressionNode, string[]], boolean | void>>} */
expressionMemberChain: new HookMap(
() => new SyncBailHook(["expression", "members"])
),
/** @type {HookMap<SyncBailHook<[ExpressionNode, string[]], boolean | void>>} */
unhandledExpressionMemberChain: new HookMap(
() => new SyncBailHook(["expression", "members"])
),
/** @type {SyncBailHook<[ExpressionNode], boolean | void>} */
expressionConditionalOperator: new SyncBailHook(["expression"]),
/** @type {SyncBailHook<[ExpressionNode], boolean | void>} */
@ -347,6 +356,45 @@ class JavascriptParser extends Parser {
.tap("JavascriptParser", _expr => {
const expr = /** @type {BinaryExpressionNode} */ (_expr);
const handleNumberOperation = fn => {
const left = this.evaluateExpression(expr.left);
const right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (left.isNumber() && right.isNumber()) {
res = new BasicEvaluatedExpression();
res.setNumber(fn(left.number, right.number));
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBigInt(fn(left.bigint, right.bigint));
res.setRange(expr.range);
return res;
}
};
const handleCompare = fn => {
const left = this.evaluateExpression(expr.left);
const right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (left.isNumber() && right.isNumber()) {
res = new BasicEvaluatedExpression();
res.setBoolean(fn(left.number, right.number));
res.setRange(expr.range);
return res;
} else if (left.isString() && right.isString()) {
res = new BasicEvaluatedExpression();
res.setBoolean(fn(left.string, right.string));
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBoolean(fn(left.bigint, right.bigint));
res.setRange(expr.range);
return res;
}
};
let left;
let right;
let res;
@ -486,66 +534,13 @@ class JavascriptParser extends Parser {
res.setRange(expr.range);
return res;
} else if (expr.operator === "-") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (left.isNumber() && right.isNumber()) {
res = new BasicEvaluatedExpression();
res.setNumber(left.number - right.number);
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBigInt(left.bigint - right.bigint);
res.setRange(expr.range);
return res;
}
return handleNumberOperation((l, r) => l - r);
} else if (expr.operator === "*") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (left.isNumber() && right.isNumber()) {
res = new BasicEvaluatedExpression();
res.setNumber(left.number * right.number);
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBigInt(left.bigint * right.bigint);
res.setRange(expr.range);
return res;
}
return handleNumberOperation((l, r) => l * r);
} else if (expr.operator === "/") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (left.isNumber() && right.isNumber()) {
res = new BasicEvaluatedExpression();
res.setNumber(left.number / right.number);
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBigInt(left.bigint / right.bigint);
res.setRange(expr.range);
return res;
}
return handleNumberOperation((l, r) => l / r);
} else if (expr.operator === "**") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (left.isNumber() && right.isNumber()) {
res = new BasicEvaluatedExpression();
res.setNumber(left.number ** right.number);
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBigInt(left.bigint ** right.bigint);
res.setRange(expr.range);
return res;
}
return handleNumberOperation((l, r) => l ** r);
} else if (expr.operator === "==" || expr.operator === "===") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
@ -581,53 +576,14 @@ class JavascriptParser extends Parser {
return res.setBoolean(false);
}
} else if (expr.operator === "&") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (left.isNumber() && right.isNumber()) {
res = new BasicEvaluatedExpression();
res.setNumber(left.number & right.number);
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBigInt(left.bigint & right.bigint);
res.setRange(expr.range);
return res;
}
return handleNumberOperation((l, r) => l & r);
} else if (expr.operator === "|") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (left.isNumber() && right.isNumber()) {
res = new BasicEvaluatedExpression();
res.setNumber(left.number | right.number);
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBigInt(left.bigint | right.bigint);
res.setRange(expr.range);
return res;
}
return handleNumberOperation((l, r) => l | r);
} else if (expr.operator === "^") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (left.isNumber() && right.isNumber()) {
res = new BasicEvaluatedExpression();
res.setNumber(left.number ^ right.number);
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBigInt(left.bigint ^ right.bigint);
res.setRange(expr.range);
return res;
}
return handleNumberOperation((l, r) => l ^ r);
} else if (expr.operator === ">>>") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
const left = this.evaluateExpression(expr.left);
const right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (!left.isNumber() || !right.isNumber()) return;
res = new BasicEvaluatedExpression();
@ -635,35 +591,17 @@ class JavascriptParser extends Parser {
res.setRange(expr.range);
return res;
} else if (expr.operator === ">>") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (left.isNumber() && right.isNumber()) {
res = new BasicEvaluatedExpression();
res.setNumber(left.number >> right.number);
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBigInt(left.bigint >> right.bigint);
res.setRange(expr.range);
return res;
}
return handleNumberOperation((l, r) => l >> r);
} else if (expr.operator === "<<") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (left.isNumber() && right.isNumber()) {
res = new BasicEvaluatedExpression();
res.setNumber(left.number << right.number);
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBigInt(left.bigint << right.bigint);
res.setRange(expr.range);
return res;
}
return handleNumberOperation((l, r) => l << r);
} else if (expr.operator === "<") {
return handleCompare((l, r) => l < r);
} else if (expr.operator === ">") {
return handleCompare((l, r) => l > r);
} else if (expr.operator === "<=") {
return handleCompare((l, r) => l <= r);
} else if (expr.operator === ">=") {
return handleCompare((l, r) => l >= r);
}
});
this.hooks.evaluate
@ -672,26 +610,39 @@ class JavascriptParser extends Parser {
const expr = /** @type {UnaryExpressionNode} */ (_expr);
if (expr.operator === "typeof") {
if (expr.argument.type === "Identifier") {
const res = this.callHooksForName(
this.hooks.evaluateTypeof,
expr.argument.name,
expr
);
if (res !== undefined) return res;
}
if (expr.argument.type === "MemberExpression") {
const res = this.callHooksForExpression(
this.hooks.evaluateTypeof,
expr.argument,
expr
);
if (res !== undefined) return res;
}
if (expr.argument.type === "FunctionExpression") {
return new BasicEvaluatedExpression()
.setString("function")
.setRange(expr.range);
switch (expr.argument.type) {
case "Identifier": {
const res = this.callHooksForName(
this.hooks.evaluateTypeof,
expr.argument.name,
expr
);
if (res !== undefined) return res;
break;
}
case "MetaProperty": {
const res = this.callHooksForName(
this.hooks.evaluateTypeof,
"import.meta",
expr
);
if (res !== undefined) return res;
break;
}
case "MemberExpression": {
const res = this.callHooksForExpression(
this.hooks.evaluateTypeof,
expr.argument,
expr
);
if (res !== undefined) return res;
break;
}
case "FunctionExpression": {
return new BasicEvaluatedExpression()
.setString("function")
.setRange(expr.range);
}
}
const arg = this.evaluateExpression(expr.argument);
if (arg.isString() || arg.isWrapped()) {
@ -704,6 +655,11 @@ class JavascriptParser extends Parser {
.setString("object")
.setRange(expr.range);
}
if (arg.isUndefined()) {
return new BasicEvaluatedExpression()
.setString("undefined")
.setRange(expr.range);
}
if (arg.isNumber()) {
return new BasicEvaluatedExpression()
.setString("number")
@ -727,36 +683,16 @@ class JavascriptParser extends Parser {
} else if (expr.operator === "!") {
const argument = this.evaluateExpression(expr.argument);
if (!argument) return;
if (argument.isBoolean()) {
return new BasicEvaluatedExpression()
.setBoolean(!argument.bool)
.setRange(expr.range);
}
if (argument.isNull()) {
return new BasicEvaluatedExpression()
.setBoolean(true)
.setRange(expr.range);
}
if (argument.isTruthy()) {
const bool = argument.asBool();
if (bool === true) {
return new BasicEvaluatedExpression()
.setBoolean(false)
.setRange(expr.range);
}
if (argument.isFalsy()) {
} else if (bool === false) {
return new BasicEvaluatedExpression()
.setBoolean(true)
.setRange(expr.range);
}
if (argument.isString()) {
return new BasicEvaluatedExpression()
.setBoolean(!argument.string)
.setRange(expr.range);
}
if (argument.isNumber()) {
return new BasicEvaluatedExpression()
.setBoolean(!argument.number)
.setRange(expr.range);
}
} else if (expr.operator === "~") {
const argument = this.evaluateExpression(expr.argument);
if (!argument) return;
@ -795,65 +731,85 @@ class JavascriptParser extends Parser {
.setString("undefined")
.setRange(expr.range);
});
this.hooks.evaluate.for("Identifier").tap("JavascriptParser", _expr => {
const expr = /** @type {IdentifierNode} */ (_expr);
return this.callHooksForNameWithFallback(
this.hooks.evaluateIdentifier,
expr.name,
name =>
new BasicEvaluatedExpression()
.setIdentifier(name, this.getVariableInfo(expr.name), () => [])
.setRange(expr.range),
() => {
const hook = this.hooks.evaluateDefinedIdentifier.get(expr.name);
if (hook !== undefined) {
return hook.call(expr);
}
},
expr
);
});
this.hooks.evaluate.for("ThisExpression").tap("JavascriptParser", _expr => {
const expr = /** @type {ThisExpressionNode} */ (_expr);
return this.callHooksForNameWithFallback(
this.hooks.evaluateIdentifier,
"this",
name =>
new BasicEvaluatedExpression()
.setIdentifier(name, this.getVariableInfo("this"), () => [])
.setRange(expr.range),
() => {
const hook = this.hooks.evaluateDefinedIdentifier.get("this");
if (hook !== undefined) {
return hook.call(expr);
}
},
expr
);
});
this.hooks.evaluate
.for("MemberExpression")
.tap("JavascriptParser", expr => {
/** @typedef {{ name: string | VariableInfo, rootInfo: string | VariableInfo, getMembers: () => string[] }} GetInfoResult */
/**
* @param {string} exprType expression type name
* @param {function(ExpressionNode): GetInfoResult | undefined} getInfo get info
* @returns {void}
*/
const tapEvaluateWithVariableInfo = (exprType, getInfo) => {
/** @type {ExpressionNode | undefined} */
let cachedExpression = undefined;
/** @type {GetInfoResult | undefined} */
let cachedInfo = undefined;
this.hooks.evaluate.for(exprType).tap("JavascriptParser", expr => {
const expression = /** @type {MemberExpressionNode} */ (expr);
return this.callHooksForExpressionWithFallback(
this.hooks.evaluateIdentifier,
expression,
(name, rootInfo, getMembers) =>
new BasicEvaluatedExpression()
.setIdentifier(name, rootInfo, getMembers)
.setRange(expression.range),
name => {
const hook = this.hooks.evaluateDefinedIdentifier.get(name);
if (hook !== undefined) {
return hook.call(expression);
}
},
expression
);
const info = getInfo(expr);
if (info !== undefined) {
return this.callHooksForInfoWithFallback(
this.hooks.evaluateIdentifier,
info.name,
name => {
cachedExpression = expression;
cachedInfo = info;
},
name => {
const hook = this.hooks.evaluateDefinedIdentifier.get(name);
if (hook !== undefined) {
return hook.call(expression);
}
},
expression
);
}
});
this.hooks.evaluate
.for(exprType)
.tap({ name: "JavascriptParser", stage: 100 }, expr => {
const info = cachedExpression === expr ? cachedInfo : getInfo(expr);
if (info !== undefined) {
return new BasicEvaluatedExpression()
.setIdentifier(info.name, info.rootInfo, info.getMembers)
.setRange(expr.range);
}
});
};
tapEvaluateWithVariableInfo("Identifier", expr => {
const info = this.getVariableInfo(
/** @type {IdentifierNode} */ (expr).name
);
if (
typeof info === "string" ||
(info instanceof VariableInfo && typeof info.freeName === "string")
) {
return { name: info, rootInfo: info, getMembers: () => [] };
}
});
tapEvaluateWithVariableInfo("ThisExpression", expr => {
const info = this.getVariableInfo("this");
if (
typeof info === "string" ||
(info instanceof VariableInfo && typeof info.freeName === "string")
) {
return { name: info, rootInfo: info, getMembers: () => [] };
}
});
this.hooks.evaluate.for("MetaProperty").tap("JavascriptParser", expr => {
const metaProperty = /** @type {MetaPropertyNode} */ (expr);
return this.callHooksForName(
this.hooks.evaluateIdentifier,
"import.meta",
metaProperty
);
});
tapEvaluateWithVariableInfo("MemberExpression", expr =>
this.getMemberExpressionInfo(/** @type {MemberExpressionNode} */ (expr), [
"expression"
])
);
this.hooks.evaluate.for("CallExpression").tap("JavascriptParser", _expr => {
const expr = /** @type {CallExpressionNode} */ (_expr);
if (
@ -878,6 +834,30 @@ class JavascriptParser extends Parser {
return hook.call(expr, param);
}
});
this.hooks.evaluateCallExpressionMember
.for("indexOf")
.tap("JavascriptParser", (expr, param) => {
if (!param.isString()) return;
if (expr.arguments.length === 0) return;
const [arg1, arg2] = expr.arguments;
if (arg1.type === "SpreadElement") return;
const arg1Eval = this.evaluateExpression(arg1);
if (!arg1Eval.isString()) return;
const arg1Value = arg1Eval.string;
let result;
if (arg2) {
if (arg2.type === "SpreadElement") return;
const arg2Eval = this.evaluateExpression(arg2);
if (!arg2Eval.isNumber()) return;
result = param.string.indexOf(arg1Value, arg2Eval.number);
} else {
result = param.string.indexOf(arg1Value);
}
return new BasicEvaluatedExpression()
.setNumber(result)
.setRange(expr.range);
});
this.hooks.evaluateCallExpressionMember
.for("replace")
.tap("JavascriptParser", (expr, param) => {
@ -1800,10 +1780,7 @@ class JavascriptParser extends Parser {
// renaming with "var a = b;"
const hook = this.hooks.rename.get(renameIdentifier);
if (hook === undefined || !hook.call(declarator.init)) {
this.setVariable(
declarator.id.name,
this.getVariableInfo(renameIdentifier)
);
this.setVariable(declarator.id.name, renameIdentifier);
}
break;
}
@ -1956,6 +1933,9 @@ class JavascriptParser extends Parser {
case "LogicalExpression":
this.walkLogicalExpression(expression);
break;
case "MetaProperty":
this.walkMetaProperty(expression);
break;
case "MemberExpression":
this.walkMemberExpression(expression);
break;
@ -2389,7 +2369,14 @@ class JavascriptParser extends Parser {
expression,
exprInfo.name,
exprInfo.rootInfo,
members
members.slice(),
() =>
this.callHooksForInfo(
this.hooks.unhandledExpressionMemberChain,
exprInfo.rootInfo,
expression,
members
)
);
return;
}
@ -2414,7 +2401,13 @@ class JavascriptParser extends Parser {
if (expression.computed === true) this.walkExpression(expression.property);
}
walkMemberExpressionWithExpressionName(expression, name, rootInfo, members) {
walkMemberExpressionWithExpressionName(
expression,
name,
rootInfo,
members,
onUnhandled
) {
const result = this.callHooksForInfo(
this.hooks.expression,
name,
@ -2432,9 +2425,10 @@ class JavascriptParser extends Parser {
expression.object,
name,
rootInfo,
members
members,
onUnhandled
);
} else {
} else if (!onUnhandled || !onUnhandled()) {
this.walkExpression(expression.object);
}
if (expression.computed === true) this.walkExpression(expression.property);
@ -2448,6 +2442,13 @@ class JavascriptParser extends Parser {
this.callHooksForName(this.hooks.expression, expression.name, expression);
}
/**
* @param {MetaPropertyNode} metaProperty meta property
*/
walkMetaProperty(metaProperty) {
this.hooks.metaProperty.call(metaProperty);
}
callHooksForExpression(hookMap, expr, ...args) {
const exprName = this.getMemberExpressionInfo(expr, ["expression"]);
if (exprName !== undefined) {
@ -3157,6 +3158,7 @@ class JavascriptParser extends Parser {
};
}
case "Identifier":
case "MetaProperty":
case "ThisExpression": {
if (!possibleTypes.has("expression")) return undefined;
const rootName = getRootName(object);

View File

@ -9,6 +9,15 @@ const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning");
const ConstDependency = require("../dependencies/ConstDependency");
const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
/** @typedef {import("estree").Expression} Expression */
/** @typedef {import("./JavascriptParser")} JavascriptParser */
/**
* @param {JavascriptParser} parser the parser
* @param {string} value the const value
* @param {string[]=} runtimeRequirements runtime requirements
* @returns {function(Expression): true} plugin function
*/
exports.toConstantDependency = (parser, value, runtimeRequirements) => {
return function constDependency(expr) {
const dep = new ConstDependency(value, expr.range, runtimeRequirements);
@ -18,12 +27,30 @@ exports.toConstantDependency = (parser, value, runtimeRequirements) => {
};
};
/**
* @param {string} value the string value
* @returns {function(Expression): BasicEvaluatedExpression} plugin function
*/
exports.evaluateToString = value => {
return function stringExpression(expr) {
return new BasicEvaluatedExpression().setString(value).setRange(expr.range);
};
};
/**
* @param {number} value the number value
* @returns {function(Expression): BasicEvaluatedExpression} plugin function
*/
exports.evaluateToNumber = value => {
return function stringExpression(expr) {
return new BasicEvaluatedExpression().setNumber(value).setRange(expr.range);
};
};
/**
* @param {boolean} value the boolean value
* @returns {function(Expression): BasicEvaluatedExpression} plugin function
*/
exports.evaluateToBoolean = value => {
return function booleanExpression(expr) {
return new BasicEvaluatedExpression()

View File

@ -108,8 +108,10 @@ module.exports = {
require("../dependencies/ModuleHotAcceptDependency"),
"dependencies/ModuleHotDeclineDependency": () =>
require("../dependencies/ModuleHotDeclineDependency"),
"dependencies/ModuleHotDependency": () =>
require("../dependencies/ModuleHotDependency"),
"dependencies/ImportMetaHotAcceptDependency": () =>
require("../dependencies/ImportMetaHotAcceptDependency"),
"dependencies/ImportMetaHotDeclineDependency": () =>
require("../dependencies/ImportMetaHotDeclineDependency"),
"dependencies/ProvidedDependency": () =>
require("../dependencies/ProvidedDependency"),
"dependencies/PureExpressionDependency": () =>

View File

@ -0,0 +1,46 @@
const { pathToFileURL } = require("url");
const url = pathToFileURL(
require("path").resolve("./test/cases/esm/import-meta/index.js")
).toString();
const webpackVersion = parseInt(
require("../../../../package.json").version,
10
);
it('typeof import.meta === "object"', () => {
expect(typeof import.meta).toBe("object");
if (typeof import.meta !== "object") require("fail");
});
it('typeof import.meta.url === "string"', () => {
expect(typeof import.meta.url).toBe("string");
if (typeof import.meta.url !== "string") require("fail");
});
it('typeof import.meta.webpack === "number"', () => {
expect(typeof import.meta.webpack).toBe("number");
if (typeof import.meta.webpack !== "number") require("fail");
});
it("should return correct import.meta.url", () => {
expect(import.meta.url).toBe(url);
expect(import.meta["url"]).toBe(url);
expect("my" + import.meta.url).toBe("my" + url);
if (import.meta.url.indexOf("index.js") === -1) require("fail");
});
it("should return correct import.meta.webpack", () => {
expect(import.meta.webpack).toBe(webpackVersion);
if (import.meta.webpack < 5) require("fail");
if (import.meta.webpack >= 5) {
} else {
require("fail");
}
});
it("should return undefined for unknown property", () => {
expect(import.meta.other).toBe(undefined);
if (typeof import.meta.other !== "undefined") require("fail");
expect(() => import.meta.other.other.other).toThrowError();
// if (typeof import.meta.other.other.other !== "undefined") require("fail");
});

View File

@ -0,0 +1 @@
export { value } from "./file";

View File

@ -0,0 +1 @@
export { value } from "./file";

View File

@ -0,0 +1,3 @@
export var value = 1;
---
export var value = 2;

View File

@ -0,0 +1,18 @@
it("should import a changed chunk", (done) => {
import("./chunk").then((chunk) => {
expect(chunk.value).toBe(1);
import("./chunk2").then((chunk2) => {
expect(chunk2.value).toBe(1);
NEXT(require("../../update")(done));
import.meta.webpackHot.accept(["./chunk", "./chunk2"], () => {
import("./chunk").then((chunk) => {
expect(chunk.value).toBe(2);
import("./chunk2").then((chunk2) => {
expect(chunk2.value).toBe(2);
done();
}).catch(done);
}).catch(done);
});
}).catch(done);
}).catch(done);
});

View File

@ -0,0 +1,11 @@
import vendor from "vendor";
import.meta.webpackHot.accept("vendor");
it("should hot update a splitted initial chunk", function (done) {
expect(vendor).toBe("1");
NEXT(
require("../../update")(done, true, () => {
expect(vendor).toBe("2");
done();
})
);
});

View File

@ -0,0 +1,3 @@
module.exports = "1";
---
module.exports = "2";

View File

@ -0,0 +1,12 @@
module.exports = {
output: {
filename: "[name].js"
},
optimization: {
chunkIds: "total-size",
splitChunks: {
chunks: "all",
minSize: 0
}
}
};

View File

@ -1,3 +1,3 @@
module.exports = "1";
---
module.exports = "2";
module.exports = "2";

View File

@ -0,0 +1,15 @@
import x from "./module";
it("should have correct this context", (done) => {
expect(x).toEqual("ok1");
(function() {
import.meta.webpackHot.accept("./module", () => {
expect(x).toEqual("ok2");
expect(this).toEqual({ ok: true });
done();
});
}).call({ ok: true });
NEXT(require("../../update")(done));
});

View File

@ -0,0 +1,3 @@
export default "ok1";
---
export default "ok2";

View File

@ -2,7 +2,7 @@ import x from "./module";
it("should have correct this context in accept handler", (done) => {
expect(x).toEqual("ok1");
(function() {
module.hot.accept("./module", () => {
expect(x).toEqual("ok2");

View File

@ -0,0 +1,8 @@
import b from "./b";
export default b;
if(import.meta.webpackHot) {
import.meta.webpackHot.decline("./b");
import.meta.webpackHot.accept();
}

View File

@ -0,0 +1 @@
export { default } from "./c"

View File

@ -0,0 +1,3 @@
export default 1;
---
export default 2;

View File

@ -0,0 +1,14 @@
import a from "./a";
it("should abort when module is declined by parent", (done) => {
expect(a).toBe(1);
NEXT(require("../../update")((err) => {
try {
expect(err.message).toMatch(/Aborted because of declined dependency: \.\/b\.js in \.\/a\.js/);
expect(err.message).toMatch(/Update propagation: \.\/c\.js -> \.\/b\.js -> \.\/a\.js/);
done();
} catch(e) {
done(e);
}
}));
});

View File

@ -0,0 +1,7 @@
import {val} from "./module";
it("should accept changes", (done) => {
expect(val).toBe(1);
NEXT(require("../../update")(done));
done();
});

View File

@ -0,0 +1,5 @@
import {value} from "dep1";
export const val = value;
import.meta.webpackHot.accept("dep1");

View File

@ -0,0 +1 @@
export {value} from "./file";

View File

@ -0,0 +1,3 @@
export var value = 1;
---
export var value = 2;

View File

@ -0,0 +1,5 @@
(() => {
throw new Error("should not resolve");
})();
export default 1;

View File

@ -0,0 +1,6 @@
{
"exports": {
"import": "./exports.js",
"default": "./main.js"
}
}

View File

@ -0,0 +1,14 @@
import {val} from "./module";
it("should fail accept changes", (done) => {
expect(val).toBe(1);
NEXT(require("../../update")((err) => {
try {
expect(err.message).toMatch(/Aborted because \.\/node_modules\/dep1\/file.js is not accepted/);
expect(err.message).toMatch(/Update propagation: \.\/node_modules\/dep1\/file.js -> \.\/node_modules\/dep1\/exports\.js -> \.\/module\.js -> \.\/index.js/);
done();
} catch(e) {
done(e);
}
}));
});

View File

@ -0,0 +1,5 @@
import {value} from "dep1";
export const val = value;
module.hot.accept("dep1");

View File

@ -0,0 +1 @@
export {value} from "./file";

View File

@ -0,0 +1,3 @@
export var value = 1;
---
export var value = 2;

View File

@ -0,0 +1,5 @@
(() => {
throw new Error("should not resolve");
})();
export default 1;

View File

@ -0,0 +1,6 @@
{
"exports": {
"import": "./exports.js",
"default": "./main.js"
}
}

12
types.d.ts vendored
View File

@ -333,6 +333,7 @@ declare abstract class BasicEvaluatedExpression {
getMembers: any;
expression: any;
isNull(): boolean;
isUndefined(): boolean;
isString(): boolean;
isNumber(): boolean;
isBigInt(): boolean;
@ -349,6 +350,7 @@ declare abstract class BasicEvaluatedExpression {
asBool(): any;
asString(): any;
setString(string?: any): BasicEvaluatedExpression;
setUndefined(): BasicEvaluatedExpression;
setNull(): BasicEvaluatedExpression;
setNumber(number?: any): BasicEvaluatedExpression;
setBigInt(bigint?: any): BasicEvaluatedExpression;
@ -3020,7 +3022,7 @@ declare abstract class JavascriptParser extends Parser {
evaluate: HookMap<SyncBailHook<[Expression], BasicEvaluatedExpression>>;
evaluateIdentifier: HookMap<
SyncBailHook<
[ThisExpression | MemberExpression | Identifier],
[ThisExpression | MemberExpression | MetaProperty | Identifier],
BasicEvaluatedExpression
>
>;
@ -3201,10 +3203,14 @@ declare abstract class JavascriptParser extends Parser {
>
>;
new: HookMap<SyncBailHook<[Expression], boolean | void>>;
metaProperty: SyncBailHook<[MetaProperty], boolean | void>;
expression: HookMap<SyncBailHook<[Expression], boolean | void>>;
expressionMemberChain: HookMap<
SyncBailHook<[Expression, string[]], boolean | void>
>;
unhandledExpressionMemberChain: HookMap<
SyncBailHook<[Expression, string[]], boolean | void>
>;
expressionConditionalOperator: SyncBailHook<[Expression], boolean | void>;
expressionLogicalOperator: SyncBailHook<[Expression], boolean | void>;
program: SyncBailHook<[Program, Comment[]], boolean | void>;
@ -3307,10 +3313,12 @@ declare abstract class JavascriptParser extends Parser {
expression?: any,
name?: any,
rootInfo?: any,
members?: any
members?: any,
onUnhandled?: any
): void;
walkThisExpression(expression?: any): void;
walkIdentifier(expression?: any): void;
walkMetaProperty(metaProperty: MetaProperty): void;
callHooksForExpression(hookMap: any, expr: any, ...args: any[]): any;
callHooksForExpressionWithFallback<T, R>(
hookMap: HookMap<SyncBailHook<T, R>>,