index.js.map 14.4 KB
{"version":3,"names":["declare","api","options","assertVersion","allowMutablePropsOnTags","Array","isArray","Error","HOISTED","WeakMap","declares","node","scope","t","isJSXIdentifier","name","path","isFunctionParent","isArrowFunctionExpression","hasOwnBinding","isHoistingScope","isLoop","isProgram","getHoistingScope","parent","targetScopeVisitor","ReferencedIdentifier","state","jsxScope","targetScope","immutabilityVisitor","enter","stop","isImmutable","skip","isJSXClosingElement","parentPath","isJSXAttribute","isJSXMemberExpression","isJSXNamespacedName","isIdentifier","binding","getBinding","constant","mutablePropsAllowed","isFunction","traverse","isPure","expressionResult","evaluate","confident","value","deopt","hoistingVisitor","visitor","JSXElement","has","openingElement","lastSegment","property","elementName","includes","current","isJSX","get","set","visitorState","getProgramParent","currentScope","id","generateUidBasedOnNode","push","identifier","replacement","template","expression","ast","isJSXElement","jsxExpressionContainer","replaceWith"],"sources":["../src/index.ts"],"sourcesContent":["import { declare } from \"@babel/helper-plugin-utils\";\nimport { types as t, template } from \"@babel/core\";\nimport type { Visitor, Scope, NodePath } from \"@babel/traverse\";\n\nexport interface Options {\n  allowMutablePropsOnTags?: null | string[];\n}\n\ninterface VisitorState {\n  isImmutable: boolean;\n  mutablePropsAllowed: boolean;\n  jsxScope: Scope;\n  targetScope: Scope;\n}\nexport default declare((api, options: Options) => {\n  api.assertVersion(7);\n\n  const { allowMutablePropsOnTags } = options;\n\n  if (\n    allowMutablePropsOnTags != null &&\n    !Array.isArray(allowMutablePropsOnTags)\n  ) {\n    throw new Error(\n      \".allowMutablePropsOnTags must be an array, null, or undefined.\",\n    );\n  }\n\n  // Element -> Target scope\n  const HOISTED = new WeakMap();\n\n  function declares(node: t.Identifier | t.JSXIdentifier, scope: Scope) {\n    if (\n      t.isJSXIdentifier(node, { name: \"this\" }) ||\n      t.isJSXIdentifier(node, { name: \"arguments\" }) ||\n      t.isJSXIdentifier(node, { name: \"super\" }) ||\n      t.isJSXIdentifier(node, { name: \"new\" })\n    ) {\n      const { path } = scope;\n      return path.isFunctionParent() && !path.isArrowFunctionExpression();\n    }\n\n    return scope.hasOwnBinding(node.name);\n  }\n\n  function isHoistingScope({ path }: Scope) {\n    return path.isFunctionParent() || path.isLoop() || path.isProgram();\n  }\n\n  function getHoistingScope(scope: Scope) {\n    while (!isHoistingScope(scope)) scope = scope.parent;\n    return scope;\n  }\n\n  const targetScopeVisitor: Visitor<VisitorState> = {\n    ReferencedIdentifier(path, state) {\n      const { node } = path;\n      let { scope } = path;\n\n      while (scope !== state.jsxScope) {\n        // If a binding is declared in an inner function, it doesn't affect hoisting.\n        if (declares(node, scope)) return;\n\n        scope = scope.parent;\n      }\n\n      while (scope) {\n        // We cannot hoist outside of the previous hoisting target\n        // scope, so we return early and we don't update it.\n        if (scope === state.targetScope) return;\n\n        // If the scope declares this identifier (or we're at the function\n        // providing the lexical env binding), we can't hoist the var any\n        // higher.\n        if (declares(node, scope)) break;\n\n        scope = scope.parent;\n      }\n\n      state.targetScope = getHoistingScope(scope);\n    },\n  };\n\n  const immutabilityVisitor: Visitor<VisitorState> = {\n    enter(path, state) {\n      const stop = () => {\n        state.isImmutable = false;\n        path.stop();\n      };\n\n      const skip = () => {\n        path.skip();\n      };\n\n      if (path.isJSXClosingElement()) return skip();\n\n      // Elements with refs are not safe to hoist.\n      if (\n        path.isJSXIdentifier({ name: \"ref\" }) &&\n        path.parentPath.isJSXAttribute({ name: path.node })\n      ) {\n        return stop();\n      }\n\n      // Ignore JSX expressions and immutable values.\n      if (\n        path.isJSXIdentifier() ||\n        path.isJSXMemberExpression() ||\n        path.isJSXNamespacedName() ||\n        path.isImmutable()\n      ) {\n        return;\n      }\n\n      // Ignore constant bindings.\n      if (path.isIdentifier()) {\n        const binding = path.scope.getBinding(path.node.name);\n        if (binding && binding.constant) return;\n      }\n\n      // If we allow mutable props, tags with function expressions can be\n      // safely hoisted.\n      const { mutablePropsAllowed } = state;\n      if (mutablePropsAllowed && path.isFunction()) {\n        path.traverse(targetScopeVisitor, state);\n        return skip();\n      }\n\n      if (!path.isPure()) return stop();\n\n      // If it's not immutable, it may still be a pure expression, such as string concatenation.\n      // It is still safe to hoist that, so long as its result is immutable.\n      // If not, it is not safe to replace as mutable values (like objects) could be mutated after render.\n      // https://github.com/facebook/react/issues/3226\n      const expressionResult = path.evaluate();\n      if (expressionResult.confident) {\n        // We know the result; check its mutability.\n        const { value } = expressionResult;\n        if (\n          mutablePropsAllowed ||\n          value === null ||\n          (typeof value !== \"object\" && typeof value !== \"function\")\n        ) {\n          // It evaluated to an immutable value, so we can hoist it.\n          return skip();\n        }\n      } else if (t.isIdentifier(expressionResult.deopt)) {\n        // It's safe to hoist here if the deopt reason is an identifier (e.g. func param).\n        // The hoister will take care of how high up it can be hoisted.\n        return;\n      }\n\n      stop();\n    },\n  };\n\n  // We cannot use traverse.visitors.merge because it doesn't support\n  // immutabilityVisitor's bare `enter` visitor.\n  // It's safe to just use ... because the two visitors don't share any key.\n  const hoistingVisitor = { ...immutabilityVisitor, ...targetScopeVisitor };\n\n  return {\n    name: \"transform-react-constant-elements\",\n\n    visitor: {\n      JSXElement(path) {\n        if (HOISTED.has(path.node)) return;\n        const name = path.node.openingElement.name;\n\n        // This transform takes the option `allowMutablePropsOnTags`, which is an array\n        // of JSX tags to allow mutable props (such as objects, functions) on. Use sparingly\n        // and only on tags you know will never modify their own props.\n        let mutablePropsAllowed = false;\n        if (allowMutablePropsOnTags != null) {\n          // Get the element's name. If it's a member expression, we use the last part of the path.\n          // So the option [\"FormattedMessage\"] would match \"Intl.FormattedMessage\".\n          let lastSegment = name;\n          while (t.isJSXMemberExpression(lastSegment)) {\n            lastSegment = lastSegment.property;\n          }\n\n          const elementName = lastSegment.name;\n          // @ts-expect-error Fixme: allowMutablePropsOnTags should handle JSXNamespacedName\n          mutablePropsAllowed = allowMutablePropsOnTags.includes(elementName);\n        }\n\n        // In order to avoid hoisting unnecessarily, we need to know which is\n        // the scope containing the current JSX element. If a parent of the\n        // current element has already been hoisted, we can consider its target\n        // scope as the base scope for the current element.\n        let jsxScope;\n        let current: NodePath<t.JSX> = path;\n        while (!jsxScope && current.parentPath.isJSX()) {\n          current = current.parentPath;\n          jsxScope = HOISTED.get(current.node);\n        }\n        jsxScope ??= path.scope;\n        // The initial HOISTED is set to jsxScope, s.t.\n        // if the element's JSX ancestor has been hoisted, it will be skipped\n        HOISTED.set(path.node, jsxScope);\n\n        const visitorState: VisitorState = {\n          isImmutable: true,\n          mutablePropsAllowed,\n          jsxScope,\n          targetScope: path.scope.getProgramParent(),\n        };\n        path.traverse(hoistingVisitor, visitorState);\n        if (!visitorState.isImmutable) return;\n\n        const { targetScope } = visitorState;\n        // Only hoist if it would give us an advantage.\n        for (let currentScope = jsxScope; ; ) {\n          if (targetScope === currentScope) return;\n          if (isHoistingScope(currentScope)) break;\n\n          currentScope = currentScope.parent;\n          if (!currentScope) {\n            throw new Error(\n              \"Internal @babel/plugin-transform-react-constant-elements error: \" +\n                \"targetScope must be an ancestor of jsxScope. \" +\n                \"This is a Babel bug, please report it.\",\n            );\n          }\n        }\n\n        const id = path.scope.generateUidBasedOnNode(name);\n        targetScope.push({ id: t.identifier(id) });\n        // If the element is to be hoisted, update HOISTED to be the target scope\n        HOISTED.set(path.node, targetScope);\n\n        let replacement: t.Expression | t.JSXExpressionContainer = template\n          .expression.ast`\n          ${t.identifier(id)} || (${t.identifier(id)} = ${path.node})\n        `;\n        if (\n          path.parentPath.isJSXElement() ||\n          path.parentPath.isJSXAttribute()\n        ) {\n          replacement = t.jsxExpressionContainer(replacement);\n        }\n\n        path.replaceWith(replacement);\n      },\n    },\n  };\n});\n"],"mappings":"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"}