Skip to content

Commit ed50812

Browse files
authored
Merge pull request webpack#5235 from faceyspacey/master
feat($context): add "weak-context" asyncMode for universal rendering
2 parents b15bc28 + 72544de commit ed50812

32 files changed

Lines changed: 322 additions & 26 deletions

File tree

lib/ContextModule.js

Lines changed: 87 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,13 @@ class ContextModule extends Module {
140140
this.addBlock(block);
141141
}
142142

143-
} else {
143+
} else if(this.async === "weak" || this.async === "async-weak") {
144+
145+
// we mark all dependencies as weak
146+
dependencies.forEach(dep => dep.weak = true);
147+
this.dependencies = dependencies;
144148

149+
} else {
145150
// if we are lazy create a new async dependency block per dependency
146151
// and add all blocks to this context
147152
dependencies.forEach((dep, idx) => {
@@ -198,27 +203,80 @@ module.exports = webpackContext;
198203
webpackContext.id = ${JSON.stringify(id)};`;
199204
}
200205

206+
getWeakSyncSource(dependencies, id) {
207+
const map = this.getUserRequestMap(dependencies);
208+
return `var map = ${JSON.stringify(map, null, "\t")};
209+
function webpackContext(req) {
210+
var id = webpackContextResolve(req);
211+
if(!__webpack_require__.m[id])
212+
throw new Error("Module '" + req + "' ('" + id + "') is not available (weak dependency)");
213+
return __webpack_require__(id);
214+
};
215+
function webpackContextResolve(req) {
216+
var id = map[req];
217+
if(!(id + 1)) // check for number or string
218+
throw new Error("Cannot find module '" + req + "'.");
219+
return id;
220+
};
221+
webpackContext.keys = function webpackContextKeys() {
222+
return Object.keys(map);
223+
};
224+
webpackContext.resolve = webpackContextResolve;
225+
webpackContext.id = ${JSON.stringify(id)};
226+
module.exports = webpackContext;`;
227+
}
228+
229+
getAsyncWeakSource(dependencies, id) {
230+
const map = this.getUserRequestMap(dependencies);
231+
232+
return `var map = ${JSON.stringify(map, null, "\t")};
233+
function webpackAsyncContext(req) {
234+
return webpackAsyncContextResolve(req).then(function(id) {
235+
if(!__webpack_require__.m[id])
236+
throw new Error("Module '" + req + "' ('" + id + "') is not available (weak dependency)");
237+
return __webpack_require__(id);
238+
});
239+
};
240+
function webpackAsyncContextResolve(req) {
241+
// Here Promise.resolve().then() is used instead of new Promise() to prevent
242+
// uncatched exception popping up in devtools
243+
return Promise.resolve().then(function() {
244+
var id = map[req];
245+
if(!(id + 1)) // check for number or string
246+
throw new Error("Cannot find module '" + req + "'.");
247+
return id;
248+
});
249+
};
250+
webpackAsyncContext.keys = function webpackAsyncContextKeys() {
251+
return Object.keys(map);
252+
};
253+
webpackAsyncContext.resolve = webpackAsyncContextResolve;
254+
webpackAsyncContext.id = ${JSON.stringify(id)};
255+
module.exports = webpackAsyncContext;`;
256+
}
257+
201258
getEagerSource(dependencies, id) {
202259
const map = this.getUserRequestMap(dependencies);
203260
return `var map = ${JSON.stringify(map, null, "\t")};
204261
function webpackAsyncContext(req) {
205262
return webpackAsyncContextResolve(req).then(__webpack_require__);
206263
};
207264
function webpackAsyncContextResolve(req) {
208-
return new Promise(function(resolve, reject) {
265+
// Here Promise.resolve().then() is used instead of new Promise() to prevent
266+
// uncatched exception popping up in devtools
267+
return Promise.resolve().then(function() {
209268
var id = map[req];
210269
if(!(id + 1)) // check for number or string
211-
reject(new Error("Cannot find module '" + req + "'."));
212-
else
213-
resolve(id);
270+
throw new Error("Cannot find module '" + req + "'.");
271+
return id;
214272
});
215273
};
216274
webpackAsyncContext.keys = function webpackAsyncContextKeys() {
217275
return Object.keys(map);
218276
};
219277
webpackAsyncContext.resolve = webpackAsyncContextResolve;
220-
module.exports = webpackAsyncContext;
221-
webpackAsyncContext.id = ${JSON.stringify(id)};`;
278+
webpackAsyncContext.id = ${JSON.stringify(id)};
279+
module.exports = webpackAsyncContext;`;
222280
}
223281

224282
getLazyOnceSource(block, dependencies, id, outputOptions, requestShortener) {
@@ -240,8 +298,8 @@ webpackAsyncContext.keys = function webpackAsyncContextKeys() {
240298
return Object.keys(map);
241299
};
242300
webpackAsyncContext.resolve = webpackAsyncContextResolve;
243-
module.exports = webpackAsyncContext;
244-
webpackAsyncContext.id = ${JSON.stringify(id)};`;
301+
webpackAsyncContext.id = ${JSON.stringify(id)};
302+
module.exports = webpackAsyncContext;`;
245303
}
246304

247305
getLazySource(blocks, id) {
@@ -282,8 +340,8 @@ function webpackAsyncContext(req) {
282340
webpackAsyncContext.keys = function webpackAsyncContextKeys() {
283341
return Object.keys(map);
284342
};
285-
module.exports = webpackAsyncContext;
286-
webpackAsyncContext.id = ${JSON.stringify(id)};`;
343+
webpackAsyncContext.id = ${JSON.stringify(id)};
344+
module.exports = webpackAsyncContext;`;
287345
}
288346

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

299357
getSourceForEmptyAsyncContext(id) {
300358
return `function webpackEmptyAsyncContext(req) {
301-
return new Promise(function(resolve, reject) { reject(new Error("Cannot find module '" + req + "'.")); });
359+
// Here Promise.resolve().then() is used instead of new Promise() to prevent
360+
// uncatched exception popping up in devtools
361+
return Promise.resolve().then(function() {
362+
throw new Error("Cannot find module '" + req + "'.");
363+
});
302364
}
303365
webpackEmptyAsyncContext.keys = function() { return []; };
304366
webpackEmptyAsyncContext.resolve = webpackEmptyAsyncContext;
@@ -318,13 +380,25 @@ webpackEmptyAsyncContext.id = ${JSON.stringify(id)};`;
318380
return this.getEagerSource(this.dependencies, this.id);
319381
}
320382
return this.getSourceForEmptyAsyncContext(this.id);
321-
} else if(asyncMode === "lazy-once") {
383+
}
384+
if(asyncMode === "lazy-once") {
322385
const block = this.blocks[0];
323386
if(block) {
324387
return this.getLazyOnceSource(block, block.dependencies, this.id, outputOptions, requestShortener);
325388
}
326389
return this.getSourceForEmptyAsyncContext(this.id);
327390
}
391+
if(asyncMode === "async-weak") {
392+
if(this.dependencies && this.dependencies.length > 0) {
393+
return this.getAsyncWeakSource(this.dependencies, this.id);
394+
}
395+
return this.getSourceForEmptyAsyncContext(this.id);
396+
}
397+
if(asyncMode === "weak") {
398+
if(this.dependencies && this.dependencies.length > 0) {
399+
return this.getWeakSyncSource(this.dependencies, this.id);
400+
}
401+
}
328402
if(this.dependencies && this.dependencies.length > 0) {
329403
return this.getSyncSource(this.dependencies, this.id);
330404
}

lib/dependencies/ImportParserPlugin.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
"use strict";
66

77
const ImportEagerContextDependency = require("./ImportEagerContextDependency");
8+
const ImportWeakDependency = require("./ImportWeakDependency");
9+
const ImportWeakContextDependency = require("./ImportWeakContextDependency");
810
const ImportLazyOnceContextDependency = require("./ImportLazyOnceContextDependency");
911
const ImportLazyContextDependency = require("./ImportLazyContextDependency");
1012
const ImportDependenciesBlock = require("./ImportDependenciesBlock");
@@ -46,26 +48,31 @@ class ImportParserPlugin {
4648
}
4749

4850
if(param.isString()) {
49-
if(mode !== "lazy" && mode !== "eager") {
50-
parser.state.module.warnings.push(new UnsupportedFeatureWarning(parser.state.module, `\`webpackMode\` expected 'lazy' or 'eager', but received: ${mode}.`));
51+
if(mode !== "lazy" && mode !== "eager" && mode !== "weak") {
52+
parser.state.module.warnings.push(new UnsupportedFeatureWarning(parser.state.module, `\`webpackMode\` expected 'lazy', 'eager' or 'weak', but received: ${mode}.`));
5153
}
5254

5355
if(mode === "eager") {
5456
const dep = new ImportEagerDependency(param.string, expr.range);
5557
parser.state.current.addDependency(dep);
58+
} else if(mode === "weak") {
59+
const dep = new ImportWeakDependency(param.string, expr.range);
60+
parser.state.current.addDependency(dep);
5661
} else {
5762
const depBlock = new ImportDependenciesBlock(param.string, expr.range, chunkName, parser.state.module, expr.loc);
5863
parser.state.current.addBlock(depBlock);
5964
}
6065
return true;
6166
} else {
62-
if(mode !== "lazy" && mode !== "lazy-once" && mode !== "eager") {
63-
parser.state.module.warnings.push(new UnsupportedFeatureWarning(parser.state.module, `\`webpackMode\` expected 'lazy', 'lazy-once' or 'eager', but received: ${mode}.`));
67+
if(mode !== "lazy" && mode !== "lazy-once" && mode !== "eager" && mode !== "weak") {
68+
parser.state.module.warnings.push(new UnsupportedFeatureWarning(parser.state.module, `\`webpackMode\` expected 'lazy', 'lazy-once', 'eager' or 'weak', but received: ${mode}.`));
6469
}
6570

6671
let Dep = ImportLazyContextDependency;
6772
if(mode === "eager") {
6873
Dep = ImportEagerContextDependency;
74+
} else if(mode === "weak") {
75+
Dep = ImportWeakContextDependency;
6976
} else if(mode === "lazy-once") {
7077
Dep = ImportLazyOnceContextDependency;
7178
}

lib/dependencies/ImportPlugin.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
const ImportDependency = require("./ImportDependency");
88
const ImportEagerDependency = require("./ImportEagerDependency");
9+
const ImportWeakDependency = require("./ImportWeakDependency");
910
const ImportEagerContextDependency = require("./ImportEagerContextDependency");
11+
const ImportWeakContextDependency = require("./ImportWeakContextDependency");
1012
const ImportLazyOnceContextDependency = require("./ImportLazyOnceContextDependency");
1113
const ImportLazyContextDependency = require("./ImportLazyContextDependency");
1214
const ImportParserPlugin = require("./ImportParserPlugin");
@@ -28,9 +30,15 @@ class ImportPlugin {
2830
compilation.dependencyFactories.set(ImportEagerDependency, normalModuleFactory);
2931
compilation.dependencyTemplates.set(ImportEagerDependency, new ImportEagerDependency.Template());
3032

33+
compilation.dependencyFactories.set(ImportWeakDependency, normalModuleFactory);
34+
compilation.dependencyTemplates.set(ImportWeakDependency, new ImportWeakDependency.Template());
35+
3136
compilation.dependencyFactories.set(ImportEagerContextDependency, contextModuleFactory);
3237
compilation.dependencyTemplates.set(ImportEagerContextDependency, new ImportEagerContextDependency.Template());
3338

39+
compilation.dependencyFactories.set(ImportWeakContextDependency, contextModuleFactory);
40+
compilation.dependencyTemplates.set(ImportWeakContextDependency, new ImportWeakContextDependency.Template());
41+
3442
compilation.dependencyFactories.set(ImportLazyOnceContextDependency, contextModuleFactory);
3543
compilation.dependencyTemplates.set(ImportLazyOnceContextDependency, new ImportLazyOnceContextDependency.Template());
3644

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
Author Tobias Koppers @sokra
4+
*/
5+
"use strict";
6+
const ImportContextDependency = require("./ImportContextDependency");
7+
const ContextDependencyTemplateAsRequireCall = require("./ContextDependencyTemplateAsRequireCall");
8+
9+
class ImportWeakContextDependency extends ImportContextDependency {
10+
constructor(request, recursive, regExp, range, valueRange, chunkName) {
11+
super(request, recursive, regExp, range, valueRange, chunkName);
12+
this.async = "async-weak";
13+
}
14+
15+
get type() {
16+
return "import() context weak";
17+
}
18+
}
19+
20+
ImportWeakContextDependency.Template = ContextDependencyTemplateAsRequireCall;
21+
22+
module.exports = ImportWeakContextDependency;
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
Author Tobias Koppers @sokra
4+
*/
5+
"use strict";
6+
const ModuleDependency = require("./ModuleDependency");
7+
const webpackMissingPromiseModule = require("./WebpackMissingModule").promise;
8+
9+
class ImportWeakDependency extends ModuleDependency {
10+
constructor(request, range) {
11+
super(request);
12+
this.range = range;
13+
this.weak = true;
14+
}
15+
16+
get type() {
17+
return "import() weak";
18+
}
19+
}
20+
21+
ImportWeakDependency.Template = class ImportDependencyTemplate {
22+
apply(dep, source, outputOptions, requestShortener) {
23+
const comment = this.getOptionalComment(outputOptions.pathinfo, requestShortener.shorten(dep.request));
24+
25+
const content = this.getContent(dep, comment);
26+
source.replace(dep.range[0], dep.range[1] - 1, content);
27+
}
28+
29+
getOptionalComment(pathinfo, shortenedRequest) {
30+
if(!pathinfo) {
31+
return "";
32+
}
33+
34+
return `/*! ${shortenedRequest} */ `;
35+
}
36+
37+
getContent(dep, comment) {
38+
if(dep.module) {
39+
const stringifiedId = JSON.stringify(dep.module.id);
40+
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); })`;
41+
}
42+
43+
return webpackMissingPromiseModule(dep.request);
44+
}
45+
};
46+
47+
module.exports = ImportWeakDependency;

lib/dependencies/RequireContextDependency.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@ const ContextDependency = require("./ContextDependency");
77
const ModuleDependencyTemplateAsRequireId = require("./ModuleDependencyTemplateAsRequireId");
88

99
class RequireContextDependency extends ContextDependency {
10-
constructor(request, recursive, regExp, range) {
10+
constructor(request, recursive, regExp, asyncMode, range) {
1111
super(request, recursive, regExp);
1212
this.range = range;
13+
14+
if(asyncMode) {
15+
this.async = asyncMode;
16+
}
1317
}
1418

1519
get type() {

lib/dependencies/RequireContextDependencyParserPlugin.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,15 @@ module.exports = class RequireContextDependencyParserPlugin {
1111
parser.plugin("call require.context", expr => {
1212
let regExp = /^\.\/.*$/;
1313
let recursive = true;
14+
let asyncMode;
1415
switch(expr.arguments.length) {
16+
case 4:
17+
{
18+
const asyncModeExpr = parser.evaluateExpression(expr.arguments[3]);
19+
if(!asyncModeExpr.isString()) return;
20+
asyncMode = asyncModeExpr.string;
21+
}
22+
// falls through
1523
case 3:
1624
{
1725
const regExpExpr = parser.evaluateExpression(expr.arguments[2]);
@@ -30,7 +38,7 @@ module.exports = class RequireContextDependencyParserPlugin {
3038
{
3139
const requestExpr = parser.evaluateExpression(expr.arguments[0]);
3240
if(!requestExpr.isString()) return;
33-
const dep = new RequireContextDependency(requestExpr.string, recursive, regExp, expr.range);
41+
const dep = new RequireContextDependency(requestExpr.string, recursive, regExp, asyncMode, expr.range);
3442
dep.loc = expr.loc;
3543
dep.optional = parser.scope.inTry;
3644
parser.state.current.addDependency(dep);

lib/dependencies/RequireResolveDependencyParserPlugin.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class RequireResolveDependencyParserPlugin {
6262
if(!dep) return;
6363
dep.loc = expr.loc;
6464
dep.optional = !!parser.scope.inTry;
65-
dep.weak = weak;
65+
dep.async = weak ? "weak" : false;
6666
parser.state.current.addDependency(dep);
6767
return true;
6868
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = 4;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
it("should not bundle context requires with asyncMode === 'weak'", function() {
2+
var contextRequire = require.context(".", false, /two/, "weak");
3+
(function() {
4+
contextRequire("./two")
5+
}).should.throw(/not available/);
6+
});
7+
8+
it("should find module with asyncMode === 'weak' when required elsewhere", function() {
9+
var contextRequire = require.context(".", false, /.+/, "weak");
10+
contextRequire("./three").should.be.eql(3);
11+
require("./three"); // in a real app would be served as a separate chunk
12+
});
13+
14+
it("should find module with asyncMode === 'weak' when required elsewhere (recursive)", function() {
15+
var contextRequire = require.context(".", true, /.+/, "weak");
16+
contextRequire("./dir/four").should.be.eql(4);
17+
require("./dir/four"); // in a real app would be served as a separate chunk
18+
});

0 commit comments

Comments
 (0)