xref: /expo/packages/html-elements/babel.js (revision 14b29cd0)
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