UNPKG

@builder.io/mitosis

Version:

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

218 lines (217 loc) 9.82 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.blockToVue = void 0; const bindings_1 = require("../../helpers/bindings"); const event_handlers_1 = require("../../helpers/event-handlers"); const filter_empty_text_nodes_1 = require("../../helpers/filter-empty-text-nodes"); const is_children_1 = __importDefault(require("../../helpers/is-children")); const is_mitosis_node_1 = require("../../helpers/is-mitosis-node"); const nullable_1 = require("../../helpers/nullable"); const remove_surrounding_block_1 = require("../../helpers/remove-surrounding-block"); const replace_identifiers_1 = require("../../helpers/replace-identifiers"); const slots_1 = require("../../helpers/slots"); const function_1 = require("fp-ts/lib/function"); const html_tags_1 = require("../../constants/html_tags"); const helpers_1 = require("./helpers"); const SPECIAL_PROPERTIES = { V_IF: 'v-if', V_FOR: 'v-for', V_ELSE: 'v-else', V_ELSE_IF: 'v-else-if', V_ON: 'v-on', V_ON_AT: '@', V_BIND: 'v-bind', }; /** * blockToVue executed after processBinding, * when processBinding is executed, * SLOT_PREFIX from `slot` change to `$slots.` */ const SLOT_PREFIX = '$slots.'; // TODO: Maybe in the future allow defining `string | function` as values const BINDING_MAPPERS = { innerHTML: 'v-html', }; const NODE_MAPPERS = { Fragment(json, options, scope) { const children = json.children.filter(filter_empty_text_nodes_1.filterEmptyTextNodes); const childrenStr = children.map((item) => (0, exports.blockToVue)(item, options)).join('\n'); return childrenStr; }, For(_json, options) { var _a, _b, _c, _d; const json = _json; const keyValue = json.bindings.key || { code: (_a = json.scope.indexName) !== null && _a !== void 0 ? _a : 'index', type: 'single' }; const forValue = `(${(_b = json.scope.forName) !== null && _b !== void 0 ? _b : '_'}, ${(_c = json.scope.indexName) !== null && _c !== void 0 ? _c : 'index'}) in ${(_d = json.bindings.each) === null || _d === void 0 ? void 0 : _d.code}`; // TODO: tmk key goes on different element (parent vs child) based on Vue 2 vs Vue 3 return `<template :key="${(0, helpers_1.encodeQuotes)((keyValue === null || keyValue === void 0 ? void 0 : keyValue.code) || 'index')}" v-for="${(0, helpers_1.encodeQuotes)(forValue)}"> ${json.children.map((item) => (0, exports.blockToVue)(item, options)).join('\n')} </template>`; }, Show(json, options, scope) { var _a; const ifValue = ((_a = json.bindings.when) === null || _a === void 0 ? void 0 : _a.code) || ''; const defaultShowTemplate = ` <template ${SPECIAL_PROPERTIES.V_IF}="${(0, helpers_1.encodeQuotes)(ifValue)}"> ${json.children.map((item) => (0, exports.blockToVue)(item, options)).join('\n')} </template> ${(0, is_mitosis_node_1.isMitosisNode)(json.meta.else) ? ` <template ${SPECIAL_PROPERTIES.V_ELSE}> ${(0, exports.blockToVue)(json.meta.else, options)} </template>` : ''} `; return defaultShowTemplate; }, Slot(json, options) { var _a, _b, _c; const slotName = ((_a = json.bindings.name) === null || _a === void 0 ? void 0 : _a.code) || json.properties.name; const renderChildren = () => { var _a; return (_a = json.children) === null || _a === void 0 ? void 0 : _a.map((item) => (0, exports.blockToVue)(item, options)).join('\n'); }; if (!slotName) { const key = Object.keys(json.bindings).find(Boolean); if (!key) { if (!((_b = json.children) === null || _b === void 0 ? void 0 : _b.length)) { return '<slot/>'; } return `<slot>${renderChildren()}</slot>`; } return ` <template #${key}> ${(_c = json.bindings[key]) === null || _c === void 0 ? void 0 : _c.code} </template> `; } if (slotName === 'default') { return `<slot>${renderChildren()}</slot>`; } return `<slot name="${(0, slots_1.toKebabSlot)(slotName, SLOT_PREFIX)}">${renderChildren()}</slot>`; }, }; const SPECIAL_HTML_TAGS = ['style', 'script']; const stringifyBinding = (node, options) => ([key, value]) => { const isValidHtmlTag = html_tags_1.VALID_HTML_TAGS.includes(node.name); if (value.type === 'spread') { return ''; // we handle this after } else if (key === 'class' && options.convertClassStringToObject) { return `:class="_classStringToObject(${value === null || value === void 0 ? void 0 : value.code})"`; // TODO: support dynamic classes as objects somehow like Vue requires // https://vuejs.org/v2/guide/class-and-style.html } else { // TODO: proper babel transform to replace. Util for this const useValue = (value === null || value === void 0 ? void 0 : value.code) || ''; if ((0, event_handlers_1.checkIsEvent)(key) && isValidHtmlTag) { // handle html native on[event] props const { arguments: cusArgs = ['event'], async } = value; let event = key.replace('on', '').toLowerCase(); const isAssignmentExpression = useValue.includes('='); let eventHandlerValue; if (async) { eventHandlerValue = (0, function_1.pipe)((0, replace_identifiers_1.replaceIdentifiers)({ code: useValue, from: cusArgs[0], to: '$event', }), isAssignmentExpression ? function_1.identity : remove_surrounding_block_1.removeSurroundingBlock, remove_surrounding_block_1.removeSurroundingBlock, helpers_1.encodeQuotes); } else { eventHandlerValue = `async (${cusArgs.join(', ')}) => ${useValue}`; } const eventHandlerKey = `${SPECIAL_PROPERTIES.V_ON_AT}${event}`; return `${eventHandlerKey}="${eventHandlerValue}"`; } else if ((0, event_handlers_1.checkIsEvent)(key)) { // handle on[custom event] props const { arguments: cusArgs = ['event'] } = node.bindings[key]; return `:${key}="(${cusArgs.join(',')}) => ${(0, helpers_1.encodeQuotes)(useValue)}"`; } else if (key === 'ref') { return `ref="${(0, helpers_1.encodeQuotes)(useValue)}"`; } else if (BINDING_MAPPERS[key]) { return `${BINDING_MAPPERS[key]}="${(0, helpers_1.encodeQuotes)(useValue.replace(/"/g, "\\'"))}"`; } else { return `:${key}="${(0, helpers_1.encodeQuotes)(useValue)}"`; } } }; const stringifySpreads = ({ node, spreadType }) => { const spreads = Object.values(node.bindings) .filter(nullable_1.checkIsDefined) .filter((binding) => binding.type === 'spread' && binding.spreadType === spreadType) .map((value) => (value.code === 'props' ? '$props' : value.code)); if (spreads.length === 0) { return ''; } const stringifiedValue = spreads.length > 1 ? `{${spreads.map((spread) => `...${spread}`).join(', ')}}` : spreads[0]; const key = spreadType === 'normal' ? SPECIAL_PROPERTIES.V_BIND : SPECIAL_PROPERTIES.V_ON; return ` ${key}="${(0, helpers_1.encodeQuotes)(stringifiedValue)}" `; }; const getBlockBindings = (node, options) => { const stringifiedProperties = Object.entries(node.properties) .map(([key, value]) => { if (key === 'className') { return ''; } else if (key === SPECIAL_PROPERTIES.V_ELSE) { return `${key}`; } else if (typeof value === 'string') { return `${key}="${(0, helpers_1.encodeQuotes)(value)}"`; } }) .join(' '); const stringifiedBindings = Object.entries(node.bindings) .map(stringifyBinding(node, options)) .join(' '); return [ stringifiedProperties, stringifiedBindings, stringifySpreads({ node, spreadType: 'normal' }), stringifySpreads({ node, spreadType: 'event-handlers' }), ].join(' '); }; const blockToVue = (node, options, scope) => { var _a; const nodeMapper = NODE_MAPPERS[node.name]; if (nodeMapper) { return nodeMapper(node, options, scope); } if ((0, is_children_1.default)({ node })) { return `<slot/>`; } if (SPECIAL_HTML_TAGS.includes(node.name)) { // Vue doesn't allow style/script tags in templates, but does support them through dynamic components. node.bindings.is = (0, bindings_1.createSingleBinding)({ code: `'${node.name}'` }); node.name = 'component'; } if (node.properties._text) { return `${node.properties._text}`; } const textCode = (_a = node.bindings._text) === null || _a === void 0 ? void 0 : _a.code; if (textCode) { if ((0, slots_1.isSlotProperty)(textCode, SLOT_PREFIX)) { const slotName = (0, slots_1.stripSlotPrefix)(textCode, SLOT_PREFIX).toLowerCase(); if (slotName === 'default') return `<slot/>`; return `<slot name="${slotName}"/>`; } return `{{${textCode}}}`; } let str = `<${node.name} `; str += getBlockBindings(node, options); if (html_tags_1.SELF_CLOSING_HTML_TAGS.has(node.name)) { return str + ' />'; } str += '>'; if (node.children) { str += node.children.map((item) => (0, exports.blockToVue)(item, options)).join(''); } return str + `</${node.name}>`; }; exports.blockToVue = blockToVue;