Skip to content

Commit

Permalink
Merge pull request webpack#3297 from Kovensky/template-strings
Browse files Browse the repository at this point in the history
Implement support for template strings in System.import
  • Loading branch information
sokra authored Nov 21, 2016
2 parents 684dbc6 + 8ffc447 commit 0170568
Show file tree
Hide file tree
Showing 12 changed files with 212 additions and 11 deletions.
17 changes: 17 additions & 0 deletions lib/BasicEvaluatedExpression.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ BasicEvaluatedExpression.prototype.isIdentifier = function() {
BasicEvaluatedExpression.prototype.isWrapped = function() {
return Object.prototype.hasOwnProperty.call(this, "prefix") || Object.prototype.hasOwnProperty.call(this, "postfix");
};
BasicEvaluatedExpression.prototype.isTemplateString = function() {
return Object.prototype.hasOwnProperty.call(this, "quasis");
}
BasicEvaluatedExpression.prototype.asBool = function() {
if(this.isBoolean()) return this.bool;
else if(this.isNull()) return false;
Expand All @@ -46,6 +49,13 @@ BasicEvaluatedExpression.prototype.asBool = function() {
else if(this.isArray()) return true;
else if(this.isConstArray()) return true;
else if(this.isWrapped()) return this.prefix && this.prefix.asBool() || this.postfix && this.postfix.asBool() ? true : undefined;
else if(this.isTemplateString()) {
if (this.quasis.length === 1) return this.quasis[0].asBool();
for (var i = 0; i < this.quasis.length; i++) {
if (this.quasis[i].asBool()) return true;
}
// can't tell if string will be empty without executing
}
return undefined;
};
BasicEvaluatedExpression.prototype.set = function(value) {
Expand Down Expand Up @@ -127,6 +137,13 @@ BasicEvaluatedExpression.prototype.setArray = function(array) {
this.array = array;
return this;
};
BasicEvaluatedExpression.prototype.setTemplateString = function(quasis) {
if (quasis === null)
delete this.quasis;
else
this.quasis = quasis;
return this;
}
BasicEvaluatedExpression.prototype.addOptions = function(options) {
if(!this.options) this.options = [];
options.forEach(function(item) {
Expand Down
39 changes: 39 additions & 0 deletions lib/Parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,45 @@ Parser.prototype.initializeEvaluating = function() {
}
return new BasicEvaluatedExpression().setString(result).setRange(expr.range);
});

/**
* @param {Parser} this
* @param {"cooked" | "raw"} kind
* @param {any[]} quasis
* @param {any[]} expressions
* @return {BasicEvaluatedExpression[]}
*/
function getSimplifiedTemplateResult(kind, quasis, expressions) {
var i = 0, parts = [];

for (i = 0; i < quasis.length; i++) {
parts.push(new BasicEvaluatedExpression().setString(quasis[i].value[kind]).setRange(quasis[i].range));

if (i > 0) {
var prevExpr = parts[parts.length - 2], lastExpr = parts[parts.length - 1];
var expr = this.evaluateExpression(expressions[i-1]);
if (!(expr.isString() || expr.isNumber())) continue;

prevExpr.setString(prevExpr.string + (expr.isString() ? expr.string : expr.number) + lastExpr.string)
prevExpr.setRange([prevExpr.range[0], lastExpr.range[1]]);
parts.pop();
}
}
return parts;
}

this.plugin("evaluate TemplateLiteral", function(node) {
var parts = getSimplifiedTemplateResult.call(this, 'cooked', node.quasis, node.expressions);
if (parts.length == 1) {
return parts[0].setRange(node.range);
}
return new BasicEvaluatedExpression().setTemplateString(parts).setRange(node.range);
});
this.plugin("evaluate TaggedTemplateExpression", function(node) {
if (this.evaluateExpression(node.tag).identifier !== 'String.raw') return;
var parts = getSimplifiedTemplateResult.call(this, 'raw', node.quasi.quasis, node.quasi.expressions);
return new BasicEvaluatedExpression().setTemplateString(parts).setRange(node.range);
});
}, this);
this.plugin("evaluate CallExpression .split", function(expr, param) {
if(!param.isString()) return;
Expand Down
55 changes: 44 additions & 11 deletions lib/dependencies/ContextDependencyHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,56 @@
*/
var ContextDependencyHelpers = exports;

/**
* Escapes regular expression metacharacters
* @param {string} str
* @return string
*/
function quotemeta(str) {
return str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&")
}

ContextDependencyHelpers.create = function(Dep, range, param, expr, options) {
var dep;
if(param.isWrapped() && (param.prefix && param.prefix.isString() || param.postfix && param.postfix.isString())) {
var prefix = param.prefix && param.prefix.isString() ? param.prefix.string : "";
var postfix = param.postfix && param.postfix.isString() ? param.postfix.string : "";
var prefixRange = param.prefix && param.prefix.isString() ? param.prefix.range : null;
var valueRange = [prefixRange ? prefixRange[1] : param.range[0], param.range[1]];
var idx = prefix.lastIndexOf("/");
var context = ".";
var dep, prefix, postfix, prefixRange, valueRange, idx, context, regExp;
if(param.isTemplateString()) {
prefix = param.quasis[0].string;
postfix = param.quasis.length > 1 ? param.quasis[param.quasis.length - 1].string : "";
prefixRange = [param.quasis[0].range[0], param.quasis[0].range[1]];
valueRange = param.range;
idx = prefix.lastIndexOf("/");
context = ".";
if(idx >= 0) {
context = prefix.substr(0, idx);
prefix = "." + prefix.substr(idx);
}
// If there are more than two quasis, maybe the generated RegExp can be more precise?
regExp = new RegExp("^" +
quotemeta(prefix) +
options.wrappedContextRegExp.source +
quotemeta(postfix) + "$");
dep = new Dep(context, options.wrappedContextRecursive, regExp, range, valueRange);
dep.loc = expr.loc;
dep.replaces = [{
range: prefixRange,
value: prefix
}];
dep.critical = options.wrappedContextCritical && "a part of the request of a dependency is an expression";
return dep;
} else if(param.isWrapped() && (param.prefix && param.prefix.isString() || param.postfix && param.postfix.isString())) {
prefix = param.prefix && param.prefix.isString() ? param.prefix.string : "";
postfix = param.postfix && param.postfix.isString() ? param.postfix.string : "";
prefixRange = param.prefix && param.prefix.isString() ? param.prefix.range : null;
valueRange = [prefixRange ? prefixRange[1] : param.range[0], param.range[1]];
idx = prefix.lastIndexOf("/");
context = ".";
if(idx >= 0) {
context = prefix.substr(0, idx);
prefix = "." + prefix.substr(idx);
}
var regExp = new RegExp("^" +
prefix.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") +
regExp = new RegExp("^" +
quotemeta(prefix) +
options.wrappedContextRegExp.source +
postfix.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") + "$");
quotemeta(postfix) + "$");
dep = new Dep(context, options.wrappedContextRecursive, regExp, range, valueRange);
dep.loc = expr.loc;
dep.prepend = param.prefix && param.prefix.isString() ? prefix : null;
Expand Down
6 changes: 6 additions & 0 deletions lib/dependencies/ContextDependencyTemplateAsId.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ ContextDependencyTemplateAsId.prototype.apply = function(dep, source, outputOpti
if(outputOptions.pathinfo) comment = "/*! " + requestShortener.shorten(dep.request) + " */ ";
if(dep.module && dep.module.dependencies && dep.module.dependencies.length > 0) {
if(dep.valueRange) {
if(Array.isArray(dep.replaces)) {
for(var i = 0; i < dep.replaces.length; i++) {
var rep = dep.replaces[i];
source.replace(rep.range[0], rep.range[1] - 1, rep.value)
}
}
source.replace(dep.valueRange[1], dep.range[1] - 1, ")");
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) : "") + "");
} else {
Expand Down
6 changes: 6 additions & 0 deletions lib/dependencies/ContextDependencyTemplateAsRequireCall.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ ContextDependencyTemplateAsRequireCall.prototype.apply = function(dep, source, o
var isAsync = dep.module && dep.module.async;
if(dep.module && (isAsync || containsDeps)) {
if(dep.valueRange) {
if(Array.isArray(dep.replaces)) {
for(var i = 0; i < dep.replaces.length; i++) {
var rep = dep.replaces[i];
source.replace(rep.range[0], rep.range[1] - 1, rep.value)
}
}
source.replace(dep.valueRange[1], dep.range[1] - 1, ")");
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) : "") + "");
} else {
Expand Down
1 change: 1 addition & 0 deletions test/cases/parsing/template-string/abc/abcTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "ok";
20 changes: 20 additions & 0 deletions test/cases/parsing/template-string/amd.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

it("should parse template strings in amd requires", function(done) {
var name = "abc";
var suffix = "Test";

var pending = [
require([`./abc/abcTest`], test),
require([`./abc/${name}Test`], test),
require([`./${name}/${name}Test`], test),
require([`./abc/${name}${suffix}`], test),
require([String.raw`./${name}/${name}${suffix}`], test)
].length;

function test (result) {
result.default.should.eql("ok")
if (--pending <= 0) {
done()
}
}
})
45 changes: 45 additions & 0 deletions test/cases/parsing/template-string/cjs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

it("should parse template strings in require.ensure requires", function(done) {
var name = "abc";
var suffix = "Test";

require.ensure([], function(require) {
var imports = [
require(`./abc/${name}Test`),
require(`./abc/${name}Test`),
require(`./${name}/${name}Test`),
require(`./abc/${name}${suffix}`),
require(String.raw`./${name}/${name}${suffix}`)
];

for (var i = 0; i < imports.length; i++) {
imports[i].default.should.eql("ok");
}
done()
})
})

it("should parse template strings in sync requires", function() {
var name = "sync";
var suffix = "Test";

var imports = [
require(`./sync/${name}Test`),
require(`./sync/${name}${suffix}`),
require(String.raw`./sync/${name.slice(0, 1)}y${name.slice(2)}${suffix}`),
require(`./sync/sync${"Test"}`),
require(String.raw`./sync/${"sync"}Test`)
];

for (var i = 0; i < imports.length; i++) {
imports[i].default.should.eql("sync");
}
})

it("should parse template strings in require.resolve", function() {
var name = "sync";

// Arbitrary assertion; can't use .ok() as it could be 0,
// can't use typeof as that depends on webpack config.
require.resolve(`./sync/${name}Test`).should.not.be.undefined;
})
20 changes: 20 additions & 0 deletions test/cases/parsing/template-string/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
var should = require('should')

it("should parse template strings in System.import", function(done) {
var name = "abc".split("");
var suffix = "Test";
Promise.all([
System.import(`./abc/${name[0]}${name[1]}${name[2]}Test`),
System.import(String.raw`./${name.join("")}/${name.join("")}Test`),
System.import(String.raw`./abc/${name.join("")}${suffix}`)
])
.then(function (imports) {
for (var i = 0; i < imports.length; i++) {
imports[i].default.should.eql("ok");
}
})
.then(function () { done(); }, done)
});

require("./cjs")
require("./amd")
1 change: 1 addition & 0 deletions test/cases/parsing/template-string/sync/syncTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "sync";
5 changes: 5 additions & 0 deletions test/cases/parsing/template-string/test.filter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
var supportsTemplateStrings = require("../../../helpers/supportsTemplateStrings");

module.exports = function(config) {
return !config.minimize && supportsTemplateStrings();
};
8 changes: 8 additions & 0 deletions test/helpers/supportsTemplateStrings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = function supportsTemplateStrings() {
try {
var f = eval("(function f() { return String.raw`a\\b`; })");
return f() === "a\\b";
} catch(e) {
return false;
}
};

0 comments on commit 0170568

Please sign in to comment.