UNPKG

@builder.io/mitosis

Version:

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

413 lines (401 loc) 17.1 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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.componentToSvelte = void 0; const babel_transform_1 = require("../../helpers/babel-transform"); const dedent_1 = require("../../helpers/dedent"); const fast_clone_1 = require("../../helpers/fast-clone"); const get_props_1 = require("../../helpers/get-props"); const get_refs_1 = require("../../helpers/get-refs"); const get_state_object_string_1 = require("../../helpers/get-state-object-string"); const getters_to_functions_1 = require("../../helpers/getters-to-functions"); const is_mitosis_node_1 = require("../../helpers/is-mitosis-node"); 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 render_imports_1 = require("../../helpers/render-imports"); const slots_1 = require("../../helpers/slots"); const strip_meta_properties_1 = require("../../helpers/strip-meta-properties"); const collect_css_1 = require("../../helpers/styles/collect-css"); const helpers_1 = require("../../helpers/styles/helpers"); const function_1 = require("fp-ts/lib/function"); const legacy_1 = __importDefault(require("neotraverse/legacy")); const prettierPluginSvelte = __importStar(require("prettier-plugin-svelte")); const parser_babel_1 = __importDefault(require("prettier/parser-babel")); const parser_html_1 = __importDefault(require("prettier/parser-html")); const parser_postcss_1 = __importDefault(require("prettier/parser-postcss")); const parser_typescript_1 = __importDefault(require("prettier/parser-typescript")); const standalone_1 = require("prettier/standalone"); const plugins_1 = require("../../modules/plugins"); const context_1 = require("../helpers/context"); const functions_1 = require("../helpers/functions"); const blocks_1 = require("./blocks"); const helpers_2 = require("./helpers"); const getContextCode = (json) => { const contextGetters = json.context.get; return Object.entries(contextGetters) .map(([key, context]) => { const { name } = context; const contextType = (0, context_1.getContextType)({ component: json, context }); switch (contextType) { case 'reactive': case 'normal': return `let ${key} = getContext(${name}.key);`; } }) .join('\n'); }; const setContextCode = ({ json, options, }) => { const processCode = (0, helpers_2.stripStateAndProps)({ json, options }); return Object.values(json.context.set) .map((context) => { const { value, name, ref } = context; const nameIsStringLiteral = (name.startsWith("'") && name.endsWith("'")) || (name.startsWith('"') && name.endsWith('"')); const key = nameIsStringLiteral ? name : `${name}.key`; const valueStr = value ? processCode((0, get_state_object_string_1.stringifyContextValue)(value)) : ref ? processCode(ref) : 'undefined'; const contextType = (0, context_1.getContextType)({ component: json, context }); switch (contextType) { case 'normal': return `setContext(${key}, ${valueStr});`; case 'reactive': const storeName = `${name}ContextStoreValue`; return ` const ${storeName} = writable(${valueStr}); setContext(${key}, ${storeName}); `; } }) .join('\n'); }; /** * Replace * <input value={state.name} onChange={event => state.name = event.target.value} * with * <input bind:value={state.name}/> * when easily identified, for more idiomatic svelte code */ const useBindValue = (json, options) => { function normalizeStr(str) { return str .trim() .replace(/\n|\r/g, '') .replace(/^{/, '') .replace(/}$/, '') .replace(/;$/, '') .replace(/\s+/g, ''); } (0, legacy_1.default)(json).forEach(function (item) { if ((0, is_mitosis_node_1.isMitosisNode)(item)) { const { value, onChange } = item.bindings; if (value && onChange) { const { arguments: cusArgs = ['event'] } = onChange; if (normalizeStr(onChange.code) === `${normalizeStr(value.code)}=${cusArgs[0]}.target.value`) { delete item.bindings.value; delete item.bindings.onChange; item.bindings['bind:value'] = value; } } } }); }; const DEFAULT_OPTIONS = { stateType: 'variables', prettier: true, }; const componentToSvelte = (userProvidedOptions) => ({ component }) => { var _a, _b, _c, _d, _e, _f, _g, _h, _j; const options = (0, merge_options_1.initializeOptions)({ target: 'svelte', component, defaults: DEFAULT_OPTIONS, userOptions: userProvidedOptions, }); options.plugins = [ ...(options.plugins || []), (0, on_event_1.processOnEventHooksPlugin)(), functions_1.FUNCTION_HACK_PLUGIN, // Strip types from any JS code that ends up in the template, because Svelte does not support TS code in templates. (0, process_code_1.CODE_PROCESSOR_PLUGIN)((codeType) => { switch (codeType) { case 'bindings': case 'properties': return babel_transform_1.convertTypeScriptToJS; case 'hooks': case 'hooks-deps': case 'hooks-deps-array': case 'state': case 'context-set': case 'dynamic-jsx-elements': case 'types': return (x) => x; } }), (0, process_code_1.CODE_PROCESSOR_PLUGIN)((codeType) => { switch (codeType) { case 'hooks': return (0, function_1.flow)((0, helpers_2.stripStateAndProps)({ json, options }), babel_transform_1.babelTransformCode); case 'bindings': case 'hooks-deps': case 'state': return (0, function_1.flow)((0, helpers_2.stripStateAndProps)({ json, options }), patterns_1.stripGetter); case 'properties': case 'context-set': return (0, function_1.flow)((0, helpers_2.stripStateAndProps)({ json, options })); case 'dynamic-jsx-elements': case 'hooks-deps-array': case 'types': return (x) => x; } }), ]; // Make a copy we can safely mutate, similar to babel's toolchain let json = (0, fast_clone_1.fastClone)(component); json = (0, plugins_1.runPreJsonPlugins)({ json, plugins: options.plugins }); useBindValue(json, options); (0, getters_to_functions_1.gettersToFunctions)(json); const filteredProps = Array.from((0, get_props_1.getProps)(json)) .filter((prop) => !(0, slots_1.isSlotProperty)(prop)) // map $prop to prop for reactive state .map((x) => (x.startsWith('$') ? x.slice(1) : x)); // this helps make sure we don't have duplicate props const props = Array.from(new Set(filteredProps)); const refs = Array.from((0, get_refs_1.getRefs)(json)) .map((0, helpers_2.stripStateAndProps)({ json, options })) .filter((x) => !props.includes(x)); json = (0, plugins_1.runPostJsonPlugins)({ json, plugins: options.plugins }); const css = (0, collect_css_1.collectCss)(json); (0, strip_meta_properties_1.stripMetaProperties)(json); let usesWritable = false; const dataString = (0, function_1.pipe)((0, get_state_object_string_1.getStateObjectStringFromComponent)(json, { data: true, functions: false, getters: false, format: options.stateType === 'proxies' ? 'object' : 'variables', keyPrefix: options.stateType === 'variables' ? 'let ' : '', valueMapper: (code, _t, _p, key) => { var _a; if (((_a = json.state[key]) === null || _a === void 0 ? void 0 : _a.propertyType) === 'reactive') { usesWritable = true; return `writable(${code})`; } return code; }, }), babel_transform_1.babelTransformCode); const getterString = (0, function_1.pipe)((0, get_state_object_string_1.getStateObjectStringFromComponent)(json, { data: false, getters: true, functions: false, format: 'variables', keyPrefix: '$: ', valueMapper: (code) => { return code .trim() .replace(/^([a-zA-Z_\$0-9]+)/, '$1 = ') .replace(/\)/, ') => '); }, }), babel_transform_1.babelTransformCode); const functionsString = (0, function_1.pipe)((0, get_state_object_string_1.getStateObjectStringFromComponent)(json, { data: false, getters: false, functions: true, format: 'variables', }), babel_transform_1.babelTransformCode); const hasData = dataString.length > 4; let str = ''; const tsLangAttribute = options.typescript ? `lang='ts'` : ''; if (options.typescript && ((_a = json.types) === null || _a === void 0 ? void 0 : _a.length)) { str += (0, dedent_1.dedent) ` <script context='module' ${tsLangAttribute}> ${json.types ? json.types.join('\n\n') + '\n' : ''} </script> \n \n `; } // prepare svelte imports let svelteImports = []; let svelteStoreImports = []; if (json.hooks.onMount.length) { svelteImports.push('onMount'); } if ((_c = (_b = json.hooks.onUpdate) === null || _b === void 0 ? void 0 : _b.filter((x) => !x.deps)) === null || _c === void 0 ? void 0 : _c.length) { svelteImports.push('afterUpdate'); } if ((_e = (_d = json.hooks.onUnMount) === null || _d === void 0 ? void 0 : _d.code) === null || _e === void 0 ? void 0 : _e.length) { svelteImports.push('onDestroy'); } if ((0, context_1.hasGetContext)(component)) { svelteImports.push('getContext'); } if ((0, context_1.hasSetContext)(component)) { svelteImports.push('setContext'); } if (usesWritable) { svelteStoreImports.push('writable'); } str += (0, dedent_1.dedent) ` <script ${tsLangAttribute}> ${!svelteImports.length ? '' : `import { ${svelteImports.sort().join(', ')} } from 'svelte'`} ${!svelteStoreImports.length ? '' : `import { ${svelteStoreImports.sort().join(', ')} } from 'svelte/store'`} ${(0, render_imports_1.renderPreComponent)({ explicitImportFileExtension: options.explicitImportFileExtension, component: json, target: 'svelte', })} ${!hasData || options.stateType === 'variables' ? '' : `import onChange from 'on-change'`} ${props .map((name) => { var _a, _b, _c; if (name === 'children') { return ''; } let propDeclaration = `export let ${name}`; if (options.typescript && json.propsTypeRef && json.propsTypeRef !== 'any') { propDeclaration += `: ${json.propsTypeRef.split(' |')[0]}['${name}']`; } if (json.defaultProps && json.defaultProps.hasOwnProperty(name)) { propDeclaration += `=${(_a = json.defaultProps[name]) === null || _a === void 0 ? void 0 : _a.code}`; } else if ((_c = (_b = json.props) === null || _b === void 0 ? void 0 : _b[name]) === null || _c === void 0 ? void 0 : _c.optional) { propDeclaration += `= undefined`; } propDeclaration += ';'; return propDeclaration; }) .join('\n')} ${ // https://github.com/sveltejs/svelte/issues/7311 (0, helpers_1.hasStyle)(json) ? (0, dedent_1.dedent) ` function stringifyStyles(stylesObj) { let styles = ''; for (let key in stylesObj) { const dashedKey = key.replace(/[A-Z]/g, function(match) { return '-' + match.toLowerCase(); }); styles += dashedKey + ":" + stylesObj[key] + ";"; } return styles; } ` : ''} ${getContextCode(json)} ${functionsString.length < 4 ? '' : functionsString} ${getterString.length < 4 ? '' : getterString} ${refs.map((ref) => `let ${ref}`).join('\n')} ${options.stateType === 'proxies' ? dataString.length < 4 ? '' : `let state = onChange(${dataString}, () => state = state)` : dataString} ${(_g = (_f = json.hooks.onInit) === null || _f === void 0 ? void 0 : _f.code) !== null && _g !== void 0 ? _g : ''} ${json.hooks.onMount.map((hook) => `onMount(() => { ${hook.code} });`).join('\n')} ${((_h = json.hooks.onUpdate) === null || _h === void 0 ? void 0 : _h.map(({ code, deps }, index) => { if (!deps) { return `afterUpdate(() => { ${code} });`; } const fnName = `onUpdateFn_${index}`; const depsArray = deps .slice(1, deps.length - 1) .split(',') .map((x) => x.trim()); const getReactiveDepName = (dep) => `${fnName}_${dep.slice(1).replace(/(\.|\?)/g, '_')}`; const isStoreAccessDep = (dep) => dep.startsWith('$'); const reactiveDepsWorkaround = depsArray .filter(isStoreAccessDep) .map((dep) => `$: ${getReactiveDepName(dep)} = ${dep};`) .join('\n'); const depsArrayStr = depsArray .map((x) => (isStoreAccessDep(x) ? getReactiveDepName(x) : x)) .join(', '); /** * We create a reactive value for each `onUpdate`'s dependency that * accesses a store so that Svelte has accurate dependency tracking. * * Otherwise, if the dependency is a value within a store, Svelte will * rerun the effect every time the parent store is changed in any way. */ return ` function ${fnName}(..._args${options.typescript ? ': any[]' : ''}) { ${code} } ${reactiveDepsWorkaround} $: ${fnName}(...[${depsArrayStr}]); `; }).join('\n')) || ''} ${ // make sure this is after all other state/code is initialized setContextCode({ json, options })} ${!((_j = json.hooks.onUnMount) === null || _j === void 0 ? void 0 : _j.code) ? '' : `onDestroy(() => { ${json.hooks.onUnMount.code} });`} </script> ${json.children .map((item) => (0, blocks_1.blockToSvelte)({ json: item, options: options, parentComponent: json, })) .join('\n')} ${!css.trim().length ? '' : `<style> ${css} </style>`} `; str = (0, plugins_1.runPreCodePlugins)({ json, code: str, plugins: options.plugins }); if (options.prettier !== false) { try { str = (0, standalone_1.format)(str, { parser: 'svelte', plugins: [ // To support running in browsers parser_html_1.default, parser_postcss_1.default, parser_babel_1.default, parser_typescript_1.default, prettierPluginSvelte, ], }); } catch (err) { console.warn('Could not prettify'); console.warn(str, err); } } str = str.replace(/<script>\n<\/script>/g, '').trim(); str = (0, plugins_1.runPostCodePlugins)({ json, code: str, plugins: options.plugins }); return str; }; exports.componentToSvelte = componentToSvelte;