1// Based on https://github.com/gregberge/svgr/tree/master/packages/babel-plugin-transform-react-native-svg 2 3const elementToComponent = { 4 a: 'A', 5 article: 'Article', 6 b: 'B', 7 br: 'BR', 8 caption: 'Caption', 9 code: 'Code', 10 div: 'Div', 11 footer: 'Footer', 12 h1: 'H1', 13 h2: 'H2', 14 pre: 'Pre', 15 h3: 'H3', 16 h4: 'H4', 17 h5: 'H5', 18 h6: 'H6', 19 header: 'Header', 20 time: 'Time', 21 hr: 'HR', 22 i: 'I', 23 mark: 'Mark', 24 del: 'Del', 25 em: 'EM', 26 li: 'LI', 27 main: 'Main', 28 nav: 'Nav', 29 p: 'P', 30 s: 'S', 31 section: 'Section', 32 table: 'Table', 33 tbody: 'TBody', 34 td: 'TD', 35 th: 'TH', 36 thead: 'THead', 37 tr: 'TR', 38 ul: 'UL', 39 strong: 'Strong', 40 span: 'Span', 41 aside: 'Aside', 42 tfoot: 'TFoot', 43 blockquote: 'BlockQuote', 44 q: 'Q', 45 46 html: 'Div', 47 body: 'Div', 48 49 // TODO: img 50 // NOTE: head, meta, link should use some special component in the future. 51}; 52 53module.exports = ({ types: t }, { expo }) => { 54 function replaceElement(path, state) { 55 const { name } = path.node.openingElement.name; 56 57 // Replace element with @expo/html-elements 58 const component = elementToComponent[name]; 59 60 if (!component) { 61 return; 62 } 63 const prefixedComponent = component; 64 const openingElementName = path.get('openingElement.name'); 65 openingElementName.replaceWith(t.jsxIdentifier(prefixedComponent)); 66 if (path.has('closingElement')) { 67 const closingElementName = path.get('closingElement.name'); 68 closingElementName.replaceWith(t.jsxIdentifier(prefixedComponent)); 69 } 70 state.replacedComponents.add(prefixedComponent); 71 } 72 73 const htmlElementVisitor = { 74 JSXElement(path, state) { 75 replaceElement(path, state); 76 path.traverse(jsxElementVisitor, state); 77 }, 78 }; 79 80 const jsxElementVisitor = { 81 JSXElement(path, state) { 82 replaceElement(path, state); 83 }, 84 }; 85 86 const importDeclarationVisitor = { 87 ImportDeclaration(path, state) { 88 if (path.get('source').isStringLiteral({ value: '@expo/html-elements' })) { 89 state.replacedComponents.forEach((component) => { 90 if ( 91 path 92 .get('specifiers') 93 .some((specifier) => specifier.get('local').isIdentifier({ name: component })) 94 ) { 95 return; 96 } 97 path.pushContainer( 98 'specifiers', 99 t.importSpecifier(t.identifier(component), t.identifier(component)) 100 ); 101 }); 102 } 103 }, 104 }; 105 106 const source = '@expo/html-elements'; 107 return { 108 name: 'Rewrite React DOM to universal Expo elements', 109 visitor: { 110 Program(path, state) { 111 state.replacedComponents = new Set(); 112 state.unsupportedComponents = new Set(); 113 114 path.traverse(htmlElementVisitor, state); 115 116 // If state.replacedComponents is not empty, then ensure `import { ... } from '@expo/html-elements'` is present 117 if (state.replacedComponents.size > 0) { 118 const importDeclaration = t.importDeclaration([], t.stringLiteral(source)); 119 path.unshiftContainer('body', importDeclaration); 120 } 121 122 path.traverse(importDeclarationVisitor, state); 123 }, 124 }, 125 }; 126}; 127