From c7de85630e7a724af27d601aac2fc1aada91a5b5 Mon Sep 17 00:00:00 2001 From: Hoikan <30694822+HoikanChan@users.noreply.github.com> Date: Fri, 24 May 2024 17:20:30 +0800 Subject: [PATCH] fix(reactivity): fix for --- .../src/analyze/pruneUnusedBit.ts | 18 +++++--- .../test/analyze/viewAnalyze.test.ts | 17 +++++++ .../test/sugarPlugins/jsxSlice.test.ts | 11 +++++ packages/transpiler/jsx-parser/src/parser.ts | 45 +++++++++++-------- .../jsx-parser/src/test/ForUnit.test.ts | 5 +-- .../reactivity-parser/src/getDependencies.ts | 12 +++-- .../reactivity-parser/src/parser.ts | 29 ++++++------ .../transpiler/reactivity-parser/src/types.ts | 2 +- 8 files changed, 90 insertions(+), 49 deletions(-) diff --git a/packages/transpiler/babel-inula-next-core/src/analyze/pruneUnusedBit.ts b/packages/transpiler/babel-inula-next-core/src/analyze/pruneUnusedBit.ts index 7a0b236a..594a4a26 100644 --- a/packages/transpiler/babel-inula-next-core/src/analyze/pruneUnusedBit.ts +++ b/packages/transpiler/babel-inula-next-core/src/analyze/pruneUnusedBit.ts @@ -31,10 +31,8 @@ export function pruneUnusedBit( index = 1, bitPositionToRemoveInParent: number[] = [] ) { - // dfs the component tree - // To store the bitmap of the properties - const bitMap = new Map(); const bitPositionToRemove: number[] = [...bitPositionToRemoveInParent]; + // dfs the component tree comp.variables.forEach(v => { if (v.type === 'reactive') { // get the origin bit, computed should keep the highest bit, etc. 0b0111 -> 0b0100 @@ -42,9 +40,7 @@ export function pruneUnusedBit( if (comp.usedBit & originBit) { v.bit = 1 << (index - bitPositionToRemove.length - 1); - bitMap.set(v.name, v.bit); if (v.dependency) { - // 去掉最高位 v.dependency.depMask = getDepMask(v.dependency.depBitmaps, bitPositionToRemove); } } else { @@ -91,11 +87,19 @@ function pruneBitmap(depMask: Bitmap, bitPositionToRemove: number[]) { return parseInt(result, 2); } +/** + * Get the depMask by pruning the bitPositionToRemove + * The reason why we need to get the depMask from depBitmaps instead of fullDepMask is that + * the fullDepMask contains the bit of used variables, which is not the direct dependency + * + * @param depBitmaps + * @param bitPositionToRemove + */ function getDepMask(depBitmaps: Bitmap[], bitPositionToRemove: number[]) { // prune each dependency bitmap and combine them return depBitmaps.reduce((acc, cur) => { - const a = pruneBitmap(cur, bitPositionToRemove); - return keepHighestBit(a) | acc; + // computed should keep the highest bit, others should be pruned + return keepHighestBit(pruneBitmap(cur, bitPositionToRemove)) | acc; }, 0); } diff --git a/packages/transpiler/babel-inula-next-core/test/analyze/viewAnalyze.test.ts b/packages/transpiler/babel-inula-next-core/test/analyze/viewAnalyze.test.ts index 2ed52525..522db010 100644 --- a/packages/transpiler/babel-inula-next-core/test/analyze/viewAnalyze.test.ts +++ b/packages/transpiler/babel-inula-next-core/test/analyze/viewAnalyze.test.ts @@ -56,4 +56,21 @@ describe('viewAnalyze', () => { expect(div.children[0].content.depMask).toEqual(0b1); expect(genCode(div.children[0].content.dependenciesNode)).toMatchInlineSnapshot(`"[info?.firstName]"`); }); + + it('should analyze for loop', () => { + const root = analyze(/*js*/ ` + Component(({}) => { + const unused = 0; + const prefix = 'test'; + const list = [{name: 1}, {name: 2}, {name: 3}]; + const list2 = [{name: 4}, {name: 5}, {name: 6}]; + return { + ({name}) =>
{prefix + name}
+ }
; + }); + `); + const forNode = root.children![0] as any; + expect(forNode.children[0].children[0].content.depMask).toEqual(0b111); + expect(genCode(forNode.children[0].children[0].content.dependenciesNode)).toMatchInlineSnapshot(`"[prefix, name]"`); + }); }); diff --git a/packages/transpiler/babel-inula-next-core/test/sugarPlugins/jsxSlice.test.ts b/packages/transpiler/babel-inula-next-core/test/sugarPlugins/jsxSlice.test.ts index 220f4533..cfaa5fdc 100644 --- a/packages/transpiler/babel-inula-next-core/test/sugarPlugins/jsxSlice.test.ts +++ b/packages/transpiler/babel-inula-next-core/test/sugarPlugins/jsxSlice.test.ts @@ -83,4 +83,15 @@ describe('jsx slice', () => { }" `); }); + + // TODO: Fix this test + it.skip('should work with jsx slice in function', () => { + // function App() { + // const fn = ([x, y, z]) => { + // return
{x}, {y}, {z}
+ // } + // + // return
{fn([1, 2, 3])}
+ // } + }); }); diff --git a/packages/transpiler/jsx-parser/src/parser.ts b/packages/transpiler/jsx-parser/src/parser.ts index f1c22861..07e08af4 100644 --- a/packages/transpiler/jsx-parser/src/parser.ts +++ b/packages/transpiler/jsx-parser/src/parser.ts @@ -537,8 +537,8 @@ export class ViewParser { private pareFor(node: t.JSXElement) { // ---- Get array - const arrayContainer = this.findProp(node, 'array'); - if (!arrayContainer) throw new Error('Missing [array] prop in for loop'); + const arrayContainer = this.findProp(node, 'each'); + if (!arrayContainer) throw new Error('Missing [each] prop in for loop'); if (!this.t.isJSXExpressionContainer(arrayContainer.value)) throw new Error('Expected expression container for [array] prop'); const array = arrayContainer.value.expression; @@ -554,24 +554,31 @@ export class ViewParser { } // ---- Get Item - const itemProp = this.findProp(node, 'item'); - if (!itemProp) throw new Error('Missing [item] prop in for loop'); - if (!this.t.isJSXExpressionContainer(itemProp.value)) - throw new Error('Expected expression container for [item] prop'); - const item = itemProp.value.expression; - if (this.t.isJSXEmptyExpression(item)) throw new Error('Expected [item] expression not empty'); - // ---- ObjectExpression to ObjectPattern / ArrayExpression to ArrayPattern - this.traverse(this.wrapWithFile(item), { - ObjectExpression: path => { - path.node.type = 'ObjectPattern' as any; - }, - ArrayExpression: path => { - path.node.type = 'ArrayPattern' as any; - }, - }); + // + // {(item, idx)=>()} + // + const jsxChildren = node.children; + if (jsxChildren.length !== 1) throw new Error('Expected 1 child'); + if (jsxChildren[0].type !== 'JSXExpressionContainer') throw new Error('Expected expression container'); + const itemFnNode = jsxChildren[0].expression; + if (this.t.isJSXEmptyExpression(itemFnNode)) throw new Error('Expected expression not empty'); - // ---- Get children - const children = this.t.jsxFragment(this.t.jsxOpeningFragment(), this.t.jsxClosingFragment(), node.children); + let children; + if (!this.t.isFunctionExpression(itemFnNode) && !this.t.isArrowFunctionExpression(itemFnNode)) { + throw new Error('For: Expected function expression'); + } + // get the return value + if (this.t.isBlockStatement(itemFnNode.body)) { + if (itemFnNode.body.body.length !== 1) throw new Error('For: Expected 1 statement in block statement'); + if (!this.t.isReturnStatement(itemFnNode.body.body[0])) + throw new Error('For: Expected return statement in block statement'); + children = itemFnNode.body.body[0].argument; + } else { + children = itemFnNode.body; + } + + const item = itemFnNode.params[0]; + if (!this.t.isJSXElement(children)) throw new Error('For: Expected jsx element in return statement'); this.viewUnits.push({ type: 'for', diff --git a/packages/transpiler/jsx-parser/src/test/ForUnit.test.ts b/packages/transpiler/jsx-parser/src/test/ForUnit.test.ts index 3fea384e..f0e2e877 100644 --- a/packages/transpiler/jsx-parser/src/test/ForUnit.test.ts +++ b/packages/transpiler/jsx-parser/src/test/ForUnit.test.ts @@ -1,11 +1,10 @@ import { describe, expect, it } from 'vitest'; import { parse } from './mock'; - describe('ForUnit', () => { it('should identify for unit', () => { - const viewUnits = parse('
{item}
'); + const viewUnits = parse('{([x, y, z], idx) => }'); expect(viewUnits.length).toBe(1); expect(viewUnits[0].type).toBe('for'); }); -}); \ No newline at end of file +}); diff --git a/packages/transpiler/reactivity-parser/src/getDependencies.ts b/packages/transpiler/reactivity-parser/src/getDependencies.ts index e825df79..d098ecec 100644 --- a/packages/transpiler/reactivity-parser/src/getDependencies.ts +++ b/packages/transpiler/reactivity-parser/src/getDependencies.ts @@ -40,7 +40,7 @@ export type Dependency = { */ export function getDependenciesFromNode( node: t.Expression | t.Statement, - reactiveMap: Map, + reactiveMap: Map, reactivityFuncNames: string[] ): Dependency { // ---- Deps: console.log(count) @@ -57,9 +57,15 @@ export function getDependenciesFromNode( if (reactiveBitmap !== undefined) { if (isAssignmentExpressionLeft(innerPath) || isAssignmentFunction(innerPath, reactivityFuncNames)) { - assignDepMask |= reactiveBitmap; + assignDepMask |= Array.isArray(reactiveBitmap) + ? reactiveBitmap.reduce((acc, cur) => acc | cur, 0) + : reactiveBitmap; } else { - depBitmaps.push(reactiveBitmap); + if (Array.isArray(reactiveBitmap)) { + depBitmaps.push(...reactiveBitmap); + } else { + depBitmaps.push(reactiveBitmap); + } if (!depNodes[propertyKey]) depNodes[propertyKey] = []; depNodes[propertyKey].push(geneDependencyNode(innerPath)); diff --git a/packages/transpiler/reactivity-parser/src/parser.ts b/packages/transpiler/reactivity-parser/src/parser.ts index 93ced2df..b9f67e04 100644 --- a/packages/transpiler/reactivity-parser/src/parser.ts +++ b/packages/transpiler/reactivity-parser/src/parser.ts @@ -319,34 +319,31 @@ export class ReactivityParser { * @returns ForParticle */ private parseFor(forUnit: ForUnit): ForParticle { - const { depMask, dependenciesNode } = this.getDependencies(forUnit.array); - const prevIdentifierDepMap = this.config.depMaskMap; - // ---- Find all the identifiers in the key and remove them from the identifierDepMap - // because once the key is changed, that identifier related dependencies will be changed too, - // so no need to update them - const keyDep = this.t.isIdentifier(forUnit.key) && forUnit.key.name; + const { fullDepMask, dependenciesNode, depBitmaps } = this.getDependencies(forUnit.array); + const prevMap = this.config.depMaskMap; + // ---- Generate an identifierDepMap to track identifiers in item and make them reactive // based on the dependencies from the array - // this.config.depMaskMap = new Map([ - // ...this.config.depMaskMap, - // ...this.getIdentifiers(this.t.assignmentExpression('=', forUnit.item, this.t.objectExpression([]))) - // .filter(id => !keyDep || id !== keyDep) - // .map(id => [id, depMask]), - // ]); + // Just wrap the item in an assignment expression to get all the identifiers + const itemWrapper = this.t.assignmentExpression('=', forUnit.item, this.t.objectExpression([])); + this.config.depMaskMap = new Map([ + ...this.config.depMaskMap, + ...this.getIdentifiers(itemWrapper).map(id => [id, depBitmaps] as const), + ]); const forParticle: ForParticle = { type: 'for', item: forUnit.item, array: { value: forUnit.array, - depBitmaps: [], - depMask: depMask, + depBitmaps, + depMask: fullDepMask, dependenciesNode, }, children: forUnit.children.map(this.parseViewParticle.bind(this)), key: forUnit.key, }; - // this.config.identifierDepMap = prevIdentifierDepMap; + this.config.depMaskMap = prevMap; return forParticle; } @@ -427,7 +424,7 @@ export class ReactivityParser { private getDependencies(node: t.Expression | t.Statement) { if (this.t.isFunctionExpression(node) || this.t.isArrowFunctionExpression(node)) { return { - depMask: 0, + fullDepMask: 0, depBitmaps: [], dependenciesNode: this.t.arrayExpression([]), }; diff --git a/packages/transpiler/reactivity-parser/src/types.ts b/packages/transpiler/reactivity-parser/src/types.ts index 0592badc..b22b2e20 100644 --- a/packages/transpiler/reactivity-parser/src/types.ts +++ b/packages/transpiler/reactivity-parser/src/types.ts @@ -104,4 +104,4 @@ export interface ReactivityParserConfig { // TODO: unify with the types in babel-inula-next-core export type Bitmap = number; -export type DepMaskMap = Map; +export type DepMaskMap = Map;