Skip to content

Commit

Permalink
Merge pull request webpack#5235 from faceyspacey/master
Browse files Browse the repository at this point in the history
feat($context): add "weak-context" asyncMode for universal rendering
  • Loading branch information
sokra authored Jul 26, 2017
2 parents b15bc28 + 72544de commit ed50812
Show file tree
Hide file tree
Showing 32 changed files with 322 additions and 26 deletions.
100 changes: 87 additions & 13 deletions lib/ContextModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,13 @@ class ContextModule extends Module {
this.addBlock(block);
}

} else {
} else if(this.async === "weak" || this.async === "async-weak") {

// we mark all dependencies as weak
dependencies.forEach(dep => dep.weak = true);
this.dependencies = dependencies;

} else {
// if we are lazy create a new async dependency block per dependency
// and add all blocks to this context
dependencies.forEach((dep, idx) => {
Expand Down Expand Up @@ -198,27 +203,80 @@ 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 '" + req + "' ('" + 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;
webpackContext.id = ${JSON.stringify(id)};
module.exports = webpackContext;`;
}

getAsyncWeakSource(dependencies, id) {
const map = this.getUserRequestMap(dependencies);

return `var map = ${JSON.stringify(map, null, "\t")};
function webpackAsyncContext(req) {
return webpackAsyncContextResolve(req).then(function(id) {
if(!__webpack_require__.m[id])
throw new Error("Module '" + req + "' ('" + id + "') is not available (weak dependency)");
return __webpack_require__(id);
});
};
function webpackAsyncContextResolve(req) {
// Here Promise.resolve().then() is used instead of new Promise() to prevent
// uncatched exception popping up in devtools
return Promise.resolve().then(function() {
var id = map[req];
if(!(id + 1)) // check for number or string
throw new Error("Cannot find module '" + req + "'.");
return id;
});
};
webpackAsyncContext.keys = function webpackAsyncContextKeys() {
return Object.keys(map);
};
webpackAsyncContext.resolve = webpackAsyncContextResolve;
webpackAsyncContext.id = ${JSON.stringify(id)};
module.exports = webpackAsyncContext;`;
}

getEagerSource(dependencies, id) {
const map = this.getUserRequestMap(dependencies);
return `var map = ${JSON.stringify(map, null, "\t")};
function webpackAsyncContext(req) {
return webpackAsyncContextResolve(req).then(__webpack_require__);
};
function webpackAsyncContextResolve(req) {
return new Promise(function(resolve, reject) {
// Here Promise.resolve().then() is used instead of new Promise() to prevent
// uncatched exception popping up in devtools
return Promise.resolve().then(function() {
var id = map[req];
if(!(id + 1)) // check for number or string
reject(new Error("Cannot find module '" + req + "'."));
else
resolve(id);
throw new Error("Cannot find module '" + req + "'.");
return id;
});
};
webpackAsyncContext.keys = function webpackAsyncContextKeys() {
return Object.keys(map);
};
webpackAsyncContext.resolve = webpackAsyncContextResolve;
module.exports = webpackAsyncContext;
webpackAsyncContext.id = ${JSON.stringify(id)};`;
webpackAsyncContext.id = ${JSON.stringify(id)};
module.exports = webpackAsyncContext;`;
}

getLazyOnceSource(block, dependencies, id, outputOptions, requestShortener) {
Expand All @@ -240,8 +298,8 @@ webpackAsyncContext.keys = function webpackAsyncContextKeys() {
return Object.keys(map);
};
webpackAsyncContext.resolve = webpackAsyncContextResolve;
module.exports = webpackAsyncContext;
webpackAsyncContext.id = ${JSON.stringify(id)};`;
webpackAsyncContext.id = ${JSON.stringify(id)};
module.exports = webpackAsyncContext;`;
}

getLazySource(blocks, id) {
Expand Down Expand Up @@ -282,8 +340,8 @@ function webpackAsyncContext(req) {
webpackAsyncContext.keys = function webpackAsyncContextKeys() {
return Object.keys(map);
};
module.exports = webpackAsyncContext;
webpackAsyncContext.id = ${JSON.stringify(id)};`;
webpackAsyncContext.id = ${JSON.stringify(id)};
module.exports = webpackAsyncContext;`;
}

getSourceForEmptyContext(id) {
Expand All @@ -298,7 +356,11 @@ webpackEmptyContext.id = ${JSON.stringify(id)};`;

getSourceForEmptyAsyncContext(id) {
return `function webpackEmptyAsyncContext(req) {
return new Promise(function(resolve, reject) { reject(new Error("Cannot find module '" + req + "'.")); });
// Here Promise.resolve().then() is used instead of new Promise() to prevent
// uncatched exception popping up in devtools
return Promise.resolve().then(function() {
throw new Error("Cannot find module '" + req + "'.");
});
}
webpackEmptyAsyncContext.keys = function() { return []; };
webpackEmptyAsyncContext.resolve = webpackEmptyAsyncContext;
Expand All @@ -318,13 +380,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 === "async-weak") {
if(this.dependencies && this.dependencies.length > 0) {
return this.getAsyncWeakSource(this.dependencies, 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);
}
Expand Down
15 changes: 11 additions & 4 deletions lib/dependencies/ImportParserPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"use strict";

const ImportEagerContextDependency = require("./ImportEagerContextDependency");
const ImportWeakDependency = require("./ImportWeakDependency");
const ImportWeakContextDependency = require("./ImportWeakContextDependency");
const ImportLazyOnceContextDependency = require("./ImportLazyOnceContextDependency");
const ImportLazyContextDependency = require("./ImportLazyContextDependency");
const ImportDependenciesBlock = require("./ImportDependenciesBlock");
Expand Down Expand Up @@ -46,26 +48,31 @@ class ImportParserPlugin {
}

if(param.isString()) {
if(mode !== "lazy" && mode !== "eager") {
parser.state.module.warnings.push(new UnsupportedFeatureWarning(parser.state.module, `\`webpackMode\` expected 'lazy' or 'eager', but received: ${mode}.`));
if(mode !== "lazy" && mode !== "eager" && mode !== "weak") {
parser.state.module.warnings.push(new UnsupportedFeatureWarning(parser.state.module, `\`webpackMode\` expected 'lazy', 'eager' or 'weak', but received: ${mode}.`));
}

if(mode === "eager") {
const dep = new ImportEagerDependency(param.string, expr.range);
parser.state.current.addDependency(dep);
} else if(mode === "weak") {
const dep = new ImportWeakDependency(param.string, expr.range);
parser.state.current.addDependency(dep);
} else {
const depBlock = new ImportDependenciesBlock(param.string, expr.range, chunkName, parser.state.module, expr.loc);
parser.state.current.addBlock(depBlock);
}
return true;
} else {
if(mode !== "lazy" && mode !== "lazy-once" && mode !== "eager") {
parser.state.module.warnings.push(new UnsupportedFeatureWarning(parser.state.module, `\`webpackMode\` expected 'lazy', 'lazy-once' or 'eager', but received: ${mode}.`));
if(mode !== "lazy" && mode !== "lazy-once" && mode !== "eager" && mode !== "weak") {
parser.state.module.warnings.push(new UnsupportedFeatureWarning(parser.state.module, `\`webpackMode\` expected 'lazy', 'lazy-once', 'eager' or 'weak', but received: ${mode}.`));
}

let Dep = ImportLazyContextDependency;
if(mode === "eager") {
Dep = ImportEagerContextDependency;
} else if(mode === "weak") {
Dep = ImportWeakContextDependency;
} else if(mode === "lazy-once") {
Dep = ImportLazyOnceContextDependency;
}
Expand Down
8 changes: 8 additions & 0 deletions lib/dependencies/ImportPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

const ImportDependency = require("./ImportDependency");
const ImportEagerDependency = require("./ImportEagerDependency");
const ImportWeakDependency = require("./ImportWeakDependency");
const ImportEagerContextDependency = require("./ImportEagerContextDependency");
const ImportWeakContextDependency = require("./ImportWeakContextDependency");
const ImportLazyOnceContextDependency = require("./ImportLazyOnceContextDependency");
const ImportLazyContextDependency = require("./ImportLazyContextDependency");
const ImportParserPlugin = require("./ImportParserPlugin");
Expand All @@ -28,9 +30,15 @@ class ImportPlugin {
compilation.dependencyFactories.set(ImportEagerDependency, normalModuleFactory);
compilation.dependencyTemplates.set(ImportEagerDependency, new ImportEagerDependency.Template());

compilation.dependencyFactories.set(ImportWeakDependency, normalModuleFactory);
compilation.dependencyTemplates.set(ImportWeakDependency, new ImportWeakDependency.Template());

compilation.dependencyFactories.set(ImportEagerContextDependency, contextModuleFactory);
compilation.dependencyTemplates.set(ImportEagerContextDependency, new ImportEagerContextDependency.Template());

compilation.dependencyFactories.set(ImportWeakContextDependency, contextModuleFactory);
compilation.dependencyTemplates.set(ImportWeakContextDependency, new ImportWeakContextDependency.Template());

compilation.dependencyFactories.set(ImportLazyOnceContextDependency, contextModuleFactory);
compilation.dependencyTemplates.set(ImportLazyOnceContextDependency, new ImportLazyOnceContextDependency.Template());

Expand Down
22 changes: 22 additions & 0 deletions lib/dependencies/ImportWeakContextDependency.js
Original file line number Diff line number Diff line change
@@ -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 ImportWeakContextDependency extends ImportContextDependency {
constructor(request, recursive, regExp, range, valueRange, chunkName) {
super(request, recursive, regExp, range, valueRange, chunkName);
this.async = "async-weak";
}

get type() {
return "import() context weak";
}
}

ImportWeakContextDependency.Template = ContextDependencyTemplateAsRequireCall;

module.exports = ImportWeakContextDependency;
47 changes: 47 additions & 0 deletions lib/dependencies/ImportWeakDependency.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const ModuleDependency = require("./ModuleDependency");
const webpackMissingPromiseModule = require("./WebpackMissingModule").promise;

class ImportWeakDependency extends ModuleDependency {
constructor(request, range) {
super(request);
this.range = range;
this.weak = true;
}

get type() {
return "import() weak";
}
}

ImportWeakDependency.Template = class ImportDependencyTemplate {
apply(dep, source, outputOptions, requestShortener) {
const comment = this.getOptionalComment(outputOptions.pathinfo, requestShortener.shorten(dep.request));

const content = this.getContent(dep, comment);
source.replace(dep.range[0], dep.range[1] - 1, content);
}

getOptionalComment(pathinfo, shortenedRequest) {
if(!pathinfo) {
return "";
}

return `/*! ${shortenedRequest} */ `;
}

getContent(dep, comment) {
if(dep.module) {
const stringifiedId = JSON.stringify(dep.module.id);
return `Promise.resolve(${comment}${stringifiedId}).then(function(id) { if(!__webpack_require__.m[id]) throw new Error("Module '" + id + "' is not available (weak dependency)"); return __webpack_require__(id); })`;
}

return webpackMissingPromiseModule(dep.request);
}
};

module.exports = ImportWeakDependency;
6 changes: 5 additions & 1 deletion lib/dependencies/RequireContextDependency.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ const ContextDependency = require("./ContextDependency");
const ModuleDependencyTemplateAsRequireId = require("./ModuleDependencyTemplateAsRequireId");

class RequireContextDependency extends ContextDependency {
constructor(request, recursive, regExp, range) {
constructor(request, recursive, regExp, asyncMode, range) {
super(request, recursive, regExp);
this.range = range;

if(asyncMode) {
this.async = asyncMode;
}
}

get type() {
Expand Down
10 changes: 9 additions & 1 deletion lib/dependencies/RequireContextDependencyParserPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@ module.exports = class RequireContextDependencyParserPlugin {
parser.plugin("call require.context", expr => {
let regExp = /^\.\/.*$/;
let recursive = true;
let asyncMode;
switch(expr.arguments.length) {
case 4:
{
const asyncModeExpr = parser.evaluateExpression(expr.arguments[3]);
if(!asyncModeExpr.isString()) return;
asyncMode = asyncModeExpr.string;
}
// falls through
case 3:
{
const regExpExpr = parser.evaluateExpression(expr.arguments[2]);
Expand All @@ -30,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, 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);
Expand Down
2 changes: 1 addition & 1 deletion lib/dependencies/RequireResolveDependencyParserPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
Expand Down
1 change: 1 addition & 0 deletions test/cases/chunks/context-weak/dir/four.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = 4;
18 changes: 18 additions & 0 deletions test/cases/chunks/context-weak/index.js
Original file line number Diff line number Diff line change
@@ -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
});
1 change: 1 addition & 0 deletions test/cases/chunks/context-weak/three.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = 3;
1 change: 1 addition & 0 deletions test/cases/chunks/context-weak/two.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = 2;
1 change: 1 addition & 0 deletions test/cases/chunks/inline-options/dir10/a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "a";
1 change: 1 addition & 0 deletions test/cases/chunks/inline-options/dir11/a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "a";
1 change: 1 addition & 0 deletions test/cases/chunks/inline-options/dir8/a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "a";
1 change: 1 addition & 0 deletions test/cases/chunks/inline-options/dir8/b.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "b";
1 change: 1 addition & 0 deletions test/cases/chunks/inline-options/dir8/c.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "c";
1 change: 1 addition & 0 deletions test/cases/chunks/inline-options/dir9/a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "a";
1 change: 1 addition & 0 deletions test/cases/chunks/inline-options/dir9/b.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "b";
1 change: 1 addition & 0 deletions test/cases/chunks/inline-options/dir9/c.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "c";
Loading

0 comments on commit ed50812

Please sign in to comment.