UNPKG

@builder.io/mitosis

Version:

Write components once, run everywhere. Compiles to Vue, React, Solid, and Liquid. Import code from Figma and Builder.io

287 lines (286 loc) 12.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.componentToReactNative = exports.collectReactNativeStyles = void 0; const bindings_1 = require("../../helpers/bindings"); const fast_clone_1 = require("../../helpers/fast-clone"); const is_children_1 = __importDefault(require("../../helpers/is-children")); const is_mitosis_node_1 = require("../../helpers/is-mitosis-node"); const merge_options_1 = require("../../helpers/merge-options"); const json5_1 = __importDefault(require("json5")); const lodash_1 = require("lodash"); const legacy_1 = __importDefault(require("neotraverse/legacy")); const html_tags_1 = require("../../constants/html_tags"); const react_1 = require("../react"); const sanitize_react_native_block_styles_1 = require("./sanitize-react-native-block-styles"); const stylePropertiesThatMustBeNumber = new Set(['lineHeight']); const MEDIA_QUERY_KEY_REGEX = /^@media.*/; const sanitizeStyle = (obj) => (key, value) => { const propertyValue = obj[key]; if (key.match(MEDIA_QUERY_KEY_REGEX)) { console.warn('Unsupported: skipping media queries for react-native: ', key, propertyValue); delete obj[key]; return; } }; const collectReactNativeStyles = (json, options) => { const styleMap = {}; const componentIndexes = {}; const getStyleSheetName = (item) => { const componentName = (0, lodash_1.camelCase)(item.name || 'view'); // If we have already seen this component name, we will increment the index. Otherwise, we will set the index to 1. const index = (componentIndexes[componentName] = (componentIndexes[componentName] || 0) + 1); return `${componentName}${index}`; }; (0, legacy_1.default)(json).forEach(function (item) { var _a, _b, _c; if (!(0, is_mitosis_node_1.isMitosisNode)(item)) { return; } let cssValue = json5_1.default.parse(((_a = item.bindings.css) === null || _a === void 0 ? void 0 : _a.code) || '{}'); delete item.bindings.css; if ((0, lodash_1.size)(cssValue)) { // Style properties like `"20px"` need to be numbers like `20` for react native for (const key in cssValue) { sanitizeStyle(cssValue)(key, cssValue[key]); cssValue = (0, sanitize_react_native_block_styles_1.sanitizeReactNativeBlockStyles)(cssValue, options); } } try { let styleValue = json5_1.default.parse(((_b = item.bindings.style) === null || _b === void 0 ? void 0 : _b.code) || '{}'); if ((0, lodash_1.size)(styleValue)) { // Style properties like `"20px"` need to be numbers like `20` for react native for (const key in styleValue) { sanitizeStyle(styleValue)(key, styleValue[key]); styleValue = (0, sanitize_react_native_block_styles_1.sanitizeReactNativeBlockStyles)(styleValue, options); } item.bindings.style.code = json5_1.default.stringify(styleValue); } } catch (e) { } if (!(0, lodash_1.size)(cssValue)) { return; } const styleSheetName = getStyleSheetName(item); const styleSheetAccess = `styles.${styleSheetName}`; styleMap[styleSheetName] = cssValue; if (!item.bindings.style) { item.bindings.style = (0, bindings_1.createSingleBinding)({ code: styleSheetAccess, }); return; } try { // run the code below only if the style binding is a JSON object json5_1.default.parse(item.bindings.style.code || '{}'); item.bindings.style = (0, bindings_1.createSingleBinding)({ code: ((_c = item.bindings.style) === null || _c === void 0 ? void 0 : _c.code.replace(/}$/, `, ...${styleSheetAccess} }`)) || styleSheetAccess, }); } catch (e) { // if not a JSON, then it's a property, so we should spread it. item.bindings.style = (0, bindings_1.createSingleBinding)({ code: `{ ...${styleSheetAccess}, ...${item.bindings.style.code} }`, }); } }); return styleMap; }; exports.collectReactNativeStyles = collectReactNativeStyles; /** * Plugin that handles necessary transformations from React to React Native: * - Converts DOM tags to <View /> and <Text /> */ const PROCESS_REACT_NATIVE_PLUGIN = () => ({ json: { pre: (json) => { (0, legacy_1.default)(json).forEach((node) => { var _a, _b, _c, _d; if ((0, is_mitosis_node_1.isMitosisNode)(node)) { // TODO: handle TextInput, Image, etc if ((0, is_children_1.default)({ node })) { node.name = ''; } else if (node.name.toLowerCase() === node.name && html_tags_1.VALID_HTML_TAGS.includes(node.name)) { if (node.name === 'input') { node.name = 'TextInput'; } else if (node.name === 'img') { node.name = 'Image'; } else if (node.name === 'a') { node.name = 'TouchableOpacity'; } else if (node.name === 'button') { node.name = 'Button'; } // if node is not button or a and still has onClick it needs to pressable else if (node.bindings.onClick) { node.name = 'Pressable'; } else { node.name = 'View'; } } else if (((_a = node.properties._text) === null || _a === void 0 ? void 0 : _a.trim().length) || ((_d = (_c = (_b = node.bindings._text) === null || _b === void 0 ? void 0 : _b.code) === null || _c === void 0 ? void 0 : _c.trim()) === null || _d === void 0 ? void 0 : _d.length)) { node.name = 'Text'; } } }); }, }, }); /** * Removes React Native className and class properties from the JSON */ const REMOVE_REACT_NATIVE_CLASSES_PLUGIN = () => ({ json: { pre: (json) => { (0, legacy_1.default)(json).forEach(function (node) { if ((0, is_mitosis_node_1.isMitosisNode)(node)) { if (node.properties.class) { delete node.properties.class; } if (node.properties.className) { delete node.properties.className; } if (node.bindings.class) { delete node.bindings.class; } if (node.bindings.className) { delete node.bindings.className; } } }); }, }, }); /** * Converts class and className properties to twrnc style syntax */ const TWRNC_STYLES_PLUGIN = () => ({ json: { post: (json) => { (0, legacy_1.default)(json).forEach(function (node) { if ((0, is_mitosis_node_1.isMitosisNode)(node)) { let staticClasses = [node.properties.class, node.properties.className] .filter(Boolean) .join(' '); let dynamicClasses = [node.bindings.class, node.bindings.className].filter(Boolean); if (staticClasses || dynamicClasses.length) { let styleCode = ''; if (staticClasses) { styleCode = `tw\`${staticClasses}\``; } if (dynamicClasses.length) { let dynamicCode = dynamicClasses .map((dc) => (dc && dc.code ? dc.code : null)) .filter(Boolean) .join(', '); if (dynamicCode) { if (styleCode) { // If we have both static and dynamic classes styleCode = `tw.style(${styleCode}, ${dynamicCode})`; } else if (dynamicClasses.length > 1) { // If we have multiple dynamic classes styleCode = `tw.style([${dynamicCode}])`; } else { // If we have a single dynamic class styleCode = `tw.style(${dynamicCode})`; } } } if (styleCode) { node.bindings.style = (0, bindings_1.createSingleBinding)({ code: styleCode }); } } // Clean up original class and className properties/bindings delete node.properties.class; delete node.properties.className; delete node.bindings.class; delete node.bindings.className; } }); }, }, }); /** * Converts class and className properties to native-wind style syntax * Note: We only support the "with babel" setup: https://www.nativewind.dev/guides/babel */ const NATIVE_WIND_STYLES_PLUGIN = () => ({ json: { post: (json) => { (0, legacy_1.default)(json).forEach(function (node) { if ((0, is_mitosis_node_1.isMitosisNode)(node)) { let combinedClasses = [ node.properties.class, node.properties.className, node.bindings.class, node.bindings.className, ] .filter(Boolean) .join(' '); if (node.properties.class) { delete node.properties.class; } if (node.properties.className) { delete node.properties.className; } if (node.bindings.class) { delete node.bindings.class; } if (node.bindings.className) { delete node.bindings.className; } if (combinedClasses) { node.properties.className = combinedClasses; } } }); }, }, }); const DEFAULT_OPTIONS = { stateType: 'useState', stylesType: 'react-native', plugins: [PROCESS_REACT_NATIVE_PLUGIN], }; const componentToReactNative = (_options = {}) => ({ component, path }) => { const json = (0, fast_clone_1.fastClone)(component); const options = (0, merge_options_1.mergeOptions)(DEFAULT_OPTIONS, _options); if (options.stylesType === 'twrnc') { options.plugins.push(TWRNC_STYLES_PLUGIN); } else if (options.stylesType === 'native-wind') { options.plugins.push(NATIVE_WIND_STYLES_PLUGIN); } else { options.plugins.push(REMOVE_REACT_NATIVE_CLASSES_PLUGIN); } return (0, react_1.componentToReact)({ ...options, type: 'native' })({ component: json, path }); }; exports.componentToReactNative = componentToReactNative; __exportStar(require("./types"), exports);