background
Suppose we now have code like this:
function MyComp() {
return <span>My Comp</span>
}
function App(props) {
if (props.children.type.name.startsWith('My')) return props.children
}
ReactDOM.render(
<App>
<MyComp />
</App>.document.getElementById('root'))Copy the code
This code basically means that within the App component, only subcomponents with the component name starting with My are rendered. But when the code was packaged and released, it didn’t work. Why? The reason is that there was a code mix-up during the packaging process, and MyComp has been converted to another character. This can be done by adding a variable to the component:
MyComp.displayName = 'MyComp'
Copy the code
The judgment logic in the App component also needs to be modified to:
if (props.children.type.displayName.startsWith('My'))
Copy the code
One component is fine, but what if there are thousands of components in a project? Hence, our topic for today: How to name the React component with Babel? The answer is simple: write a Babel plug-in.
Babel plug-in
Demand analysis
First, we need to examine the possible definitions of our React components and their node types in Babel. Here are a few common ones:
/ / 1
function Comp() {
return <span>Comp</span>
}
/* { type: 'FunctionDeclaration', id: { type: 'Identifier', name: 'Comp', loc: undefined }, } */
/ / 2
const Comp = () = > {
return <span>Comp</span>
}
/* { type: 'VariableDeclaration', kind: 'const', declarations: [ { type: 'VariableDeclarator', id: [Object], init: [Object], loc: undefined } ], ... } * /
/ / 3
export default() = > {return <span>Comp</span>
}
/* { type: 'ExportDefaultDeclaration', declaration: { type: 'ArrowFunctionExpression', ... },... } * /
/ / 4
class Comp extends React.Component {
render() {
return <span>Comp</span>}}/* { type: 'ClassDeclaration', id: { type: 'Identifier', name: 'Comp', loc: undefined }, body: { type: 'ClassBody', body: [ [Object] ], loc: undefined }, ... } * /
/ / 5
export default class extends React.Component {
render() {
return <span>Comp</span>}}/* { type: 'ExportDefaultDeclaration', declaration: { type: 'ClassDeclaration', id: null, body: { type: 'ClassBody', body: [Array], loc: undefined }, ... },... } * /
Copy the code
There is also the question of how to distinguish a normal function/class from a React component. The answer is to see if any of these nodes’ children contain JSXElement nodes. The idea is there, the code is there.
A plugin
The following lists the visitor for both cases, but the other cases are similar and will not be described.
function createDisplayNameNode(elementName, property = 'displayName') {
const node = t.expressionStatement(
t.assignmentExpression(
'=',
t.memberExpression(t.identifier(elementName), t.identifier(property)),
t.stringLiteral(elementName)
)
)
return node
}
function hasJSXElement(path) {
let hasJSXElement = false
path.traverse({
JSXElement(path) {
hasJSXElement = true}})return hasJSXElement
}
function myCustomPlugin() {
return {
visitor: {
FunctionDeclaration(path) {
if (hasJSXElement(path)) {
path.insertAfter(createDisplayNameNode(path.node.id.name))
}
},
/** * const C1 = () => {return C1}, C2 = () => {return C2} */
VariableDeclaration(path) {
const arr = []
path.traverse({
VariableDeclarator(path) {
if (hasJSXElement(path)) {
arr.push(path.node.id.name)
}
},
})
arr.forEach((name) = >path.insertAfter(createDisplayNameNode(name))) }, ... }}},Copy the code
Note that all of the above code is processed in the Enter stage. The @babel/plugin-transform-react-jsx plugin must be placed before the @babel/plugin-transform-react-jsx plugin. Because @babel/plugin-transform-react-jsx will replace JSXElement when exit JSXElement.
conclusion
This paper extends the topic of how to add names to React components from actual development scenarios, analyzes several common definitions of React components, and finally realizes our requirements by writing Babel plug-in.