6157ee402652176288761496cc578bf64319a3a751864268da105f93c5f59d9e0625fb35fd6eca53e82c199b69a79127ef993e684c7dce35f53ff94c47fee6 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  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. if (processed.has(path.node)) return path.skip();
  30. // Grab the expressions from the original tag.
  31. // tag`a${'hello'}` // ['hello']
  32. const expressions = path.node.quasi.expressions;
  33. // Create an identity function helper:
  34. // identity = t => t
  35. let identity = state.get("identity");
  36. if (!identity) {
  37. identity = path.scope
  38. .getProgramParent()
  39. .generateDeclaredUidIdentifier("_");
  40. state.set("identity", identity);
  41. const binding = path.scope.getBinding(identity.name);
  42. binding.path.get("init").replaceWith(
  43. t.arrowFunctionExpression(
  44. // re-use the helper identifier for compressability
  45. [t.identifier("t")],
  46. t.identifier("t")
  47. )
  48. );
  49. }
  50. // Use the identity function helper to get a reference to the template's Strings.
  51. // We replace all expressions with `0` ensure Strings has the same shape.
  52. // identity`a${0}`
  53. const template = t.taggedTemplateExpression(
  54. t.cloneNode(identity),
  55. t.templateLiteral(
  56. path.node.quasi.quasis,
  57. expressions.map(() => t.numericLiteral(0))
  58. )
  59. );
  60. processed.add(template);
  61. // Install an inline cache at the callsite using the global variable:
  62. // _t || (_t = identity`a${0}`)
  63. const ident = path.scope
  64. .getProgramParent()
  65. .generateDeclaredUidIdentifier("t");
  66. path.scope.getBinding(ident.name).path.parent.kind = "let";
  67. const inlineCache = t.logicalExpression(
  68. "||",
  69. ident,
  70. t.assignmentExpression("=", t.cloneNode(ident), template)
  71. );
  72. // The original tag function becomes a plain function call.
  73. // The expressions omitted from the cached Strings tag are directly applied as arguments.
  74. // tag(_t || (_t = Object`a${0}`), 'hello')
  75. const node = t.callExpression(path.node.tag, [
  76. inlineCache,
  77. ...expressions,
  78. ]);
  79. path.replaceWith(node);
  80. },
  81. },
  82. });