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