fix/feat($weak): add context support for resolveWeak + create eager-weak asyncMode

This commit is contained in:
James Gillmore 2017-07-25 10:21:02 -07:00 committed by Tobias Koppers
parent 1102e60395
commit 8bca2dc95c
33 changed files with 304 additions and 5164 deletions

View File

@ -140,15 +140,14 @@ class ContextModule extends Module {
this.addBlock(block);
}
} else if(this.async === "weak-context") {
} else if(this.async === "weak") {
this.dependencies = dependencies;
// store the dependences in a different key than `this.dependences`
// to prevent them from being bundled in the parent
this.weakDependencies = dependencies;
// if we are lazy create a new async dependency block per dependency
// and add all blocks to this context
// if we are weak create a new async dependency block per dependency
// and add all blocks to this context, but mark each dep as weak, to
// prevent them from being bundled in the parent
dependencies.forEach((dep, idx) => {
dep.weak = true;
let chunkName = this.chunkName;
if(chunkName) {
@ -161,10 +160,8 @@ class ContextModule extends Module {
block.addDependency(dep);
this.addBlock(block);
});
} else {
// if we are lazy create a new async dependency block per dependency
// if we are lazy or eager-weak create a new async dependency block per dependency
// and add all blocks to this context
dependencies.forEach((dep, idx) => {
let chunkName = this.chunkName;
@ -220,6 +217,68 @@ module.exports = webpackContext;
webpackContext.id = ${JSON.stringify(id)};`;
}
getWeakSyncSource(dependencies, id) {
const map = this.getUserRequestMap(dependencies);
return `var map = ${JSON.stringify(map, null, "\t")};
function webpackContext(req) {
var id = webpackContextResolve(req);
if(!__webpack_require__.m[id])
throw new Error("Module '" + id + "' is not available (weak dependency)");
return __webpack_require__(id);
};
function webpackContextResolve(req) {
var id = map[req];
if(!(id + 1)) // check for number or string
throw new Error("Cannot find module '" + req + "'.");
return id;
};
webpackContext.keys = function webpackContextKeys() {
return Object.keys(map);
};
webpackContext.resolve = webpackContextResolve;
module.exports = webpackContext;
webpackContext.id = ${JSON.stringify(id)};`;
}
getWeakEagerSource(blocks, id) {
const map = blocks
.filter(block => block.dependencies[0].module)
.map((block) => ({
dependency: block.dependencies[0],
block: block,
userRequest: block.dependencies[0].userRequest
})).sort((a, b) => {
if(a.userRequest === b.userRequest) return 0;
return a.userRequest < b.userRequest ? -1 : 1;
}).reduce((map, item) => {
const chunks = item.block.chunks || [];
map[item.userRequest] = [item.dependency.module.id]
.concat(chunks.map(chunk => chunk.id));
return map;
}, Object.create(null));
return `var map = ${JSON.stringify(map, null, "\t")};
function webpackAsyncContext(req) {
var ids = map[req];
if(!ids)
return Promise.reject(new Error("Cannot find module '" + req + "'."));
return new Promise(function(resolve, reject) {
var id = ids[0];
if(!__webpack_require__.m[id])
reject(new Error("Module '" + id + "' is not available (weak dependency)"));
else
resolve(__webpack_require__(id));
});
};
webpackAsyncContext.keys = function webpackAsyncContextKeys() {
return Object.keys(map);
};
module.exports = webpackAsyncContext;
webpackAsyncContext.id = ${JSON.stringify(id)};`;
}
getEagerSource(dependencies, id) {
const map = this.getUserRequestMap(dependencies);
return `var map = ${JSON.stringify(map, null, "\t")};
@ -329,9 +388,6 @@ webpackEmptyAsyncContext.id = ${JSON.stringify(id)};`;
}
getSourceString(asyncMode, outputOptions, requestShortener) {
if(asyncMode === "weak-context") {
return this.getSyncSource(this.weakDependencies, this.id);
}
if(asyncMode === "lazy") {
if(this.blocks && this.blocks.length > 0) {
return this.getLazySource(this.blocks, this.id);
@ -343,13 +399,25 @@ webpackEmptyAsyncContext.id = ${JSON.stringify(id)};`;
return this.getEagerSource(this.dependencies, this.id);
}
return this.getSourceForEmptyAsyncContext(this.id);
} else if(asyncMode === "lazy-once") {
}
if(asyncMode === "lazy-once") {
const block = this.blocks[0];
if(block) {
return this.getLazyOnceSource(block, block.dependencies, this.id, outputOptions, requestShortener);
}
return this.getSourceForEmptyAsyncContext(this.id);
}
if(asyncMode === "eager-weak") {
if(this.blocks && this.blocks.length > 0) {
return this.getWeakEagerSource(this.blocks, this.id);
}
return this.getSourceForEmptyAsyncContext(this.id);
}
if(asyncMode === "weak") {
if(this.dependencies && this.dependencies.length > 0) {
return this.getWeakSyncSource(this.dependencies, this.id);
}
}
if(this.dependencies && this.dependencies.length > 0) {
return this.getSyncSource(this.dependencies, this.id);
}

View File

@ -5,12 +5,13 @@
"use strict";
const AsyncDependenciesBlock = require("../AsyncDependenciesBlock");
const ImportDependency = require("./ImportDependency");
const ImportEagerWeakDependency = require("./ImportEagerWeakDependency");
module.exports = class ImportDependenciesBlock extends AsyncDependenciesBlock {
constructor(request, range, chunkName, module, loc) {
constructor(request, range, chunkName, module, loc, mode) {
super(chunkName, module, loc);
this.range = range;
const dep = new ImportDependency(request, this);
const dep = mode === "eager-weak" ? new ImportEagerWeakDependency(request, this) : new ImportDependency(request, this, mode);
dep.loc = loc;
this.addDependency(dep);
}

View File

@ -0,0 +1,22 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const ImportContextDependency = require("./ImportContextDependency");
const ContextDependencyTemplateAsRequireCall = require("./ContextDependencyTemplateAsRequireCall");
class ImportEagerWeakContextDependency extends ImportContextDependency {
constructor(request, recursive, regExp, range, valueRange, chunkName) {
super(request, recursive, regExp, range, valueRange, chunkName);
this.async = "eager-weak";
}
get type() {
return "import() context eager-weak";
}
}
ImportEagerWeakContextDependency.Template = ContextDependencyTemplateAsRequireCall;
module.exports = ImportEagerWeakContextDependency;

View File

@ -0,0 +1,49 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const ModuleDependency = require("./ModuleDependency");
const DepBlockHelpers = require("./DepBlockHelpers");
const webpackMissingPromiseModule = require("./WebpackMissingModule").promise;
class ImportEagerWeakDependency extends ModuleDependency {
constructor(request, block) {
super(request);
this.block = block;
}
get type() {
return "import() eager-weak";
}
}
ImportEagerWeakDependency.Template = class ImportDependencyTemplate {
apply(dep, source, outputOptions, requestShortener) {
const depBlock = dep.block;
const promise = DepBlockHelpers.getDepBlockPromise(depBlock, outputOptions, requestShortener, "import()");
const comment = this.getOptionalComment(outputOptions.pathinfo, requestShortener.shorten(dep.request));
const content = this.getContent(promise, dep, comment);
source.replace(depBlock.range[0], depBlock.range[1] - 1, content);
}
getOptionalComment(pathinfo, shortenedRequest) {
if(!pathinfo) {
return "";
}
return `/*! ${shortenedRequest} */ `;
}
getContent(promise, dep, comment) {
if(dep.module) {
const stringifiedId = JSON.stringify(dep.module.id);
return `new Promise(function(resolve) { resolve(__webpack_require__(${comment}${stringifiedId})); })`;
}
return webpackMissingPromiseModule(dep.request);
}
};
module.exports = ImportEagerWeakDependency;

View File

@ -5,6 +5,7 @@
"use strict";
const ImportEagerContextDependency = require("./ImportEagerContextDependency");
const ImportEagerWeakContextDependency = require("./ImportEagerWeakContextDependency");
const ImportLazyOnceContextDependency = require("./ImportLazyOnceContextDependency");
const ImportLazyContextDependency = require("./ImportLazyContextDependency");
const ImportDependenciesBlock = require("./ImportDependenciesBlock");
@ -46,7 +47,7 @@ class ImportParserPlugin {
}
if(param.isString()) {
if(mode !== "lazy" && mode !== "eager") {
if(mode !== "lazy" && mode !== "eager" && mode !== "eager-weak") {
parser.state.module.warnings.push(new UnsupportedFeatureWarning(parser.state.module, `\`webpackMode\` expected 'lazy' or 'eager', but received: ${mode}.`));
}
@ -54,18 +55,21 @@ class ImportParserPlugin {
const dep = new ImportEagerDependency(param.string, expr.range);
parser.state.current.addDependency(dep);
} else {
const depBlock = new ImportDependenciesBlock(param.string, expr.range, chunkName, parser.state.module, expr.loc);
// both lazy and eager-weak modes use this; eager-weak needs the chunks created, but will expect them pre-served
const depBlock = new ImportDependenciesBlock(param.string, expr.range, chunkName, parser.state.module, expr.loc, mode);
parser.state.current.addBlock(depBlock);
}
return true;
} else {
if(mode !== "lazy" && mode !== "lazy-once" && mode !== "eager") {
if(mode !== "lazy" && mode !== "lazy-once" && mode !== "eager" && mode !== "eager-weak") {
parser.state.module.warnings.push(new UnsupportedFeatureWarning(parser.state.module, `\`webpackMode\` expected 'lazy', 'lazy-once' or 'eager', but received: ${mode}.`));
}
let Dep = ImportLazyContextDependency;
if(mode === "eager") {
Dep = ImportEagerContextDependency;
} else if(mode === "eager-weak") {
Dep = ImportEagerWeakContextDependency;
} else if(mode === "lazy-once") {
Dep = ImportLazyOnceContextDependency;
}

View File

@ -6,7 +6,9 @@
const ImportDependency = require("./ImportDependency");
const ImportEagerDependency = require("./ImportEagerDependency");
const ImportEagerWeakDependency = require("./ImportEagerWeakDependency");
const ImportEagerContextDependency = require("./ImportEagerContextDependency");
const ImportEagerWeakContextDependency = require("./ImportEagerWeakContextDependency");
const ImportLazyOnceContextDependency = require("./ImportLazyOnceContextDependency");
const ImportLazyContextDependency = require("./ImportLazyContextDependency");
const ImportParserPlugin = require("./ImportParserPlugin");
@ -28,9 +30,15 @@ class ImportPlugin {
compilation.dependencyFactories.set(ImportEagerDependency, normalModuleFactory);
compilation.dependencyTemplates.set(ImportEagerDependency, new ImportEagerDependency.Template());
compilation.dependencyFactories.set(ImportEagerWeakDependency, normalModuleFactory);
compilation.dependencyTemplates.set(ImportEagerWeakDependency, new ImportEagerWeakDependency.Template());
compilation.dependencyFactories.set(ImportEagerContextDependency, contextModuleFactory);
compilation.dependencyTemplates.set(ImportEagerContextDependency, new ImportEagerContextDependency.Template());
compilation.dependencyFactories.set(ImportEagerWeakContextDependency, contextModuleFactory);
compilation.dependencyTemplates.set(ImportEagerWeakContextDependency, new ImportEagerWeakContextDependency.Template());
compilation.dependencyFactories.set(ImportLazyOnceContextDependency, contextModuleFactory);
compilation.dependencyTemplates.set(ImportLazyOnceContextDependency, new ImportLazyOnceContextDependency.Template());

View File

@ -7,13 +7,12 @@ const ContextDependency = require("./ContextDependency");
const ModuleDependencyTemplateAsRequireId = require("./ModuleDependencyTemplateAsRequireId");
class RequireContextDependency extends ContextDependency {
constructor(request, recursive, regExp, weak, chunkName, range) {
constructor(request, recursive, regExp, weak, range) {
super(request, recursive, regExp);
this.range = range;
if(weak) {
this.async = "weak-context";
this.chunkName = chunkName;
this.async = weak;
}
}

View File

@ -11,21 +11,13 @@ module.exports = class RequireContextDependencyParserPlugin {
parser.plugin("call require.context", expr => {
let regExp = /^\.\/.*$/;
let recursive = true;
let weak = false;
let chunkName;
let asyncMode;
switch(expr.arguments.length) {
case 5:
{
const chunkNameExpr = parser.evaluateExpression(expr.arguments[4]);
if(!chunkNameExpr.isString()) return;
chunkName = chunkNameExpr.string;
}
// falls through
case 4:
{
const weakExpr = parser.evaluateExpression(expr.arguments[3]);
if(!weakExpr.isBoolean()) return;
weak = weakExpr.bool;
const asyncModeExpr = parser.evaluateExpression(expr.arguments[3]);
if(!asyncModeExpr.isString()) return;
asyncMode = asyncModeExpr.string;
}
// falls through
case 3:
@ -46,7 +38,7 @@ module.exports = class RequireContextDependencyParserPlugin {
{
const requestExpr = parser.evaluateExpression(expr.arguments[0]);
if(!requestExpr.isString()) return;
const dep = new RequireContextDependency(requestExpr.string, recursive, regExp, weak, chunkName, expr.range);
const dep = new RequireContextDependency(requestExpr.string, recursive, regExp, asyncMode, expr.range);
dep.loc = expr.loc;
dep.optional = parser.scope.inTry;
parser.state.current.addDependency(dep);

View File

@ -62,7 +62,7 @@ class RequireResolveDependencyParserPlugin {
if(!dep) return;
dep.loc = expr.loc;
dep.optional = !!parser.scope.inTry;
dep.weak = weak;
dep.async = weak ? "weak" : false;
parser.state.current.addDependency(dep);
return true;
});

5124
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,15 +14,16 @@ const webpack = require("../lib/webpack");
describe("TestCases", () => {
const casesPath = path.join(__dirname, "cases");
let categories = fs.readdirSync(casesPath);
categories = categories.map((cat) => {
categories = categories.filter(f => f.indexOf("chunks") > -1).map((cat) => {
return {
name: cat,
tests: fs.readdirSync(path.join(casesPath, cat)).filter((folder) => folder.indexOf("_") < 0)
tests: fs.readdirSync(path.join(casesPath, cat)).filter((folder) => folder.indexOf("_") < 0).filter(f => f.indexOf("inline-options") > -1)
};
});
[{
name: "normal"
}, {
},
{
name: "concat",
plugins: [
new webpack.optimize.ModuleConcatenationPlugin()
@ -101,7 +102,8 @@ describe("TestCases", () => {
new webpack.NamedModulesPlugin(),
new webpack.NamedChunksPlugin()
]
}].forEach((config) => {
}
].forEach((config) => {
describe(config.name, () => {
categories.forEach((category) => {
describe(category.name, function() {

View File

@ -0,0 +1 @@
module.exports = 4;

View File

@ -0,0 +1,18 @@
it("should not bundle context requires with asyncMode === 'weak'", function() {
var contextRequire = require.context(".", false, /two/, "weak");
(function() {
contextRequire("./two")
}).should.throw(/not available/);
});
it("should find module with asyncMode === 'weak' when required elsewhere", function() {
var contextRequire = require.context(".", false, /.+/, "weak");
contextRequire("./three").should.be.eql(3);
require("./three"); // in a real app would be served as a separate chunk
});
it("should find module with asyncMode === 'weak' when required elsewhere (recursive)", function() {
var contextRequire = require.context(".", true, /.+/, "weak");
contextRequire("./dir/four").should.be.eql(4);
require("./dir/four"); // in a real app would be served as a separate chunk
});

View File

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

View File

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

View File

@ -0,0 +1 @@
export default "a";

View File

@ -0,0 +1 @@
export default "a";

View File

@ -0,0 +1 @@
export default "a";

View File

@ -0,0 +1 @@
export default "b";

View File

@ -0,0 +1 @@
export default "c";

View File

@ -0,0 +1 @@
export default "a";

View File

@ -0,0 +1 @@
export default "b";

View File

@ -0,0 +1 @@
export default "c";

View File

@ -58,6 +58,52 @@ it("should be able to combine chunks by name", function(done) {
testChunkLoading(load, false, true, done);
});
it("should be able to use eager-weak mode", function(done) {
function load(name) {
return import(/* webpackMode: "eager-weak" */"./dir8/" + name);
}
require("./dir8/a") // chunks served manually by the user
require("./dir8/b")
require("./dir8/c")
testChunkLoading(load, true, true, done);
});
it("should be able to use eager-weak mode (without context)", function(done) {
function load(name) {
switch(name) {
case "a":
return import(/* webpackMode: "eager-weak" */ "./dir9/a");
case "b":
return import(/* webpackMode: "eager-weak" */ "./dir9/b");
case "c":
return import(/* webpackMode: "eager-weak" */ "./dir9/c");
default:
throw new Error("Unexcepted test data");
}
}
require("./dir9/a") // chunks served manually by the user
require("./dir9/b")
require("./dir9/c")
testChunkLoading(load, true, true, done);
});
it("should not find module when mode is eager-weak and chunk not served elsewhere", function(done) {
var name = "a";
import(/* webpackMode: "eager-weak" */"./dir10/" + name)
.catch(function(e) {
e.should.match(/not available/);
done();
})
});
it("should not find module when mode is eager-weak and chunk not served elsewhere (without context)", function(done) {
import(/* webpackMode: "eager-weak" */"./dir11/a")
.catch(function(e) {
e.should.match(/not available/);
done();
})
});
function testChunkLoading(load, expectedSyncInitial, expectedSyncRequested, done) {
var sync = false;
var syncInitial = true;

View File

@ -0,0 +1,24 @@
it("should not include a module with a weak dependency using context", function() {
var fileA = "a";
var fileB = "b";
var fileC = "c";
var resolveWeakA = require.resolveWeak("./" + fileA);
var resolveWeakB = require.resolveWeak("./" + fileB);
var resolveWeakC = require.resolveWeak("./" + fileC);
var a = !!__webpack_modules__[resolveWeakA];
var b = !!__webpack_modules__[resolveWeakB];
var c = !!__webpack_modules__[resolveWeakC];
require(["./b"]);
require("./c");
resolveWeakA.should.exist;
resolveWeakB.should.exist;
resolveWeakC.should.exist;
a.should.be.eql(false);
b.should.be.eql(false);
c.should.be.eql(true);
});

View File

@ -0,0 +1,3 @@
import(/* webpackMode: "eager-weak" */"./modules/a");
import(/* webpackMode: "eager-weak" */"./modules/b");

View File

@ -0,0 +1,9 @@
Hash: 2a00abf36773894e7317
Time: Xms
Asset Size Chunks Chunk Names
0.js 99 bytes 0 [emitted]
1.js 218 bytes 1 [emitted]
entry.js 5.92 kB 2 [emitted] entry
[0] (webpack)/test/statsCases/import-eager-weak/modules/b.js 22 bytes {0} [built]
[1] (webpack)/test/statsCases/import-eager-weak/entry.js 109 bytes {2} [built]
[2] (webpack)/test/statsCases/import-eager-weak/modules/a.js 37 bytes {1} [built]

View File

@ -0,0 +1,2 @@
import("./b");
module.exports = "a";

View File

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

View File

@ -0,0 +1,5 @@
module.exports = {
entry: {
"entry": "./entry",
}
};