Repository: Rich-Harris/buble
Pull request #67 (2017-12-05 14:41:45)

Reuse same object for tagged template quasis.
Spec tagged template literals pass the same object to each invocation of
the template tag function. This way that object can be used as a cache
key, for example, using a `Map`, so that parsing of the string contents
can be cached.

This patch changes the tagged template literal transform to declare a
frozen top-level array with the quasi strings, and passes that array to
each invocation. If this lands I think it's important to adhere to the spec re:
freezing, because otherwise people might try to attach properties to the
object itself as a sort of caching mechanism, which doesn't work in spec
tagged template literals.

I'm unsure if this is the _best_ way to declare a top-level variable,
but it seems to work OK :)


Patch:
diff --git a/src/program/Program.js b/src/program/Program.js
index 5a6065d..b85a104 100644
--- a/src/program/Program.js
+++ b/src/program/Program.js
@@ -18,6 +18,8 @@ export default function Program(source, ast, transforms, options) {
 	wrap((this.body = ast), this);
 	this.body.__proto__ = BlockStatement.prototype;
 
+	this.templateLiteralQuasis = Object.create(null);
+
 	this.indentExclusionElements = [];
 	this.body.initialise(transforms);
 
diff --git a/src/program/types/TaggedTemplateExpression.js b/src/program/types/TaggedTemplateExpression.js
index 987aabe..2c014e0 100644
--- a/src/program/types/TaggedTemplateExpression.js
+++ b/src/program/types/TaggedTemplateExpression.js
@@ -22,14 +22,26 @@ export default class TaggedTemplateExpression extends Node {
 				.concat(this.quasi.quasis)
 				.sort((a, b) => a.start - b.start);
 
+			const program = this.program;
+			const rootScope = program.body.scope;
+
 			// insert strings at start
 			const templateStrings = this.quasi.quasis.map(quasi =>
 				JSON.stringify(quasi.value.cooked)
-			);
+			).join(', ');
+
+			let templateObject = this.program.templateLiteralQuasis[templateStrings];
+			if (!templateObject) {
+				templateObject = rootScope.createIdentifier('templateObject');
+				code.prependRight(0, `var ${templateObject} = Object.freeze([${templateStrings}]);\n`);
+
+				this.program.templateLiteralQuasis[templateStrings] = templateObject;
+			}
+
 			code.overwrite(
 				this.tag.end,
 				ordered[0].start,
-				`([${templateStrings.join(', ')}]`
+				`(${templateObject}`
 			);
 
 			let lastIndex = ordered[0].start;


Test patch:
diff --git a/test/samples/template-strings.js b/test/samples/template-strings.js
index 6b69b50..de7a426 100644
--- a/test/samples/template-strings.js
+++ b/test/samples/template-strings.js
@@ -41,7 +41,7 @@ module.exports = [
 			'transpiles tagged template literals with `transforms.dangerousTaggedTemplateString = true`',
 		options: { transforms: { dangerousTaggedTemplateString: true } },
 		input: 'var str = x`y${(() => 42)()}`;',
-		output: `var str = x(["y", ""], (function () { return 42; })());`
+		output: `var templateObject = Object.freeze(["y", ""]);\nvar str = x(templateObject, (function () { return 42; })());`
 	},
 
 	{
@@ -49,7 +49,14 @@ module.exports = [
 			'transpiles tagged template literals with `transforms.dangerousTaggedTemplateString = true`',
 		options: { transforms: { dangerousTaggedTemplateString: true } },
 		input: 'var str = x`${(() => 42)()}y`;',
-		output: `var str = x(["", "y"], (function () { return 42; })());`
+		output: `var templateObject = Object.freeze(["", "y"]);\nvar str = x(templateObject, (function () { return 42; })());`
+	},
+
+	{
+		description: 'reuses quasi array for identical tagged template strings',
+		options: { transforms: { dangerousTaggedTemplateString: true } },
+		input: 'x`a${a}b`, x`a${b}b`, x`b${c}a`',
+		output: `var templateObject$1 = Object.freeze(["b", "a"]);\nvar templateObject = Object.freeze(["a", "b"]);\nx(templateObject, a), x(templateObject, b), x(templateObject$1, c)`
 	},
 
 	{