Skip to content

Commit 22e7d25

Browse files
committed
Add webpackMode optios for import()
Allow lazy, lazy-once and eager mode for import() Fix a bug which caused context chunks to be merged when using chunk name Allow to work in env where there is no Promise.resolve
1 parent 7bc08e1 commit 22e7d25

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+440
-61
lines changed

lib/ContextModule.js

Lines changed: 132 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ const Module = require("./Module");
88
const OriginalSource = require("webpack-sources").OriginalSource;
99
const RawSource = require("webpack-sources").RawSource;
1010
const AsyncDependenciesBlock = require("./AsyncDependenciesBlock");
11+
const DepBlockHelpers = require("./dependencies/DepBlockHelpers");
12+
const Template = require("./Template");
1113

1214
class ContextModule extends Module {
13-
constructor(resolveDependencies, context, recursive, regExp, addon, isAsync, chunkName) {
15+
constructor(resolveDependencies, context, recursive, regExp, addon, asyncMode, chunkName) {
1416
super();
1517
this.resolveDependencies = resolveDependencies;
1618
this.context = context;
1719
this.recursive = recursive;
1820
this.regExp = regExp;
1921
this.addon = addon;
20-
this.async = !!isAsync;
22+
this.async = asyncMode;
2123
this.cacheable = true;
2224
this.contextDependencies = [context];
2325
this.built = false;
@@ -44,7 +46,7 @@ class ContextModule extends Module {
4446
identifier() {
4547
let identifier = this.context;
4648
if(this.async)
47-
identifier += " async";
49+
identifier += ` ${this.async}`;
4850
if(!this.recursive)
4951
identifier += " nonrecursive";
5052
if(this.addon)
@@ -58,7 +60,7 @@ class ContextModule extends Module {
5860
readableIdentifier(requestShortener) {
5961
let identifier = requestShortener.shorten(this.context);
6062
if(this.async)
61-
identifier += " async";
63+
identifier += ` ${this.async}`;
6264
if(!this.recursive)
6365
identifier += " nonrecursive";
6466
if(this.addon)
@@ -72,7 +74,7 @@ class ContextModule extends Module {
7274
libIdent(options) {
7375
let identifier = this.contextify(options.context, this.context);
7476
if(this.async)
75-
identifier += " async";
77+
identifier += ` ${this.async}`;
7678
if(this.recursive)
7779
identifier += " recursive";
7880
if(this.addon)
@@ -103,42 +105,67 @@ class ContextModule extends Module {
103105
this.resolveDependencies(fs, this.context, this.recursive, this.regExp, (err, dependencies) => {
104106
if(err) return callback(err);
105107

108+
// Reset children
109+
this.dependencies = [];
110+
this.blocks = [];
111+
112+
// abort if something failed
113+
// this will create an empty context
106114
if(!dependencies) {
107-
this.dependencies = [];
108115
callback();
109116
return;
110117
}
111118

112-
// enhance dependencies
119+
// enhance dependencies with meta info
113120
dependencies.forEach(dep => {
114121
dep.loc = dep.userRequest;
115122
dep.request = this.addon + dep.request;
116123
});
117124

118-
// if these we are not a async context
119-
// add dependencies and continue
120-
if(!this.async) {
125+
if(!this.async || this.async === "eager") {
126+
127+
// if we have an sync or eager context
128+
// just add all dependencies and continue
121129
this.dependencies = dependencies;
122-
callback();
123-
return;
124-
}
125130

126-
// if we are async however create a new async dependency block
127-
// and add that block to this context
128-
dependencies.forEach(dep => {
129-
const block = new AsyncDependenciesBlock(this.chunkName, dep.module, dep.loc);
130-
block.addDependency(dep);
131-
this.addBlock(block);
132-
});
131+
} else if(this.async === "lazy-once") {
132+
133+
// for the lazy-once mode create a new async dependency block
134+
// and add that block to this context
135+
if(dependencies.length > 0) {
136+
const block = new AsyncDependenciesBlock(this.chunkName, this);
137+
dependencies.forEach(dep => {
138+
block.addDependency(dep);
139+
});
140+
this.addBlock(block);
141+
}
142+
143+
} else {
144+
145+
// if we are lazy create a new async dependency block per dependency
146+
// and add all blocks to this context
147+
dependencies.forEach((dep, idx) => {
148+
let chunkName = this.chunkName;
149+
if(chunkName) {
150+
if(!/\[(index|request)\]/.test(chunkName))
151+
chunkName += "[index]";
152+
chunkName = chunkName.replace(/\[index\]/g, idx);
153+
chunkName = chunkName.replace(/\[request\]/g, Template.toPath(dep.userRequest));
154+
}
155+
const block = new AsyncDependenciesBlock(chunkName, dep.module, dep.loc);
156+
block.addDependency(dep);
157+
this.addBlock(block);
158+
});
159+
}
133160
callback();
134161
});
135162
}
136163

137-
getSourceWithDependencies(dependencies, id) {
164+
getUserRequestMap(dependencies) {
138165
// if we filter first we get a new array
139166
// therefor we dont need to create a clone of dependencies explicitly
140167
// therefore the order of this is !important!
141-
const map = dependencies
168+
return dependencies
142169
.filter(dependency => dependency.module)
143170
.sort((a, b) => {
144171
if(a.userRequest === b.userRequest) {
@@ -149,6 +176,10 @@ class ContextModule extends Module {
149176
map[dep.userRequest] = dep.module.id;
150177
return map;
151178
}, Object.create(null));
179+
}
180+
181+
getSyncSource(dependencies, id) {
182+
const map = this.getUserRequestMap(dependencies);
152183
return `var map = ${JSON.stringify(map, null, "\t")};
153184
function webpackContext(req) {
154185
return __webpack_require__(webpackContextResolve(req));
@@ -167,7 +198,53 @@ module.exports = webpackContext;
167198
webpackContext.id = ${JSON.stringify(id)};`;
168199
}
169200

170-
getSourceWithBlocks(blocks, id) {
201+
getEagerSource(dependencies, id) {
202+
const map = this.getUserRequestMap(dependencies);
203+
return `var map = ${JSON.stringify(map, null, "\t")};
204+
function webpackAsyncContext(req) {
205+
return webpackAsyncContextResolve(req).then(__webpack_require__);
206+
};
207+
function webpackAsyncContextResolve(req) {
208+
return new Promise(function(resolve, reject) {
209+
var id = map[req];
210+
if(!(id + 1)) // check for number or string
211+
reject(new Error("Cannot find module '" + req + "'."));
212+
else
213+
resolve(id);
214+
});
215+
};
216+
webpackAsyncContext.keys = function webpackAsyncContextKeys() {
217+
return Object.keys(map);
218+
};
219+
webpackAsyncContext.resolve = webpackAsyncContextResolve;
220+
module.exports = webpackAsyncContext;
221+
webpackAsyncContext.id = ${JSON.stringify(id)};`;
222+
}
223+
224+
getLazyOnceSource(block, dependencies, id, outputOptions, requestShortener) {
225+
const promise = DepBlockHelpers.getDepBlockPromise(block, outputOptions, requestShortener, "lazy-once context");
226+
const map = this.getUserRequestMap(dependencies);
227+
return `var map = ${JSON.stringify(map, null, "\t")};
228+
function webpackAsyncContext(req) {
229+
return webpackAsyncContextResolve(req).then(__webpack_require__);
230+
};
231+
function webpackAsyncContextResolve(req) {
232+
return ${promise}.then(function() {
233+
var id = map[req];
234+
if(!(id + 1)) // check for number or string
235+
throw new Error("Cannot find module '" + req + "'.");
236+
return id;
237+
});
238+
};
239+
webpackAsyncContext.keys = function webpackAsyncContextKeys() {
240+
return Object.keys(map);
241+
};
242+
webpackAsyncContext.resolve = webpackAsyncContextResolve;
243+
module.exports = webpackAsyncContext;
244+
webpackAsyncContext.id = ${JSON.stringify(id)};`;
245+
}
246+
247+
getLazySource(blocks, id) {
171248
let hasMultipleOrNoChunks = false;
172249
const map = blocks
173250
.filter(block => block.dependencies[0].module)
@@ -219,15 +296,38 @@ module.exports = webpackEmptyContext;
219296
webpackEmptyContext.id = ${JSON.stringify(id)};`;
220297
}
221298

222-
getSourceString() {
223-
if(this.dependencies && this.dependencies.length > 0) {
224-
return this.getSourceWithDependencies(this.dependencies, this.id);
225-
}
299+
getSourceForEmptyAsyncContext(id) {
300+
return `function webpackEmptyAsyncContext(req) {
301+
return new Promise(function(resolve, reject) { reject(new Error("Cannot find module '" + req + "'.")); });
302+
}
303+
webpackEmptyAsyncContext.keys = function() { return []; };
304+
webpackEmptyAsyncContext.resolve = webpackEmptyAsyncContext;
305+
module.exports = webpackEmptyAsyncContext;
306+
webpackEmptyAsyncContext.id = ${JSON.stringify(id)};`;
307+
}
226308

227-
if(this.blocks && this.blocks.length > 0) {
228-
return this.getSourceWithBlocks(this.blocks, this.id);
309+
getSourceString(asyncMode, outputOptions, requestShortener) {
310+
if(asyncMode === "lazy") {
311+
if(this.blocks && this.blocks.length > 0) {
312+
return this.getLazySource(this.blocks, this.id);
313+
}
314+
return this.getSourceForEmptyAsyncContext(this.id);
315+
}
316+
if(asyncMode === "eager") {
317+
if(this.dependencies && this.dependencies.length > 0) {
318+
return this.getEagerSource(this.dependencies, this.id);
319+
}
320+
return this.getSourceForEmptyAsyncContext(this.id);
321+
} else if(asyncMode === "lazy-once") {
322+
const block = this.blocks[0];
323+
if(block) {
324+
return this.getLazyOnceSource(block, block.dependencies, this.id, outputOptions, requestShortener);
325+
}
326+
return this.getSourceForEmptyAsyncContext(this.id);
327+
}
328+
if(this.dependencies && this.dependencies.length > 0) {
329+
return this.getSyncSource(this.dependencies, this.id);
229330
}
230-
231331
return this.getSourceForEmptyContext(this.id);
232332
}
233333

@@ -238,9 +338,9 @@ webpackEmptyContext.id = ${JSON.stringify(id)};`;
238338
return new RawSource(sourceString);
239339
}
240340

241-
source() {
341+
source(dependencyTemplates, outputOptions, requestShortener) {
242342
return this.getSource(
243-
this.getSourceString()
343+
this.getSourceString(this.async, outputOptions, requestShortener)
244344
);
245345
}
246346

lib/HotModuleReplacement.runtime.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,8 @@ module.exports = function() {
578578
}
579579

580580
hotSetStatus("idle");
581-
return Promise.resolve(outdatedModules);
581+
return new Promise(function(resolve) {
582+
resolve(outdatedModules);
583+
});
582584
}
583585
};

lib/JsonpMainTemplatePlugin.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ class JsonpMainTemplatePlugin {
1919
this.indent(
2020
chunk.ids.map(id => `${JSON.stringify(id)}: 0`).join(",\n")
2121
),
22-
"};"
22+
"};",
23+
"",
24+
"var resolvedPromise = new Promise(function(resolve) { resolve(); });"
2325
]);
2426
}
2527
return source;
@@ -81,7 +83,7 @@ class JsonpMainTemplatePlugin {
8183
return this.asString([
8284
"if(installedChunks[chunkId] === 0) {",
8385
this.indent([
84-
"return Promise.resolve();"
86+
"return resolvedPromise;"
8587
]),
8688
"}",
8789
"",

lib/Template.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ module.exports = class Template extends Tapable {
2626
return str.replace(/^[^a-zA-Z$_]/, "_").replace(/[^a-zA-Z0-9$_]/g, "_");
2727
}
2828

29+
static toPath(str) {
30+
if(typeof str !== "string") return "";
31+
return str.replace(/[^a-zA-Z0-9_!§$()=\-\^°]+/g, "-").replace(/^-|-$/, "");
32+
}
33+
2934
// map number to a single character a-z, A-Z or <_ + number> if number is too big
3035
static numberToIdentifer(n) {
3136
// lower case

lib/dependencies/DepBlockHelpers.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ DepBlockHelpers.getDepBlockPromise = function(depBlock, outputOptions, requestSh
3333
"])";
3434
}
3535
}
36-
return "Promise.resolve()";
36+
return "new Promise(function(resolve) { resolve(); })";
3737
};
3838

3939
function asComment(str) {

lib/dependencies/ImportContextDependency.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ class ImportContextDependency extends ContextDependency {
1212
super(request, recursive, regExp);
1313
this.range = range;
1414
this.valueRange = valueRange;
15-
this.async = true;
1615
this.chunkName = chunkName;
1716
}
1817

lib/dependencies/ImportDependency.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ ImportDependency.Template = class ImportDependencyTemplate {
4444

4545
if(dep.module) {
4646
const stringifiedId = JSON.stringify(dep.module.id);
47-
return `Promise.resolve(__webpack_require__(${comment}${stringifiedId}))`;
47+
return `new Promise(function(resolve) { resolve(__webpack_require__(${comment}${stringifiedId})); })`;
4848
}
4949

5050
return webpackMissingPromiseModule(dep.request);
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 ImportEagerContextDependency extends ImportContextDependency {
10+
constructor(request, recursive, regExp, range, valueRange, chunkName) {
11+
super(request, recursive, regExp, range, valueRange, chunkName);
12+
this.async = "eager";
13+
}
14+
15+
get type() {
16+
return "import() context eager";
17+
}
18+
}
19+
20+
ImportEagerContextDependency.Template = ContextDependencyTemplateAsRequireCall;
21+
22+
module.exports = ImportEagerContextDependency;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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 ImportEagerDependency extends ModuleDependency {
10+
constructor(request, range) {
11+
super(request);
12+
this.range = range;
13+
}
14+
15+
get type() {
16+
return "import()";
17+
}
18+
}
19+
20+
ImportEagerDependency.Template = class ImportEagerDependencyTemplate {
21+
apply(dep, source, outputOptions, requestShortener) {
22+
const comment = this.getOptionalComment(outputOptions.pathinfo, requestShortener.shorten(dep.request));
23+
24+
const content = this.getContent(dep, comment);
25+
source.replace(dep.range[0], dep.range[1] - 1, content);
26+
}
27+
28+
getOptionalComment(pathinfo, shortenedRequest) {
29+
if(!pathinfo) {
30+
return "";
31+
}
32+
33+
return `/*! ${shortenedRequest} */ `;
34+
}
35+
36+
getContent(dep, comment) {
37+
if(dep.module) {
38+
const stringifiedId = JSON.stringify(dep.module.id);
39+
return `new Promise(function(resolve) { resolve(__webpack_require__(${comment}${stringifiedId})); })`;
40+
}
41+
42+
return webpackMissingPromiseModule(dep.request);
43+
}
44+
};
45+
46+
module.exports = ImportEagerDependency;

0 commit comments

Comments
 (0)