Skip to content

Commit

Permalink
feat: add jsx list
Browse files Browse the repository at this point in the history
  • Loading branch information
andycall committed Jun 23, 2022
1 parent bc63ccd commit 71a9dc2
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 9 deletions.
12 changes: 12 additions & 0 deletions __tests__/__fixtures__/example/actual.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createElement } from 'react';

function Foo() {
return (
<View>
<View x-for={array}>hello</View>
<View x-for={item in array}>item: {item}</View>
<View x-for={(item, key) in foo}>key: {key}, item: {item}</View>
<View x-for={(item, key) in exp()}>key: {key}, item: {item}</View>
</View>
)
}
14 changes: 14 additions & 0 deletions __tests__/__fixtures__/example/expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createList as __create_list__ } from "babel-runtime-jsx-plus";
import { createElement } from "react";
function Foo() {
return /*#__PURE__*/ React.createElement(View, null, __create_list__.call(this, array, function() {
return React.createElement(View, null, "hello");
}), __create_list__.call(this, array, function(item) {
return React.createElement(View, null, "item: ", item);
}), __create_list__.call(this, foo, function(item, key) {
return React.createElement(View, null, "key: ", key, ", item: ", item);
}), __create_list__.call(this, exp(), function(item, key) {
return React.createElement(View, null, "key: ", key, ", item: ", item);
}));
}

11 changes: 11 additions & 0 deletions __tests__/__fixtures__/side-effects/actual.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const items = [1, 2, 3, 4];

export default function List() {
return (
<div>
<div x-for={(it, idx) in items}>
<span>{it}</span>
</div>
</div>
);
}
13 changes: 13 additions & 0 deletions __tests__/__fixtures__/side-effects/expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createList as __create_list__ } from "babel-runtime-jsx-plus";
var items = [
1,
2,
3,
4
];
export default function List() {
return /*#__PURE__*/ React.createElement("div", null, __create_list__.call(this, items, function(it, idx) {
return React.createElement("div", null, /*#__PURE__*/ React.createElement("span", null, it));
}));
};

124 changes: 118 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,137 @@
import {
Expression,
JSXElement, JSXText, Program,
Expression, JSXAttribute, JSXAttrValue,
JSXElement, JSXExpression, JSXText, Program,
} from '@swc/core';
import Visitor from '@swc/core/Visitor';
import {
ExprOrSpread, JSXElementChild
ExprOrSpread, JSXElementChild, Pattern
} from '@swc/core/types';
import {
buildArrayExpression,
buildArrowFunctionExpression, buildBooleanLiteral, buildCallExpression, buildIdentifier, buildImportDeclaration,
buildArrowFunctionExpression,
buildBooleanLiteral,
buildCallExpression,
buildIdentifier,
buildImportDeclaration,
buildJSXElement,
buildJSXExpressionContainer, buildJSXText, buildNamedImportSpecifier, buildNullLiteral, buildStringLiteral
buildJSXExpressionContainer,
buildJSXText,
buildMemberExpression,
buildNamedImportSpecifier,
buildNullLiteral,
buildStringLiteral, buildThisExpression
} from './utils';

function JSXListToStandard(n: JSXElement) {
let openingAttributes = n.opening.attributes;

if (openingAttributes) {
openingAttributes = openingAttributes.filter((attribute) => {
if (attribute.type === 'JSXAttribute' && attribute.name.type === 'Identifier' && attribute.name.value === 'x-for') {
return false;
}
return true;
});
}
return buildJSXElement({
...n.opening,
attributes: openingAttributes
}, n.children, n.closing)
}

function transformJSXList(n: JSXElement, currentList: JSXElementChild[], currentIndex: number): JSXElement | JSXText {
n.children = n.children.map((c, i) => {
if (c.type === 'JSXElement' && isJSXList(c)) {
return transformJSXList(c, n.children, i);
}
return c;
});

if (isJSXList(n)) {
let attrValue = getJSXList(n);
if (!attrValue || attrValue.type !== 'JSXExpressionContainer') {
console.warn('ignore x-for due to stynax error.');
return n;
}

// @ts-ignore
if (n.__listHandled) return n;
// @ts-ignore
n.__listHandled = true;

let { expression } = attrValue;
let params: Pattern[] = [];
let iterValue: Expression;

if (expression.type === 'BinaryExpression' && expression.operator === 'in') {
// x-for={(item, index) in value}
const { left, right } = expression;
iterValue = right;

if (left.type === 'ParenthesisExpression' && left.expression.type === 'SequenceExpression') {
// x-for={(item, key) in value}
params = left.expression.expressions;
} else if (left.type === 'Identifier') {
// x-for={item in value}
params.push(buildIdentifier(left.value));
} else {
// x-for={??? in value}
throw new Error('Stynax error of x-for.');
}
} else {
// x-for={value}, x-for={callExp()}, ...
iterValue = expression;
}

let callee = buildMemberExpression(buildIdentifier('__create_list__'), buildIdentifier('call'));
let body = buildCallExpression(callee, [
{
expression: buildThisExpression()
},
{
expression: iterValue
},
{
expression: buildArrowFunctionExpression(params, JSXListToStandard(n))
}
]) as any;

return buildJSXExpressionContainer(body) as any;
}

return n;
}

function getJSXList(n: JSXElement): JSXAttrValue | undefined {
let opening = n.opening;
let openingAttributes = opening.attributes;

if (openingAttributes) {
for (let attribute of openingAttributes) {
if (attribute.type === 'JSXAttribute' && attribute.name.type === 'Identifier' && attribute.name.value === 'x-for') {
return attribute.value;
}
}
}
}

function isJSXList(n: JSXElement): boolean {
let opening = n.opening;
let openingAttributes = opening.attributes;

if (openingAttributes) {
for (let attribute of openingAttributes) {
if (attribute.type === 'JSXAttribute' && attribute.name.type === 'Identifier' && attribute.name.value === 'x-for') {
return true;
}
}
}
return false;
}

class JSXListTransformer extends Visitor {
visitJSXElement(n: JSXElement): JSXElement {
return n;
return transformJSXList(n, [], -1) as JSXElement;
}
}

Expand Down
20 changes: 17 additions & 3 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import {
CallExpression,
Expression, Identifier, ImportDeclaration,
JSXElement,
JSXExpressionContainer, JSXText,
NullLiteral
JSXExpressionContainer, JSXText, MemberExpression,
NullLiteral, ThisExpression
} from '@swc/core';
import {
Argument,
Expand Down Expand Up @@ -105,6 +105,14 @@ export function buildNamedImportSpecifier(local: Identifier, imported: Identifie
});
}

export function buildMemberExpression(object: Identifier, property: Identifier): MemberExpression {
return buildBaseExpression<MemberExpression>({
type: 'MemberExpression',
object: object,
property: property
})
}

export function buildCallExpression(callee: Expression | Super | Import, args: Argument[]): CallExpression {
return buildBaseExpression({
type: 'CallExpression',
Expand All @@ -113,7 +121,13 @@ export function buildCallExpression(callee: Expression | Super | Import, args: A
})
}

export function buildIdentifier(name: string, optional: boolean): Identifier {
export function buildThisExpression(): ThisExpression {
return buildBaseExpression({
type: 'ThisExpression'
});
}

export function buildIdentifier(name: string, optional?: boolean): Identifier {
return buildBaseExpression({
type: 'Identifier',
value: name,
Expand Down

0 comments on commit 71a9dc2

Please sign in to comment.