@builder.io/mitosis
Version:
Write components once, run everywhere. Compiles to Vue, React, Solid, and Liquid. Import code from Figma and Builder.io
224 lines (223 loc) • 9.99 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.renderJSXNodes = void 0;
const is_mitosis_node_1 = require("../../helpers/is-mitosis-node");
const directives_1 = require("./directives");
const src_generator_1 = require("./src-generator");
/**
* Convert a Mitosis nodes to a JSX nodes.
*
* @param file File into which the output will be written to.
* @param directives Store for directives which we came across so that they can be imported.
* @param handlers A set of handlers which we came across so that they can be rendered
* @param children A list of children to convert to JSX
* @param styles Store for styles which we came across so that they can be rendered.
* @param key Key to be used for the node if needed
* @param parentSymbolBindings A set of bindings from parent to be written into the child.
* @param root True if this is the root JSX, and may need a Fragment wrapper.
* @returns
*/
function renderJSXNodes(file, directives, handlers, children, styles, key, parentSymbolBindings, root = true) {
return function () {
const srcBuilder = this;
if (children.length == 0)
return;
if (root)
this.emit('(');
const needsFragment = root &&
(children.length > 1 ||
(children.length && (isInlinedDirective(children[0]) || isTextNode(children[0]))));
file.import(file.qwikModule, 'h');
const fragmentSymbol = file.import(file.qwikModule, 'Fragment');
if (needsFragment) {
this.jsxBeginFragment(fragmentSymbol);
}
children.forEach((child) => {
var _a, _b;
if (isEmptyTextNode(child))
return;
if (isTextNode(child)) {
const text = child.properties._text;
const textExpr = (_a = child.bindings._text) === null || _a === void 0 ? void 0 : _a.code;
if (typeof text == 'string') {
this.isJSX ? this.emit(text) : this.jsxTextBinding((0, src_generator_1.quote)(text));
}
else if (typeof textExpr == 'string') {
this.isJSX ? this.emit('{', textExpr, '}') : this.jsxTextBinding(textExpr);
}
}
else if (isSlotProjection(child)) {
this.file.import(this.file.qwikModule, 'Slot');
this.jsxBegin('Slot', {}, {});
this.jsxEnd('Slot');
}
else {
let childName = child.name;
const directive = directives_1.DIRECTIVES[childName];
if (typeof directive == 'function') {
const blockFn = mitosisNodeToRenderBlock(child.children);
const meta = child.meta;
Object.keys(meta).forEach((key) => {
const value = meta[key];
if ((0, is_mitosis_node_1.isMitosisNode)(value)) {
blockFn[key] = mitosisNodeToRenderBlock([value]);
}
});
this.emit(directive(child, blockFn));
!this.isJSX && this.emit(',');
includedHelperDirectives(directive.toString(), directives);
}
else {
if (childName === 'Slot') {
this.file.import(this.file.qwikModule, 'Slot');
}
else {
if (typeof directive == 'string') {
directives.set(childName, directive);
includedHelperDirectives(directive, directives);
if (file.module !== 'med' && file.imports.hasImport(childName)) {
file.import('./med.js', childName);
}
}
if (isSymbol(childName)) {
// TODO(misko): We are hard coding './med.js' which is not right.
!file.imports.hasImport(childName) && file.import('./med.js', childName);
let exportedChildName = file.exports.get(childName);
if (exportedChildName) {
childName = exportedChildName;
}
}
}
let props = child.properties;
const css = (_b = child.bindings.css) === null || _b === void 0 ? void 0 : _b.code;
const specialBindings = {};
if (css) {
props = { ...props };
const styleProps = styles.get(css);
const imageMaxWidth = childName == 'Image' && styleProps.maxWidth;
if (imageMaxWidth && imageMaxWidth.endsWith('px')) {
// special case for Images. We want to make sure that we include the maxWidth in a srcset
specialBindings.srcsetSizes = Number.parseInt(imageMaxWidth);
}
if (styleProps === null || styleProps === void 0 ? void 0 : styleProps.CLASS_NAME) {
props.class = addClass(styleProps.CLASS_NAME, props.class);
}
}
key = props['builder-id'] || key;
if (props.innerHTML) {
// Special case. innerHTML requires `key` in Qwik
props = {
key: key || 'default',
...props,
};
}
const symbolBindings = {};
const bindings = rewriteHandlers(file, handlers, child.bindings, symbolBindings);
this.jsxBegin(childName, props, {
...bindings,
...parentSymbolBindings,
...specialBindings,
});
renderJSXNodes(file, directives, handlers, child.children, styles, key, symbolBindings, false).call(this);
this.jsxEnd(childName);
}
}
});
if (needsFragment) {
this.jsxEndFragment();
}
if (root)
this.emit(')');
function mitosisNodeToRenderBlock(children) {
return () => {
children = children.filter((c) => !isEmptyTextNode(c));
const childNeedsFragment = children.length > 1 || (children.length && isTextNode(children[0]));
childNeedsFragment && srcBuilder.jsxBeginFragment(fragmentSymbol);
renderJSXNodes(file, directives, handlers, children, styles, null, {}, false).call(srcBuilder);
childNeedsFragment && srcBuilder.jsxEndFragment();
};
}
};
}
exports.renderJSXNodes = renderJSXNodes;
function includedHelperDirectives(directive, directives) {
Array.from(directive.matchAll(/(__[\w]+__)/g)).forEach((match) => {
const name = match[0];
const code = directives_1.DIRECTIVES[name];
typeof code == 'string' && directives.set(name, code);
});
}
function isSymbol(name) {
return (name.charAt(0) === name.charAt(0).toUpperCase() &&
// we want to exclude any property access, as that can't be a symbol
!name.includes('.'));
}
function addClass(className, existingClass) {
return [className, ...(existingClass ? existingClass.split(' ') : [])].join(' ');
}
function isEmptyTextNode(child) {
var _a;
return ((_a = child.properties._text) === null || _a === void 0 ? void 0 : _a.trim()) == '';
}
function isTextNode(child) {
var _a;
if (child.properties._text !== undefined) {
return true;
}
const code = (_a = child.bindings._text) === null || _a === void 0 ? void 0 : _a.code;
if (code !== undefined && code !== 'props.children') {
return true;
}
return false;
}
function isSlotProjection(child) {
var _a;
return ((_a = child.bindings._text) === null || _a === void 0 ? void 0 : _a.code) === 'props.children';
}
/**
* Rewrites bindings:
* - Remove `css`
* - Rewrites event handles
* - Extracts symbol bindings.
*
* @param file
* @param handlers
* @param bindings
* @param symbolBindings Options record which will receive the symbol bindings
* @returns
*/
function rewriteHandlers(file, handlers, bindings, symbolBindings) {
const outBindings = {};
for (let key in bindings) {
if (Object.prototype.hasOwnProperty.call(bindings, key)) {
const bindingValue = bindings[key];
let bindingExpr = bindingValue.code;
const handlerBlock = handlers.get(bindingExpr);
if (key == 'css') {
continue;
}
else if (handlerBlock) {
key = `${key}$`;
bindingExpr = (0, src_generator_1.invoke)(file.import(file.qwikModule, 'qrl'), [
(0, src_generator_1.quote)(file.qrlPrefix + 'high.js'),
(0, src_generator_1.quote)(handlerBlock),
file.options.isBuilder ? '[s,l]' : '[state]',
]);
}
else if (symbolBindings && key.startsWith('symbol.data.')) {
symbolBindings[(0, src_generator_1.lastProperty)(key)] = bindingExpr;
}
else if (key.startsWith('component.options.')) {
key = (0, src_generator_1.lastProperty)(key);
}
outBindings[key] = {
...bindingValue,
code: bindingExpr,
};
}
}
return outBindings;
}
function isInlinedDirective(node) {
return ((0, is_mitosis_node_1.isMitosisNode)(node) && node.name == 'Show') || node.name == 'For';
}
;