@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
JavaScript
"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;