index.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. var _helperPluginUtils = require("@babel/helper-plugin-utils");
  7. var _core = require("@babel/core");
  8. var _default = (0, _helperPluginUtils.declare)((api, options) => {
  9. api.assertVersion(7);
  10. const {
  11. allowMutablePropsOnTags
  12. } = options;
  13. if (allowMutablePropsOnTags != null && !Array.isArray(allowMutablePropsOnTags)) {
  14. throw new Error(".allowMutablePropsOnTags must be an array, null, or undefined.");
  15. }
  16. const HOISTED = new WeakMap();
  17. function declares(node, scope) {
  18. if (_core.types.isJSXIdentifier(node, {
  19. name: "this"
  20. }) || _core.types.isJSXIdentifier(node, {
  21. name: "arguments"
  22. }) || _core.types.isJSXIdentifier(node, {
  23. name: "super"
  24. }) || _core.types.isJSXIdentifier(node, {
  25. name: "new"
  26. })) {
  27. const {
  28. path
  29. } = scope;
  30. return path.isFunctionParent() && !path.isArrowFunctionExpression();
  31. }
  32. return scope.hasOwnBinding(node.name);
  33. }
  34. function isHoistingScope({
  35. path
  36. }) {
  37. return path.isFunctionParent() || path.isLoop() || path.isProgram();
  38. }
  39. function getHoistingScope(scope) {
  40. while (!isHoistingScope(scope)) scope = scope.parent;
  41. return scope;
  42. }
  43. const analyzer = {
  44. enter(path, state) {
  45. const stop = () => {
  46. state.isImmutable = false;
  47. path.stop();
  48. };
  49. if (path.isJSXClosingElement()) {
  50. path.skip();
  51. return;
  52. }
  53. if (path.isJSXIdentifier({
  54. name: "ref"
  55. }) && path.parentPath.isJSXAttribute({
  56. name: path.node
  57. })) {
  58. return stop();
  59. }
  60. if (path.isJSXIdentifier() || path.isJSXMemberExpression() || path.isJSXNamespacedName()) {
  61. return;
  62. }
  63. if (path.isIdentifier()) {
  64. const binding = path.scope.getBinding(path.node.name);
  65. if (binding && binding.constant) return;
  66. }
  67. if (!path.isImmutable()) {
  68. if (path.isPure()) {
  69. const expressionResult = path.evaluate();
  70. if (expressionResult.confident) {
  71. const {
  72. value
  73. } = expressionResult;
  74. const isMutable = !state.mutablePropsAllowed && value && typeof value === "object" || typeof value === "function";
  75. if (!isMutable) {
  76. path.skip();
  77. return;
  78. }
  79. } else if (_core.types.isIdentifier(expressionResult.deopt)) {
  80. return;
  81. }
  82. }
  83. stop();
  84. }
  85. },
  86. ReferencedIdentifier(path, state) {
  87. const {
  88. node
  89. } = path;
  90. let {
  91. scope
  92. } = path;
  93. while (scope) {
  94. if (scope === state.targetScope) return;
  95. if (declares(node, scope)) break;
  96. scope = scope.parent;
  97. }
  98. state.targetScope = getHoistingScope(scope);
  99. }
  100. };
  101. return {
  102. name: "transform-react-constant-elements",
  103. visitor: {
  104. JSXElement(path) {
  105. var _jsxScope;
  106. if (HOISTED.has(path.node)) return;
  107. HOISTED.set(path.node, path.scope);
  108. const name = path.node.openingElement.name;
  109. let mutablePropsAllowed = false;
  110. if (allowMutablePropsOnTags != null) {
  111. let lastSegment = name;
  112. while (_core.types.isJSXMemberExpression(lastSegment)) {
  113. lastSegment = lastSegment.property;
  114. }
  115. const elementName = lastSegment.name;
  116. mutablePropsAllowed = allowMutablePropsOnTags.includes(elementName);
  117. }
  118. const state = {
  119. isImmutable: true,
  120. mutablePropsAllowed,
  121. targetScope: path.scope.getProgramParent()
  122. };
  123. path.traverse(analyzer, state);
  124. if (!state.isImmutable) return;
  125. const {
  126. targetScope
  127. } = state;
  128. HOISTED.set(path.node, targetScope);
  129. let jsxScope;
  130. let current = path;
  131. while (!jsxScope && current.parentPath.isJSX()) {
  132. current = current.parentPath;
  133. jsxScope = HOISTED.get(current.node);
  134. }
  135. (_jsxScope = jsxScope) != null ? _jsxScope : jsxScope = getHoistingScope(path.scope);
  136. if (targetScope === jsxScope) return;
  137. const id = path.scope.generateUidBasedOnNode(name);
  138. targetScope.push({
  139. id: _core.types.identifier(id)
  140. });
  141. let replacement = _core.template.expression.ast`
  142. ${_core.types.identifier(id)} || (${_core.types.identifier(id)} = ${path.node})
  143. `;
  144. if (path.parentPath.isJSXElement() || path.parentPath.isJSXAttribute()) {
  145. replacement = _core.types.jsxExpressionContainer(replacement);
  146. }
  147. path.replaceWith(replacement);
  148. }
  149. }
  150. };
  151. });
  152. exports.default = _default;