@builder.io/mitosis
Version:
Write components once, run everywhere. Compiles to Vue, React, Solid, and Liquid. Import code from Figma and Builder.io
317 lines (312 loc) • 12.8 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.markoFormatHtml = exports.postprocessHtml = exports.preprocessHtml = exports.componentToMarko = void 0;
const html_tags_1 = require("../../constants/html_tags");
const dash_case_1 = require("../../helpers/dash-case");
const dedent_1 = require("../../helpers/dedent");
const event_handlers_1 = require("../../helpers/event-handlers");
const fast_clone_1 = require("../../helpers/fast-clone");
const filter_empty_text_nodes_1 = require("../../helpers/filter-empty-text-nodes");
const get_refs_1 = require("../../helpers/get-refs");
const get_state_object_string_1 = require("../../helpers/get-state-object-string");
const has_props_1 = require("../../helpers/has-props");
const indent_1 = require("../../helpers/indent");
const map_refs_1 = require("../../helpers/map-refs");
const merge_options_1 = require("../../helpers/merge-options");
const for_1 = require("../../helpers/nodes/for");
const render_imports_1 = require("../../helpers/render-imports");
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");
const hash_sum_1 = __importDefault(require("hash-sum"));
const lodash_1 = require("lodash");
const standalone_1 = require("prettier/standalone");
const on_mount_1 = require("../helpers/on-mount");
// Having issues with this, so off for now
const USE_MARKO_PRETTIER = false;
/**
* Return the names of properties (basic literal values) on state
*/
function getStatePropertyNames(json) {
return Object.keys(json.state).filter((key) => { var _a; return ((_a = json.state[key]) === null || _a === void 0 ? void 0 : _a.type) === 'property'; });
}
const blockToMarko = (json, options) => {
var _a, _b, _c, _d, _e;
if (json.properties._text) {
return json.properties._text;
}
if ((_a = json.bindings._text) === null || _a === void 0 ? void 0 : _a.code) {
return `\${${processBinding(options.component, (_b = json.bindings) === null || _b === void 0 ? void 0 : _b._text.code)}}`;
}
if (json.name === 'Fragment') {
return json.children.map((child) => blockToMarko(child, options)).join('\n');
}
if ((0, mitosis_node_1.checkIsForNode)(json)) {
const forArguments = (0, for_1.getForArguments)(json).join(',');
return `<for|${forArguments}| of=(${processBinding(options.component, (_c = json.bindings.each) === null || _c === void 0 ? void 0 : _c.code)})>
${json.children
.filter(filter_empty_text_nodes_1.filterEmptyTextNodes)
.map((item) => blockToMarko(item, options))
.join('\n')}
</for>`;
}
else if (json.name === 'Show') {
return `<if(${processBinding(options.component, (_d = json.bindings.when) === null || _d === void 0 ? void 0 : _d.code)})>
${json.children
.filter(filter_empty_text_nodes_1.filterEmptyTextNodes)
.map((item) => blockToMarko(item, options))
.join('\n')}</if>
${!json.meta.else ? '' : `<else>${blockToMarko(json.meta.else, options)}</else>`}`;
}
let str = '';
str += `<${json.name} `;
for (const key in json.properties) {
const value = json.properties[key];
str += ` ${key}="${value}" `;
}
for (const key in json.bindings) {
const { code, arguments: cusArgs = ['event'], type, async } = json.bindings[key];
if (type === 'spread') {
str += ` ...(${code}) `;
}
else if (key === 'ref') {
str += ` key="${(0, lodash_1.camelCase)(code)}" `;
}
else if ((0, event_handlers_1.checkIsEvent)(key)) {
const asyncKeyword = async ? 'async ' : '';
const useKey = key === 'onChange' && json.name === 'input' ? 'onInput' : key;
str += ` ${(0, dash_case_1.dashCase)(useKey)}=(${asyncKeyword}(${cusArgs.join(',')}) => ${processBinding(options.component, code)}) `;
}
else if (key !== 'innerHTML') {
str += ` ${key}=(${processBinding(options.component, code)}) `;
}
}
if (html_tags_1.SELF_CLOSING_HTML_TAGS.has(json.name)) {
return str + ' />';
}
str += '>';
if ((_e = json.bindings.innerHTML) === null || _e === void 0 ? void 0 : _e.code) {
str += `$!{${processBinding(options.component, json.bindings.innerHTML.code)}}`;
}
if (json.children) {
str += json.children.map((item) => blockToMarko(item, options)).join('\n');
}
str += `</${json.name}>`;
return str;
};
function processBinding(json, code, type = 'attribute') {
try {
return (0, strip_state_and_props_refs_1.stripStateAndPropsRefs)((0, strip_state_and_props_refs_1.stripStateAndPropsRefs)(code, {
replaceWith: type === 'state' ? 'input.' : type === 'class' ? 'this.input.' : 'input.',
includeProps: true,
includeState: false,
}), {
replaceWith: (key) => {
const isProperty = getStatePropertyNames(json).includes(key);
if (isProperty) {
return (type === 'state' || type === 'class' ? 'this.state.' : 'state.') + key;
}
return (type === 'class' || type === 'state' ? 'this.' : 'component.') + key;
},
includeProps: false,
includeState: true,
});
}
catch (error) {
console.error('Marko: could not process binding', code);
return code;
}
}
const componentToMarko = (userOptions = {}) => ({ component }) => {
var _a, _b;
let json = (0, fast_clone_1.fastClone)(component);
const options = (0, merge_options_1.initializeOptions)({
target: 'marko',
component,
defaults: {
...userOptions,
component: json,
},
});
if (options.plugins) {
json = (0, plugins_1.runPreJsonPlugins)({ json, plugins: options.plugins });
}
let css = (0, collect_css_1.collectCss)(json, {
prefix: (0, hash_sum_1.default)(json),
});
const domRefs = (0, get_refs_1.getRefs)(json);
(0, map_refs_1.mapRefs)(json, (refName) => `this.${(0, lodash_1.camelCase)(refName)}`);
if (options.plugins) {
json = (0, plugins_1.runPostJsonPlugins)({ json, plugins: options.plugins });
}
(0, strip_meta_properties_1.stripMetaProperties)(json);
const dataString = (0, get_state_object_string_1.getStateObjectStringFromComponent)(json, {
format: 'object',
data: true,
functions: false,
getters: false,
valueMapper: (code) => processBinding(json, code, 'state'),
});
const thisHasProps = (0, has_props_1.hasProps)(json);
const methodsString = (0, get_state_object_string_1.getStateObjectStringFromComponent)(json, {
format: 'class',
data: false,
functions: true,
getters: true,
valueMapper: (code) => processBinding(json, code, 'class'),
});
const hasState = dataString.trim().length > 5;
if (options.prettier !== false) {
try {
css = (0, standalone_1.format)(css, {
parser: 'css',
plugins: [require('prettier/parser-postcss')],
});
}
catch (err) {
console.warn('Could not format css', err);
}
}
let jsString = (0, dedent_1.dedent) `
${(0, render_imports_1.renderPreComponent)({
explicitImportFileExtension: options.explicitImportFileExtension,
component: json,
target: 'marko',
})}
class {
${methodsString}
${!hasState
? ''
: `onCreate(${thisHasProps ? 'input' : ''}) {
this.state = ${dataString}
}`}
${Array.from(domRefs)
.map((refName) => `get ${(0, lodash_1.camelCase)(refName)}() {
return this.getEl('${(0, lodash_1.camelCase)(refName)}')
}`)
.join('\n')}
${!json.hooks.onMount.length
? ''
: `onMount() { ${processBinding(json, (0, on_mount_1.stringifySingleScopeOnMount)(json), 'class')} }`}
${!((_a = json.hooks.onUnMount) === null || _a === void 0 ? void 0 : _a.code)
? ''
: `onDestroy() { ${processBinding(json, json.hooks.onUnMount.code, 'class')} }`}
${!((_b = json.hooks.onUpdate) === null || _b === void 0 ? void 0 : _b.length)
? ''
: `onRender() { ${json.hooks.onUpdate
.map((hook) => processBinding(json, hook.code, 'class'))
.join('\n\n')} }`}
}
`;
let htmlString = json.children.map((item) => blockToMarko(item, options)).join('\n');
const cssString = css.length
? `style {
${(0, indent_1.indent)(css, 2).trim()}
}`
: '';
if (options.prettier !== false && !USE_MARKO_PRETTIER) {
try {
htmlString = markoFormatHtml(htmlString);
}
catch (err) {
console.warn('Could not format html', err);
}
try {
jsString = (0, standalone_1.format)(jsString, {
parser: 'typescript',
plugins: [require('prettier/parser-typescript')],
});
}
catch (err) {
console.warn('Could not format js', err);
}
}
htmlString = htmlString
// Convert on-click=(...) -> on-click(...)
.replace(/(on-[a-z]+)=\(/g, (_match, group) => group + '(')
// Fix a weird edge case where </if> becomes </if \n > which is invalid in marko
.replace(/<\/([a-z]+)\s+>/gi, '</$1>');
let finalStr = `
${jsString}
${cssString}
${htmlString}
`
.replace(/\n{3,}/g, '\n\n')
.trim();
if (options.plugins) {
finalStr = (0, plugins_1.runPreCodePlugins)({ json, code: finalStr, plugins: options.plugins });
}
if (USE_MARKO_PRETTIER && options.prettier !== false) {
// Commented out for now as there are strange module import issues as
// a result, causing builds to fail
// format(finalStr, {
// parser: 'marko',
// plugins: [require('prettier-plugin-marko')],
// });
}
if (options.plugins) {
finalStr = (0, plugins_1.runPostCodePlugins)({ json, code: finalStr, plugins: options.plugins });
}
return finalStr;
};
exports.componentToMarko = componentToMarko;
/**
* Convert marko expressions to valid html
*
* <div on-click=(() => doSomething())> -> <div on-click="() => doSomething()">
*/
function preprocessHtml(htmlString) {
return (htmlString
// Convert <for|foo| to <for |foo|, otherwise HTML will think the tag is not just <for> and complain
// when we close it with </for>
.replace(/<for\|/g, '<for |')
// Convert <if(foo) to <if _="foo", otherwise HTML will think the tag is not just <if> and complain
// when we close it with </if>
.replace(/<if\(([\s\S]+?)\)\s*>/g, (_match, group) => {
return `<if _="${encodeAttributeValue(group)}">`;
})
.replace(/=\(([\s\S]*?)\)(\s*[a-z\/>])/g, (_match, group, after) => {
return `="(${encodeAttributeValue(group)})"${after}`;
}));
}
exports.preprocessHtml = preprocessHtml;
/**
* Convert HTML back to marko expressions
*
* <div on-click="() => doSomething()"> -> <div on-click=(() => doSomething())>
*/
function postprocessHtml(htmlString) {
return htmlString
.replace(/<for \|/g, '<for|')
.replace(/<if _="([\s\S]+?)"\s*>/g, (_match, group) => {
return `<if(${decodeAttributeValue(group)})>`;
})
.replace(/="\(([\s\S]*?)\)"(\s*[a-z\/>])/g, (_match, group, after) => {
return `=(${decodeAttributeValue(group)})${after}`;
});
}
exports.postprocessHtml = postprocessHtml;
// Encode quotes and spaces for HTML attribute values
function encodeAttributeValue(value) {
return value.replace(/"/g, '"').replace(/\n/g, ' ');
}
// Decode quotes and spaces for HTML attribute values
function decodeAttributeValue(value) {
return value.replace(/"/g, '"').replace(/ /g, '\n');
}
/**
* Format Marko HTML using the built-in HTML parser for prettier,
* given issues with Marko's plugin
*/
function markoFormatHtml(htmlString) {
return postprocessHtml((0, standalone_1.format)(preprocessHtml(htmlString), {
parser: 'html',
plugins: [require('prettier/parser-html')],
}));
}
exports.markoFormatHtml = markoFormatHtml;
;