Skip to content

Commit 71a9dc2

Browse files
committed
feat: add jsx list
1 parent bc63ccd commit 71a9dc2

File tree

6 files changed

+185
-9
lines changed

6 files changed

+185
-9
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { createElement } from 'react';
2+
3+
function Foo() {
4+
return (
5+
<View>
6+
<View x-for={array}>hello</View>
7+
<View x-for={item in array}>item: {item}</View>
8+
<View x-for={(item, key) in foo}>key: {key}, item: {item}</View>
9+
<View x-for={(item, key) in exp()}>key: {key}, item: {item}</View>
10+
</View>
11+
)
12+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { createList as __create_list__ } from "babel-runtime-jsx-plus";
2+
import { createElement } from "react";
3+
function Foo() {
4+
return /*#__PURE__*/ React.createElement(View, null, __create_list__.call(this, array, function() {
5+
return React.createElement(View, null, "hello");
6+
}), __create_list__.call(this, array, function(item) {
7+
return React.createElement(View, null, "item: ", item);
8+
}), __create_list__.call(this, foo, function(item, key) {
9+
return React.createElement(View, null, "key: ", key, ", item: ", item);
10+
}), __create_list__.call(this, exp(), function(item, key) {
11+
return React.createElement(View, null, "key: ", key, ", item: ", item);
12+
}));
13+
}
14+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const items = [1, 2, 3, 4];
2+
3+
export default function List() {
4+
return (
5+
<div>
6+
<div x-for={(it, idx) in items}>
7+
<span>{it}</span>
8+
</div>
9+
</div>
10+
);
11+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { createList as __create_list__ } from "babel-runtime-jsx-plus";
2+
var items = [
3+
1,
4+
2,
5+
3,
6+
4
7+
];
8+
export default function List() {
9+
return /*#__PURE__*/ React.createElement("div", null, __create_list__.call(this, items, function(it, idx) {
10+
return React.createElement("div", null, /*#__PURE__*/ React.createElement("span", null, it));
11+
}));
12+
};
13+

src/index.ts

Lines changed: 118 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,137 @@
11
import {
2-
Expression,
3-
JSXElement, JSXText, Program,
2+
Expression, JSXAttribute, JSXAttrValue,
3+
JSXElement, JSXExpression, JSXText, Program,
44
} from '@swc/core';
55
import Visitor from '@swc/core/Visitor';
66
import {
7-
ExprOrSpread, JSXElementChild
7+
ExprOrSpread, JSXElementChild, Pattern
88
} from '@swc/core/types';
99
import {
1010
buildArrayExpression,
11-
buildArrowFunctionExpression, buildBooleanLiteral, buildCallExpression, buildIdentifier, buildImportDeclaration,
11+
buildArrowFunctionExpression,
12+
buildBooleanLiteral,
13+
buildCallExpression,
14+
buildIdentifier,
15+
buildImportDeclaration,
1216
buildJSXElement,
13-
buildJSXExpressionContainer, buildJSXText, buildNamedImportSpecifier, buildNullLiteral, buildStringLiteral
17+
buildJSXExpressionContainer,
18+
buildJSXText,
19+
buildMemberExpression,
20+
buildNamedImportSpecifier,
21+
buildNullLiteral,
22+
buildStringLiteral, buildThisExpression
1423
} from './utils';
1524

25+
function JSXListToStandard(n: JSXElement) {
26+
let openingAttributes = n.opening.attributes;
27+
28+
if (openingAttributes) {
29+
openingAttributes = openingAttributes.filter((attribute) => {
30+
if (attribute.type === 'JSXAttribute' && attribute.name.type === 'Identifier' && attribute.name.value === 'x-for') {
31+
return false;
32+
}
33+
return true;
34+
});
35+
}
36+
return buildJSXElement({
37+
...n.opening,
38+
attributes: openingAttributes
39+
}, n.children, n.closing)
40+
}
41+
1642
function transformJSXList(n: JSXElement, currentList: JSXElementChild[], currentIndex: number): JSXElement | JSXText {
43+
n.children = n.children.map((c, i) => {
44+
if (c.type === 'JSXElement' && isJSXList(c)) {
45+
return transformJSXList(c, n.children, i);
46+
}
47+
return c;
48+
});
49+
50+
if (isJSXList(n)) {
51+
let attrValue = getJSXList(n);
52+
if (!attrValue || attrValue.type !== 'JSXExpressionContainer') {
53+
console.warn('ignore x-for due to stynax error.');
54+
return n;
55+
}
56+
57+
// @ts-ignore
58+
if (n.__listHandled) return n;
59+
// @ts-ignore
60+
n.__listHandled = true;
61+
62+
let { expression } = attrValue;
63+
let params: Pattern[] = [];
64+
let iterValue: Expression;
65+
66+
if (expression.type === 'BinaryExpression' && expression.operator === 'in') {
67+
// x-for={(item, index) in value}
68+
const { left, right } = expression;
69+
iterValue = right;
70+
71+
if (left.type === 'ParenthesisExpression' && left.expression.type === 'SequenceExpression') {
72+
// x-for={(item, key) in value}
73+
params = left.expression.expressions;
74+
} else if (left.type === 'Identifier') {
75+
// x-for={item in value}
76+
params.push(buildIdentifier(left.value));
77+
} else {
78+
// x-for={??? in value}
79+
throw new Error('Stynax error of x-for.');
80+
}
81+
} else {
82+
// x-for={value}, x-for={callExp()}, ...
83+
iterValue = expression;
84+
}
85+
86+
let callee = buildMemberExpression(buildIdentifier('__create_list__'), buildIdentifier('call'));
87+
let body = buildCallExpression(callee, [
88+
{
89+
expression: buildThisExpression()
90+
},
91+
{
92+
expression: iterValue
93+
},
94+
{
95+
expression: buildArrowFunctionExpression(params, JSXListToStandard(n))
96+
}
97+
]) as any;
98+
99+
return buildJSXExpressionContainer(body) as any;
100+
}
101+
17102
return n;
18103
}
19104

105+
function getJSXList(n: JSXElement): JSXAttrValue | undefined {
106+
let opening = n.opening;
107+
let openingAttributes = opening.attributes;
108+
109+
if (openingAttributes) {
110+
for (let attribute of openingAttributes) {
111+
if (attribute.type === 'JSXAttribute' && attribute.name.type === 'Identifier' && attribute.name.value === 'x-for') {
112+
return attribute.value;
113+
}
114+
}
115+
}
116+
}
117+
118+
function isJSXList(n: JSXElement): boolean {
119+
let opening = n.opening;
120+
let openingAttributes = opening.attributes;
121+
122+
if (openingAttributes) {
123+
for (let attribute of openingAttributes) {
124+
if (attribute.type === 'JSXAttribute' && attribute.name.type === 'Identifier' && attribute.name.value === 'x-for') {
125+
return true;
126+
}
127+
}
128+
}
129+
return false;
130+
}
131+
20132
class JSXListTransformer extends Visitor {
21133
visitJSXElement(n: JSXElement): JSXElement {
22-
return n;
134+
return transformJSXList(n, [], -1) as JSXElement;
23135
}
24136
}
25137

src/utils.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import {
33
CallExpression,
44
Expression, Identifier, ImportDeclaration,
55
JSXElement,
6-
JSXExpressionContainer, JSXText,
7-
NullLiteral
6+
JSXExpressionContainer, JSXText, MemberExpression,
7+
NullLiteral, ThisExpression
88
} from '@swc/core';
99
import {
1010
Argument,
@@ -105,6 +105,14 @@ export function buildNamedImportSpecifier(local: Identifier, imported: Identifie
105105
});
106106
}
107107

108+
export function buildMemberExpression(object: Identifier, property: Identifier): MemberExpression {
109+
return buildBaseExpression<MemberExpression>({
110+
type: 'MemberExpression',
111+
object: object,
112+
property: property
113+
})
114+
}
115+
108116
export function buildCallExpression(callee: Expression | Super | Import, args: Argument[]): CallExpression {
109117
return buildBaseExpression({
110118
type: 'CallExpression',
@@ -113,7 +121,13 @@ export function buildCallExpression(callee: Expression | Super | Import, args: A
113121
})
114122
}
115123

116-
export function buildIdentifier(name: string, optional: boolean): Identifier {
124+
export function buildThisExpression(): ThisExpression {
125+
return buildBaseExpression({
126+
type: 'ThisExpression'
127+
});
128+
}
129+
130+
export function buildIdentifier(name: string, optional?: boolean): Identifier {
117131
return buildBaseExpression({
118132
type: 'Identifier',
119133
value: name,

0 commit comments

Comments
 (0)