UNPKG

@builder.io/mitosis

Version:

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

330 lines (321 loc) 13.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.componentToMitosis = exports.blockToMitosis = exports.DEFAULT_FORMAT = void 0; const hooks_1 = require("../../constants/hooks"); const html_tags_1 = require("../../constants/html_tags"); const dedent_1 = require("../../helpers/dedent"); const event_handlers_1 = require("../../helpers/event-handlers"); const fast_clone_1 = require("../../helpers/fast-clone"); const get_components_1 = require("../../helpers/get-components"); const get_refs_1 = require("../../helpers/get-refs"); const get_state_object_string_1 = require("../../helpers/get-state-object-string"); const is_mitosis_node_1 = require("../../helpers/is-mitosis-node"); const is_root_text_node_1 = require("../../helpers/is-root-text-node"); const map_refs_1 = require("../../helpers/map-refs"); const render_imports_1 = require("../../helpers/render-imports"); const state_1 = require("../../helpers/state"); const plugins_1 = require("../../modules/plugins"); const mitosis_node_1 = require("../../types/mitosis-node"); const json5_1 = __importDefault(require("json5")); const standalone_1 = require("prettier/standalone"); const react_1 = require("../react"); exports.DEFAULT_FORMAT = 'legacy'; // Special isValidAttributeName for Mitosis so we can allow for $ in names const isValidAttributeName = (str) => { return Boolean(str && /^[$a-z0-9\-_:]+$/i.test(str)); }; const isInvalidJsxAttributeName = (str) => { let attr = str.trim(); if (attr.startsWith(':') || str.startsWith('@')) { return true; } return false; }; const blockToMitosis = (json, toMitosisOptions = {}, component, insideJsx) => { var _a, _b, _c, _d, _e, _f, _g, _h; const options = { format: exports.DEFAULT_FORMAT, ...toMitosisOptions, }; if (options.format === 'react') { return (0, react_1.blockToReact)(json, { format: 'lite', stateType: 'useState', stylesType: 'emotion', type: 'dom', prettier: options.prettier, }, component, insideJsx); } if ((0, mitosis_node_1.checkIsShowNode)(json)) { const when = (_a = json.bindings.when) === null || _a === void 0 ? void 0 : _a.code; const elseCase = json.meta.else; if (options.nativeConditionals) { const needsWrapper = json.children.length !== 1; const renderChildren = `${needsWrapper ? '<>' : ''} ${json.children .map((child) => (0, exports.blockToMitosis)(child, options, component, needsWrapper)) .join('\n')} ${needsWrapper ? '</>' : ''}`; const renderElse = elseCase && (0, is_mitosis_node_1.isMitosisNode)(elseCase) ? (0, exports.blockToMitosis)(elseCase, options, component, false) : 'null'; return `${insideJsx ? '{' : ''}(${when}) ? ${renderChildren} : ${renderElse}${insideJsx ? '}' : ''}`; } else { const elseHandler = elseCase ? ` else={${(0, exports.blockToMitosis)(elseCase, options, component, false)}}` : ''; return `<Show when={${when}}${elseHandler}> ${json.children.map((child) => (0, exports.blockToMitosis)(child, options, component, true)).join('\n')} </Show>`; } } if ((0, mitosis_node_1.checkIsForNode)(json)) { const needsWrapper = json.children.length !== 1; if (options.nativeLoops) { const a = `${insideJsx ? '{' : ''}(${(_b = json.bindings.each) === null || _b === void 0 ? void 0 : _b.code}).map( (${json.scope.forName || '_'}, ${json.scope.indexName || 'index'}) => ( ${needsWrapper ? '<>' : ''} ${json.children .map((child) => (0, exports.blockToMitosis)(child, options, component, needsWrapper)) .join('\n')} ${needsWrapper ? '</>' : ''} ))${insideJsx ? '}' : ''}`; return a; } return `<For each={${(_c = json.bindings.each) === null || _c === void 0 ? void 0 : _c.code}}> {(${json.scope.forName || '_'}, ${json.scope.indexName || 'index'}) => ${needsWrapper ? '<>' : ''} ${json.children.map((child) => (0, exports.blockToMitosis)(child, options, component, needsWrapper))}} ${needsWrapper ? '</>' : ''} </For>`; } if (json.properties._text) { let text = json.properties._text // Convert HTML <br> to JSX-valid <br /> .replace(/<br\s*>/g, '<br />'); let isInvalidJsx = text.includes('{') || text.includes('}'); if (text.includes('<') || text.includes('>')) { // test if this can parse as jsx try { /** * We intentionally use the typescript parser here because texts like ">" will crash * in the typescript parser but will not crash in the babel parser. The Prettier * formatting that is run after JSX is generated also uses the typescript parser, * so we want to make sure that doesn't crash. */ (0, standalone_1.check)(`let _ = <>${text}</>;`, { parser: 'typescript', plugins: [ require('prettier/parser-typescript'), // To support running in browsers ], }); isInvalidJsx = false; } catch (e) { isInvalidJsx = true; } } if (isInvalidJsx) { text = text .replace(/>/g, '&gt;') .replace(/</g, '&lt;') .replace(/{/g, '&#123;') .replace(/}/g, '&#125;') .replace(/&/g, '&amp;'); } if (insideJsx) { return `${text}`; } else { return `<>${text}</>`; } } if ((_d = json.bindings._text) === null || _d === void 0 ? void 0 : _d.code) { if (insideJsx) { return `{${json.bindings._text.code}}`; } else { return `${json.bindings._text.code}`; } } let str = ''; str += `<${json.name} `; for (const key in json.properties) { if (isInvalidJsxAttributeName(key)) { console.warn('Skipping invalid attribute name:', key); continue; } const value = (json.properties[key] || '').replace(/"/g, '&quot;').replace(/\n/g, '\\n'); if (!isValidAttributeName(key)) { console.warn('Skipping invalid attribute name:', key); } else { str += ` ${key}="${value}" `; } } for (const key in json.bindings) { if (isInvalidJsxAttributeName(key)) { console.warn('Skipping invalid attribute name:', key); continue; } const value = (_e = json.bindings[key]) === null || _e === void 0 ? void 0 : _e.code; if (!value || ((_f = json.slots) === null || _f === void 0 ? void 0 : _f[key])) { continue; } if (((_g = json.bindings[key]) === null || _g === void 0 ? void 0 : _g.type) === 'spread') { str += ` {...(${(_h = json.bindings[key]) === null || _h === void 0 ? void 0 : _h.code})} `; } else if ((0, event_handlers_1.checkIsEvent)(key)) { const { arguments: cusArgs = ['event'], async } = json.bindings[key]; const asyncKeyword = async ? 'async ' : ''; str += ` ${key}={${asyncKeyword}(${cusArgs.join(',')}) => ${value.replace(/\s*;$/, '')}} `; } else { if (!isValidAttributeName(key)) { console.warn('Skipping invalid attribute name:', key); } else { str += ` ${key}={${value}} `; } } } for (const key in json.slots) { const value = json.slots[key]; str += ` ${key}={`; if (value.length > 1) { str += '<>'; } str += json.slots[key] .map((item) => (0, exports.blockToMitosis)(item, options, component, insideJsx)) .join('\n'); if (value.length > 1) { str += '</>'; } str += `}`; } if (html_tags_1.SELF_CLOSING_HTML_TAGS.has(json.name)) { return str + ' />'; } // Self close by default if no children if (!json.children.length) { str += ' />'; return str; } str += '>'; if (json.children) { str += json.children.map((item) => (0, exports.blockToMitosis)(item, options, component, true)).join('\n'); } str += `</${json.name}>`; return str; }; exports.blockToMitosis = blockToMitosis; const getRefsString = (json, refs = Array.from((0, get_refs_1.getRefs)(json))) => { var _a, _b; let str = ''; for (const ref of refs) { const typeParameter = ((_a = json['refs'][ref]) === null || _a === void 0 ? void 0 : _a.typeParameter) || ''; const argument = ((_b = json['refs'][ref]) === null || _b === void 0 ? void 0 : _b.argument) || ''; str += `\nconst ${ref} = useRef${typeParameter ? `<${typeParameter}>` : ''}(${argument});`; } return str; }; const mitosisCoreComponents = ['Show', 'For']; const componentToMitosis = (toMitosisOptions = {}) => ({ component }) => { var _a; const options = { format: exports.DEFAULT_FORMAT, ...toMitosisOptions, }; if (options.format === 'react') { return (0, react_1.componentToReact)({ format: 'lite', stateType: 'useState', stylesType: 'emotion', prettier: options.prettier, })({ component }); } let json = (0, fast_clone_1.fastClone)(component); if (options.plugins) { json = (0, plugins_1.runPreJsonPlugins)({ json, plugins: options.plugins }); } const domRefs = (0, get_refs_1.getRefs)(component); // grab refs not used for bindings const jsRefs = Object.keys(component.refs).filter((ref) => domRefs.has(ref)); const refs = [...jsRefs, ...Array.from(domRefs)]; (0, map_refs_1.mapRefs)(json, (refName) => { return `${refName}${domRefs.has(refName) ? `.current` : ''}`; }); const addWrapper = json.children.length !== 1 || (0, is_root_text_node_1.isRootTextNode)(json); const components = Array.from((0, get_components_1.getComponents)(json)); const mitosisCoreComponents = []; if (!options.nativeConditionals) { mitosisCoreComponents.push('Show'); } if (!options.nativeLoops) { mitosisCoreComponents.push('For'); } const mitosisComponents = components.filter((item) => mitosisCoreComponents.includes(item)); const otherComponents = components.filter((item) => !mitosisCoreComponents.includes(item)); if (options.plugins) { json = (0, plugins_1.runPostJsonPlugins)({ json, plugins: options.plugins }); } const hasState = (0, state_1.checkHasState)(component); const needsMitosisCoreImport = Boolean(hasState || refs.length || mitosisComponents.length); const stringifiedUseMetadata = json5_1.default.stringify(component.meta.useMetadata); // TODO: smart only pull in imports as needed let str = (0, dedent_1.dedent) ` ${!needsMitosisCoreImport ? '' : `import { ${!hasState ? '' : 'useStore, '} ${!refs.length ? '' : 'useRef, '} ${mitosisComponents.join(', ')} } from '../..';`} ${!otherComponents.length ? '' : `import { ${otherComponents.join(',')} } from '@components';`} ${json.types ? json.types.join('\n') : ''} ${(0, render_imports_1.renderPreComponent)({ explicitImportFileExtension: options.explicitImportFileExtension, component: json, target: 'mitosis', })} ${stringifiedUseMetadata && stringifiedUseMetadata !== '{}' ? `${hooks_1.HOOKS.METADATA}(${stringifiedUseMetadata})` : ''} export default function ${component.name}(props) { ${!hasState ? '' : `const state = useStore(${(0, get_state_object_string_1.getStateObjectStringFromComponent)(json)});`} ${getRefsString(json, refs)} ${json.hooks.onMount.map((hook) => `onMount(() => { ${hook.code} })`)} ${!((_a = json.hooks.onUnMount) === null || _a === void 0 ? void 0 : _a.code) ? '' : `onUnMount(() => { ${json.hooks.onUnMount.code} })`} ${json.style ? `useStyle(\`${json.style}\`)` : ''} return ${options.returnArray ? '[' : '('}${addWrapper ? '<>' : ''} ${json.children .map((item) => (0, exports.blockToMitosis)(item, options, component, addWrapper)) .join('\n')} ${addWrapper ? '</>' : ''}${options.returnArray ? ']' : ')'} } `; if (options.plugins) { str = (0, plugins_1.runPreCodePlugins)({ json, code: str, plugins: options.plugins }); } if (options.prettier !== false) { try { str = (0, standalone_1.format)(str, { parser: 'typescript', plugins: [ require('prettier/parser-typescript'), // To support running in browsers ], }); } catch (err) { if (process.env.NODE_ENV !== 'test') { console.error('Format error for file:', str, JSON.stringify(json, null, 2)); } throw err; } } if (options.plugins) { str = (0, plugins_1.runPostCodePlugins)({ json, code: str, plugins: options.plugins }); } return str; }; exports.componentToMitosis = componentToMitosis;