UNPKG

@builder.io/mitosis

Version:

Write components once, run everywhere. Compiles to Vue, React, Solid, and Liquid. Import code from Figma and Builder.io

272 lines (262 loc) 10.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.componentToLit = void 0; 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_props_1 = require("../../helpers/get-props"); const get_refs_1 = require("../../helpers/get-refs"); const get_state_object_string_1 = require("../../helpers/get-state-object-string"); const has_1 = require("../../helpers/has"); const indent_1 = require("../../helpers/indent"); const is_upper_case_1 = require("../../helpers/is-upper-case"); const map_refs_1 = require("../../helpers/map-refs"); const merge_options_1 = require("../../helpers/merge-options"); 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 mitosis_node_1 = require("../../types/mitosis-node"); const lodash_1 = require("lodash"); const standalone_1 = require("prettier/standalone"); const html_tags_1 = require("../../constants/html_tags"); const plugins_1 = require("../../modules/plugins"); const on_mount_1 = require("../helpers/on-mount"); const collect_class_string_1 = require("./collect-class-string"); const getCustomTagName = (name, options) => { if (!name || !(0, is_upper_case_1.isUpperCase)(name[0])) { return name; } const kebabCaseName = (0, dash_case_1.dashCase)(name); if (!kebabCaseName.includes('-')) { // TODO: option to choose your prefix return 'my-' + kebabCaseName; } return kebabCaseName; }; const blockToLit = (json, options = {}) => { var _a, _b, _c, _d, _e, _f, _g; if (json.properties._text) { return json.properties._text; } if ((_a = json.bindings._text) === null || _a === void 0 ? void 0 : _a.code) { return `\${${processBinding((_b = json.bindings) === null || _b === void 0 ? void 0 : _b._text.code)}}`; } if ((0, mitosis_node_1.checkIsForNode)(json)) { return `\${${processBinding((_c = json.bindings.each) === null || _c === void 0 ? void 0 : _c.code)}?.map((${(_d = json.scope.forName) !== null && _d !== void 0 ? _d : '_'}, ${(_e = json.scope.indexName) !== null && _e !== void 0 ? _e : 'index'}) => ( html\`${json.children .filter(filter_empty_text_nodes_1.filterEmptyTextNodes) .map((item) => blockToLit(item, options)) .join('\n')}\` ))}`; } else if (json.name === 'Show') { return `\${${processBinding((_f = json.bindings.when) === null || _f === void 0 ? void 0 : _f.code)} ? html\`${json.children .filter(filter_empty_text_nodes_1.filterEmptyTextNodes) .map((item) => blockToLit(item, options)) .join('\n')}\` : ${!json.meta.else ? 'null' : `html\`${blockToLit(json.meta.else, options)}\``}}`; } let str = ''; const tagName = getCustomTagName(json.name, options); str += `<${tagName} `; const classString = (0, collect_class_string_1.collectClassString)(json); if (classString) { str += ` class=${classString} `; } 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 } = json.bindings[key]; if (type === 'spread') { str += ` \${spread(${code})} `; } else if (key === 'ref') { // TODO: maybe use ref directive instead // https://lit.dev/docs/templates/directives/#ref str += ` ref="${code}" `; } else if ((0, event_handlers_1.checkIsEvent)(key)) { const asyncKeyword = ((_g = json.bindings[key]) === null || _g === void 0 ? void 0 : _g.async) ? 'async ' : ''; const useKey = '@' + key.substring(2).toLowerCase(); str += ` ${useKey}=\${${asyncKeyword}(${cusArgs.join(',')}) => ${processBinding(code)}} `; } else { const value = processBinding(code); // If they key includes a '-' it's an attribute, not a property if (key.includes('-')) { str += ` ${key}=\${${value}} `; } else { // TODO: handle boolean attributes too by matching list of html boolean attributes // https://lit.dev/docs/templates/expressions/#boolean-attribute-expressions str += ` .${key}=\${${value}} `; } } } if (html_tags_1.SELF_CLOSING_HTML_TAGS.has(json.name)) { return str + ' />'; } str += '>'; if (json.children) { str += json.children.map((item) => blockToLit(item, options)).join('\n'); } str += `</${tagName}>`; return str; }; function processBinding(code) { return (0, strip_state_and_props_refs_1.stripStateAndPropsRefs)(code, { replaceWith: 'this.' }); } const componentToLit = (_options = {}) => ({ component }) => { var _a, _b, _c; const options = (0, merge_options_1.initializeOptions)({ target: 'lit', component, defaults: _options }); let json = (0, fast_clone_1.fastClone)(component); if (options.plugins) { json = (0, plugins_1.runPreJsonPlugins)({ json, plugins: options.plugins }); } const props = (0, get_props_1.getProps)(component); let css = (0, collect_css_1.collectCss)(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: 'class', data: true, functions: false, getters: false, keyPrefix: '@state() ', valueMapper: (code) => processBinding(code), }); const methodsString = (0, get_state_object_string_1.getStateObjectStringFromComponent)(json, { format: 'class', data: false, functions: true, getters: true, valueMapper: (code) => processBinding(code), }); 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 html = json.children.map((item) => blockToLit(item, options)).join('\n'); const hasSpread = (0, has_1.has)(json, (node) => (0, lodash_1.some)(node.bindings, { type: 'spread' })); 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); } try { html = (0, standalone_1.format)(html, { parser: 'html', plugins: [require('prettier/parser-html')], }); } catch (err) { // If can't format HTML (this can happen with lit given it is tagged template strings), // at least remove excess fspace html = html.replace(/\n{3,}/g, '\n\n'); } } let str = (0, dedent_1.dedent) ` ${(0, render_imports_1.renderPreComponent)({ explicitImportFileExtension: options.explicitImportFileExtension, component: json, target: 'lit', })} import { LitElement, html, css } from 'lit'; import { customElement, property, state, query } from 'lit/decorators.js'; ${json.types ? json.types.join('\n') : ''} ${hasSpread ? ` const spread = (properties) => directive((part) => { for (const property in properties) { const value = properties[attr]; part.element[property] = value; } }); ` : ''} @customElement('${((_a = json.meta.useMetadata) === null || _a === void 0 ? void 0 : _a.tagName) || getCustomTagName(json.name, options)}') export default class ${json.name} extends LitElement { ${options.useShadowDom ? '' : ` createRenderRoot() { return this; } `} ${options.useShadowDom && css.length ? `static styles = css\` ${(0, indent_1.indent)(css, 8)}\`;` : ''} ${Array.from(domRefs) .map((refName) => ` @query('[ref="${refName}"]') ${(0, lodash_1.camelCase)(refName)}!: HTMLElement; `) .join('\n')} ${Array.from(props) .map((item) => `@property() ${item}: any`) .join('\n')} ${dataString} ${methodsString} ${json.hooks.onMount.length === 0 ? '' : `connectedCallback() { ${processBinding((0, on_mount_1.stringifySingleScopeOnMount)(json))} }`} ${!((_b = json.hooks.onUnMount) === null || _b === void 0 ? void 0 : _b.code) ? '' : `disconnectedCallback() { ${processBinding(json.hooks.onUnMount.code)} }`} ${!((_c = json.hooks.onUpdate) === null || _c === void 0 ? void 0 : _c.length) ? '' : `updated() { ${json.hooks.onUpdate.map((hook) => processBinding(hook.code)).join('\n\n')} }`} render() { return html\` ${options.useShadowDom || !css.length ? '' : `<style>${css}</style>`} ${(0, indent_1.indent)(html, 8)} \` } } `; 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: 'typescript', plugins: [require('prettier/parser-typescript')], }); } catch (err) { console.warn('Could not format Lit typescript', err); } } if (options.plugins) { str = (0, plugins_1.runPostCodePlugins)({ json, code: str, plugins: options.plugins }); } return str; }; exports.componentToLit = componentToLit;