@builder.io/mitosis
Version:
Write components once, run everywhere. Compiles to Vue, React, Solid, and Liquid. Import code from Figma and Builder.io
564 lines (563 loc) • 24.5 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.compileAwayBuilderComponents = exports.compileAwayBuilderComponentsFromTree = exports.components = void 0;
const get_tag_name_1 = require("../helpers/get-tag-name");
const sdk_1 = require("@builder.io/sdk");
const json5_1 = __importDefault(require("json5"));
const lodash_1 = require("lodash");
const legacy_1 = __importDefault(require("neotraverse/legacy"));
const bindings_1 = require("../helpers/bindings");
const create_mitosis_node_1 = require("../helpers/create-mitosis-node");
const filter_empty_text_nodes_1 = require("../helpers/filter-empty-text-nodes");
const is_mitosis_node_1 = require("../helpers/is-mitosis-node");
const builder_1 = require("../parsers/builder");
const getCssFromNode = (node) => {
var _a;
const css = (_a = node.bindings.css) === null || _a === void 0 ? void 0 : _a.code;
if (css) {
return json5_1.default.parse(css);
}
return {};
};
function getComponentInputNames(componentName) {
var _a;
const componentInfo = sdk_1.Builder.components.find((item) => item.name === componentName);
return ((_a = componentInfo === null || componentInfo === void 0 ? void 0 : componentInfo.inputs) === null || _a === void 0 ? void 0 : _a.map((item) => item.name)) || [];
}
const wrapOutput = (node, child, components) => {
const inputNames = getComponentInputNames(node.name);
(0, exports.compileAwayBuilderComponentsFromTree)(child, components);
return (0, create_mitosis_node_1.createMitosisNode)({
...node,
properties: {
...(0, lodash_1.omit)(node.properties, ...inputNames),
},
bindings: {
...(0, lodash_1.omit)(node.bindings, ...inputNames),
},
// TODO: forward tagName as a $tagName="..."
name: node.properties._tagName || (0, get_tag_name_1.getBuilderTagName)(node) || 'div',
children: Array.isArray(child) ? child : [child],
});
};
exports.components = {
CoreButton(node, context, components) {
const properties = {};
const bindings = {};
if (!node.properties.href && node.bindings.css) {
const css = json5_1.default.parse(node.bindings.css.code);
// When using button tag ensure we have all: unset and
// be sure that is the first style in the list
node.bindings.css.code = json5_1.default.stringify({
all: 'unset',
...css,
});
}
if ('link' in node.properties) {
properties.href = node.properties.link;
}
if ('link' in node.bindings) {
bindings.href = node.properties.link;
}
if ('text' in node.properties) {
node.children = [
(0, create_mitosis_node_1.createMitosisNode)({
properties: {
_text: node.properties.text,
},
}),
];
}
if ('text' in node.bindings) {
node.children = [
(0, create_mitosis_node_1.createMitosisNode)({
bindings: {
_text: node.bindings.text,
},
}),
];
}
if ('openInNewTab' in node.bindings) {
bindings.target = `${node.bindings.openInNewTab} ? '_blank' : '_self'`;
}
const omitFields = ['link', 'openInNewTab', 'text'];
const hasLink = node.properties.link || node.bindings.link;
return (0, create_mitosis_node_1.createMitosisNode)({
...node,
// TODO: use 'button' tag for no link, and add `all: unset` to CSS string only then
name: hasLink ? 'a' : 'button',
properties: {
...(0, lodash_1.omit)(node.properties, omitFields),
...properties,
},
bindings: {
...(0, lodash_1.omit)(node.bindings, omitFields),
...bindings,
},
});
},
Embed(node, context, components) {
return wrapOutput(node, (0, create_mitosis_node_1.createMitosisNode)({
name: node.properties.builderTag || 'div',
properties: {
innerHTML: node.properties.content || '',
},
}), components);
},
BuilderAccordion(node, context, components) {
var _a;
const itemsJSON = ((_a = node.bindings.items) === null || _a === void 0 ? void 0 : _a.code) || '[]';
const accordionItems = json5_1.default.parse(itemsJSON);
const children = accordionItems.map((accordionItem) => {
const titleChildren = accordionItem.title.map((element) => (0, builder_1.builderElementToMitosisNode)(element, {
includeBuilderExtras: true,
preserveTextBlocks: true,
}));
const detailChildren = accordionItem.detail.map((element) => (0, builder_1.builderElementToMitosisNode)(element, {
includeBuilderExtras: true,
preserveTextBlocks: true,
}));
return (0, create_mitosis_node_1.createMitosisNode)({
name: 'div',
properties: { builder: 'accordion' },
children: [
(0, create_mitosis_node_1.createMitosisNode)({
name: 'div',
properties: { builder: 'accordion-title' },
children: titleChildren,
}),
(0, create_mitosis_node_1.createMitosisNode)({
name: 'div',
properties: { builder: 'accordion-detail' },
children: detailChildren,
}),
],
});
});
return wrapOutput(node, (0, create_mitosis_node_1.createMitosisNode)({
name: node.properties.builderTag || 'div',
properties: {
$name: 'accordion',
},
children: children,
}), components);
},
BuilderMasonry() {
// TODO
return (0, create_mitosis_node_1.createMitosisNode)({
name: 'div',
properties: { 'data-missing-component': 'BuilderMasonry' },
});
},
BuilderTabs() {
// TODO
return (0, create_mitosis_node_1.createMitosisNode)({
name: 'div',
properties: { 'data-missing-component': 'BuilderTabs' },
});
},
BuilderCarousel() {
// TODO
return (0, create_mitosis_node_1.createMitosisNode)({
name: 'div',
properties: { 'data-missing-component': 'BuilderCarousel' },
});
},
CustomCode(node, context, components) {
var _a;
const bindings = {};
if ((_a = node === null || node === void 0 ? void 0 : node.bindings) === null || _a === void 0 ? void 0 : _a.code) {
bindings.innerHTML = node.bindings.code;
}
return wrapOutput({
...node,
properties: (0, lodash_1.omit)(node.properties, 'code'),
}, (0, create_mitosis_node_1.createMitosisNode)({
name: node.properties.builderTag || 'div',
properties: {
innerHTML: node.properties.code || '',
},
bindings: bindings,
}), components);
},
CoreSection(node, context, components) {
var _a, _b;
const css = getCssFromNode(node);
return wrapOutput(node, (0, create_mitosis_node_1.createMitosisNode)({
name: 'section',
properties: {
...node.properties,
$name: 'section',
...(((_a = node.bindings.lazyLoad) === null || _a === void 0 ? void 0 : _a.code) === 'true' && {
lazyLoad: 'true',
}),
},
bindings: {
css: (0, bindings_1.createSingleBinding)({
code: JSON.stringify({
...css,
width: '100%',
alignSelf: 'stretch',
flexGrow: '1',
boxSizing: 'border-box',
maxWidth: `${(((_b = node.bindings.maxWidth) === null || _b === void 0 ? void 0 : _b.code) && Number(node.bindings.maxWidth.code)) || 1200}px`,
display: 'flex',
flexDirection: 'column',
alignItems: 'stretch',
marginLeft: 'auto',
marginRight: 'auto',
}),
}),
},
children: node.children,
}), components);
},
Columns(node, context, components) {
const columns = node.children.filter(filter_empty_text_nodes_1.filterEmptyTextNodes).map((item) => {
var _a;
return ({
width: parseFloat(item.properties.width || ((_a = item.bindings.width) === null || _a === void 0 ? void 0 : _a.code) || '0') || 0,
children: item.children,
});
});
const gutterSize = (node.properties.getterSize && parseFloat(node.properties.getterSize)) || 20;
function getWidth(index) {
return (columns[index] && columns[index].width) || 100 / columns.length;
}
function getColumnWidth(index) {
return `${Math.round(getWidth(index))}%`;
}
const { properties } = node;
return wrapOutput(node, (0, create_mitosis_node_1.createMitosisNode)({
name: 'div',
bindings: {
css: (0, bindings_1.createSingleBinding)({
code: JSON.stringify({
gap: `${gutterSize}px`,
display: 'flex',
...(properties.stackColumnsAt === 'never'
? {}
: {
[`@media (max-width: ${properties.stackColumnsAt === 'mobile' ? 640 : 991}px)`]: {
flexDirection: properties.reverseColumnsWhenStacked === 'true'
? 'column-reverse'
: 'column',
alignItems: 'stretch',
gap: `0px`,
},
}),
}),
}),
},
children: columns.map((col, index) => {
return (0, create_mitosis_node_1.createMitosisNode)({
name: 'div',
properties: {
$name: 'column',
},
bindings: {
css: (0, bindings_1.createSingleBinding)({
code: JSON.stringify({
display: 'flex',
flexDirection: 'column',
alignItems: 'stretch',
lineHeight: 'normal',
width: `${getColumnWidth(index)}`,
marginLeft: `${index === 0 ? 0 : gutterSize}px`,
...(properties.stackColumnsAt === 'never'
? {}
: {
[`@media (max-width: ${properties.stackColumnsAt === 'mobile' ? 640 : 991}px)`]: {
width: '100%',
marginLeft: 0,
},
}),
}),
}),
},
children: col.children,
});
}),
}), components);
},
Image(node, context, components) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
const { backgroundSize, backgroundPosition } = node.properties;
const { srcset } = node.properties;
let aspectRatio = ((_a = node.bindings.aspectRatio) === null || _a === void 0 ? void 0 : _a.code)
? parseFloat(node.bindings.aspectRatio.code)
: null;
if (typeof aspectRatio === 'number' && isNaN(aspectRatio)) {
aspectRatio = null;
}
const image = node.properties.image;
const srcSet = srcset || generateBuilderIoSrcSet(image);
const css = getCssFromNode(node);
const noWebp = ((_b = node.bindings.noWebp) === null || _b === void 0 ? void 0 : _b.code) === 'true';
const img = (0, create_mitosis_node_1.createMitosisNode)({
name: 'img',
properties: noUndefined({
loading: 'lazy',
sizes: node.properties.sizes,
alt: node.properties.altText,
// We set noWebp to true for SVGs. in this case, we
// also don't need srcset, just a src is better
...(noWebp
? {
src: image,
}
: {
srcSet: srcSet || null,
}),
}),
bindings: noUndefined({
src: ((_c = node.bindings.image) === null || _c === void 0 ? void 0 : _c.code) && { code: (_d = node.bindings.image) === null || _d === void 0 ? void 0 : _d.code },
sizes: ((_e = node.bindings.sizes) === null || _e === void 0 ? void 0 : _e.code) && { code: (_f = node.bindings.sizes) === null || _f === void 0 ? void 0 : _f.code },
style: ((_g = node.bindings.style) === null || _g === void 0 ? void 0 : _g.code) && { code: (_h = node.bindings.style) === null || _h === void 0 ? void 0 : _h.code },
css: (0, bindings_1.createSingleBinding)({
code: JSON.stringify({
aspectRatio: aspectRatio ? String((0, lodash_1.round)(1 / aspectRatio, 2)) : undefined,
objectFit: backgroundSize || 'cover',
objectPosition: backgroundPosition || 'center',
width: '100%',
...css,
display: undefined,
flexDirection: undefined,
position: css.position === 'relative' ? undefined : css.position,
}),
}),
}),
});
if (!((_j = node.children) === null || _j === void 0 ? void 0 : _j.length)) {
return img;
}
// TODO: deal with links: anchor tag and href
const root = (0, create_mitosis_node_1.createMitosisNode)({
name: 'div',
bindings: noUndefined({
css: (0, bindings_1.createSingleBinding)({
code: JSON.stringify({
display: 'flex',
flexDirection: 'column',
...css,
position: 'relative',
}),
}),
}),
children: [
{
...img,
bindings: {
...img.bindings,
css: (0, bindings_1.createSingleBinding)({
code: JSON.stringify({
position: 'absolute',
inset: '0',
height: '100%',
width: '100%',
objectFit: backgroundSize || 'cover',
objectPosition: backgroundPosition || 'center',
}),
}),
},
},
...node.children.map((child) => {
const newChild = {
...child,
bindings: {
...child.bindings,
css: (0, bindings_1.createSingleBinding)({
code: JSON.stringify({
position: 'relative',
...getCssFromNode(child),
}),
}),
},
};
(0, exports.compileAwayBuilderComponentsFromTree)(newChild, components);
return newChild;
}),
],
});
return root;
},
Video(node, context, components) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
let aspectRatio = ((_a = node.bindings.aspectRatio) === null || _a === void 0 ? void 0 : _a.code)
? parseFloat(node.bindings.aspectRatio.code)
: null;
if (typeof aspectRatio === 'number' && isNaN(aspectRatio)) {
aspectRatio = null;
}
const videoContainerNodes = [];
const css = getCssFromNode(node);
videoContainerNodes.push((0, create_mitosis_node_1.createMitosisNode)({
name: 'video',
properties: noUndefined({
poster: node.properties.posterImage,
autoplay: node.properties.autoPlay,
muted: node.properties.muted,
controls: node.properties.controls,
loop: node.properties.loop,
playsinline: node.properties.playsInline,
preload: node.properties.lazy ? 'none' : undefined,
}),
bindings: noUndefined({
poster: ((_b = node.bindings.posterImage) === null || _b === void 0 ? void 0 : _b.code) && {
code: (_c = node.bindings.posterImage) === null || _c === void 0 ? void 0 : _c.code,
},
autoplay: ((_d = node.bindings.autoPlay) === null || _d === void 0 ? void 0 : _d.code) && {
code: (_e = node.bindings.autoPlay) === null || _e === void 0 ? void 0 : _e.code,
},
muted: ((_f = node.bindings.muted) === null || _f === void 0 ? void 0 : _f.code) && {
code: (_g = node.bindings.muted) === null || _g === void 0 ? void 0 : _g.code,
},
controls: ((_h = node.bindings.controls) === null || _h === void 0 ? void 0 : _h.code) && {
code: (_j = node.bindings.controls) === null || _j === void 0 ? void 0 : _j.code,
},
playsinline: ((_k = node.bindings.playsInline) === null || _k === void 0 ? void 0 : _k.code) && {
code: (_l = node.bindings.playsInline) === null || _l === void 0 ? void 0 : _l.code,
},
loop: ((_m = node.bindings.loop) === null || _m === void 0 ? void 0 : _m.code) && { code: (_o = node.bindings.loop) === null || _o === void 0 ? void 0 : _o.code },
css: (0, bindings_1.createSingleBinding)({
code: JSON.stringify({
width: '100%',
height: '100%',
objectFit: node.properties.fit,
objectPosition: node.properties.position,
borderRadius: '1',
position: aspectRatio ? 'absolute' : '',
...css,
}),
}),
}),
children: [
(0, create_mitosis_node_1.createMitosisNode)({
name: 'source',
properties: {
type: 'video/mp4',
src: node.properties.video,
},
bindings: noUndefined({
src: ((_p = node.bindings.video) === null || _p === void 0 ? void 0 : _p.code) && {
code: (_q = node.bindings.video) === null || _q === void 0 ? void 0 : _q.code,
},
}),
}),
],
}));
aspectRatio &&
videoContainerNodes.push((0, create_mitosis_node_1.createMitosisNode)({
name: 'div',
bindings: {
css: (0, bindings_1.createSingleBinding)({
code: JSON.stringify({
width: '100%',
paddingTop: aspectRatio * 100 + '%',
pointerEvents: 'none',
fontSize: '0',
}),
}),
},
}));
node.children &&
node.children.length &&
videoContainerNodes.push((0, create_mitosis_node_1.createMitosisNode)({
name: 'div',
bindings: {
css: (0, bindings_1.createSingleBinding)({
code: JSON.stringify({
display: 'flex',
flexDirection: 'column',
alignItems: 'stretch',
position: 'absolute',
top: '0',
left: '0',
width: '100%',
height: '100%',
}),
}),
},
children: node.children,
}));
const videoContainer = (0, create_mitosis_node_1.createMitosisNode)({
name: 'div',
bindings: {
css: (0, bindings_1.createSingleBinding)({ code: JSON.stringify({ position: 'relative' }) }),
},
children: videoContainerNodes,
});
return wrapOutput(node, videoContainer, components);
},
};
const compileAwayBuilderComponentsFromTree = (tree, components) => {
(0, legacy_1.default)(tree).forEach(function (item) {
if ((0, is_mitosis_node_1.isMitosisNode)(item)) {
const mapper = components[item.name];
if (mapper) {
const result = mapper(item, this, components);
if (result) {
this.update(result);
}
}
}
});
};
exports.compileAwayBuilderComponentsFromTree = compileAwayBuilderComponentsFromTree;
const compileAwayBuilderComponents = (pluginOptions = {}) => {
let obj = exports.components;
if (pluginOptions.omit) {
obj = (0, lodash_1.omit)(obj, pluginOptions.omit);
}
if (pluginOptions.only) {
obj = (0, lodash_1.pick)(obj, pluginOptions.only);
}
return (options) => ({
json: {
pre: (json) => {
(0, exports.compileAwayBuilderComponentsFromTree)(json, obj);
},
},
});
};
exports.compileAwayBuilderComponents = compileAwayBuilderComponents;
function updateQueryParam(uri = '', key, value) {
const re = new RegExp('([?&])' + key + '=.*?(&|$)', 'i');
const separator = uri.indexOf('?') !== -1 ? '&' : '?';
if (uri.match(re)) {
return uri.replace(re, '$1' + key + '=' + encodeURIComponent(value) + '$2');
}
return uri + separator + key + '=' + encodeURIComponent(value);
}
function generateBuilderIoSrcSet(image) {
const isBuilderIo = !!(image || '').match(/builder\.io/);
return isBuilderIo
? [100, 200, 400, 800, 1200, 1600, 2000]
.map((size) => `${updateQueryParam(image, 'width', String(size))} ${size}w`)
.concat([image])
.join(', ')
: '';
}
function noUndefined(obj) {
const cleanObj = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const value = obj[key];
if (value != null) {
if (typeof value == 'object') {
const ret = noUndefined(value);
if (Object.keys(ret).length) {
cleanObj[key] = ret;
}
}
else {
cleanObj[key] = value;
}
}
}
}
return cleanObj;
}
;