@builder.io/mitosis
Version:
Write components once, run everywhere. Compiles to Vue, React, Solid, and Liquid. Import code from Figma and Builder.io
281 lines (279 loc) • 11.7 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.blockToSvelte = void 0;
const bindings_1 = require("../../helpers/bindings");
const event_handlers_1 = require("../../helpers/event-handlers");
const is_children_1 = __importDefault(require("../../helpers/is-children"));
const is_upper_case_1 = require("../../helpers/is-upper-case");
const for_1 = require("../../helpers/nodes/for");
const remove_surrounding_block_1 = require("../../helpers/remove-surrounding-block");
const slots_1 = require("../../helpers/slots");
const html_tags_1 = require("../../constants/html_tags");
const helpers_1 = require("./helpers");
/**
* blockToSvelte executed after stripStateAndProps,
* when stripStateAndProps is executed,
* SLOT_PREFIX from `slot` change to `$$slots.`
*/
const SLOT_PREFIX = '$$slots.';
const mappers = {
style: ({ json, options, parentComponent }) => {
var _a;
let props = '';
for (const key in json.properties) {
const value = json.properties[key];
props += ` ${key}="${value}" `;
}
let bindings = '';
for (const key in json.bindings) {
const value = json.bindings[key];
if (value && key !== 'innerHTML') {
bindings += ` ${key}=\${${value.code}} `;
}
}
const innerText = ((_a = json.bindings.innerHTML) === null || _a === void 0 ? void 0 : _a.code) || '';
// We have to obfuscate `"style"` due to a limitation in the svelte-preprocessor plugin.
// https://github.com/sveltejs/vite-plugin-svelte/issues/315#issuecomment-1109000027
return `{@html \`<\${'style'} ${bindings} ${props}>\${${innerText}}<\${'/style'}>\`}`;
},
script: ({ json, options, parentComponent }) => {
var _a;
let props = '';
for (const key in json.properties) {
const value = json.properties[key];
props += ` ${key}="${value}" `;
}
let bindings = '';
for (const key in json.bindings) {
const value = json.bindings[key];
if (value && key !== 'innerHTML') {
bindings += ` ${key}=\${${value.code}} `;
}
}
const innerText = ((_a = json.bindings.innerHTML) === null || _a === void 0 ? void 0 : _a.code) || '';
return `{@html \`<script ${bindings} ${props}>\${${innerText}}</script>\`}`;
},
Fragment: ({ json, options, parentComponent }) => {
var _a;
if ((_a = json.bindings.innerHTML) === null || _a === void 0 ? void 0 : _a.code) {
return BINDINGS_MAPPER.innerHTML(json, options);
}
else if (json.children.length > 0) {
return `${json.children
.map((item) => (0, exports.blockToSvelte)({ json: item, options, parentComponent }))
.join('\n')}`;
}
else {
return '';
}
},
For: ({ json, options, parentComponent }) => {
var _a, _b;
const firstChild = json.children[0];
const keyValue = firstChild.properties.key || ((_a = firstChild.bindings.key) === null || _a === void 0 ? void 0 : _a.code);
if (keyValue) {
// we remove extraneous prop which Svelte does not use
delete firstChild.properties.key;
delete firstChild.bindings.key;
}
const args = (0, for_1.getForArguments)(json, { excludeCollectionName: true }).join(', ');
return `
{#each ${(_b = json.bindings.each) === null || _b === void 0 ? void 0 : _b.code} as ${args} ${keyValue ? `(${keyValue})` : ''}}
${json.children.map((item) => (0, exports.blockToSvelte)({ json: item, options, parentComponent })).join('\n')}
{/each}
`;
},
Show: ({ json, options, parentComponent }) => {
var _a;
return `
{#if ${(_a = json.bindings.when) === null || _a === void 0 ? void 0 : _a.code} }
${json.children.map((item) => (0, exports.blockToSvelte)({ json: item, options, parentComponent })).join('\n')}
${json.meta.else
? `
{:else}
${(0, exports.blockToSvelte)({
json: json.meta.else,
options,
parentComponent,
})}
`
: ''}
{/if}`;
},
Slot({ json, options, parentComponent }) {
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.blockToSvelte)({ json: item, options, parentComponent })).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 `
<span #${key}>
${(_c = json.bindings[key]) === null || _c === void 0 ? void 0 : _c.code}
</span>
`;
}
return `<slot name="${(0, slots_1.toKebabSlot)(slotName, SLOT_PREFIX)}">${renderChildren()}</slot>`;
},
};
const BINDINGS_MAPPER = {
innerHTML: (json, options) => { var _a; return `{@html ${(_a = json.bindings.innerHTML) === null || _a === void 0 ? void 0 : _a.code}}`; },
};
const SVELTE_SPECIAL_TAGS = {
COMPONENT: 'svelte:component',
ELEMENT: 'svelte:element',
SELF: 'svelte:self',
};
const getTagName = ({ json, parentComponent, options, }) => {
if (parentComponent && json.name === parentComponent.name) {
return SVELTE_SPECIAL_TAGS.SELF;
}
/**
* These are special HTML tags that svelte requires `<svelte:element this={TAG}>`
*/
const SPECIAL_HTML_TAGS = ['script', 'template'];
if (SPECIAL_HTML_TAGS.includes(json.name)) {
json.bindings.this = (0, bindings_1.createSingleBinding)({
code: `"${json.name}"`,
});
return SVELTE_SPECIAL_TAGS.ELEMENT;
}
const isValidHtmlTag = html_tags_1.VALID_HTML_TAGS.includes(json.name);
const isSpecialSvelteTag = json.name.startsWith('svelte:');
// Check if any import matches `json.name`
const hasMatchingImport = parentComponent.imports.some(({ imports }) => Object.keys(imports).some((name) => name === json.name));
// If none of these are true, then we have some type of dynamic tag name
if (!isValidHtmlTag && !isSpecialSvelteTag && !hasMatchingImport) {
json.bindings.this = (0, bindings_1.createSingleBinding)({
code: (0, helpers_1.stripStateAndProps)({ json: parentComponent, options })(json.name),
});
// TO-DO: no way to perfectly decide between <svelte:component> and <svelte:element> for dynamic
// values...need to do that through metadata overrides for now.
return SVELTE_SPECIAL_TAGS.COMPONENT;
}
return json.name;
};
const stringifyBinding = (node, options) => ([key, binding]) => {
if (key === 'innerHTML' || !binding) {
return '';
}
const { code, arguments: cusArgs = ['event'], type } = binding;
const isValidHtmlTag = html_tags_1.VALID_HTML_TAGS.includes(node.name) || node.name === 'svelte:element';
if (type === 'spread') {
const spreadValue = key === 'props' ? '$$props' : code;
return ` {...${spreadValue}} `;
}
else if ((0, event_handlers_1.checkIsEvent)(key) && isValidHtmlTag) {
const { async } = binding;
// handle html native on[event] props
const event = key.replace('on', '').toLowerCase();
// TODO: handle quotes in event handler values
const valueWithoutBlock = (0, remove_surrounding_block_1.removeSurroundingBlock)(code);
if (valueWithoutBlock === key && !async) {
return ` on:${event}={${valueWithoutBlock}} `;
}
else {
const asyncKeyword = async ? 'async ' : '';
return ` on:${event}="{${asyncKeyword}(${cusArgs.join(',')}) => {${valueWithoutBlock}}}" `;
}
}
else if ((0, event_handlers_1.checkIsEvent)(key)) {
// handle on[custom event] props
const valueWithoutBlock = (0, remove_surrounding_block_1.removeSurroundingBlock)(code);
if (valueWithoutBlock === key) {
return ` ${key}={${valueWithoutBlock}} `;
}
else {
return ` ${key}={(${cusArgs.join(',')}) => ${valueWithoutBlock}}`;
}
}
else if (key === 'ref') {
return ` bind:this={${code}} `;
}
else {
return ` ${key}={${code}} `;
}
};
const blockToSvelte = ({ json, options, parentComponent }) => {
var _a, _b, _c, _d;
// Handling key binding by wrapping the element in a #key block
if (json.bindings.key) {
const keyCode = json.bindings.key.code;
delete json.bindings.key;
const str = `
{#key ${keyCode}}
${(0, exports.blockToSvelte)({ json, options, parentComponent })}
{/key}
`;
return str;
}
if (mappers[json.name]) {
return mappers[json.name]({
json: json,
options,
parentComponent,
});
}
const tagName = getTagName({ json, parentComponent, options });
if ((0, is_children_1.default)({ node: json, extraMatches: ['$$slots.default'] })) {
return `<slot></slot>`;
}
if (json.properties._text) {
return json.properties._text;
}
const textCode = (_a = json.bindings._text) === null || _a === void 0 ? void 0 : _a.code;
if (textCode) {
if ((0, slots_1.isSlotProperty)(textCode, SLOT_PREFIX)) {
return `<slot name="${(0, slots_1.stripSlotPrefix)(textCode, SLOT_PREFIX).toLowerCase()}"/>`;
}
return `{${textCode}}`;
}
let str = '';
str += `<${tagName} `;
const isComponent = Boolean(tagName[0] && (0, is_upper_case_1.isUpperCase)(tagName[0]));
if ((((_b = json.bindings.style) === null || _b === void 0 ? void 0 : _b.code) || json.properties.style) && !isComponent) {
const useValue = ((_c = json.bindings.style) === null || _c === void 0 ? void 0 : _c.code) || json.properties.style;
str += `style={stringifyStyles(${useValue})}`;
delete json.bindings.style;
delete json.properties.style;
}
for (const key in json.properties) {
const value = json.properties[key];
str += ` ${key}="${value}" `;
}
const stringifiedBindings = Object.entries(json.bindings)
.map(stringifyBinding(json, options))
.join('');
str += stringifiedBindings;
// if we have innerHTML, it doesn't matter whether we have closing tags or not, or children or not.
// we use the innerHTML content as children and don't render the self-closing tag.
if ((_d = json.bindings.innerHTML) === null || _d === void 0 ? void 0 : _d.code) {
str += '>';
str += BINDINGS_MAPPER.innerHTML(json, options);
str += `</${tagName}>`;
return str;
}
if (html_tags_1.SELF_CLOSING_HTML_TAGS.has(tagName)) {
return str + ' />';
}
str += '>';
if (json.children) {
str += json.children
.map((item) => (0, exports.blockToSvelte)({ json: item, options, parentComponent }))
.join('');
}
str += `</${tagName}>`;
return str;
};
exports.blockToSvelte = blockToSvelte;
;