hmr support update

- import.meta.hot to import.meta.webpackHot
- create separate tests for import.meta.webpackHot
- remove HMRApiDependency in favor of ConstDependency
This commit is contained in:
Ivan Kopeykin 2020-06-24 14:41:44 +03:00
parent 1beb4e5707
commit 4cd526f970
37 changed files with 166 additions and 154 deletions

View File

@ -12,7 +12,7 @@ const Compilation = require("./Compilation");
const HotUpdateChunk = require("./HotUpdateChunk");
const NormalModule = require("./NormalModule");
const RuntimeGlobals = require("./RuntimeGlobals");
const HMRApiDependency = require("./dependencies/HMRApiDependency");
const ConstDependency = require("./dependencies/ConstDependency");
const ImportMetaHotAcceptDependency = require("./dependencies/ImportMetaHotAcceptDependency");
const ImportMetaHotDeclineDependency = require("./dependencies/ImportMetaHotDeclineDependency");
const ModuleHotAcceptDependency = require("./dependencies/ModuleHotAcceptDependency");
@ -83,17 +83,23 @@ class HotModuleReplacementPlugin {
return callback();
}
);
const runtimeRequirements = [RuntimeGlobals.module];
const createAcceptHandler = (parser, paramDependency) => {
const createAcceptHandler = (parser, ParamDependency) => {
const {
hotAcceptCallback,
hotAcceptWithoutCallback
} = HotModuleReplacementPlugin.getParserHooks(parser);
return expr => {
const dep = new HMRApiDependency(expr.callee.range, "accept");
const module = parser.state.module;
const dep = new ConstDependency(
`${module.moduleArgument}.hot.accept`,
expr.callee.range,
runtimeRequirements
);
dep.loc = expr.loc;
parser.state.module.addDependency(dep);
module.addPresentationalDependency(dep);
if (expr.arguments.length >= 1) {
const arg = parser.evaluateExpression(expr.arguments[0]);
let params = [];
@ -106,11 +112,11 @@ class HotModuleReplacementPlugin {
if (params.length > 0) {
params.forEach((param, idx) => {
const request = param.string;
const dep = new paramDependency(request, param.range);
const dep = new ParamDependency(request, param.range);
dep.optional = true;
dep.loc = Object.create(expr.loc);
dep.loc.index = idx;
parser.state.module.addDependency(dep);
module.addDependency(dep);
requests.push(request);
});
if (expr.arguments.length > 1) {
@ -128,10 +134,15 @@ class HotModuleReplacementPlugin {
};
};
const createDeclineHandler = (parser, paramDependency) => expr => {
const dep = new HMRApiDependency(expr.callee.range, "decline");
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;
parser.state.module.addDependency(dep);
module.addPresentationalDependency(dep);
if (expr.arguments.length === 1) {
const arg = parser.evaluateExpression(expr.arguments[0]);
let params = [];
@ -141,20 +152,25 @@ class HotModuleReplacementPlugin {
params = arg.items.filter(param => param.isString());
}
params.forEach((param, idx) => {
const dep = new paramDependency(param.string, param.range);
const dep = new ParamDependency(param.string, param.range);
dep.optional = true;
dep.loc = Object.create(expr.loc);
dep.loc.index = idx;
parser.state.module.addDependency(dep);
module.addDependency(dep);
});
}
return true;
};
const createHMRExpressionHandler = parser => expr => {
const dep = new HMRApiDependency(expr.range);
const module = parser.state.module;
const dep = new ConstDependency(
`${module.moduleArgument}.hot`,
expr.range,
runtimeRequirements
);
dep.loc = expr.loc;
parser.state.module.addDependency(dep);
module.addPresentationalDependency(dep);
return true;
};
@ -192,29 +208,29 @@ class HotModuleReplacementPlugin {
const applyImportMetaHot = parser => {
parser.hooks.evaluateIdentifier
.for("import.meta.hot")
.for("import.meta.webpackHot")
.tap("HotModuleReplacementPlugin", expr => {
return evaluateToIdentifier(
"import.meta.hot",
"import.meta.webpackHot",
"import.meta",
() => ["hot"],
() => ["webpackHot"],
true
)(expr);
});
parser.hooks.call
.for("import.meta.hot.accept")
.for("import.meta.webpackHot.accept")
.tap(
"HotModuleReplacementPlugin",
createAcceptHandler(parser, ImportMetaHotAcceptDependency)
);
parser.hooks.call
.for("import.meta.hot.decline")
.for("import.meta.webpackHot.decline")
.tap(
"HotModuleReplacementPlugin",
createDeclineHandler(parser, ImportMetaHotDeclineDependency)
);
parser.hooks.expression
.for("import.meta.hot")
.for("import.meta.webpackHot")
.tap("HotModuleReplacementPlugin", createHMRExpressionHandler(parser));
};
@ -258,7 +274,7 @@ class HotModuleReplacementPlugin {
);
//#endregion
//#region import.meta.hot.* API
//#region import.meta.webpackHot.* API
compilation.dependencyFactories.set(
ImportMetaHotAcceptDependency,
normalModuleFactory
@ -277,11 +293,6 @@ class HotModuleReplacementPlugin {
);
//#endregion
compilation.dependencyTemplates.set(
HMRApiDependency,
new HMRApiDependency.Template()
);
compilation.hooks.record.tap(
"HotModuleReplacementPlugin",
(compilation, records) => {

View File

@ -1,67 +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 HMRApiDependency extends NullDependency {
constructor(range, apiMethod) {
super();
this.range = range;
this.apiMethod = apiMethod;
}
get type() {
return "module.hot and import.meta.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(HMRApiDependency, "webpack/lib/dependencies/HMRApiDependency");
HMRApiDependency.Template = class HMRApiDependencyTemplate 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 {HMRApiDependency} */ (dependency);
runtimeRequirements.add(RuntimeGlobals.module);
source.replace(
dep.range[0],
dep.range[1] - 1,
`${module.moduleArgument}.hot${dep.apiMethod ? `.${dep.apiMethod}` : ""}`
);
}
};
module.exports = HMRApiDependency;

View File

@ -17,7 +17,7 @@ class ImportMetaHotAcceptDependency extends ModuleDependency {
}
get type() {
return "import.meta.hot.accept";
return "import.meta.webpackHot.accept";
}
get category() {

View File

@ -18,7 +18,7 @@ class ImportMetaHotDeclineDependency extends ModuleDependency {
}
get type() {
return "import.meta.hot.decline";
return "import.meta.webpackHot.decline";
}
get category() {

View File

@ -113,8 +113,6 @@ module.exports = {
require("../dependencies/ImportMetaHotAcceptDependency"),
"dependencies/ImportMetaHotDeclineDependency": () =>
require("../dependencies/ImportMetaHotDeclineDependency"),
"dependencies/HMRApiDependency": () =>
require("../dependencies/HMRApiDependency"),
"dependencies/ProvidedDependency": () =>
require("../dependencies/ProvidedDependency"),
"dependencies/PureExpressionDependency": () =>

View File

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

View File

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

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

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

View File

@ -1,4 +1,4 @@
it("should import a changed chunk using module.hot.accept API", (done) => {
it("should import a changed chunk", (done) => {
import("./chunk").then((chunk) => {
expect(chunk.value).toBe(1);
import("./chunk2").then((chunk2) => {
@ -16,22 +16,3 @@ it("should import a changed chunk using module.hot.accept API", (done) => {
}).catch(done);
}).catch(done);
});
it("should import a changed chunk using import.meta.hot.accept API", (done) => {
import("./chunk").then((chunk) => {
expect(chunk.value).toBe(2);
import("./chunk2").then((chunk2) => {
expect(chunk2.value).toBe(2);
NEXT(require("../../update")(done));
import.meta.hot.accept(["./chunk", "./chunk2"], () => {
import("./chunk").then((chunk) => {
expect(chunk.value).toBe(3);
import("./chunk2").then((chunk2) => {
expect(chunk2.value).toBe(3);
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,6 +1,6 @@
import vendor from "vendor";
import.meta.hot.accept("vendor");
it("should hot update a splitted initial chunk using import.meta.hot.* API", function (done) {
module.hot.accept("vendor");
it("should hot update a splitted initial chunk", function (done) {
expect(vendor).toBe("1");
NEXT(
require("../../update")(done, true, () => {
@ -9,14 +9,3 @@ it("should hot update a splitted initial chunk using import.meta.hot.* API", fun
})
);
});
it("should hot update a splitted initial chunk using module.hot.* API", function (done) {
expect(vendor).toBe("2");
module.hot.accept("vendor");
NEXT(
require("../../update")(done, true, () => {
expect(vendor).toBe("3");
done();
})
);
});

View File

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

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

@ -1,6 +1,6 @@
import x from "./module";
it("should have correct this context in module.hot.accept handler", (done) => {
it("should have correct this context in accept handler", (done) => {
expect(x).toEqual("ok1");
(function() {
@ -13,17 +13,3 @@ it("should have correct this context in module.hot.accept handler", (done) => {
NEXT(require("../../update")(done));
});
it("should have correct this context in import.meta.hot.accept handler", (done) => {
expect(x).toEqual("ok2");
(function() {
import.meta.hot.accept("./module", () => {
expect(x).toEqual("ok3");
expect(this).toEqual({ ok: true });
done();
});
}).call({ ok: true });
NEXT(require("../../update")(done));
});

View File

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

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

@ -2,7 +2,7 @@ import b from "./b";
export default b;
if(import.meta.hot) {
import.meta.hot.decline("./b");
import.meta.hot.accept();
if(module.hot) {
module.hot.decline("./b");
module.hot.accept();
}

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,3 @@
export var value = 1;
---
export var value = 2;

View File

@ -1,6 +1,6 @@
import {val} from "./module";
it("should fail import a changed chunk using module.hot.accept API", (done) => {
it("should fail accept changes", (done) => {
expect(val).toBe(1);
NEXT(require("../../update")((err) => {
try {

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"
}
}