Public commit
This commit is contained in:
100
scripts/plugins/babel-constant-element.ts
Normal file
100
scripts/plugins/babel-constant-element.ts
Normal file
@ -0,0 +1,100 @@
|
||||
// https://github.com/babel/babel/blob/c38bf12f010520ea7abe8a286f62922b2d1e1f1b/packages/babel-plugin-transform-react-constant-elements/src/index.ts
|
||||
import type { Visitor } from "@babel/core"
|
||||
import type { PluginObj, types as t } from "@babel/core"
|
||||
|
||||
interface PluginState {
|
||||
isImmutable: boolean
|
||||
mutablePropsAllowed?: boolean
|
||||
}
|
||||
|
||||
export default (allowMutablePropsOnTags?: string[]): PluginObj => {
|
||||
const HOISTED = new WeakSet()
|
||||
|
||||
const immutabilityVisitor: Visitor<PluginState> = {
|
||||
enter(path, state) {
|
||||
const stop = () => {
|
||||
state.isImmutable = false
|
||||
path.stop()
|
||||
}
|
||||
|
||||
if (path.isJSXClosingElement()) {
|
||||
path.skip()
|
||||
return
|
||||
}
|
||||
|
||||
// Elements with refs are not safe to hoist.
|
||||
if (
|
||||
path.isJSXIdentifier({ name: "ref" }) &&
|
||||
path.parentPath.isJSXAttribute({ name: path.node })
|
||||
) {
|
||||
return stop()
|
||||
}
|
||||
|
||||
// Ignore identifiers & JSX expressions.
|
||||
if (path.isJSXIdentifier() || path.isIdentifier() || path.isJSXMemberExpression()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!path.isImmutable()) {
|
||||
// If it's not immutable, it may still be a pure expression, such as string concatenation.
|
||||
// It is still safe to hoist that, so long as its result is immutable.
|
||||
// If not, it is not safe to replace as mutable values (like objects) could be mutated after render.
|
||||
// https://github.com/facebook/react/issues/3226
|
||||
if (path.isPure()) {
|
||||
const { confident, value } = path.evaluate()
|
||||
if (confident) {
|
||||
// We know the result; check its mutability.
|
||||
const isMutable =
|
||||
(!state.mutablePropsAllowed && value && typeof value === "object") ||
|
||||
typeof value === "function"
|
||||
if (!isMutable) {
|
||||
// It evaluated to an immutable value, so we can hoist it.
|
||||
path.skip()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
stop()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
name: "transform-react-constant-elements",
|
||||
|
||||
visitor: {
|
||||
Program(program) {
|
||||
program.traverse({
|
||||
JSXElement(path) {
|
||||
if (HOISTED.has(path.node)) return
|
||||
HOISTED.add(path.node)
|
||||
|
||||
const state: PluginState = { isImmutable: true }
|
||||
|
||||
// This transform takes the option `allowMutablePropsOnTags`, which is an array
|
||||
// of JSX tags to allow mutable props (such as objects, functions) on. Use sparingly
|
||||
// and only on tags you know will never modify their own props.
|
||||
if (allowMutablePropsOnTags != null) {
|
||||
// Get the element's name. If it's a member expression, we use the last part of the path.
|
||||
// So the option ["FormattedMessage"] would match "Intl.FormattedMessage".
|
||||
let namePath = path.get("openingElement").get("name")
|
||||
while (namePath.isJSXMemberExpression()) {
|
||||
namePath = namePath.get("property")
|
||||
}
|
||||
|
||||
const elementName = (namePath.node as t.JSXIdentifier).name
|
||||
state.mutablePropsAllowed = allowMutablePropsOnTags.includes(elementName)
|
||||
}
|
||||
|
||||
// Traverse all props passed to this element for immutability.
|
||||
path.traverse(immutabilityVisitor, state)
|
||||
|
||||
if (state.isImmutable) {
|
||||
path.hoist(path.scope)
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user