Skip to content

Commit

Permalink
Refactor: Solid generator (BuilderIO#978)
Browse files Browse the repository at this point in the history
* publish

* add missing cases

* cleanup override file logic

* fix react imports

* check all extensions

* fix override filename logic

* move

* remove logs

* remove

* refactor

* fix imports
  • Loading branch information
samijaber authored Jan 13, 2023
1 parent abcfddc commit c3b0933
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 204 deletions.
128 changes: 128 additions & 0 deletions packages/core/src/generators/solid/blocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { types } from '@babel/core';
import { kebabCase } from 'lodash';
import { babelTransformExpression } from '../../helpers/babel-transform';
import { filterEmptyTextNodes } from '../../helpers/filter-empty-text-nodes';
import { objectHasKey } from '../../helpers/typescript';
import { selfClosingTags } from '../../parsers/jsx';
import { MitosisComponent } from '../../types/mitosis-component';
import { MitosisNode, checkIsForNode } from '../../types/mitosis-node';
import { collectClassString } from './helpers/styles';
import { ToSolidOptions } from './types';

const ATTTRIBUTE_MAPPERS = {
for: 'htmlFor',
};

const transformAttributeName = (name: string) => {
if (objectHasKey(ATTTRIBUTE_MAPPERS, name)) return ATTTRIBUTE_MAPPERS[name];
return name;
};

export const blockToSolid = ({
json,
options,
component,
}: {
json: MitosisNode;
options: ToSolidOptions;
component: MitosisComponent;
}): string => {
if (json.properties._text) {
return json.properties._text;
}
if (json.bindings._text?.code) {
return `{${json.bindings._text.code}}`;
}

if (checkIsForNode(json)) {
const needsWrapper = json.children.length !== 1;
// The SolidJS `<For>` component has a special index() signal function.
// https://www.solidjs.com/docs/latest#%3Cfor%3E
return `<For each={${json.bindings.each?.code}}>
{(${json.scope.forName}, _index) => {
const ${json.scope.indexName || 'index'} = _index();
return ${needsWrapper ? '<>' : ''}${json.children
.filter(filterEmptyTextNodes)
.map((child) => blockToSolid({ component, json: child, options }))}}}
${needsWrapper ? '</>' : ''}
</For>`;
}

let str = '';

if (json.name === 'Fragment') {
str += '<';
} else {
str += `<${json.name} `;
}

if (json.name === 'Show' && json.meta.else) {
str += `fallback={${blockToSolid({ component, json: json.meta.else as any, options })}}`;
}

const classString = collectClassString(json, options);
if (classString) {
str += ` class=${classString} `;
}

for (const key in json.properties) {
const value = json.properties[key];
const newKey = transformAttributeName(key);
str += ` ${newKey}="${value}" `;
}
for (const key in json.bindings) {
const { code, arguments: cusArg = ['event'], type } = json.bindings[key]!;
if (!code) continue;

if (type === 'spread') {
str += ` {...(${code})} `;
} else if (key.startsWith('on')) {
const useKey = key === 'onChange' && json.name === 'input' ? 'onInput' : key;
str += ` ${useKey}={(${cusArg.join(',')}) => ${code}} `;
} else {
let useValue = code;
if (key === 'style') {
// Convert camelCase keys to kebab-case
// TODO: support more than top level objects, may need
// a runtime helper for expressions that are not a direct
// object literal, such as ternaries and other expression
// types
useValue = babelTransformExpression(code, {
ObjectExpression(path: babel.NodePath<babel.types.ObjectExpression>) {
// TODO: limit to top level objects only
for (const property of path.node.properties) {
if (types.isObjectProperty(property)) {
if (types.isIdentifier(property.key) || types.isStringLiteral(property.key)) {
const key = types.isIdentifier(property.key)
? property.key.name
: property.key.value;
property.key = types.stringLiteral(kebabCase(key));
}
}
}
},
});
}
const newKey = transformAttributeName(key);
str += ` ${newKey}={${useValue}} `;
}
}
if (selfClosingTags.has(json.name)) {
return str + ' />';
}
str += '>';
if (json.children) {
str += json.children
.filter(filterEmptyTextNodes)
.map((item) => blockToSolid({ component, json: item, options }))
.join('\n');
}

if (json.name === 'Fragment') {
str += '</>';
} else {
str += `</${json.name}>`;
}

return str;
};
55 changes: 55 additions & 0 deletions packages/core/src/generators/solid/helpers/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { MitosisNode } from '../../../types/mitosis-node';
import { ToSolidOptions } from '../types';

// This should really be a preprocessor mapping the `class` attribute binding based on what other values have
// to make this more pluggable
export const collectClassString = (json: MitosisNode, options: ToSolidOptions): string | null => {
const staticClasses: string[] = [];

if (json.properties.class) {
staticClasses.push(json.properties.class);
delete json.properties.class;
}
if (json.properties.className) {
staticClasses.push(json.properties.className);
delete json.properties.className;
}

const dynamicClasses: string[] = [];
if (typeof json.bindings.class?.code === 'string') {
dynamicClasses.push(json.bindings.class.code as any);
delete json.bindings.class;
}
if (typeof json.bindings.className?.code === 'string') {
dynamicClasses.push(json.bindings.className.code as any);
delete json.bindings.className;
}
if (
typeof json.bindings.css?.code === 'string' &&
json.bindings.css.code.trim().length > 4 &&
options.stylesType === 'styled-components'
) {
dynamicClasses.push(`css(${json.bindings.css.code})`);
}
delete json.bindings.css;
const staticClassesString = staticClasses.join(' ');

const dynamicClassesString = dynamicClasses.join(" + ' ' + ");

const hasStaticClasses = Boolean(staticClasses.length);
const hasDynamicClasses = Boolean(dynamicClasses.length);

if (hasStaticClasses && !hasDynamicClasses) {
return `"${staticClassesString}"`;
}

if (hasDynamicClasses && !hasStaticClasses) {
return `{${dynamicClassesString}}`;
}

if (hasDynamicClasses && hasStaticClasses) {
return `{"${staticClassesString} " + ${dynamicClassesString}}`;
}

return null;
};
Loading

0 comments on commit c3b0933

Please sign in to comment.