@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
JavaScript
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);
;