UNPKG

@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
"use strict"; 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, '&quot;').replace(/\n/g, '&#10;'); } // Decode quotes and spaces for HTML attribute values function decodeAttributeValue(value) { return value.replace(/&quot;/g, '"').replace(/&#10;/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;