fix(reactivity): fix for
This commit is contained in:
parent
00418eba82
commit
c7de85630e
|
@ -31,10 +31,8 @@ export function pruneUnusedBit(
|
||||||
index = 1,
|
index = 1,
|
||||||
bitPositionToRemoveInParent: number[] = []
|
bitPositionToRemoveInParent: number[] = []
|
||||||
) {
|
) {
|
||||||
// dfs the component tree
|
|
||||||
// To store the bitmap of the properties
|
|
||||||
const bitMap = new Map<string, number>();
|
|
||||||
const bitPositionToRemove: number[] = [...bitPositionToRemoveInParent];
|
const bitPositionToRemove: number[] = [...bitPositionToRemoveInParent];
|
||||||
|
// dfs the component tree
|
||||||
comp.variables.forEach(v => {
|
comp.variables.forEach(v => {
|
||||||
if (v.type === 'reactive') {
|
if (v.type === 'reactive') {
|
||||||
// get the origin bit, computed should keep the highest bit, etc. 0b0111 -> 0b0100
|
// get the origin bit, computed should keep the highest bit, etc. 0b0111 -> 0b0100
|
||||||
|
@ -42,9 +40,7 @@ export function pruneUnusedBit(
|
||||||
|
|
||||||
if (comp.usedBit & originBit) {
|
if (comp.usedBit & originBit) {
|
||||||
v.bit = 1 << (index - bitPositionToRemove.length - 1);
|
v.bit = 1 << (index - bitPositionToRemove.length - 1);
|
||||||
bitMap.set(v.name, v.bit);
|
|
||||||
if (v.dependency) {
|
if (v.dependency) {
|
||||||
// 去掉最高位
|
|
||||||
v.dependency.depMask = getDepMask(v.dependency.depBitmaps, bitPositionToRemove);
|
v.dependency.depMask = getDepMask(v.dependency.depBitmaps, bitPositionToRemove);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -91,11 +87,19 @@ function pruneBitmap(depMask: Bitmap, bitPositionToRemove: number[]) {
|
||||||
return parseInt(result, 2);
|
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[]) {
|
function getDepMask(depBitmaps: Bitmap[], bitPositionToRemove: number[]) {
|
||||||
// prune each dependency bitmap and combine them
|
// prune each dependency bitmap and combine them
|
||||||
return depBitmaps.reduce((acc, cur) => {
|
return depBitmaps.reduce((acc, cur) => {
|
||||||
const a = pruneBitmap(cur, bitPositionToRemove);
|
// computed should keep the highest bit, others should be pruned
|
||||||
return keepHighestBit(a) | acc;
|
return keepHighestBit(pruneBitmap(cur, bitPositionToRemove)) | acc;
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,4 +56,21 @@ describe('viewAnalyze', () => {
|
||||||
expect(div.children[0].content.depMask).toEqual(0b1);
|
expect(div.children[0].content.depMask).toEqual(0b1);
|
||||||
expect(genCode(div.children[0].content.dependenciesNode)).toMatchInlineSnapshot(`"[info?.firstName]"`);
|
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 <for each={[...list, ...list2]}>{
|
||||||
|
({name}) => <div>{prefix + name}</div>
|
||||||
|
}</for>;
|
||||||
|
});
|
||||||
|
`);
|
||||||
|
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]"`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 <div>{x}, {y}, {z}</div>
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return <div>{fn([1, 2, 3])}</div>
|
||||||
|
// }
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -537,8 +537,8 @@ export class ViewParser {
|
||||||
|
|
||||||
private pareFor(node: t.JSXElement) {
|
private pareFor(node: t.JSXElement) {
|
||||||
// ---- Get array
|
// ---- Get array
|
||||||
const arrayContainer = this.findProp(node, 'array');
|
const arrayContainer = this.findProp(node, 'each');
|
||||||
if (!arrayContainer) throw new Error('Missing [array] prop in for loop');
|
if (!arrayContainer) throw new Error('Missing [each] prop in for loop');
|
||||||
if (!this.t.isJSXExpressionContainer(arrayContainer.value))
|
if (!this.t.isJSXExpressionContainer(arrayContainer.value))
|
||||||
throw new Error('Expected expression container for [array] prop');
|
throw new Error('Expected expression container for [array] prop');
|
||||||
const array = arrayContainer.value.expression;
|
const array = arrayContainer.value.expression;
|
||||||
|
@ -554,24 +554,31 @@ export class ViewParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Get Item
|
// ---- Get Item
|
||||||
const itemProp = this.findProp(node, 'item');
|
// <for each={data}>
|
||||||
if (!itemProp) throw new Error('Missing [item] prop in for loop');
|
// {(item, idx)=>(<Comp_$id1$item={item}idx={idx}/>)}
|
||||||
if (!this.t.isJSXExpressionContainer(itemProp.value))
|
// </for>
|
||||||
throw new Error('Expected expression container for [item] prop');
|
const jsxChildren = node.children;
|
||||||
const item = itemProp.value.expression;
|
if (jsxChildren.length !== 1) throw new Error('Expected 1 child');
|
||||||
if (this.t.isJSXEmptyExpression(item)) throw new Error('Expected [item] expression not empty');
|
if (jsxChildren[0].type !== 'JSXExpressionContainer') throw new Error('Expected expression container');
|
||||||
// ---- ObjectExpression to ObjectPattern / ArrayExpression to ArrayPattern
|
const itemFnNode = jsxChildren[0].expression;
|
||||||
this.traverse(this.wrapWithFile(item), {
|
if (this.t.isJSXEmptyExpression(itemFnNode)) throw new Error('Expected expression not empty');
|
||||||
ObjectExpression: path => {
|
|
||||||
path.node.type = 'ObjectPattern' as any;
|
|
||||||
},
|
|
||||||
ArrayExpression: path => {
|
|
||||||
path.node.type = 'ArrayPattern' as any;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// ---- Get children
|
let children;
|
||||||
const children = this.t.jsxFragment(this.t.jsxOpeningFragment(), this.t.jsxClosingFragment(), node.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({
|
this.viewUnits.push({
|
||||||
type: 'for',
|
type: 'for',
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { parse } from './mock';
|
import { parse } from './mock';
|
||||||
|
|
||||||
|
|
||||||
describe('ForUnit', () => {
|
describe('ForUnit', () => {
|
||||||
it('should identify for unit', () => {
|
it('should identify for unit', () => {
|
||||||
const viewUnits = parse('<for array={items} item={item}> <div>{item}</div> </for>');
|
const viewUnits = parse('<for each={items}>{([x, y, z], idx) => <Comp$1 x={x} y={y} z={z} idx={idx}/>}</for>');
|
||||||
expect(viewUnits.length).toBe(1);
|
expect(viewUnits.length).toBe(1);
|
||||||
expect(viewUnits[0].type).toBe('for');
|
expect(viewUnits[0].type).toBe('for');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -40,7 +40,7 @@ export type Dependency = {
|
||||||
*/
|
*/
|
||||||
export function getDependenciesFromNode(
|
export function getDependenciesFromNode(
|
||||||
node: t.Expression | t.Statement,
|
node: t.Expression | t.Statement,
|
||||||
reactiveMap: Map<string, number>,
|
reactiveMap: Map<string, Bitmap | Bitmap[]>,
|
||||||
reactivityFuncNames: string[]
|
reactivityFuncNames: string[]
|
||||||
): Dependency {
|
): Dependency {
|
||||||
// ---- Deps: console.log(count)
|
// ---- Deps: console.log(count)
|
||||||
|
@ -57,9 +57,15 @@ export function getDependenciesFromNode(
|
||||||
|
|
||||||
if (reactiveBitmap !== undefined) {
|
if (reactiveBitmap !== undefined) {
|
||||||
if (isAssignmentExpressionLeft(innerPath) || isAssignmentFunction(innerPath, reactivityFuncNames)) {
|
if (isAssignmentExpressionLeft(innerPath) || isAssignmentFunction(innerPath, reactivityFuncNames)) {
|
||||||
assignDepMask |= reactiveBitmap;
|
assignDepMask |= Array.isArray(reactiveBitmap)
|
||||||
|
? reactiveBitmap.reduce((acc, cur) => acc | cur, 0)
|
||||||
|
: reactiveBitmap;
|
||||||
} else {
|
} else {
|
||||||
depBitmaps.push(reactiveBitmap);
|
if (Array.isArray(reactiveBitmap)) {
|
||||||
|
depBitmaps.push(...reactiveBitmap);
|
||||||
|
} else {
|
||||||
|
depBitmaps.push(reactiveBitmap);
|
||||||
|
}
|
||||||
|
|
||||||
if (!depNodes[propertyKey]) depNodes[propertyKey] = [];
|
if (!depNodes[propertyKey]) depNodes[propertyKey] = [];
|
||||||
depNodes[propertyKey].push(geneDependencyNode(innerPath));
|
depNodes[propertyKey].push(geneDependencyNode(innerPath));
|
||||||
|
|
|
@ -319,34 +319,31 @@ export class ReactivityParser {
|
||||||
* @returns ForParticle
|
* @returns ForParticle
|
||||||
*/
|
*/
|
||||||
private parseFor(forUnit: ForUnit): ForParticle {
|
private parseFor(forUnit: ForUnit): ForParticle {
|
||||||
const { depMask, dependenciesNode } = this.getDependencies(forUnit.array);
|
const { fullDepMask, dependenciesNode, depBitmaps } = this.getDependencies(forUnit.array);
|
||||||
const prevIdentifierDepMap = this.config.depMaskMap;
|
const prevMap = 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;
|
|
||||||
// ---- Generate an identifierDepMap to track identifiers in item and make them reactive
|
// ---- Generate an identifierDepMap to track identifiers in item and make them reactive
|
||||||
// based on the dependencies from the array
|
// based on the dependencies from the array
|
||||||
// this.config.depMaskMap = new Map([
|
// Just wrap the item in an assignment expression to get all the identifiers
|
||||||
// ...this.config.depMaskMap,
|
const itemWrapper = this.t.assignmentExpression('=', forUnit.item, this.t.objectExpression([]));
|
||||||
// ...this.getIdentifiers(this.t.assignmentExpression('=', forUnit.item, this.t.objectExpression([])))
|
this.config.depMaskMap = new Map([
|
||||||
// .filter(id => !keyDep || id !== keyDep)
|
...this.config.depMaskMap,
|
||||||
// .map(id => [id, depMask]),
|
...this.getIdentifiers(itemWrapper).map(id => [id, depBitmaps] as const),
|
||||||
// ]);
|
]);
|
||||||
|
|
||||||
const forParticle: ForParticle = {
|
const forParticle: ForParticle = {
|
||||||
type: 'for',
|
type: 'for',
|
||||||
item: forUnit.item,
|
item: forUnit.item,
|
||||||
array: {
|
array: {
|
||||||
value: forUnit.array,
|
value: forUnit.array,
|
||||||
depBitmaps: [],
|
depBitmaps,
|
||||||
depMask: depMask,
|
depMask: fullDepMask,
|
||||||
dependenciesNode,
|
dependenciesNode,
|
||||||
},
|
},
|
||||||
children: forUnit.children.map(this.parseViewParticle.bind(this)),
|
children: forUnit.children.map(this.parseViewParticle.bind(this)),
|
||||||
key: forUnit.key,
|
key: forUnit.key,
|
||||||
};
|
};
|
||||||
// this.config.identifierDepMap = prevIdentifierDepMap;
|
this.config.depMaskMap = prevMap;
|
||||||
return forParticle;
|
return forParticle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,7 +424,7 @@ export class ReactivityParser {
|
||||||
private getDependencies(node: t.Expression | t.Statement) {
|
private getDependencies(node: t.Expression | t.Statement) {
|
||||||
if (this.t.isFunctionExpression(node) || this.t.isArrowFunctionExpression(node)) {
|
if (this.t.isFunctionExpression(node) || this.t.isArrowFunctionExpression(node)) {
|
||||||
return {
|
return {
|
||||||
depMask: 0,
|
fullDepMask: 0,
|
||||||
depBitmaps: [],
|
depBitmaps: [],
|
||||||
dependenciesNode: this.t.arrayExpression([]),
|
dependenciesNode: this.t.arrayExpression([]),
|
||||||
};
|
};
|
||||||
|
|
|
@ -104,4 +104,4 @@ export interface ReactivityParserConfig {
|
||||||
|
|
||||||
// TODO: unify with the types in babel-inula-next-core
|
// TODO: unify with the types in babel-inula-next-core
|
||||||
export type Bitmap = number;
|
export type Bitmap = number;
|
||||||
export type DepMaskMap = Map<string, Bitmap>;
|
export type DepMaskMap = Map<string, Bitmap | Bitmap[]>;
|
||||||
|
|
Loading…
Reference in New Issue