Skip to content

Commit 0170568

Browse files
authored
Merge pull request webpack#3297 from Kovensky/template-strings
Implement support for template strings in System.import
2 parents 684dbc6 + 8ffc447 commit 0170568

12 files changed

Lines changed: 212 additions & 11 deletions

lib/BasicEvaluatedExpression.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ BasicEvaluatedExpression.prototype.isIdentifier = function() {
3737
BasicEvaluatedExpression.prototype.isWrapped = function() {
3838
return Object.prototype.hasOwnProperty.call(this, "prefix") || Object.prototype.hasOwnProperty.call(this, "postfix");
3939
};
40+
BasicEvaluatedExpression.prototype.isTemplateString = function() {
41+
return Object.prototype.hasOwnProperty.call(this, "quasis");
42+
}
4043
BasicEvaluatedExpression.prototype.asBool = function() {
4144
if(this.isBoolean()) return this.bool;
4245
else if(this.isNull()) return false;
@@ -46,6 +49,13 @@ BasicEvaluatedExpression.prototype.asBool = function() {
4649
else if(this.isArray()) return true;
4750
else if(this.isConstArray()) return true;
4851
else if(this.isWrapped()) return this.prefix && this.prefix.asBool() || this.postfix && this.postfix.asBool() ? true : undefined;
52+
else if(this.isTemplateString()) {
53+
if (this.quasis.length === 1) return this.quasis[0].asBool();
54+
for (var i = 0; i < this.quasis.length; i++) {
55+
if (this.quasis[i].asBool()) return true;
56+
}
57+
// can't tell if string will be empty without executing
58+
}
4959
return undefined;
5060
};
5161
BasicEvaluatedExpression.prototype.set = function(value) {
@@ -127,6 +137,13 @@ BasicEvaluatedExpression.prototype.setArray = function(array) {
127137
this.array = array;
128138
return this;
129139
};
140+
BasicEvaluatedExpression.prototype.setTemplateString = function(quasis) {
141+
if (quasis === null)
142+
delete this.quasis;
143+
else
144+
this.quasis = quasis;
145+
return this;
146+
}
130147
BasicEvaluatedExpression.prototype.addOptions = function(options) {
131148
if(!this.options) this.options = [];
132149
options.forEach(function(item) {

lib/Parser.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,45 @@ Parser.prototype.initializeEvaluating = function() {
297297
}
298298
return new BasicEvaluatedExpression().setString(result).setRange(expr.range);
299299
});
300+
301+
/**
302+
* @param {Parser} this
303+
* @param {"cooked" | "raw"} kind
304+
* @param {any[]} quasis
305+
* @param {any[]} expressions
306+
* @return {BasicEvaluatedExpression[]}
307+
*/
308+
function getSimplifiedTemplateResult(kind, quasis, expressions) {
309+
var i = 0, parts = [];
310+
311+
for (i = 0; i < quasis.length; i++) {
312+
parts.push(new BasicEvaluatedExpression().setString(quasis[i].value[kind]).setRange(quasis[i].range));
313+
314+
if (i > 0) {
315+
var prevExpr = parts[parts.length - 2], lastExpr = parts[parts.length - 1];
316+
var expr = this.evaluateExpression(expressions[i-1]);
317+
if (!(expr.isString() || expr.isNumber())) continue;
318+
319+
prevExpr.setString(prevExpr.string + (expr.isString() ? expr.string : expr.number) + lastExpr.string)
320+
prevExpr.setRange([prevExpr.range[0], lastExpr.range[1]]);
321+
parts.pop();
322+
}
323+
}
324+
return parts;
325+
}
326+
327+
this.plugin("evaluate TemplateLiteral", function(node) {
328+
var parts = getSimplifiedTemplateResult.call(this, 'cooked', node.quasis, node.expressions);
329+
if (parts.length == 1) {
330+
return parts[0].setRange(node.range);
331+
}
332+
return new BasicEvaluatedExpression().setTemplateString(parts).setRange(node.range);
333+
});
334+
this.plugin("evaluate TaggedTemplateExpression", function(node) {
335+
if (this.evaluateExpression(node.tag).identifier !== 'String.raw') return;
336+
var parts = getSimplifiedTemplateResult.call(this, 'raw', node.quasi.quasis, node.quasi.expressions);
337+
return new BasicEvaluatedExpression().setTemplateString(parts).setRange(node.range);
338+
});
300339
}, this);
301340
this.plugin("evaluate CallExpression .split", function(expr, param) {
302341
if(!param.isString()) return;

lib/dependencies/ContextDependencyHelpers.js

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,56 @@
44
*/
55
var ContextDependencyHelpers = exports;
66

7+
/**
8+
* Escapes regular expression metacharacters
9+
* @param {string} str
10+
* @return string
11+
*/
12+
function quotemeta(str) {
13+
return str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&")
14+
}
15+
716
ContextDependencyHelpers.create = function(Dep, range, param, expr, options) {
8-
var dep;
9-
if(param.isWrapped() && (param.prefix && param.prefix.isString() || param.postfix && param.postfix.isString())) {
10-
var prefix = param.prefix && param.prefix.isString() ? param.prefix.string : "";
11-
var postfix = param.postfix && param.postfix.isString() ? param.postfix.string : "";
12-
var prefixRange = param.prefix && param.prefix.isString() ? param.prefix.range : null;
13-
var valueRange = [prefixRange ? prefixRange[1] : param.range[0], param.range[1]];
14-
var idx = prefix.lastIndexOf("/");
15-
var context = ".";
17+
var dep, prefix, postfix, prefixRange, valueRange, idx, context, regExp;
18+
if(param.isTemplateString()) {
19+
prefix = param.quasis[0].string;
20+
postfix = param.quasis.length > 1 ? param.quasis[param.quasis.length - 1].string : "";
21+
prefixRange = [param.quasis[0].range[0], param.quasis[0].range[1]];
22+
valueRange = param.range;
23+
idx = prefix.lastIndexOf("/");
24+
context = ".";
25+
if(idx >= 0) {
26+
context = prefix.substr(0, idx);
27+
prefix = "." + prefix.substr(idx);
28+
}
29+
// If there are more than two quasis, maybe the generated RegExp can be more precise?
30+
regExp = new RegExp("^" +
31+
quotemeta(prefix) +
32+
options.wrappedContextRegExp.source +
33+
quotemeta(postfix) + "$");
34+
dep = new Dep(context, options.wrappedContextRecursive, regExp, range, valueRange);
35+
dep.loc = expr.loc;
36+
dep.replaces = [{
37+
range: prefixRange,
38+
value: prefix
39+
}];
40+
dep.critical = options.wrappedContextCritical && "a part of the request of a dependency is an expression";
41+
return dep;
42+
} else if(param.isWrapped() && (param.prefix && param.prefix.isString() || param.postfix && param.postfix.isString())) {
43+
prefix = param.prefix && param.prefix.isString() ? param.prefix.string : "";
44+
postfix = param.postfix && param.postfix.isString() ? param.postfix.string : "";
45+
prefixRange = param.prefix && param.prefix.isString() ? param.prefix.range : null;
46+
valueRange = [prefixRange ? prefixRange[1] : param.range[0], param.range[1]];
47+
idx = prefix.lastIndexOf("/");
48+
context = ".";
1649
if(idx >= 0) {
1750
context = prefix.substr(0, idx);
1851
prefix = "." + prefix.substr(idx);
1952
}
20-
var regExp = new RegExp("^" +
21-
prefix.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") +
53+
regExp = new RegExp("^" +
54+
quotemeta(prefix) +
2255
options.wrappedContextRegExp.source +
23-
postfix.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") + "$");
56+
quotemeta(postfix) + "$");
2457
dep = new Dep(context, options.wrappedContextRecursive, regExp, range, valueRange);
2558
dep.loc = expr.loc;
2659
dep.prepend = param.prefix && param.prefix.isString() ? prefix : null;

lib/dependencies/ContextDependencyTemplateAsId.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ ContextDependencyTemplateAsId.prototype.apply = function(dep, source, outputOpti
1010
if(outputOptions.pathinfo) comment = "/*! " + requestShortener.shorten(dep.request) + " */ ";
1111
if(dep.module && dep.module.dependencies && dep.module.dependencies.length > 0) {
1212
if(dep.valueRange) {
13+
if(Array.isArray(dep.replaces)) {
14+
for(var i = 0; i < dep.replaces.length; i++) {
15+
var rep = dep.replaces[i];
16+
source.replace(rep.range[0], rep.range[1] - 1, rep.value)
17+
}
18+
}
1319
source.replace(dep.valueRange[1], dep.range[1] - 1, ")");
1420
source.replace(dep.range[0], dep.valueRange[0] - 1, "__webpack_require__(" + comment + JSON.stringify(dep.module.id) + ").resolve(" + (typeof dep.prepend === "string" ? JSON.stringify(dep.prepend) : "") + "");
1521
} else {

lib/dependencies/ContextDependencyTemplateAsRequireCall.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ ContextDependencyTemplateAsRequireCall.prototype.apply = function(dep, source, o
1212
var isAsync = dep.module && dep.module.async;
1313
if(dep.module && (isAsync || containsDeps)) {
1414
if(dep.valueRange) {
15+
if(Array.isArray(dep.replaces)) {
16+
for(var i = 0; i < dep.replaces.length; i++) {
17+
var rep = dep.replaces[i];
18+
source.replace(rep.range[0], rep.range[1] - 1, rep.value)
19+
}
20+
}
1521
source.replace(dep.valueRange[1], dep.range[1] - 1, ")");
1622
source.replace(dep.range[0], dep.valueRange[0] - 1, "__webpack_require__(" + comment + JSON.stringify(dep.module.id) + ")(" + (typeof dep.prepend === "string" ? JSON.stringify(dep.prepend) : "") + "");
1723
} else {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default "ok";
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
it("should parse template strings in amd requires", function(done) {
3+
var name = "abc";
4+
var suffix = "Test";
5+
6+
var pending = [
7+
require([`./abc/abcTest`], test),
8+
require([`./abc/${name}Test`], test),
9+
require([`./${name}/${name}Test`], test),
10+
require([`./abc/${name}${suffix}`], test),
11+
require([String.raw`./${name}/${name}${suffix}`], test)
12+
].length;
13+
14+
function test (result) {
15+
result.default.should.eql("ok")
16+
if (--pending <= 0) {
17+
done()
18+
}
19+
}
20+
})
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
2+
it("should parse template strings in require.ensure requires", function(done) {
3+
var name = "abc";
4+
var suffix = "Test";
5+
6+
require.ensure([], function(require) {
7+
var imports = [
8+
require(`./abc/${name}Test`),
9+
require(`./abc/${name}Test`),
10+
require(`./${name}/${name}Test`),
11+
require(`./abc/${name}${suffix}`),
12+
require(String.raw`./${name}/${name}${suffix}`)
13+
];
14+
15+
for (var i = 0; i < imports.length; i++) {
16+
imports[i].default.should.eql("ok");
17+
}
18+
done()
19+
})
20+
})
21+
22+
it("should parse template strings in sync requires", function() {
23+
var name = "sync";
24+
var suffix = "Test";
25+
26+
var imports = [
27+
require(`./sync/${name}Test`),
28+
require(`./sync/${name}${suffix}`),
29+
require(String.raw`./sync/${name.slice(0, 1)}y${name.slice(2)}${suffix}`),
30+
require(`./sync/sync${"Test"}`),
31+
require(String.raw`./sync/${"sync"}Test`)
32+
];
33+
34+
for (var i = 0; i < imports.length; i++) {
35+
imports[i].default.should.eql("sync");
36+
}
37+
})
38+
39+
it("should parse template strings in require.resolve", function() {
40+
var name = "sync";
41+
42+
// Arbitrary assertion; can't use .ok() as it could be 0,
43+
// can't use typeof as that depends on webpack config.
44+
require.resolve(`./sync/${name}Test`).should.not.be.undefined;
45+
})
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
var should = require('should')
2+
3+
it("should parse template strings in System.import", function(done) {
4+
var name = "abc".split("");
5+
var suffix = "Test";
6+
Promise.all([
7+
System.import(`./abc/${name[0]}${name[1]}${name[2]}Test`),
8+
System.import(String.raw`./${name.join("")}/${name.join("")}Test`),
9+
System.import(String.raw`./abc/${name.join("")}${suffix}`)
10+
])
11+
.then(function (imports) {
12+
for (var i = 0; i < imports.length; i++) {
13+
imports[i].default.should.eql("ok");
14+
}
15+
})
16+
.then(function () { done(); }, done)
17+
});
18+
19+
require("./cjs")
20+
require("./amd")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default "sync";

0 commit comments

Comments
 (0)