diff --git a/.gitignore b/.gitignore index 8636e699..b4546b5d 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,7 @@ pnpm-lock.yaml build /packages/inula-router/connectRouter /packages/inula-router/router + +.history +**/dist +**/node_modules \ No newline at end of file diff --git a/api-2.1.md b/api-2.1.md new file mode 100644 index 00000000..83bcdabc --- /dev/null +++ b/api-2.1.md @@ -0,0 +1,1870 @@ +# Identify a Component/hook +## 1. Auto detection config turned on in Babel +```json +{ + "autoNamingDetection": true +} +``` +Will follow the following rules: +* For Components: + 1. Inside a .jsx file + 2. Function/ArrowFunction Named as PascalCase + ```jsx + // ✅ + function MyComp() { + return ( +
+

Hello

+
+ ) + } + // ❌ + function foo() { + return ( +
+

Hello

+
+ ) + } + ``` +* For hooks: + 1. Function/ArrowFunction starts with "use" + ```jsx + // ✅ + 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 `Component` function +```jsx +const myComp = Component(function() { + return ( +
+

Hello

+
+ ) +}) +``` +* For hooks, wrapped with `Hook` function +```jsx +const myHook = Hook(function() { + return 1 +}) +``` + + +# States/Computed +```jsx +function MyComp() { + let a, b + let count = 100 + let doubleCount = count * 2 + let [a1, a2, a3] = getArray() + ... +} + +``` + +```jsx +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 +```jsx +function MyComp({ prop1, prop2: [p20, p21], ...otherProps }) { + ... +} +``` +Collected as: +```js +props = ["prop1", "prop2"] +destructured = [ + ["prop2": astOf("[p20, p21]")], +] +restProps = "otherProps" +``` +### Deconstructing assignment / Direct usage in the function body +#### 1. Deconstructing assignment +```jsx +function MyComp(props) { + const { prop1, prop2: [p20, p21], ...otherProps } = props + ... +} +``` +Collected as: +```js +props = ["prop1", "prop2"] +destructured = [ + ["prop2", astOf("[p20, p21]")], +] +restProps = "otherProps" +``` +After: +* Delete this statement + +#### 2. Direct usage +```jsx +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: +```js +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.propName` with `propName` + +Goals of these two "After"s: +* Make sure no `props` is used in the function body + +## Step2: Generate Props in class +With the collected props/destructured/restProps: +```jsx +props = ["prop1", "prop2"] +destructured = [ + ["prop2", astOf("[p20, p21]")], +] +restProps = "otherProps" +``` +Generate: +```jsx +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 +1. Multiple deconstructed statements are allowed: +```jsx +function MyComp(props) { + const { prop1, prop2: [p20, p21] } = props + const { prop1: p1, prop2: [p20A, p21A] } = props + ... +} +``` +will be collected as: +```js +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: +```jsx +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 + } +} +``` + +2. Multiple restProps are NOT allowed: +```jsx +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. + +3. Anywhere that `props` is used in the function body will be replaced with the prop name directly: +```jsx +function MyComp(props) { + let p1 = props.prop1 + console.log(props.prop2) + console.log(props.prop3.xx[0].yy) + ... +} +``` +will be replaced as: +```jsx +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): +```jsx +function MyComp() { + let arr = [1, 2, 3] + return ( + {(item) => +
{item}
+ }
+ ) +} +``` + + +## functional syntax level support +### 1. React inherited flavor: Expression mapping +```jsx +function MyComp() { + let arr = [1, 2, 3] + return ( + <> + {arr.map(item =>
{item}
)} + + ) +} +``` +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): +```jsx +function MyComp() { + let arr = [1, 2, 3] + return ( + {(item) => +
{item}
+ }
+ ) +} +``` +NOTE: only detect the last map function: +```jsx +function MyComp() { + let arr = [1, 2, 3] + return ( + <> + { + Object.values(arr) + .map(item =>

{item}

) + .map(item =>
{item}
) + } + + ) +} +``` +will be converted into: +```jsx +function MyComp() { + let arr = [1, 2, 3] + return ( +

{item}

) + }>{(item) => +
{item}
+ }
+ ) +} +``` + +### 2. Children +1. Simple returned value (no local variables). Children will be treated as regular elements/components +```jsx +function MyComp() { + return ( + {(item) => +
{item}
+ }
+ ) +} +``` +children collected in the parser will only be: +```jsx +
{item}
+``` + +2. With local variables. Transformed into a new Component: +```jsx +function MyComp() { + return ( + {(info) => { + const { item } = info + return
{item}
+ }}
+ ) +} +``` +will be converted into: +```jsx +function MyComp() { + function Comp_$id$({ info }) { + const { item } = info + return
{item}
+ } + + return ( + {(info) => ( + + )} + ) +} +``` +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: +```jsx +function MyComp() { + let arr = [1, 2, 3] + return ( + {(item) => +
{item}
+ }
+ ) +} +``` + + +# 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: +```jsx +function MyComp() { + let count = 0 + let jsxSlice =
{count}
+ ... +} +``` +We have different options: +1. jsx -> array of nodes + +we can use an IIFE to convert the JSX into an array of nodes: +```jsx +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: +```jsx +function MyComp() { + let count = 0 + function Comp_$id$() { + return
{count}
+ } + let jsxSlice = + ... +} +``` +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: +```jsx +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: +```jsx +function MyComp() { + let count = 100 + const jsxSlice =
{count}
+ const jsxArray = [
1
,
{count}
] + function jsxFunc() { + // This is a function that returns JSX + // because the function name is smallCamelCased + return
{count}
+ } + function InternalComp({ doubleCount }) { + return ( +
+ {count /* This is Parent's state */} + {doubleCount /* This is current's prop */} +
+ ) + } + + return ( +
+ {jsxSlice} + {jsxArray} + {jsxFunc()} + +
+ ) +} +``` +first, we convert the JSX into nested components: +```jsx +function MyComp() { + let count = 100 + function Comp_$id1$() { + return
{count}
+ } + const jsxSlice = + function Comp_$id2$() { + return
1
+ } + function Comp_$id3$() { + return
{count}
+ } + const jsxArray = [, ] + function jsxFunc() { + function Comp_$id4$() { + return
{count}
+ } + // This is a function that returns JSX + // because the function name is smallCamelCased + return + } + function InternalComp({ doubleCount }) { + return ( +
+ {count /* This is Parent's state */} + {doubleCount /* This is current's prop */} +
+ ) + } + + return ( +
+ {jsxSlice} + {jsxArray} + {jsxFunc()} + +
+ ) +} +``` +will be converted into: +```jsx +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.... + +```jsx +function MyComp() { + let count = 100 + if (count === 100) { + return
100
+ } + let flag = true + return
Not 100 {flag}
+} +``` +it will be converted in a functional level: +```jsx +function MyComp() { + let count = 100 + function Comp_$id1$() { + return
100
+ } + function Comp_$id2$() { + let flag = true + return
Not 100 {flag}
+ } + + return ( + <> + + + + + + + + ) +} +``` +and then be converted into class using the same jsx logic as the previous section. + +A more complex example: +```jsx +function MyComp() { + let count = 100 + if (count === 100) { + return
100
+ } + let flag = true + if (flag) { + let cc = 0 + if (cc === 200) { + return
cc is 200
+ } else if (cc === 100) { + return
cc is 100
+ } + return
Flag is true
+ } + return
Not 100 {flag}
+} +``` +will be converted into: +```jsx +function MyComp() { + let count = 100 + function Comp_$id1$() { + return
100
+ } + function Comp_$id2$() { + let flag = true + function Comp_$id3$() { + let cc = 0 + function Comp_$id4$() { + return
cc is 200
+ } + function Comp_$id5$() { + return
cc is 100
+ } + function Comp_$id6$() { + return
Flag is true
+ } + return ( + <> + + + + + + + + + + + ) + } + function Comp_$id7$() { + return
Not 100 {flag}
+ } + return ( + <> + + + + + + + + ) + } + return ( + <> + + + + + + + + ) +} +``` +Same rule will be applied to switch statements(TBD). + +## How to pass view props to another component +Consider the following 3 types of solutions: +1. A calculated jsx slice: +```jsx +function MyComp1() { + let count = 0 + return {count}}/> +} +function Comp({ view }) { + return <>{view} +} +``` +will be converted into: +```jsx +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! + +2. Functional nested Component: +```jsx +function MyComp2() { + let count = 0 + function SubComp() { + return
{count}
+ } + return +} +function Comp({ View }) { + return +} +``` +will be converted into: +```jsx +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`. + +3. Functional calculated JSX slice: +```jsx +function MyComp3() { + let count = 0 + return
{count}
}/> +} +function Comp({ viewFunc }) { + return <>{viewFunc()} +} +``` +will be converted into: +```jsx +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 +```jsx +function MyComp() { + let count = 100 + console.log(count) + console.log("Hello") + anyFunction() + ... +} +``` +will be converted as: +```jsx +class MyComp extends View { + count = 100 + + willMount() { + console.log(this.count) + console.log("Hello") + anyFunction() + } +} +``` + +# Lifecycle +## 1. willMount +```jsx +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: +```jsx +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 +```jsx +function MyComp() { + didMount(() => { + console.log("didMount") + }) + willUnmount(async () => { + console.log("willUnmount") + }) + didUnmount(() => { + console.log("didUnmount") + }) +} +``` +will be converted as: +```jsx +class MyComp extends View { + didMount() { + (() => { + console.log("didMount") + })() + } + willUnmount() { + (async () => { + console.log("willUnmount") + })() + } + didUnmount() { + (() => { + console.log("didUnmount") + })() + } +} +``` + +# Watch +```jsx +function MyComp() { + let count = 100 + watch(() => { + console.log(count) + }) +} +``` +will be converted as: +```jsx +class MyComp extends View { + count = 100 + + @Watch + _$watch_$id$() { + console.log(this.count) + } +} +``` +With manual dependencies: +```jsx +function MyComp() { + let count = 100 + let doubleCount = count * 2 + watch(() => { + console.log(count, doubleCount) + }, [count, doubleCount]) +} +``` +will be converted as: +```jsx +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 +```jsx +function useMyHook() { + let count = 100 + + return count +} +``` +will be converted into: +```jsx +class MyHook extends Model { + count = 100 + + _$return = this.count +} +``` + +used in a component: +```jsx +function MyComp() { + const count = useMyHook() + return
{count}
+} +``` +will be converted into: +```jsx +class MyComp extends View { + count = use(useMyHook)._$return + + Body() { + toBeCompiled(
{this.count}
) + } +} +``` +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: +```jsx +function useMyHook() { + let count = 100 + let doubleCount = count * 2 + + return { count, doubleCount } +} +``` +will be converted into: +```jsx +class MyHook extends Model { + count = 100 + doubleCount = this.count * 2 + + _$return = { count: this.count, doubleCount: this.doubleCount } +} +``` + +used in a component: +```jsx +function MyComp() { + const { count, doubleCount } = useMyHook() + return
{count} {doubleCount}
+} +``` +will be converted into: +```jsx +class MyComp extends View { + count + doubleCount + @Watch + _$deconstruct_$id$() { + const { count, doubleCount } = use(useMyHook)._$return + this.count = count + this.doubleCount = doubleCount + } + + Body() { + toBeCompiled(
{this.count} {this.doubleCount}
) + } +} +``` + +Early return in hooks: +```jsx +function useMyHook() { + let count = 100 + if (count === 100) { + return 100 + } + let flag = count === 200 + return flag +} +``` +will be converted first into: +```jsx +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: +```jsx +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: +```jsx +function MyComp(props) { + let { prop1, prop2: [p20, p21], ...otherProps } = props + let count = 100 + let data = props.data + const jsxSlice =
{count}
+ + const jsxFunc = () => { + return
{count}
+ } + + function InnerComp() { + let newCount = 100 + return
{newCount}
+ } + console.log("hello") + didMount(() => { + console.log("didMount") + }) + + if (count === 100) { + return
100
+ } + + let flag = true + + return ( +
+ {flag} +

{count}

+ {(item) => { + const {id, data} = item + return
{data}
+ }}
+ {data.map(item =>
{item}
)} + {jsxSlice} + +
+ ) +} +``` + +## 1. Auto Naming Compiler +```jsx +const MyComp = Component(props => { + let { prop1, prop2: [p20, p21], ...otherProps } = props + let count = 100 + let data = props.data + const jsxSlice =
{count}
+ + const jsxFunc = () => { + return
{count}
+ } + + const InnerComp = Component(() => { + let newCount = 100 + return
{newCount}
+ }) + + console.log("hello") + didMount(() => { + console.log("didMount") + }) + + if (count === 100) { + return
100
+ } + + let flag = true + + return ( +
+ {flag} +

{count}

+ {(item, idx) => { + const {id, data} = item + return
{data}{idx}
+ }}
+ {data.map(item =>
{item}
)} + {jsxSlice} + +
+ ) +}) +``` + +## 2. JSX Slice Compiler +Identify all non-returning JSX expressions +```jsx +const MyComp = Component(props => { + let { prop1, prop2: [p20, p21], ...otherProps } = props + let count = 100 + let data = props.data + + const jsxSlice = new (Component(() => { + return
{count}
+ }))() + + const jsxFunc = () => { + return new (Component(() => { + return
{count}
+ }))() + } + + const InnerComp = Component(() => { + let newCount = 100 + return
{newCount}
+ }) + + console.log("hello") + didMount(() => { + console.log("didMount") + }) + + if (count === 100) { + return
100
+ } + + let flag = true + + return ( +
+ {flag} +

{count}

+ {(item, idx) => { + const {id, data} = item + return
{data}{idx}
+ }}
+ {data.map(item =>
{item}
)} + {jsxSlice} + +
+ ) +}) +``` + +## 3. For Loop Compiler - mapping to for tag +```jsx +const MyComp = Component(props => { + let { prop1, prop2: [p20, p21], ...otherProps } = props + let count = 100 + let data = props.data + + const jsxSlice = new (Component(() => { + return
{count}
+ }))() + + const jsxFunc = () => { + return new (Component(() => { + return
{count}
+ }))() + } + + const InnerComp = Component(() => { + let newCount = 100 + return
{newCount}
+ }) + + console.log("hello") + didMount(() => { + console.log("didMount") + }) + + if (count === 100) { + return
100
+ } + + let flag = true + + return ( +
+ {flag} +

{count}

+ {(item, idx) => { + const {id, data} = item + return
{data}{idx}
+ }}
+ {(item) => ( +
{item}
+ )}
+ {jsxSlice} + +
+ ) +}) +``` + +## 4. For Loop Compiler - sub component extraction +```jsx +const MyComp = Component(props => { + let { prop1, prop2: [p20, p21], ...otherProps } = props + let count = 100 + let data = props.data + + const jsxSlice = new (Component(() => { + return
{count}
+ }))() + + const jsxFunc = () => { + return new (Component(() => { + return
{count}
+ }))() + } + + const InnerComp = Component(() => { + let newCount = 100 + return
{newCount}
+ }) + + console.log("hello") + didMount(() => { + console.log("didMount") + }) + + if (count === 100) { + return
100
+ } + + const Comp_je104nt = Component(({ item, idx }) => { + const {id, data} = item + return
{data}{idx}
+ }) + + return ( +
+

{count}

+ {(item, idx) => ( + + )} + {(item) => ( +
{item}
+ )}
+ {jsxSlice} + +
+ ) +}) +``` + +## 5. Early return Compiler +```jsx +const MyComp = Component(props => { + let { prop1, prop2: [p20, p21], ...otherProps } = props + let count = 100 + let data = props.data + + const jsxSlice = new (Component(() => { + return
{count}
+ }))() + + const jsxFunc = () => { + return new (Component(() => { + return
{count}
+ }))() + } + + const InnerComp = Component(() => { + let newCount = 100 + return
{newCount}
+ }) + + console.log("hello") + didMount(() => { + console.log("didMount") + }) + + const Comp_je104nt = Component(({ item, idx }) => { + const {id, data} = item + return
{data}{idx}
+ }) + + const Comp_jf91a2 = Component(() => { + return
100
+ }) + + const Comp_ao528j = Component(() => { + let flag = true + return ( +
+ {flag} +

{count}

+ {(item, idx) => ( + + )} + {(item) => ( +
{item}
+ )}
+ {jsxSlice} + +
+ ) + }) + + return ( + + + + + + + ) +}) +``` + +---- +Till now, all the implicit components are created, we can now recursively convert them into classes. +---- + +## 6. Props Compiler +```jsx +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
{count}
+ }))() + + const jsxFunc = () => { + return new (Component(() => { + return
{count}
+ }))() + } + + const InnerComp = Component(() => { + let newCount = 100 + return
{newCount}
+ }) + + console.log("hello") + didMount(() => { + console.log("didMount") + }) + + const Comp_je104nt = Component(({ item, idx }) => { + let id, data + watch(() => { + {id, data} = item + }) + return
{data}{idx}
+ }) + + const Comp_jf91a2 = Component(() => { + return
100
+ }) + + const Comp_ao528j = Component(() => { + let flag = true + return ( +
+ {flag} +

{count}

+ {(item, idx) => ( + + )} + {(item) => ( +
{item}
+ )}
+ {jsxSlice} + +
+ ) + }) + + return ( + + + + + + + ) +}) +``` + + +## 7. Class converter +`jsxSlice` and other inner components will be tagged as `InnerComponents`, with extra `_$this` scope: +```jsx +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() { +
{count}
+ } + }() + + jsxFunc = () => { + return new class extends View { + Body() { +
{count}
+ } + }() + } + + InnerComp = class extends View { + newCount = 100 + Body() { +
{newCount}
+ } + } + + Comp_je104nt = class extends View { + @Prop item + @Prop idx + id + data + @Watch + _$watch_15oae3$() { + {id, data} = this.item + } + + Body() { +
{data}{idx}
+ } + } + + Comp_jf91a2 = class extends View { + Body() { +
100
+ } + } + + Comp_ao528j = class extends View { + flag = true + Body() { +
+ {flag} +

{count}

+ {(item, idx) => ( + + )} + {(item) => ( +
{item}
+ )}
+ {jsxSlice} + +
+ } + } + + willMount() { + console.log("hello") + } + didMount() { + (() => { + console.log("didMount") + })() + } + + + Body() { + + + + + + + } +} +``` + +## 8. This converter +Auto add `this` to variables also record the `_$thisX` scope to auto add: +```jsx +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() { +
{_$this0.count}
+ } + } + })())() + + jsxFunc = () => { + return new ((() => { + const _$this0 = this + return class extends View { + Body() { +
{_$this0.count}
+ } + } + })())() + } + + InnerComp = class extends View { + newCount = 100 + Body() { +
{this.newCount}
+ } + } + + Comp_je104nt = class extends View { + @Prop item + @Prop idx + id + data + @Watch + _$watch_15oae3$() { + {this.id, this.data} = this.item + } + + Body() { +
{this.data}{this.idx}
+ } + } + + Comp_jf91a2 = class extends View { + Body() { +
100
+ } + } + + Comp_ao528j = (() => { + const _$this0 = this + return class extends View { + flag = true + Body() { +
+ {this.flag} +

{_$this0.count}

+ {(item, idx) => ( + <_$this0.Comp_$id1$ item={item} idx={idx}/> + )} + {(item) => ( +
{item}
+ )}
+ {_$this0.jsxSlice} + <_$this0.InnerComp /> +
+ } + } + })() + + willMount() { + console.log("hello") + } + didMount() { + (() => { + console.log("didMount") + })() + } + + + Body() { + + + + + + + } +} +``` + +## 8. Decorator Compiler + + +## 9. JSX Compiler +InnerComponent need to +1. add a `willUnmount` lifecycle to clear the parent updates. +2. push the `update` function to all the parent's updates. + +```jsx + InnerComp = class extends View { + newCount = 100 + Body() { +
{this.newCount}
+ } + } +``` +To +```jsx + 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 + } + } +``` \ No newline at end of file