import { NodePath, types } from '@babel/core'; import { createMacro } from 'babel-plugin-macros'; import type { ReactElement } from 'react'; import { ensureImport } from './helpers'; type ObserverMacro = (props: { children?(): ReactElement; render?(): ReactElement; }) => ReactElement; /** * Macro to make mobx-react `{() => <>}` syntax simpler. * * Import this macro as `Observer`. * * Converts: * ```tsx * * ... * * ``` * to: * ```tsx * * {() => <> * ... * } * * ``` */ export default createMacro(({ references, state, babel: { types: t } }) => { references.default.forEach(ref => { if (ref.parentPath.type === 'JSXOpeningElement') { const program = ref.scope.getProgramParent().path; ensureImport(t, program, 'Observer', 'mobx-react'); const attributes = ref.parentPath.get('attributes'); const children = ref.parentPath.parentPath.get('children') as NodePath[]; if (!isSingleFuncChild(children)) { const frag = t.jsxFragment(t.jsxOpeningFragment(), t.jsxClosingFragment(), children.map(np => np.node as any)) const fnExpr = t.arrowFunctionExpression([], frag); ref.parentPath.parentPath.set('children', [ t.jsxExpressionContainer(fnExpr) ] as any) } } }) }) as ObserverMacro; function isSingleFuncChild(children: NodePath[]) { const filteredChildren = children.filter(np => { if (types.isJSXText(np.node) && np.node.value.trim() == '') { return false; } return true; }) if (filteredChildren.length > 1) return false; const ch = filteredChildren[0] if (types.isJSXExpressionContainer(ch)) { if (types.isFunctionDeclaration(ch) || types.isArrowFunctionExpression(ch)) { return true; } } return false; }