36 KiB
Identify a Component/hook
1. Auto detection config turned on in Babel
{
"autoNamingDetection": true
}
Will follow the following rules:
- For Components:
- Inside a .jsx file
- Function/ArrowFunction Named as PascalCase
// ✅ function MyComp() { return ( <div> <h1>Hello</h1> </div> ) } // ❌ function foo() { return ( <div> <h1>Hello</h1> </div> ) } - For hooks:
- Function/ArrowFunction starts with "use"
// ✅ function useMyHook() { return 1 } // ❌ function foo() { return 2
2. Manual detection
No naming convention is required. The user will have to manually wrap the function with Component or Hook function.
- For Components, wrapped with
Componentfunction
const myComp = Component(function() {
return (
<div>
<h1>Hello</h1>
</div>
)
})
- For hooks, wrapped with
Hookfunction
const myHook = Hook(function() {
return 1
})
States/Computed
function MyComp() {
let a, b
let count = 100
let doubleCount = count * 2
let [a1, a2, a3] = getArray()
...
}
class MyComp extends View {
// Multiple declarations in one line
a
b
// Single declaration
count = 100
// Computed states
doubleCount = this.count * 2
// Deconstructing assignment
a1
a2
a3
// Using @Watch to deconstruct
@Watch
_$deconstruct_$id$() {
[this.a1, this.a2, this.a3] = getArray()
}
}
Props
Step 1: Collect props/destructured/restProps
Deconstructed in the function signature
function MyComp({ prop1, prop2: [p20, p21], ...otherProps }) {
...
}
Collected as:
props = ["prop1", "prop2"]
destructured = [
["prop2": astOf("[p20, p21]")],
]
restProps = "otherProps"
Deconstructing assignment / Direct usage in the function body
1. Deconstructing assignment
function MyComp(props) {
const { prop1, prop2: [p20, p21], ...otherProps } = props
...
}
Collected as:
props = ["prop1", "prop2"]
destructured = [
["prop2", astOf("[p20, p21]")],
]
restProps = "otherProps"
After:
- Delete this statement
2. Direct usage
function MyComp(props) {
let prop1 = props.prop1
let [p20, p21] = props.prop2
// Not just assignment, but also any other type of usage
console.log(props.prop3)
...
}
collected as:
props = ["prop1", "prop2", "prop3"]
destructured = [] // The reason why it's empty is that the deconstructed props are treated like Computed States
restProps = null
After:
- Replace all
props.propNamewithpropName
Goals of these two "After"s:
- Make sure no
propsis used in the function body
Step2: Generate Props in class
With the collected props/destructured/restProps:
props = ["prop1", "prop2"]
destructured = [
["prop2", astOf("[p20, p21]")],
]
restProps = "otherProps"
Generate:
class MyComp extends View {
// Props with @Prop decorator
@Prop prop1
@Prop prop2
// Destructured props, same logic as Computed States
p20
p21
@Watch
_$deconstruct_$id$() {
[this.p20, this.p21] = this.prop2
}
// Rest props
@RestProps otherProps
}
Things to note
- Multiple deconstructed statements are allowed:
function MyComp(props) {
const { prop1, prop2: [p20, p21] } = props
const { prop1: p1, prop2: [p20A, p21A] } = props
...
}
will be collected as:
props = ["prop1", "prop2"]
destructured = [
["prop2", astOf("[p20, p21]")],
["prop2", astOf("[p20A, p21A)")]
] // this is why destructured is an array of arrays instead of an object
and be generated into:
class MyComp extends View {
@Prop prop1
@Prop prop2
p20
p21
@Watch
_$deconstruct_$id1$() {
[this.p20, this.p21] = this.prop2
}
p20A
p21A
@Watch
_$deconstruct_$id2$() {
[this.p20A, this.p21A] = this.prop2
}
}
- Multiple restProps are NOT allowed:
function MyComp(props) {
const { prop1, prop2, ...otherProps1 } = props
const { prop2, prop3, ...otherProps2 } = props
...
}
Will throw an error because this will confuse the compiler what props are explicitly used and what are collected as restProps.
- Anywhere that
propsis used in the function body will be replaced with the prop name directly:
function MyComp(props) {
let p1 = props.prop1
console.log(props.prop2)
console.log(props.prop3.xx[0].yy)
...
}
will be replaced as:
class MyComp extends View {
@Prop prop1
@Prop prop2
@Prop prop3
p1 = this.prop1
willMount() {
console.log(this.prop2)
console.log(this.prop3.xx[0].yy)
}
}
For loops
for tag basic usage (just like solid's For):
function MyComp() {
let arr = [1, 2, 3]
return (
<for each={arr}> {(item) =>
<div>{item}</div>
}</for>
)
}
functional syntax level support
1. React inherited flavor: Expression mapping
function MyComp() {
let arr = [1, 2, 3]
return (
<>
{arr.map(item => <div>{item}</div>)}
</>
)
}
when it's detected as a map function and the returned value is a JSX, it will be converted into a for tag(for better performance):
function MyComp() {
let arr = [1, 2, 3]
return (
<for each={arr}> {(item) =>
<div>{item}</div>
}</for>
)
}
NOTE: only detect the last map function:
function MyComp() {
let arr = [1, 2, 3]
return (
<>
{
Object.values(arr)
.map(item => <h1>{item}</h1>)
.map(item => <div>{item}</div>)
}
</>
)
}
will be converted into:
function MyComp() {
let arr = [1, 2, 3]
return (
<for each={
Object.values(arr)
.map(item => <h1>{item}</h1>)
}>{(item) =>
<div>{item}</div>
}</for>
)
}
2. Children
- Simple returned value (no local variables). Children will be treated as regular elements/components
function MyComp() {
return (
<for each={arr}>{(item) =>
<div>{item}</div>
}</for>
)
}
children collected in the parser will only be:
<div>{item}</div>
- With local variables. Transformed into a new Component:
function MyComp() {
return (
<for each={arr}>{(info) => {
const { item } = info
return <div>{item}</div>
}}</for>
)
}
will be converted into:
function MyComp() {
function Comp_$id$({ info }) {
const { item } = info
return <div>{item}</div>
}
return (
<for each={arr}>{(info) => (
<Comp_$id$ info={info}/>
)}</for>
)
}
which creates a new component for the children and converts the children into the first situation(1. Simple returned value)
Key prop
Just like React, we can add a key prop to first level children of the for tag:
function MyComp() {
let arr = [1, 2, 3]
return (
<for each={arr}> {(item) =>
<div key={item}>{item}</div>
}</for>
)
}
JSX
JSX is tricky because we need to allow different types of JSX to be used in the function body.
Let's consider a piece of JSX:
function MyComp() {
let count = 0
let jsxSlice = <div>{count}</div>
...
}
We have different options:
- jsx -> array of nodes
we can use an IIFE to convert the JSX into an array of nodes:
class MyComp extends View {
count = 0
jsxSlice = (() => {
const node0 = createElement("div")
node0.textContent = this.count
return node0
})()
}
This looks good but when count changes, the whole jsxSlice will be re-calculated, which means elements will be re-created. This is not good for performance.
So instead of using an IIFE, we can first extract the JSX into a nested component:
function MyComp() {
let count = 0
function Comp_$id$() {
return <div>{count}</div>
}
let jsxSlice = <Comp_$id$/>
...
}
In this case, no matter how many times count changes, the jsxSlice will not be re-calculated, we only need to call the update function of the Comp_$id$ component.
So we'll convert this type of JSX into a nested component:
class MyComp extends View {
count = 0
// Use an IIFE to return a class because we need to forward the parent's this. X in _$thisX represents the level of nesting
// Also it needs to be static(not a state variable)
@Static Comp_$id$ = (() => {
const _$this0 = this
return class extends View {
willUnmount() {
// Remove all parent level when unmount
clear(_$this0)
}
Body() {
const node0 = createElement("div")
const node1 = new ExpressionNode(_$this0.count)
appendChild(node0, [node1])
const update = changed => {
if (changed & 0x0001) {
node1.update(_$this0.count)
}
}
// Because we need to confine the changed scope, we put update into all parent level's updates
this._$updates.add(update) // current level
_$this0._$updates.add(update) // parent level
// _$this1._$updates.add(update) // if there's a third level
return node0
}
}
})()
jsxSlice = new this.Comp_$id$()
}
A more complex example:
function MyComp() {
let count = 100
const jsxSlice = <div>{count}</div>
const jsxArray = [<div>1</div>, <div>{count}</div>]
function jsxFunc() {
// This is a function that returns JSX
// because the function name is smallCamelCased
return <div>{count}</div>
}
function InternalComp({ doubleCount }) {
return (
<div>
{count /* This is Parent's state */}
{doubleCount /* This is current's prop */}
</div>
)
}
return (
<div>
{jsxSlice}
{jsxArray}
{jsxFunc()}
<InternalComp doubleCount={count * 2}/>
</div>
)
}
first, we convert the JSX into nested components:
function MyComp() {
let count = 100
function Comp_$id1$() {
return <div>{count}</div>
}
const jsxSlice = <Comp_$id1$/>
function Comp_$id2$() {
return <div>1</div>
}
function Comp_$id3$() {
return <div>{count}</div>
}
const jsxArray = [<Comp_$id2$/>, <Comp_$id3$/>]
function jsxFunc() {
function Comp_$id4$() {
return <div>{count}</div>
}
// This is a function that returns JSX
// because the function name is smallCamelCased
return <Comp_$id4$/>
}
function InternalComp({ doubleCount }) {
return (
<div>
{count /* This is Parent's state */}
{doubleCount /* This is current's prop */}
</div>
)
}
return (
<div>
{jsxSlice}
{jsxArray}
{jsxFunc()}
<InternalComp doubleCount={count * 2}/>
</div>
)
}
will be converted into:
class MyComp extends View {
count = 100
// $$count = 0x0001 // Indicate count is the first state
@Static Comp_$id1$ = (() => {
const _$this0 = this
return class extends View {
willUnmount() {
// Remove all parent level when unmount
clear(_$this0)
}
Body() {
const node0 = createElement("div")
const node1 = new ExpressionNode(_$this0.count)
appendChild(node0, [node1])
const update = changed => {
if (changed & 0x0001) {
node1.update(_$this0.count)
}
}
this._$updates.add(update)
_$this0._$updates.add(update)
return node0
}
}
})()
jsxSlice = new this.Comp_$id$(null, [...this._$scopes, this])
@Static Comp_$id2$ = class extends View {
Body() {
const node0 = createElement("div")
node0.textContent = "1"
return node0
}
}
Comp_$id3$ = /*same as Comp_$id1$*/
jsxArray = [new this.Comp_$id2$(), new this.Comp_$id3$()]
jsxFunc() {
const Comp_$id4$ = /*same as Comp_$id1$*/
return new Comp_$id4$()
}
@Static InternalComp = (() => {
const _$this0 = this
return class extends View {
@Prop doubleCount
// $$doubleCount = 0x0010 // Index will be INHERITED from the parent!
willUnmount() {
// Remove all parent level when unmount
clear(_$this0)
}
Body() {
const node1 = createElement("div")
const node2 = new ExpressionNode(_$this0.count)
const node3 = new ExpressionNode(this.doubleCount)
appendChild(node1, [node2, node3])
const update = changed => {
if (changed & 0x0001) {
node2.update(_$this0.count)
}
if (changed & 0x0010) {
node3.update(this.doubleCount)
}
}
this._$updates.add(update)
_$this0._$updates.add(update)
return [node1]
}
}
})()
Body() {
const node0 = createElement("div")
const node1 = new ExpressionNode(this.jsxSlice)
const node2 = new ExpressionNode(this.jsxArray)
const node3 = new ExpressionNode(this.jsxFunc())
const node4 = new this.InternalComp({
doubleCount: this.count * 2
})
// second argument is the parent scopes, use this to access parent's states/variables
appendChild(node0, [node1, node2, node3, node4])
const update = changed => {
if (changed & 0x0001) {
// _$this0.jsxFunc will be re-called
node3.update(this.jsxFunc(this.count))
// doubleCount prop will be updated due to count change
node4.updateProp("doubleCount", this.count * 2)
}
}
this._$updates.add(update)
return node0
}
}
Early return
Early return is also tricky....
function MyComp() {
let count = 100
if (count === 100) {
return <div>100</div>
}
let flag = true
return <div>Not 100 {flag}</div>
}
it will be converted in a functional level:
function MyComp() {
let count = 100
function Comp_$id1$() {
return <div>100</div>
}
function Comp_$id2$() {
let flag = true
return <div>Not 100 {flag}</div>
}
return (
<>
<if condition={count === 100}>
<Comp_$id1$/>
</if>
<else>
<Comp_$id2$ />
</else>
</>
)
}
and then be converted into class using the same jsx logic as the previous section.
A more complex example:
function MyComp() {
let count = 100
if (count === 100) {
return <div>100</div>
}
let flag = true
if (flag) {
let cc = 0
if (cc === 200) {
return <div>cc is 200</div>
} else if (cc === 100) {
return <div>cc is 100</div>
}
return <div>Flag is true</div>
}
return <div>Not 100 {flag}</div>
}
will be converted into:
function MyComp() {
let count = 100
function Comp_$id1$() {
return <div>100</div>
}
function Comp_$id2$() {
let flag = true
function Comp_$id3$() {
let cc = 0
function Comp_$id4$() {
return <div>cc is 200</div>
}
function Comp_$id5$() {
return <div>cc is 100</div>
}
function Comp_$id6$() {
return <div>Flag is true</div>
}
return (
<>
<if cond={cc === 200}>
<Comp_$id4$/>
</if>
<else-if cond={cc === 100}>
<Comp_$id5$/>
</else-if>
<else>
<Comp_$id6$/>
</else>
</>
)
}
function Comp_$id7$() {
return <div>Not 100 {flag}</div>
}
return (
<>
<if cond={flag}>
<Comp_$id3$/>
</if>
<else>
<Comp_$id7$/>
</else>
</>
)
}
return (
<>
<if cond={count === 100}>
<Comp_$id1$/>
</if>
<else>
<Comp_$id2$/>
</else>
</>
)
}
Same rule will be applied to switch statements(TBD).
How to pass view props to another component
Consider the following 3 types of solutions:
- A calculated jsx slice:
function MyComp1() {
let count = 0
return <Comp view={<div>{count}</div>}/>
}
function Comp({ view }) {
return <>{view}</>
}
will be converted into:
class MyComp1 extends View {
count = 0
@Static Comp_$id$ = (() => {
const _$this0 = this
return class extends View {
willUnmount() {
// Remove all parent level when unmount
clear(_$this0)
}
Body() {
const node0 = createElement("div")
const node1 = new ExpressionNode(_$this0.count)
appendChild(node0, [node1])
const update = changed => {
if (changed & 0x0001) {
node1.update(_$this0.count)
}
}
this._$updates.add(update)
return node0
}
}
})()
Body() {
const node0 = new Comp({
view: new this.Comp_$id$()
})
return node0
}
}
class Comp extends View {
@Prop view
Body() {
const node0 = new Fragment()
const node1 = new ExpressionNode(this.view)
appendChild(node0, [node1])
const update = changed => {
if (changed & 0x0001) {
node1.update(this.view)
}
}
this._$updates.add(update)
return node0
}
}
In this case, the view prop is a calculated JSX slice, but updating the count state won't re-render the whole view, only the count part inside the Comp_$id$ component. So feel free to use it!
- Functional nested Component:
function MyComp2() {
let count = 0
function SubComp() {
return <div>{count}</div>
}
return <Comp View={SubComp}/>
}
function Comp({ View }) {
return <View/>
}
will be converted into:
class MyComp2 extends View {
count = 0
@Static SubComp = (() => {
const _$this0 = this
return class extends View {
willUnmount() {
// Remove all parent level when unmount
clear(_$this0)
}
Body() {
const node0 = createElement("div")
const node1 = new ExpressionNode(_$this0.count)
appendChild(node0, [node1])
const update = changed => {
if (changed & 0x0001) {
node1.update(_$this0.count)
}
}
this._$updates.add(update)
_$this0._$updates.add(update)
return node0
}
}
})()
Body() {
const node0 = new Comp({
View: this.SubComp
})
return node0
}
}
class Comp extends View {
@Prop View
Body() {
const node0 = new this.View()
return node0
}
}
This is a good way to pass props to another component if you want to add some props in the Comp.
- Functional calculated JSX slice:
function MyComp3() {
let count = 0
return <Comp viewFunc={() => <div>{count}</div>}/>
}
function Comp({ viewFunc }) {
return <>{viewFunc()}</>
}
will be converted into:
class MyComp3 extends View {
count = 0
Body() {
const node0 = new Fragment()
const node1 = new Comp({
viewFunc: () => {
const Comp_$id$ = (() => {
const _$this0 = this
return class extends View {
willUnmount() {
// Remove all parent level when unmount
clear(_$this0)
}
Body() {
const node0 = createElement("div")
const node1 = new ExpressionNode(_$this0.count)
appendChild(node0, [node1])
const update = changed => {
if (changed & 0x0001) {
node1.update(_$this0.count)
}
}
this._$updates.add(update)
return node0
}
}
})()
return new Comp_$id$()
}
})
appendChild(node0, [node1])
const update = changed => {
if (changed & 0x0001) {
node1.updateProp("viewFunc", () => {
const Comp_$id$ = (() => {
const _$this0 = this
return class extends View {
willUnmount() {
clear(_$this0)
}
Body() {
const node0 = createElement("div")
const node1 = new ExpressionNode(_$this0.count)
appendChild(node0, [node1])
const update = changed => {
if (changed & 0x0001) {
node1.update(_$this0.count)
}
}
this._$updates.add(update)
_$this0._$updates.add(update)
return node0
}
}
})()
return new Comp_$id$()
})
}
}
this._$updates.add(update)
return node0
}
}
All the view will be re-calculated when the count state changes. So Don't Ever Use This!
Statements
1. Assignment will be converted to class properties as stated above
2. If statements will be treated as part of the early return logic
3. All other statements will be converted as part of the willMount lifecycle
function MyComp() {
let count = 100
console.log(count)
console.log("Hello")
anyFunction()
...
}
will be converted as:
class MyComp extends View {
count = 100
willMount() {
console.log(this.count)
console.log("Hello")
anyFunction()
}
}
Lifecycle
1. willMount
function MyComp() {
let count = 100
console.log("willMount1")
willMount(() => {
const a = 1
console.log("willMount2")
})
willMount(() => {
const a = 1
console.log("willMount3")
})
}
will be converted as:
class MyComp extends View {
count = 100
willMount() {
console.log("willMount1")
// transform into iife because of its internal variables
(() => {
const a = 1
console.log("willMount2")
})()
(() => {
const a = 1
console.log("willMount3")
})()
}
}
2. didMount/willUnmount/didUnmount
function MyComp() {
didMount(() => {
console.log("didMount")
})
willUnmount(async () => {
console.log("willUnmount")
})
didUnmount(() => {
console.log("didUnmount")
})
}
will be converted as:
class MyComp extends View {
didMount() {
(() => {
console.log("didMount")
})()
}
willUnmount() {
(async () => {
console.log("willUnmount")
})()
}
didUnmount() {
(() => {
console.log("didUnmount")
})()
}
}
Watch
function MyComp() {
let count = 100
watch(() => {
console.log(count)
})
}
will be converted as:
class MyComp extends View {
count = 100
@Watch
_$watch_$id$() {
console.log(this.count)
}
}
With manual dependencies:
function MyComp() {
let count = 100
let doubleCount = count * 2
watch(() => {
console.log(count, doubleCount)
}, [count, doubleCount])
}
will be converted as:
class MyComp extends View {
count = 100
doubleCount = this.count * 2
@Watch("count", "doubleCount")
_$watch_$id$() {
console.log(this.count)
}
}
NOTE: convert identifier to string literal in the @Watch decorator
Hook
Same logic as the Component
function useMyHook() {
let count = 100
return count
}
will be converted into:
class MyHook extends Model {
count = 100
_$return = this.count
}
used in a component:
function MyComp() {
const count = useMyHook()
return <div>{count}</div>
}
will be converted into:
class MyComp extends View {
count = use(useMyHook)._$return
Body() {
toBeCompiled(<div>{this.count}</div>)
}
}
Basically turn useMyHook(props) into a property of use(useMyHook, props)._$return. The logic for props handling is the same as the Component.
NOTE: We have a constraint here that we only accept an object of props like we do in the Component function.
Another example:
function useMyHook() {
let count = 100
let doubleCount = count * 2
return { count, doubleCount }
}
will be converted into:
class MyHook extends Model {
count = 100
doubleCount = this.count * 2
_$return = { count: this.count, doubleCount: this.doubleCount }
}
used in a component:
function MyComp() {
const { count, doubleCount } = useMyHook()
return <div>{count} {doubleCount}</div>
}
will be converted into:
class MyComp extends View {
count
doubleCount
@Watch
_$deconstruct_$id$() {
const { count, doubleCount } = use(useMyHook)._$return
this.count = count
this.doubleCount = doubleCount
}
Body() {
toBeCompiled(<div>{this.count} {this.doubleCount}</div>)
}
}
Early return in hooks:
function useMyHook() {
let count = 100
if (count === 100) {
return 100
}
let flag = count === 200
return flag
}
will be converted first into:
function useMyHook() {
let count = 100
function Hook_$id1$() {
return count
}
function Hook_$id2$() {
let flag = count === 200
return flag
}
if (count === 100) {
return Hook_$id1$()
} else {
return Hook_$id2$()
}
}
and then into:
class useMyHook extends Model {
count = 100
Hook_$id1$ = (() => {
const _$this0 = this
return class {
_$return = _$this0.count
}
})()
Hook_$id2$ = (() => {
const _$this0 = this
return class {
_$return = _$this0.count === 200
}
})()
_$return = (() => {
let ifIdx, model
if (this.count === 100) {
if (ifIdx !== 0) {
ifIdx = 0
model = use(this.Hook_$id1$)._$return
}
} else {
if (ifIdx !== 1) {
ifIdx = 1
model = use(this.Hook_$id2$)._$return
}
}
return model
})()
}
Compilers
Starter Example:
function MyComp(props) {
let { prop1, prop2: [p20, p21], ...otherProps } = props
let count = 100
let data = props.data
const jsxSlice = <div>{count}</div>
const jsxFunc = () => {
return <div>{count}</div>
}
function InnerComp() {
let newCount = 100
return <div>{newCount}</div>
}
console.log("hello")
didMount(() => {
console.log("didMount")
})
if (count === 100) {
return <div>100</div>
}
let flag = true
return (
<div>
{flag}
<h1>{count}</h1>
<for each={data}> {(item) => {
const {id, data} = item
return <div key={id}>{data}</div>
}}</for>
{data.map(item => <div>{item}</div>)}
{jsxSlice}
<InnerComp />
</div>
)
}
1. Auto Naming Compiler
const MyComp = Component(props => {
let { prop1, prop2: [p20, p21], ...otherProps } = props
let count = 100
let data = props.data
const jsxSlice = <div>{count}</div>
const jsxFunc = () => {
return <div>{count}</div>
}
const InnerComp = Component(() => {
let newCount = 100
return <div>{newCount}</div>
})
console.log("hello")
didMount(() => {
console.log("didMount")
})
if (count === 100) {
return <div>100</div>
}
let flag = true
return (
<div>
{flag}
<h1>{count}</h1>
<for each={data}> {(item, idx) => {
const {id, data} = item
return <div key={id}>{data}{idx}</div>
}}</for>
{data.map(item => <div>{item}</div>)}
{jsxSlice}
<InnerComp />
</div>
)
})
2. JSX Slice Compiler
Identify all non-returning JSX expressions
const MyComp = Component(props => {
let { prop1, prop2: [p20, p21], ...otherProps } = props
let count = 100
let data = props.data
const jsxSlice = new (Component(() => {
return <div>{count}</div>
}))()
const jsxFunc = () => {
return new (Component(() => {
return <div>{count}</div>
}))()
}
const InnerComp = Component(() => {
let newCount = 100
return <div>{newCount}</div>
})
console.log("hello")
didMount(() => {
console.log("didMount")
})
if (count === 100) {
return <div>100</div>
}
let flag = true
return (
<div>
{flag}
<h1>{count}</h1>
<for each={data}> {(item, idx) => {
const {id, data} = item
return <div key={id}>{data}{idx}</div>
}}</for>
{data.map(item => <div>{item}</div>)}
{jsxSlice}
<InnerComp />
</div>
)
})
3. For Loop Compiler - mapping to for tag
const MyComp = Component(props => {
let { prop1, prop2: [p20, p21], ...otherProps } = props
let count = 100
let data = props.data
const jsxSlice = new (Component(() => {
return <div>{count}</div>
}))()
const jsxFunc = () => {
return new (Component(() => {
return <div>{count}</div>
}))()
}
const InnerComp = Component(() => {
let newCount = 100
return <div>{newCount}</div>
})
console.log("hello")
didMount(() => {
console.log("didMount")
})
if (count === 100) {
return <div>100</div>
}
let flag = true
return (
<div>
{flag}
<h1>{count}</h1>
<for each={data}> {(item, idx) => {
const {id, data} = item
return <div key={id}>{data}{idx}</div>
}}</for>
<for each={data}> {(item) => (
<div>{item}</div>
)}</for>
{jsxSlice}
<InnerComp />
</div>
)
})
4. For Loop Compiler - sub component extraction
const MyComp = Component(props => {
let { prop1, prop2: [p20, p21], ...otherProps } = props
let count = 100
let data = props.data
const jsxSlice = new (Component(() => {
return <div>{count}</div>
}))()
const jsxFunc = () => {
return new (Component(() => {
return <div>{count}</div>
}))()
}
const InnerComp = Component(() => {
let newCount = 100
return <div>{newCount}</div>
})
console.log("hello")
didMount(() => {
console.log("didMount")
})
if (count === 100) {
return <div>100</div>
}
const Comp_je104nt = Component(({ item, idx }) => {
const {id, data} = item
return <div key={id}>{data}{idx}</div>
})
return (
<div>
<h1>{count}</h1>
<for each={data}> {(item, idx) => (
<Comp_$id1$ item={item} idx={idx}/>
)}</for>
<for each={data}> {(item) => (
<div>{item}</div>
)}</for>
{jsxSlice}
<InnerComp />
</div>
)
})
5. Early return Compiler
const MyComp = Component(props => {
let { prop1, prop2: [p20, p21], ...otherProps } = props
let count = 100
let data = props.data
const jsxSlice = new (Component(() => {
return <div>{count}</div>
}))()
const jsxFunc = () => {
return new (Component(() => {
return <div>{count}</div>
}))()
}
const InnerComp = Component(() => {
let newCount = 100
return <div>{newCount}</div>
})
console.log("hello")
didMount(() => {
console.log("didMount")
})
const Comp_je104nt = Component(({ item, idx }) => {
const {id, data} = item
return <div key={id}>{data}{idx}</div>
})
const Comp_jf91a2 = Component(() => {
return <div>100</div>
})
const Comp_ao528j = Component(() => {
let flag = true
return (
<div>
{flag}
<h1>{count}</h1>
<for each={data}> {(item, idx) => (
<Comp_$id1$ item={item} idx={idx}/>
)}</for>
<for each={data}> {(item) => (
<div>{item}</div>
)}</for>
{jsxSlice}
<InnerComp />
</div>
)
})
return (
<if cond={count === 100}>
<Comp_jf91a2/>
</if>
<else>
<Comp_ao528j/>
</else>
)
})
Till now, all the implicit components are created, we can now recursively convert them into classes.
6. Props Compiler
const MyComp = Component({ prop1, prop2, data, ...otherProps } => {
let p20, p21
watch(() => {
[p20, p21] = prop2
})
let count = 100
// let data = data -> automatically deleted
const jsxSlice = new (Component(() => {
return <div>{count}</div>
}))()
const jsxFunc = () => {
return new (Component(() => {
return <div>{count}</div>
}))()
}
const InnerComp = Component(() => {
let newCount = 100
return <div>{newCount}</div>
})
console.log("hello")
didMount(() => {
console.log("didMount")
})
const Comp_je104nt = Component(({ item, idx }) => {
let id, data
watch(() => {
{id, data} = item
})
return <div key={id}>{data}{idx}</div>
})
const Comp_jf91a2 = Component(() => {
return <div>100</div>
})
const Comp_ao528j = Component(() => {
let flag = true
return (
<div>
{flag}
<h1>{count}</h1>
<for each={data}> {(item, idx) => (
<Comp_$id1$ item={item} idx={idx}/>
)}</for>
<for each={data}> {(item) => (
<div>{item}</div>
)}</for>
{jsxSlice}
<InnerComp />
</div>
)
})
return (
<if cond={count === 100}>
<Comp_jf91a2/>
</if>
<else>
<Comp_ao528j/>
</else>
)
})
7. Class converter
jsxSlice and other inner components will be tagged as InnerComponents, with extra _$this scope:
const MyComp = class extends View {
@Prop prop1
@Prop prop2
@Prop data
@RestProps otherProps
p20
p21
@Watch
_$watch_aei1nf$() {
[p20, p21] = this.prop2
}
count = 100
jsxSlice = new class extends View {
Body() {
<div>{count}</div>
}
}()
jsxFunc = () => {
return new class extends View {
Body() {
<div>{count}</div>
}
}()
}
InnerComp = class extends View {
newCount = 100
Body() {
<div>{newCount}</div>
}
}
Comp_je104nt = class extends View {
@Prop item
@Prop idx
id
data
@Watch
_$watch_15oae3$() {
{id, data} = this.item
}
Body() {
<div key={id}>{data}{idx}</div>
}
}
Comp_jf91a2 = class extends View {
Body() {
<div>100</div>
}
}
Comp_ao528j = class extends View {
flag = true
Body() {
<div>
{flag}
<h1>{count}</h1>
<for each={data}> {(item, idx) => (
<Comp_$id1$ item={item} idx={idx}/>
)}</for>
<for each={data}> {(item) => (
<div>{item}</div>
)}</for>
{jsxSlice}
<InnerComp />
</div>
}
}
willMount() {
console.log("hello")
}
didMount() {
(() => {
console.log("didMount")
})()
}
Body() {
<if cond={count === 100}>
<Comp_jf91a2 />
</if>
<else>
<Comp_ao528j />
</else>
}
}
8. This converter
Auto add this to variables also record the _$thisX scope to auto add:
const MyComp = class extends View {
@Prop prop1
@Prop prop2
@Prop data
@RestProps otherProps
p20
p21
@Watch
_$watch_aei1nf$() {
[this.p20, this.p21] = this.prop2
}
count = 100
jsxSlice = new ((() => {
const _$this0 = this
return class extends View {
Body() {
<div>{_$this0.count}</div>
}
}
})())()
jsxFunc = () => {
return new ((() => {
const _$this0 = this
return class extends View {
Body() {
<div>{_$this0.count}</div>
}
}
})())()
}
InnerComp = class extends View {
newCount = 100
Body() {
<div>{this.newCount}</div>
}
}
Comp_je104nt = class extends View {
@Prop item
@Prop idx
id
data
@Watch
_$watch_15oae3$() {
{this.id, this.data} = this.item
}
Body() {
<div key={this.id}>{this.data}{this.idx}</div>
}
}
Comp_jf91a2 = class extends View {
Body() {
<div>100</div>
}
}
Comp_ao528j = (() => {
const _$this0 = this
return class extends View {
flag = true
Body() {
<div>
{this.flag}
<h1>{_$this0.count}</h1>
<for each={_$this0.data}> {(item, idx) => (
<_$this0.Comp_$id1$ item={item} idx={idx}/>
)}</for>
<for each={_$this0.data}> {(item) => (
<div>{item}</div>
)}</for>
{_$this0.jsxSlice}
<_$this0.InnerComp />
</div>
}
}
})()
willMount() {
console.log("hello")
}
didMount() {
(() => {
console.log("didMount")
})()
}
Body() {
<if cond={this.count === 100}>
<this.Comp_jf91a2 />
</if>
<else>
<this.Comp_ao528j />
</else>
}
}
8. Decorator Compiler
9. JSX Compiler
InnerComponent need to
- add a
willUnmountlifecycle to clear the parent updates. - push the
updatefunction to all the parent's updates.
InnerComp = class extends View {
newCount = 100
Body() {
<div>{this.newCount}</div>
}
}
To
InnerComp = class extends View {
newCount = 100
willUnmount() {
clear(this)
}
Body() {
const node0 = createElement("div")
const node1 = new ExpressionNode(this.newCount)
appendChild(node0, [node1])
const update = changed => {
if (changed & 0x0001) {
node1.update(this.newCount)
}
}
this._$updates.add(update)
return node0
}
}