UNPKG

@builder.io/mitosis

Version:

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

273 lines (266 loc) 13.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.componentToVue = void 0; const apply_meta_tagname_1 = require("../../helpers/apply-meta-tagname"); const babel_transform_1 = require("../../helpers/babel-transform"); const bindings_1 = require("../../helpers/bindings"); const dedent_1 = require("../../helpers/dedent"); const fast_clone_1 = require("../../helpers/fast-clone"); const get_props_1 = require("../../helpers/get-props"); const is_mitosis_node_1 = require("../../helpers/is-mitosis-node"); const map_refs_1 = require("../../helpers/map-refs"); const merge_options_1 = require("../../helpers/merge-options"); const on_event_1 = require("../../helpers/on-event"); const process_code_1 = require("../../helpers/plugins/process-code"); const process_http_requests_1 = require("../../helpers/process-http-requests"); const render_imports_1 = require("../../helpers/render-imports"); const replace_identifiers_1 = require("../../helpers/replace-identifiers"); 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 function_1 = require("fp-ts/lib/function"); const lodash_1 = require("lodash"); const legacy_1 = __importDefault(require("neotraverse/legacy")); const standalone_1 = require("prettier/standalone"); const plugins_1 = require("../../modules/plugins"); const functions_1 = require("../helpers/functions"); const blocks_1 = require("./blocks"); const compositionApi_1 = require("./compositionApi"); const helpers_1 = require("./helpers"); const optionsApi_1 = require("./optionsApi"); // Transform <foo.bar key="value" /> to <component :is="foo.bar" key="value" /> function processDynamicComponents(json, _options) { (0, legacy_1.default)(json).forEach((node) => { if ((0, is_mitosis_node_1.isMitosisNode)(node)) { if (node.name.includes('.')) { node.bindings.is = (0, bindings_1.createSingleBinding)({ code: node.name }); node.name = 'component'; } } }); } function processForKeys(json, _options) { (0, legacy_1.default)(json).forEach((node) => { if ((0, is_mitosis_node_1.isMitosisNode)(node)) { if (node.name === 'For') { const firstChild = node.children[0]; if (firstChild && firstChild.bindings.key) { node.bindings.key = firstChild.bindings.key; delete firstChild.bindings.key; } } } }); } /** * This plugin handle `onUpdate` code that watches dependencies. * We need to apply this workaround to be able to watch specific dependencies in Vue 2: https://stackoverflow.com/a/45853349 * * We add a `computed` property for the dependencies, and a matching `watch` function for the `onUpdate` code */ const onUpdatePlugin = (options) => ({ json: { post: (component) => { if (component.hooks.onUpdate) { component.hooks.onUpdate .filter((hook) => { var _a; return (_a = hook.deps) === null || _a === void 0 ? void 0 : _a.length; }) .forEach((hook, index) => { var _a; const code = `get ${(0, helpers_1.getOnUpdateHookName)(index)} () { return { ${(_a = hook.deps) === null || _a === void 0 ? void 0 : _a.slice(1, -1).split(',').map((dep, k) => { const val = dep.trim(); return `${k}: ${val}`; }).join(',')} } }`; component.state[(0, helpers_1.getOnUpdateHookName)(index)] = { code, type: 'getter', }; }); } }, }, }); const BASE_OPTIONS = { api: 'options', defineComponent: true, casing: 'pascal', }; const componentToVue = (userOptions) => ({ component: _component, path }) => { var _a, _b, _c, _d, _e, _f; // Make a copy we can safely mutate, similar to babel's toolchain can be used let component = (0, fast_clone_1.fastClone)(_component); const options = (0, merge_options_1.initializeOptions)({ target: 'vue', component, defaults: BASE_OPTIONS, userOptions: userOptions, }); if (options.api === 'composition') { options.asyncComponentImports = false; } options.plugins.unshift((0, on_event_1.processOnEventHooksPlugin)(), ...(options.api === 'options' ? [onUpdatePlugin] : []), ...(options.api === 'composition' ? [functions_1.FUNCTION_HACK_PLUGIN] : []), (0, process_code_1.CODE_PROCESSOR_PLUGIN)((codeType) => { if (options.api === 'composition') { switch (codeType) { case 'hooks': return (code) => (0, helpers_1.processBinding)({ code, options, json: component }); case 'state': return (code) => (0, helpers_1.processBinding)({ code, options, json: component }); case 'bindings': return (0, function_1.flow)( // Strip types from any JS code that ends up in the template, because Vue does not support TS code in templates. babel_transform_1.convertTypeScriptToJS, (code) => (0, helpers_1.processBinding)({ code, options, json: component, codeType })); case 'context-set': return (code) => (0, helpers_1.processBinding)({ code, options, json: component, preserveGetter: true }); case 'hooks-deps': return (0, replace_identifiers_1.replaceStateIdentifier)(null); case 'properties': case 'dynamic-jsx-elements': case 'hooks-deps-array': case 'types': return (c) => c; } } else { switch (codeType) { case 'hooks': return (code) => (0, helpers_1.processBinding)({ code, options, json: component }); case 'bindings': return (0, function_1.flow)( // Strip types from any JS code that ends up in the template, because Vue does not support TS code in templates. babel_transform_1.convertTypeScriptToJS, (code) => (0, helpers_1.processBinding)({ code, options, json: component, codeType })); case 'properties': case 'dynamic-jsx-elements': case 'hooks-deps': case 'hooks-deps-array': case 'types': return (c) => c; case 'state': return (c) => (0, helpers_1.processBinding)({ code: c, options, json: component }); case 'context-set': return (code) => (0, helpers_1.processBinding)({ code, options, json: component, thisPrefix: '_this', preserveGetter: true, }); } } })); (0, process_http_requests_1.processHttpRequests)(component); processDynamicComponents(component, options); processForKeys(component, options); component = (0, plugins_1.runPreJsonPlugins)({ json: component, plugins: options.plugins }); if (options.api === 'options') { (0, map_refs_1.mapRefs)(component, (refName) => `this.$refs.${refName}`); } // need to run this before we process the component's code const props = Array.from((0, get_props_1.getProps)(component)); const elementProps = props.filter((prop) => !(0, slots_1.isSlotProperty)(prop)); const slotsProps = props.filter((prop) => (0, slots_1.isSlotProperty)(prop)); component = (0, plugins_1.runPostJsonPlugins)({ json: component, plugins: options.plugins }); const css = (0, collect_css_1.collectCss)(component, { prefix: (_b = (_a = options.cssNamespace) === null || _a === void 0 ? void 0 : _a.call(options)) !== null && _b !== void 0 ? _b : undefined, }); // Apply the meta tagName to the component BEFORE we strip the meta properties (0, apply_meta_tagname_1.applyMetaTagName)(component); (0, strip_meta_properties_1.stripMetaProperties)(component); const templateStrBody = component.children .map((item) => (0, blocks_1.blockToVue)(item, options, { isRootNode: true })) .join('\n'); const template = options.casing === 'kebab' ? (0, helpers_1.renameMitosisComponentsToKebabCase)(templateStrBody) : templateStrBody; const onUpdateWithDeps = ((_c = component.hooks.onUpdate) === null || _c === void 0 ? void 0 : _c.filter((hook) => { var _a; return (_a = hook.deps) === null || _a === void 0 ? void 0 : _a.length; })) || []; const onUpdateWithoutDeps = ((_d = component.hooks.onUpdate) === null || _d === void 0 ? void 0 : _d.filter((hook) => { var _a; return !((_a = hook.deps) === null || _a === void 0 ? void 0 : _a.length); })) || []; const getterKeys = Object.keys((0, lodash_1.pickBy)(component.state, (i) => (i === null || i === void 0 ? void 0 : i.type) === 'getter')); // import from vue let vueImports = []; if (options.asyncComponentImports) { vueImports.push('defineAsyncComponent'); } if (options.api === 'options' && options.defineComponent) { vueImports.push('defineComponent'); } if (options.api === 'composition') { onUpdateWithDeps.length && vueImports.push('watch'); component.hooks.onMount.length && vueImports.push('onMounted'); ((_e = component.hooks.onUnMount) === null || _e === void 0 ? void 0 : _e.code) && vueImports.push('onUnmounted'); onUpdateWithoutDeps.length && vueImports.push('onUpdated'); (0, lodash_1.size)(getterKeys) && vueImports.push('computed'); (0, lodash_1.size)(component.context.set) && vueImports.push('provide'); (0, lodash_1.size)(component.context.get) && vueImports.push('inject'); (0, lodash_1.size)(Object.keys(component.state).filter((key) => { var _a; return ((_a = component.state[key]) === null || _a === void 0 ? void 0 : _a.type) === 'property'; })) + (0, lodash_1.size)(component.refs) && vueImports.push('ref'); (0, lodash_1.size)(slotsProps) && vueImports.push('useSlots'); } const tsLangAttribute = options.typescript ? `lang='ts'` : ''; let str = (0, dedent_1.dedent) ` ${template.trim().length > 0 ? `<template> ${template} </template>` : ''} <script ${options.api === 'composition' ? 'setup' : ''} ${tsLangAttribute}> ${vueImports.length ? `import { ${(0, lodash_1.uniq)(vueImports).sort().join(', ')} } from "vue"` : ''} ${(0, render_imports_1.renderPreComponent)({ explicitImportFileExtension: options.explicitImportFileExtension, component, target: 'vue', asyncComponentImports: options.asyncComponentImports, })} ${(options.typescript && ((_f = component.types) === null || _f === void 0 ? void 0 : _f.join('\n'))) || ''} ${options.api === 'composition' ? (0, compositionApi_1.generateCompositionApiScript)(component, options, template, elementProps, onUpdateWithDeps, onUpdateWithoutDeps) : (0, optionsApi_1.generateOptionsApiScript)(component, options, path, template, elementProps, onUpdateWithDeps, onUpdateWithoutDeps)} </script> ${!css.trim().length ? '' : `<style scoped> ${css} </style>`} `; str = (0, plugins_1.runPreCodePlugins)({ json: component, code: str, plugins: options.plugins, options: { json: component }, }); if (true || options.prettier !== false) { try { str = (0, standalone_1.format)(str, { parser: 'vue', plugins: [ // To support running in browsers require('prettier/parser-typescript'), require('prettier/parser-html'), require('prettier/parser-postcss'), require('prettier/parser-babel'), ], }); } catch (err) { console.warn('Could not prettify', { string: str }, err); } } str = (0, plugins_1.runPostCodePlugins)({ json: component, code: str, plugins: options.plugins }); for (const pattern of removePatterns) { str = str.replace(pattern, '').trim(); } str = str.replace(/<script(.*)>\n?<\/script>/g, '').trim(); return str; }; exports.componentToVue = componentToVue; // Remove unused artifacts like empty script or style tags const removePatterns = [ `<script> export default {}; </script>`, `<style> </style>`, ];