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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"}