@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
JavaScript
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;
;