1 | /**
|
---|
2 | * Converts destructured parameters with default values to non-shorthand syntax.
|
---|
3 | * This fixes the only Tagged Templates-related bug in ES Modules-supporting browsers (Safari 10 & 11).
|
---|
4 | * Use this plugin instead of `@babel/plugin-transform-template-literals` when targeting ES Modules.
|
---|
5 | *
|
---|
6 | * @example
|
---|
7 | * // Bug 1: Safari 10/11 doesn't reliably return the same Strings value.
|
---|
8 | * // The value changes depending on invocation and function optimization state.
|
---|
9 | * function f() { return Object`` }
|
---|
10 | * f() === new f() // false, should be true.
|
---|
11 | *
|
---|
12 | * @example
|
---|
13 | * // Bug 2: Safari 10/11 use the same cached strings value when the string parts are the same.
|
---|
14 | * // This behavior comes from an earlier version of the spec, and can cause tricky bugs.
|
---|
15 | * Object``===Object`` // true, should be false.
|
---|
16 | *
|
---|
17 | * Benchmarks: https://jsperf.com/compiled-tagged-template-performance
|
---|
18 | */
|
---|
19 | export default ({ types: t }) => ({
|
---|
20 | name: "transform-tagged-template-caching",
|
---|
21 | visitor: {
|
---|
22 | TaggedTemplateExpression(path, state) {
|
---|
23 | // tagged templates we've already dealt with
|
---|
24 | let processed = state.get("processed");
|
---|
25 | if (!processed) {
|
---|
26 | processed = new WeakSet();
|
---|
27 | state.set("processed", processed);
|
---|
28 | }
|
---|
29 |
|
---|
30 | if (processed.has(path.node)) return path.skip();
|
---|
31 |
|
---|
32 | // Grab the expressions from the original tag.
|
---|
33 | // tag`a${'hello'}` // ['hello']
|
---|
34 | const expressions = path.node.quasi.expressions;
|
---|
35 |
|
---|
36 | // Create an identity function helper:
|
---|
37 | // identity = t => t
|
---|
38 | let identity = state.get("identity");
|
---|
39 | if (!identity) {
|
---|
40 | identity = path.scope
|
---|
41 | .getProgramParent()
|
---|
42 | .generateDeclaredUidIdentifier("_");
|
---|
43 | state.set("identity", identity);
|
---|
44 | const binding = path.scope.getBinding(identity.name);
|
---|
45 | binding.path.get("init").replaceWith(
|
---|
46 | t.arrowFunctionExpression(
|
---|
47 | // re-use the helper identifier for compressability
|
---|
48 | [t.identifier("t")],
|
---|
49 | t.identifier("t")
|
---|
50 | )
|
---|
51 | );
|
---|
52 | }
|
---|
53 |
|
---|
54 | // Use the identity function helper to get a reference to the template's Strings.
|
---|
55 | // We replace all expressions with `0` ensure Strings has the same shape.
|
---|
56 | // identity`a${0}`
|
---|
57 | const template = t.taggedTemplateExpression(
|
---|
58 | t.cloneNode(identity),
|
---|
59 | t.templateLiteral(
|
---|
60 | path.node.quasi.quasis,
|
---|
61 | expressions.map(() => t.numericLiteral(0))
|
---|
62 | )
|
---|
63 | );
|
---|
64 | processed.add(template);
|
---|
65 |
|
---|
66 | // Install an inline cache at the callsite using the global variable:
|
---|
67 | // _t || (_t = identity`a${0}`)
|
---|
68 | const ident = path.scope
|
---|
69 | .getProgramParent()
|
---|
70 | .generateDeclaredUidIdentifier("t");
|
---|
71 | path.scope.getBinding(ident.name).path.parent.kind = "let";
|
---|
72 | const inlineCache = t.logicalExpression(
|
---|
73 | "||",
|
---|
74 | ident,
|
---|
75 | t.assignmentExpression("=", t.cloneNode(ident), template)
|
---|
76 | );
|
---|
77 |
|
---|
78 | // The original tag function becomes a plain function call.
|
---|
79 | // The expressions omitted from the cached Strings tag are directly applied as arguments.
|
---|
80 | // tag(_t || (_t = Object`a${0}`), 'hello')
|
---|
81 | const node = t.callExpression(path.node.tag, [
|
---|
82 | inlineCache,
|
---|
83 | ...expressions,
|
---|
84 | ]);
|
---|
85 | path.replaceWith(node);
|
---|
86 | },
|
---|
87 | },
|
---|
88 | });
|
---|