@builder.io/mitosis
Version:
Write components once, run everywhere. Compiles to Vue, React, Solid, and Liquid. Import code from Figma and Builder.io
288 lines (283 loc) • 13.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.componentToSwift = void 0;
const dedent_1 = require("../../helpers/dedent");
const fast_clone_1 = require("../../helpers/fast-clone");
const generic_format_1 = require("../../helpers/generic-format");
const get_props_1 = require("../../helpers/get-props");
const get_refs_1 = require("../../helpers/get-refs");
const getters_to_functions_1 = require("../../helpers/getters-to-functions");
const merge_options_1 = require("../../helpers/merge-options");
const on_event_1 = require("../../helpers/on-event");
const patterns_1 = require("../../helpers/patterns");
const process_code_1 = require("../../helpers/plugins/process-code");
const slots_1 = require("../../helpers/slots");
const strip_meta_properties_1 = require("../../helpers/strip-meta-properties");
const plugins_1 = require("../../modules/plugins");
const blocks_1 = require("./blocks");
const helpers_1 = require("./helpers");
const DEFAULT_OPTIONS = {
stateType: 'state',
formatCode: true,
includeTypes: true,
includePreview: true,
classPrefix: '',
};
const componentToSwift = (userProvidedOptions) => ({ component }) => {
var _a, _b;
const options = (0, merge_options_1.initializeOptions)({
target: 'swift',
component,
defaults: DEFAULT_OPTIONS,
userOptions: userProvidedOptions,
});
options.plugins = [
...(options.plugins || []),
(0, on_event_1.processOnEventHooksPlugin)(),
(0, process_code_1.CODE_PROCESSOR_PLUGIN)((codeType) => {
switch (codeType) {
case 'bindings':
case 'properties':
case 'hooks':
case 'hooks-deps':
case 'hooks-deps-array':
case 'state':
case 'context-set':
case 'dynamic-jsx-elements':
case 'types':
return (x) => (0, helpers_1.convertConsoleLogToPrint)(x);
}
}),
(0, process_code_1.CODE_PROCESSOR_PLUGIN)((codeType) => {
switch (codeType) {
case 'hooks':
return (code) => (0, helpers_1.stripStateAndProps)({ json, options })(code);
case 'bindings':
case 'hooks-deps':
case 'state':
return (code) => (0, patterns_1.stripGetter)((0, helpers_1.stripStateAndProps)({ json, options })(code));
case 'properties':
case 'context-set':
return (code) => (0, helpers_1.stripStateAndProps)({ json, options })(code);
case 'dynamic-jsx-elements':
case 'hooks-deps-array':
case 'types':
return (x) => (0, helpers_1.convertConsoleLogToPrint)(x);
}
}),
];
// Make a copy we can safely mutate
let json = (0, fast_clone_1.fastClone)(component);
json = (0, plugins_1.runPreJsonPlugins)({ json, plugins: options.plugins });
(0, getters_to_functions_1.gettersToFunctions)(json);
const componentName = options.classPrefix + (json.name || ((_a = json.meta.useMetadata) === null || _a === void 0 ? void 0 : _a.name) || 'MitosisComponent');
// Process props
const filteredProps = Array.from((0, get_props_1.getProps)(json)).filter((prop) => !(0, slots_1.isSlotProperty)(prop));
const props = Array.from(new Set(filteredProps));
// Process refs (not directly applicable in SwiftUI, will be converted to @State)
const refs = Array.from((0, get_refs_1.getRefs)(json))
.map((0, helpers_1.stripStateAndProps)({ json, options }))
.filter((x) => !props.includes(x));
json = (0, plugins_1.runPostJsonPlugins)({ json, plugins: options.plugins });
(0, strip_meta_properties_1.stripMetaProperties)(json);
// Generate state variables
const stateProperties = Object.entries(json.state)
.filter(([_, value]) => {
// Skip methods - they'll be handled separately
return !((value === null || value === void 0 ? void 0 : value.type) === 'method' ||
((value === null || value === void 0 ? void 0 : value.code) && (value.code.includes('function') || value.code.includes('=>'))));
})
.map(([key, value]) => {
var _a, _b, _c, _d, _e;
// Check for value properties safely
const propertyType = value === null || value === void 0 ? void 0 : value.propertyType;
// Determine Swift type - handle missing type property
let valueType = 'Any';
if (value === null || value === void 0 ? void 0 : value.typeParameter) {
valueType = value.typeParameter;
}
else {
// Try to infer type from code if possible
if (((_a = value === null || value === void 0 ? void 0 : value.code) === null || _a === void 0 ? void 0 : _a.includes('"')) || ((_b = value === null || value === void 0 ? void 0 : value.code) === null || _b === void 0 ? void 0 : _b.includes("'"))) {
valueType = 'string';
}
else if ((_c = value === null || value === void 0 ? void 0 : value.code) === null || _c === void 0 ? void 0 : _c.match(/^[0-9]+(\.[0-9]+)?$/)) {
valueType = 'number';
}
else if ((value === null || value === void 0 ? void 0 : value.code) === 'true' || (value === null || value === void 0 ? void 0 : value.code) === 'false') {
valueType = 'boolean';
}
else if (((_d = value === null || value === void 0 ? void 0 : value.code) === null || _d === void 0 ? void 0 : _d.startsWith('[')) || ((_e = value === null || value === void 0 ? void 0 : value.code) === null || _e === void 0 ? void 0 : _e.startsWith('Array'))) {
valueType = 'Array<Any>';
}
}
const typeAnnotation = (0, helpers_1.getStatePropertyTypeAnnotation)(propertyType, valueType);
const swiftType = (0, helpers_1.getSwiftType)(valueType);
let stateDeclaration = `${typeAnnotation} ${key}: ${swiftType}`;
// Add default value if present
if (value === null || value === void 0 ? void 0 : value.code) {
stateDeclaration += ` = ${value.code}`;
}
else {
// Add default initialization based on type
switch (swiftType) {
case 'String':
stateDeclaration += ' = ""';
break;
case 'Bool':
stateDeclaration += ' = false';
break;
case 'Double':
case 'Int':
stateDeclaration += ' = 0';
break;
case 'Array<String>':
case '[String]':
stateDeclaration += ' = []';
break;
default:
if (swiftType.includes('Array') || swiftType.includes('[')) {
stateDeclaration += ' = []';
}
else if (swiftType !== 'Any' && swiftType !== 'Void') {
stateDeclaration += '/* initialize with appropriate default */';
}
}
}
return stateDeclaration;
})
.join('\n ');
// Generate state function variables with inline closure assignment
const functionStateProperties = [];
Object.entries(json.state)
.filter(([_, value]) => {
return ((value === null || value === void 0 ? void 0 : value.type) === 'method' ||
((value === null || value === void 0 ? void 0 : value.code) && (value.code.includes('function') || value.code.includes('=>'))));
})
.forEach(([key, value]) => {
if (!(value === null || value === void 0 ? void 0 : value.code)) {
// Handle empty function with inline closure
functionStateProperties.push(`private var ${key}: () -> Void = { () in /* Empty function */ }`);
return;
}
// Convert the JS function to Swift
const processedCode = (0, helpers_1.stripStateAndProps)({ json, options })(value.code);
const { swiftCode, signature } = (0, helpers_1.convertJsFunctionToSwift)(processedCode, `_${key}`);
// Parse signature to get parameter list and return type
const signatureMatch = signature.match(/func _([^(]+)\(([^)]*)\) -> ([^{]+)/);
if (signatureMatch) {
const [, funcName, params, returnType] = signatureMatch;
// Create the function type for the state variable
const paramTypes = params
? params
.split(',')
.map((p) => { var _a; return ((_a = p.trim().split(':')[1]) === null || _a === void 0 ? void 0 : _a.trim()) || 'Any'; })
.join(', ')
: '';
const functionType = params
? `(${paramTypes}) -> ${returnType.trim()}`
: `() -> ${returnType.trim()}`;
// Extract function body from swiftCode
const bodyMatch = swiftCode.match(/{\s*([\s\S]*?)\s*}/);
const functionBody = bodyMatch ? bodyMatch[1].trim() : '/* Empty function body */';
// Build closure syntax for inline assignment
const closureSyntax = params
? `{ (${params}) -> ${returnType.trim()} in\n ${functionBody}\n }`
: `{ () -> ${returnType.trim()} in\n ${functionBody}\n }`;
// Add the state variable declaration with inline closure assignment
functionStateProperties.push(`var ${key}: ${functionType} = ${closureSyntax}`);
}
else {
// Fallback if signature parsing fails
functionStateProperties.push(`var ${key}: () -> Void = { () in /* Could not parse function */ }`);
}
});
// Process lifecycle methods
const lifecycleMethods = [];
// Only add onInit if needed and if there are no function state properties
// (to avoid duplicate initializers)
if ((_b = json.hooks.onInit) === null || _b === void 0 ? void 0 : _b.code) {
lifecycleMethods.push((0, dedent_1.dedent) `
init() {
${json.hooks.onInit.code}
}
`);
}
// Generate SwiftUI component
let str = (0, dedent_1.dedent) `
import SwiftUI
struct ${componentName}: View {
${props
.map((prop) => {
var _a, _b, _c, _d;
const propType = ((_b = (_a = json.props) === null || _a === void 0 ? void 0 : _a[prop]) === null || _b === void 0 ? void 0 : _b.propertyType) || 'Any';
const swiftType = (0, helpers_1.getSwiftType)(propType);
return `let ${prop}: ${swiftType}${((_d = (_c = json.props) === null || _c === void 0 ? void 0 : _c[prop]) === null || _d === void 0 ? void 0 : _d.optional) ? '?' : ''}`;
})
.join('\n ')}
${stateProperties}
${functionStateProperties.length > 0
? '\n // Function state variables\n ' + functionStateProperties.join('\n ')
: ''}
var body: some View {
${json.children
.map((item) => (0, blocks_1.blockToSwift)({
json: item,
options: options,
parentComponent: json,
}))
.join('\n')}
${lifecycleMethods.filter((method) => method.startsWith('.')).join('\n ')}
}
${lifecycleMethods.filter((method) => !method.startsWith('.')).join('\n ')}
}
`;
// Add preview if enabled
if (options.includePreview) {
str += (0, dedent_1.dedent) `
\n
#if DEBUG
struct ${componentName}_Previews: PreviewProvider {
static var previews: some View {
${componentName}(
${props
.map((prop) => {
var _a, _b, _c, _d;
const propType = ((_b = (_a = json.props) === null || _a === void 0 ? void 0 : _a[prop]) === null || _b === void 0 ? void 0 : _b.propertyType) || 'Any';
const swiftType = (0, helpers_1.getSwiftType)(propType);
// Generate appropriate preview values based on type
let previewValue = '';
switch (swiftType) {
case 'String':
previewValue = '"Preview"';
break;
case 'Bool':
previewValue = 'false';
break;
case 'Double':
case 'Int':
previewValue = '0';
break;
default:
if ((_d = (_c = json.props) === null || _c === void 0 ? void 0 : _c[prop]) === null || _d === void 0 ? void 0 : _d.optional) {
previewValue = 'nil';
}
else {
previewValue = '/* provide preview value */';
}
}
return `${prop}: ${previewValue}`;
})
.join(',\n ')}
)
}
}
#endif
`;
}
str = (0, plugins_1.runPreCodePlugins)({ json, code: str, plugins: options.plugins });
str = (0, generic_format_1.format)(str);
str = (0, plugins_1.runPostCodePlugins)({ json, code: str, plugins: options.plugins });
return str;
};
exports.componentToSwift = componentToSwift;