UNPKG

@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
"use strict"; 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; }