index.js 3.1 KB
Newer Older
Rosanny Sihombing's avatar
Rosanny Sihombing committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
"use strict";

exports.__esModule = true;
exports.default = void 0;

/**
 * Converts destructured parameters with default values to non-shorthand syntax.
 * This fixes the only Tagged Templates-related bug in ES Modules-supporting browsers (Safari 10 & 11).
 * Use this plugin instead of `@babel/plugin-transform-template-literals` when targeting ES Modules.
 *
 * @example
 *   // Bug 1: Safari 10/11 doesn't reliably return the same Strings value.
 *   // The value changes depending on invocation and function optimization state.
 *   function f() { return Object`` }
 *   f() === new f()  // false, should be true.
 *
 * @example
 *   // Bug 2: Safari 10/11 use the same cached strings value when the string parts are the same.
 *   // This behavior comes from an earlier version of the spec, and can cause tricky bugs.
 *   Object``===Object``  // true, should be false.
 *
 * Benchmarks: https://jsperf.com/compiled-tagged-template-performance
 */
var _default = ({
  types: t
}) => ({
  name: "transform-tagged-template-caching",
  visitor: {
    TaggedTemplateExpression(path, state) {
      // tagged templates we've already dealt with
      let processed = state.get("processed");

      if (!processed) {
        processed = new WeakSet();
        state.set("processed", processed);
      }

      if (processed.has(path.node)) return path.skip(); // Grab the expressions from the original tag.
      //   tag`a${'hello'}`  // ['hello']

      const expressions = path.node.quasi.expressions; // Create an identity function helper:
      //   identity = t => t

      let identity = state.get("identity");

      if (!identity) {
        identity = path.scope.getProgramParent().generateDeclaredUidIdentifier("_");
        state.set("identity", identity);
        const binding = path.scope.getBinding(identity.name);
        binding.path.get("init").replaceWith(t.arrowFunctionExpression( // re-use the helper identifier for compressability
        [t.identifier("t")], t.identifier("t")));
      } // Use the identity function helper to get a reference to the template's Strings.
      // We replace all expressions with `0` ensure Strings has the same shape.
      //   identity`a${0}`


      const template = t.taggedTemplateExpression(t.cloneNode(identity), t.templateLiteral(path.node.quasi.quasis, expressions.map(() => t.numericLiteral(0))));
      processed.add(template); // Install an inline cache at the callsite using the global variable:
      //   _t || (_t = identity`a${0}`)

      const ident = path.scope.getProgramParent().generateDeclaredUidIdentifier("t");
      path.scope.getBinding(ident.name).path.parent.kind = "let";
      const inlineCache = t.logicalExpression("||", ident, t.assignmentExpression("=", t.cloneNode(ident), template)); // The original tag function becomes a plain function call.
      // The expressions omitted from the cached Strings tag are directly applied as arguments.
      //   tag(_t || (_t = Object`a${0}`), 'hello')

      const node = t.callExpression(path.node.tag, [inlineCache, ...expressions]);
      path.replaceWith(node);
    }

  }
});

exports.default = _default;
module.exports = exports.default;