@builder.io/mitosis
Version:
Write components once, run everywhere. Compiles to Vue, React, Solid, and Liquid. Import code from Figma and Builder.io
162 lines (161 loc) • 7.02 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.componentToLiquid = exports.isValidLiquidBinding = void 0;
const event_handlers_1 = require("../../helpers/event-handlers");
const standalone_1 = require("prettier/standalone");
const html_tags_1 = require("../../constants/html_tags");
const fast_clone_1 = require("../../helpers/fast-clone");
const get_state_object_string_1 = require("../../helpers/get-state-object-string");
const strip_meta_properties_1 = require("../../helpers/strip-meta-properties");
const strip_state_and_props_refs_1 = require("../../helpers/strip-state-and-props-refs");
const collect_css_1 = require("../../helpers/styles/collect-css");
const plugins_1 = require("../../modules/plugins");
const mitosis_node_1 = require("../../types/mitosis-node");
/**
* Test if the binding expression would be likely to generate
* valid or invalid liquid. If we generate invalid liquid tags
* Shopify will reject our PUT to update the template
*/
const isValidLiquidBinding = (str = '') => {
const strictMatches = Boolean(
// Test for our `context.shopify.liquid.*(expression), which
// we regex out later to transform back into valid liquid expressions
str.match(/(context|ctx)\s*(\.shopify\s*)?\.liquid\s*\./));
return (strictMatches ||
// Test is the expression is simple and would map to Shopify bindings // Test for our `context.shopify.liquid.*(expression), which
// e.g. `state.product.price` -> `{{product.price}} // we regex out later to transform back into valid liquid expressions
Boolean(str.match(/^[a-z0-9_\.\s]+$/i)));
};
exports.isValidLiquidBinding = isValidLiquidBinding;
const mappers = {
Fragment: (json, options) => {
return `<div>${json.children.map((item) => blockToLiquid(item, options)).join('\n')}</div>`;
},
};
// TODO: spread support
const blockToLiquid = (json, options = {}) => {
var _a, _b, _c, _d, _e, _f;
if (mappers[json.name]) {
return mappers[json.name](json, options);
}
// TODO: Add support for `{props.children}` bindings
if (json.properties._text) {
return json.properties._text;
}
if ((_a = json.bindings._text) === null || _a === void 0 ? void 0 : _a.code) {
if (!(0, exports.isValidLiquidBinding)(json.bindings._text.code)) {
return '';
}
return `{{${(0, strip_state_and_props_refs_1.stripStateAndPropsRefs)(json.bindings._text.code)}}}`;
}
let str = '';
if ((0, mitosis_node_1.checkIsForNode)(json)) {
if (!((0, exports.isValidLiquidBinding)((_b = json.bindings.each) === null || _b === void 0 ? void 0 : _b.code) && (0, exports.isValidLiquidBinding)(json.scope.forName))) {
return str;
}
str += `{% for ${json.scope.forName} in ${(0, strip_state_and_props_refs_1.stripStateAndPropsRefs)((_c = json.bindings.each) === null || _c === void 0 ? void 0 : _c.code)} %}`;
if (json.children) {
str += json.children.map((item) => blockToLiquid(item, options)).join('\n');
}
str += '{% endfor %}';
}
else if (json.name === 'Show') {
if (!(0, exports.isValidLiquidBinding)((_d = json.bindings.when) === null || _d === void 0 ? void 0 : _d.code)) {
return str;
}
str += `{% if ${(0, strip_state_and_props_refs_1.stripStateAndPropsRefs)((_e = json.bindings.when) === null || _e === void 0 ? void 0 : _e.code)} %}`;
if (json.children) {
str += json.children.map((item) => blockToLiquid(item, options)).join('\n');
}
str += '{% endif %}';
}
else {
str += `<${json.name} `;
if (((_f = json.bindings._spread) === null || _f === void 0 ? void 0 : _f.code) === '_spread' &&
(0, exports.isValidLiquidBinding)(json.bindings._spread.code)) {
str += `
{% for _attr in ${json.bindings._spread.code} %}
{{ _attr[0] }}="{{ _attr[1] }}"
{% endfor %}
`;
}
for (const key in json.properties) {
const value = json.properties[key];
str += ` ${key}="${value}" `;
}
for (const key in json.bindings) {
if (key === '_spread' || key === 'ref' || key === 'css') {
continue;
}
const { code: value } = json.bindings[key];
// TODO: proper babel transform to replace. Util for this
const useValue = (0, strip_state_and_props_refs_1.stripStateAndPropsRefs)(value);
if ((0, event_handlers_1.checkIsEvent)(key)) {
// Do nothing
}
else if ((0, exports.isValidLiquidBinding)(useValue)) {
str += ` ${key}="{{${useValue}}}" `;
}
}
if (html_tags_1.SELF_CLOSING_HTML_TAGS.has(json.name)) {
return str + ' />';
}
str += '>';
if (json.children) {
str += json.children.map((item) => blockToLiquid(item, options)).join('\n');
}
str += `</${json.name}>`;
}
return str;
};
// TODO: add JS support similar to componentToHtml()
const componentToLiquid = (options = {}) => ({ component }) => {
let json = (0, fast_clone_1.fastClone)(component);
if (options.plugins) {
json = (0, plugins_1.runPreJsonPlugins)({ json, plugins: options.plugins });
}
const css = (0, collect_css_1.collectCss)(json);
(0, strip_meta_properties_1.stripMetaProperties)(json);
if (options.plugins) {
json = (0, plugins_1.runPostJsonPlugins)({ json, plugins: options.plugins });
}
let str = json.children.map((item) => blockToLiquid(item)).join('\n');
if (css.trim().length) {
str += `<style>${css}</style>`;
}
if (options.reactive) {
const stateObjectString = (0, get_state_object_string_1.getStateObjectStringFromComponent)(json);
if (stateObjectString.trim().length > 4) {
str += `<script reactive>
export default {
state: ${stateObjectString}
}
</script>`;
}
}
if (options.plugins) {
str = (0, plugins_1.runPreCodePlugins)({ json, code: str, plugins: options.plugins });
}
if (options.prettier !== false) {
try {
str = (0, standalone_1.format)(str, {
parser: 'html',
htmlWhitespaceSensitivity: 'ignore',
plugins: [
// To support running in browsers
require('prettier/parser-html'),
require('prettier/parser-postcss'),
require('prettier/parser-babel'),
],
});
}
catch (err) {
console.warn('Could not prettify', { string: str }, err);
}
}
if (options.plugins) {
str = (0, plugins_1.runPostCodePlugins)({ json, code: str, plugins: options.plugins });
}
return str;
};
exports.componentToLiquid = componentToLiquid;
;