refactor(parse): use bitmap instead of dependency map
This commit is contained in:
parent
0dcad572f3
commit
601381032d
|
@ -19,6 +19,7 @@ import { addLifecycle, addWatch } from './nodeFactory';
|
|||
import { types as t } from '@openinula/babel-api';
|
||||
import { ON_MOUNT, ON_UNMOUNT, WATCH, WILL_MOUNT, WILL_UNMOUNT } from '../constants';
|
||||
import { extractFnFromMacro, getFnBody } from '../utils';
|
||||
import { getDependenciesFromNode } from './reactive/getDependencies';
|
||||
|
||||
function isLifeCycleName(name: string): name is LifeCycle {
|
||||
return [WILL_MOUNT, ON_MOUNT, WILL_UNMOUNT, ON_UNMOUNT].includes(name);
|
||||
|
@ -51,9 +52,9 @@ export function functionalMacroAnalyze(): Visitor {
|
|||
if (calleeName === WATCH) {
|
||||
const fnNode = extractFnFromMacro(expression, WATCH);
|
||||
const deps = getWatchDeps(expression);
|
||||
if (!deps) {
|
||||
// we auto collect the deps from the function body
|
||||
}
|
||||
|
||||
const depBits = getDependenciesFromNode(deps ?? fnNode, ctx);
|
||||
|
||||
addWatch(ctx.current, fnNode, deps);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
*/
|
||||
|
||||
import { NodePath, type types as t } from '@babel/core';
|
||||
import { ComponentNode, FunctionalExpression, LifeCycle, ReactiveVariable } from './types';
|
||||
import type { ComponentNode, FunctionalExpression, LifeCycle, ReactiveVariable, Bitmap } from './types';
|
||||
import { PropType } from '../constants';
|
||||
import { ViewParticle } from '@openinula/reactivity-parser';
|
||||
|
||||
|
@ -53,7 +53,7 @@ export function addProperty(comp: ComponentNode, name: string, value: t.Expressi
|
|||
const bitmap = depBits ? depBits | bit : bit;
|
||||
|
||||
comp._reactiveBitMap.set(name, bitmap);
|
||||
comp.variables.push({ name, value, isComputed: !!depBits, type: 'reactive', bitmap, level: comp.level });
|
||||
comp.variables.push({ name, value, isComputed: !!depBits, type: 'reactive', depMask: bitmap, level: comp.level });
|
||||
}
|
||||
|
||||
export function addMethod(comp: ComponentNode, name: string, value: FunctionalExpression) {
|
||||
|
@ -87,13 +87,13 @@ export function addLifecycle(comp: ComponentNode, lifeCycle: LifeCycle, block: t
|
|||
export function addWatch(
|
||||
comp: ComponentNode,
|
||||
callback: NodePath<t.ArrowFunctionExpression> | NodePath<t.FunctionExpression>,
|
||||
deps: NodePath<t.ArrayExpression> | null
|
||||
depMask: Bitmap
|
||||
) {
|
||||
// if watch not exist, create a new one
|
||||
if (!comp.watch) {
|
||||
comp.watch = [];
|
||||
}
|
||||
comp.watch.push({ callback, deps });
|
||||
comp.watch.push({ callback, depMask });
|
||||
}
|
||||
|
||||
export function setViewChild(comp: ComponentNode, view: ViewParticle[], usedPropertySet: Set<string>) {
|
||||
|
|
|
@ -26,14 +26,13 @@ import { reactivityFuncNames } from '../../const';
|
|||
* @returns
|
||||
*/
|
||||
export function getDependenciesFromNode(
|
||||
propertyKey: string,
|
||||
path: NodePath<t.Expression | t.ClassDeclaration>,
|
||||
{ current }: AnalyzeContext
|
||||
) {
|
||||
// ---- Deps: console.log(count)
|
||||
let depsBit = 0;
|
||||
let depMask = 0;
|
||||
// ---- Assign deps: count = 1 or count++
|
||||
let assignDepBit = 0;
|
||||
let assignDepMask = 0;
|
||||
const depNodes: Record<string, t.Expression[]> = {};
|
||||
|
||||
const visitor = (innerPath: NodePath<t.Identifier>) => {
|
||||
|
@ -42,9 +41,9 @@ export function getDependenciesFromNode(
|
|||
|
||||
if (reactiveBitmap !== undefined) {
|
||||
if (isAssignmentExpressionLeft(innerPath) || isAssignmentFunction(innerPath)) {
|
||||
assignDepBit |= reactiveBitmap;
|
||||
assignDepMask |= reactiveBitmap;
|
||||
} else {
|
||||
depsBit |= reactiveBitmap;
|
||||
depMask |= reactiveBitmap;
|
||||
if (!depNodes[propertyKey]) depNodes[propertyKey] = [];
|
||||
depNodes[propertyKey].push(t.cloneNode(innerPath.node));
|
||||
}
|
||||
|
@ -61,11 +60,11 @@ export function getDependenciesFromNode(
|
|||
// e.g. { console.log(count); count = 1 }
|
||||
// this will cause infinite loop
|
||||
// so we eliminate "count" from deps
|
||||
if (assignDepBit & depsBit) {
|
||||
if (assignDepMask & depMask) {
|
||||
// TODO: I think we should throw an error here to indicate the user that there is a loop
|
||||
}
|
||||
|
||||
return depsBit;
|
||||
return depMask;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
|
||||
import { type NodePath, types as t } from '@babel/core';
|
||||
import { ON_MOUNT, ON_UNMOUNT, PropType, WILL_MOUNT, WILL_UNMOUNT } from '../constants';
|
||||
import { ViewParticle, PrevMap } from '@openinula/reactivity-parser';
|
||||
import { ViewParticle } from '@openinula/reactivity-parser';
|
||||
|
||||
export type LifeCycle = typeof WILL_MOUNT | typeof ON_MOUNT | typeof WILL_UNMOUNT | typeof ON_UNMOUNT;
|
||||
type Bitmap = number;
|
||||
export type Bitmap = number;
|
||||
|
||||
export type FunctionalExpression = t.FunctionExpression | t.ArrowFunctionExpression;
|
||||
|
||||
|
@ -37,7 +37,7 @@ export interface ReactiveVariable extends BaseVariable<t.Expression | null> {
|
|||
* let age = 18; // age's bitmap is 0x0010
|
||||
* let greeting = `Hello, ${name}`; // greeting's bitmap is 0x0101
|
||||
*/
|
||||
bitmap: Bitmap;
|
||||
depMask: Bitmap;
|
||||
// need a flag for computed to gen a getter
|
||||
// watch is a static computed
|
||||
isComputed: boolean;
|
||||
|
@ -93,8 +93,7 @@ export interface ComponentNode<Type = 'comp'> {
|
|||
* The watch fn in the component
|
||||
*/
|
||||
watch?: {
|
||||
bit: Bitmap;
|
||||
deps: NodePath<t.ArrayExpression> | null;
|
||||
depMask: Bitmap;
|
||||
callback: NodePath<t.ArrowFunctionExpression> | NodePath<t.FunctionExpression>;
|
||||
}[];
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ export function variablesAnalyze(): Visitor {
|
|||
return;
|
||||
}
|
||||
|
||||
depBits = getDependenciesFromNode(id.node.name, init, ctx);
|
||||
depBits = getDependenciesFromNode(init, ctx);
|
||||
}
|
||||
addProperty(ctx.current, id.node.name, init.node || null, depBits);
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ describe('analyze properties', () => {
|
|||
const barVar = root.variables[1] as ReactiveVariable;
|
||||
expect(barVar.isComputed).toBe(true);
|
||||
expect(genCode(barVar.value)).toBe('foo');
|
||||
expect(barVar.bitmap).toEqual(0b11);
|
||||
expect(barVar.depMask).toEqual(0b11);
|
||||
});
|
||||
|
||||
it('should analyze dependency from state in different shape', () => {
|
||||
|
@ -68,7 +68,7 @@ describe('analyze properties', () => {
|
|||
foo: foo ? a : b
|
||||
}"
|
||||
`);
|
||||
expect(barVar.bitmap).toEqual(0b1111);
|
||||
expect(barVar.depMask).toEqual(0b1111);
|
||||
});
|
||||
|
||||
// TODO:MOVE TO PROPS PLUGIN TEST
|
||||
|
@ -108,7 +108,7 @@ describe('analyze properties', () => {
|
|||
expect(root.variables.length).toBe(1);
|
||||
const barVar = root.variables[0] as ReactiveVariable;
|
||||
expect(barVar.isComputed).toBe(false);
|
||||
expect(barVar.bitmap).toEqual(0b1);
|
||||
expect(barVar.depMask).toEqual(0b1);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -123,8 +123,8 @@ describe('analyze properties', () => {
|
|||
})
|
||||
`);
|
||||
expect(root.variables.length).toBe(2);
|
||||
expect(root.availableVariables[0].bitmap).toEqual(0b1);
|
||||
expect((root.variables[1] as SubCompVariable).ownAvailableVariables[0].bitmap).toBe(0b11);
|
||||
expect(root.availableVariables[0].depMask).toEqual(0b1);
|
||||
expect((root.variables[1] as SubCompVariable).ownAvailableVariables[0].depMask).toBe(0b11);
|
||||
});
|
||||
|
||||
it('should analyze dependency in parent', () => {
|
||||
|
@ -144,12 +144,12 @@ describe('analyze properties', () => {
|
|||
`);
|
||||
const sonNode = root.variables[3] as SubCompVariable;
|
||||
// Son > middleName
|
||||
expect(sonNode.ownAvailableVariables[0].bitmap).toBe(0b1111);
|
||||
expect(sonNode.ownAvailableVariables[0].depMask).toBe(0b1111);
|
||||
// Son > name
|
||||
expect(sonNode.ownAvailableVariables[1].bitmap).toBe(0b11111);
|
||||
expect(sonNode.ownAvailableVariables[1].depMask).toBe(0b11111);
|
||||
const grandSonNode = sonNode.variables[2] as SubCompVariable;
|
||||
// GrandSon > grandSonName
|
||||
expect(grandSonNode.ownAvailableVariables[0].bitmap).toBe(0b100001);
|
||||
expect(grandSonNode.ownAvailableVariables[0].depMask).toBe(0b100001);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { variablesAnalyze } from '../../src/analyzer/variablesAnalyze';
|
||||
import { propsAnalyze } from '../../src/analyzer/propsAnalyze';
|
||||
import { ComponentNode } from '../../src/analyzer/types';
|
||||
import { viewAnalyze } from '../../src/analyzer/viewAnalyze';
|
||||
import { genCode, mockAnalyze } from '../mock';
|
||||
|
@ -23,51 +22,27 @@ describe('viewAnalyze', () => {
|
|||
});
|
||||
`);
|
||||
const div = root.children![0] as any;
|
||||
expect(div.children[0].content.dependencyIndexArr).toMatchInlineSnapshot(`
|
||||
[
|
||||
4,
|
||||
3,
|
||||
2,
|
||||
0,
|
||||
]
|
||||
`);
|
||||
expect(div.children[0].content.depMask).toEqual(0b11101);
|
||||
expect(genCode(div.children[0].content.dependenciesNode)).toMatchInlineSnapshot('"[doubleCount2]"');
|
||||
expect(div.props.className.dependencyIndexArr).toMatchInlineSnapshot(`
|
||||
[
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
]
|
||||
`);
|
||||
expect(div.props.className.depMask).toEqual(0b111);
|
||||
expect(genCode(div.props.className.value)).toMatchInlineSnapshot('"className + count"');
|
||||
|
||||
// @ts-expect-error ignore ts here
|
||||
const InputCompNode = (root.variables[3] as ComponentNode).value;
|
||||
const InputCompNode = root.variables[5] as ComponentNode;
|
||||
expect(InputCompNode.usedPropertySet).toMatchInlineSnapshot(`
|
||||
Set {
|
||||
"count",
|
||||
"doubleCount",
|
||||
"name",
|
||||
}
|
||||
`);
|
||||
// it's the {count}
|
||||
const inputFirstExp = InputCompNode.children[0].children[0];
|
||||
expect(inputFirstExp.content.dependencyIndexArr).toMatchInlineSnapshot(`
|
||||
[
|
||||
5,
|
||||
]
|
||||
`);
|
||||
const inputFirstExp = InputCompNode.children![0].children[0];
|
||||
expect(inputFirstExp.content.depMask).toEqual(0b100000);
|
||||
expect(genCode(inputFirstExp.content.dependenciesNode)).toMatchInlineSnapshot('"[count]"');
|
||||
|
||||
// it's the {doubleCount}
|
||||
const inputSecondExp = InputCompNode.children[0].children[1];
|
||||
expect(inputSecondExp.content.dependencyIndexArr).toMatchInlineSnapshot(`
|
||||
[
|
||||
3,
|
||||
2,
|
||||
0,
|
||||
]
|
||||
`);
|
||||
expect(inputSecondExp.content.depMask).toEqual(0b1101);
|
||||
expect(genCode(inputSecondExp.content.dependenciesNode)).toMatchInlineSnapshot('"[doubleCount]"');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,9 +22,9 @@ describe('watchAnalyze', () => {
|
|||
// watch expression
|
||||
}"
|
||||
`);
|
||||
if (!root.watch[0].deps) {
|
||||
if (!root.watch[0].depMask) {
|
||||
throw new Error('watch deps not found');
|
||||
}
|
||||
expect(genCode(root.watch[0].deps.node)).toMatchInlineSnapshot('"[a, b]"');
|
||||
expect(genCode(root.watch[0].depMask)).toMatchInlineSnapshot('"[a, b]"');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -240,7 +240,7 @@ export class ReactivityParser {
|
|||
key: 'value',
|
||||
path: [...path, idx],
|
||||
value: child.content,
|
||||
depsBit: [],
|
||||
depMask: 0,
|
||||
dependenciesNode: this.t.arrayExpression([]),
|
||||
dynamic: false,
|
||||
});
|
||||
|
@ -280,7 +280,7 @@ export class ReactivityParser {
|
|||
* @returns ExpParticle | HTMLParticle
|
||||
*/
|
||||
private parseHTML(htmlUnit: HTMLUnit): ExpParticle | HTMLParticle {
|
||||
const { depsBit, dependenciesNode } = this.getDependencies(htmlUnit.tag);
|
||||
const { depMask, dependenciesNode, dynamic } = this.getDependencies(htmlUnit.tag);
|
||||
|
||||
const innerHTMLParticle: HTMLParticle = {
|
||||
type: 'html',
|
||||
|
@ -295,6 +295,9 @@ export class ReactivityParser {
|
|||
|
||||
innerHTMLParticle.children = htmlUnit.children.map(this.parseViewParticle.bind(this));
|
||||
|
||||
// ---- Not a dynamic tag
|
||||
if (!dynamic) return innerHTMLParticle;
|
||||
|
||||
// ---- Dynamic tag, wrap it in an ExpParticle to make the tag reactive
|
||||
const id = this.uid();
|
||||
return {
|
||||
|
@ -304,7 +307,7 @@ export class ReactivityParser {
|
|||
viewPropMap: {
|
||||
[id]: [innerHTMLParticle],
|
||||
},
|
||||
depsBit,
|
||||
depMask: depMask,
|
||||
dependenciesNode,
|
||||
},
|
||||
props: {},
|
||||
|
@ -320,7 +323,7 @@ export class ReactivityParser {
|
|||
* @returns CompParticle | ExpParticle
|
||||
*/
|
||||
private parseComp(compUnit: CompUnit): CompParticle | ExpParticle {
|
||||
const { depsBit, dependenciesNode } = this.getDependencies(compUnit.tag);
|
||||
const { depMask, dependenciesNode, dynamic } = this.getDependencies(compUnit.tag);
|
||||
|
||||
const compParticle: CompParticle = {
|
||||
type: 'comp',
|
||||
|
@ -344,7 +347,7 @@ export class ReactivityParser {
|
|||
viewPropMap: {
|
||||
[id]: [compParticle],
|
||||
},
|
||||
depsBit,
|
||||
depMask: depMask,
|
||||
dependenciesNode,
|
||||
dynamic,
|
||||
},
|
||||
|
@ -360,7 +363,7 @@ export class ReactivityParser {
|
|||
* @returns ForParticle
|
||||
*/
|
||||
private parseFor(forUnit: ForUnit): ForParticle {
|
||||
const { depsBit, dependenciesNode } = this.getDependencies(forUnit.array);
|
||||
const { depMask, dependenciesNode } = this.getDependencies(forUnit.array);
|
||||
const prevIdentifierDepMap = this.config.identifierDepMap;
|
||||
// ---- 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,
|
||||
|
@ -371,7 +374,7 @@ export class ReactivityParser {
|
|||
this.config.identifierDepMap = Object.fromEntries(
|
||||
this.getIdentifiers(this.t.assignmentExpression('=', forUnit.item, this.t.objectExpression([])))
|
||||
.filter(id => !keyDep || id !== keyDep)
|
||||
.map(id => [id, depsBit.map(n => this.availableProperties[n])])
|
||||
.map(id => [id, depMask.map(n => this.availableProperties[n])])
|
||||
);
|
||||
|
||||
const forParticle: ForParticle = {
|
||||
|
@ -380,7 +383,7 @@ export class ReactivityParser {
|
|||
array: {
|
||||
value: forUnit.array,
|
||||
dynamic,
|
||||
depsBit,
|
||||
depMask: depMask,
|
||||
dependenciesNode,
|
||||
},
|
||||
children: forUnit.children.map(this.parseViewParticle.bind(this)),
|
||||
|
@ -470,12 +473,14 @@ export class ReactivityParser {
|
|||
* @returns dependency index array
|
||||
*/
|
||||
private getDependencies(node: t.Expression | t.Statement): {
|
||||
depsBit: number;
|
||||
dynamic: boolean;
|
||||
depMask: number;
|
||||
dependenciesNode: t.ArrayExpression;
|
||||
} {
|
||||
if (this.t.isFunctionExpression(node) || this.t.isArrowFunctionExpression(node)) {
|
||||
return {
|
||||
depsBit: 0,
|
||||
dynamic: false,
|
||||
depMask: 0,
|
||||
dependenciesNode: this.t.arrayExpression([]),
|
||||
};
|
||||
}
|
||||
|
@ -487,7 +492,8 @@ export class ReactivityParser {
|
|||
const depNodes = [...propertyDepNodes] as t.Expression[];
|
||||
|
||||
return {
|
||||
depsBit: deps,
|
||||
dynamic: depNodes.length > 0 || !!deps,
|
||||
depMask: deps,
|
||||
dependenciesNode: this.t.arrayExpression(depNodes),
|
||||
};
|
||||
}
|
||||
|
@ -559,10 +565,11 @@ export class ReactivityParser {
|
|||
*/
|
||||
private getPropertyDependencies(node: t.Expression | t.Statement): [number, t.Node[]] {
|
||||
// ---- Deps: console.log(count)
|
||||
let depsBit = 0;
|
||||
let depMask = 0;
|
||||
// ---- Assign deps: count = 1 or count++
|
||||
let assignDepBit = 0;
|
||||
const depNodes: Record<string, t.Node[]> = {};
|
||||
const deps = new Set<string>();
|
||||
|
||||
const wrappedNode = this.valueWrapper(node);
|
||||
this.traverse(wrappedNode, {
|
||||
|
@ -574,7 +581,9 @@ export class ReactivityParser {
|
|||
if (this.isAssignmentExpressionLeft(innerPath) || this.isAssignmentFunction(innerPath)) {
|
||||
assignDepBit |= reactiveBitmap;
|
||||
} else {
|
||||
depsBit |= reactiveBitmap;
|
||||
depMask |= reactiveBitmap;
|
||||
deps.add(propertyKey);
|
||||
|
||||
if (!depNodes[propertyKey]) depNodes[propertyKey] = [];
|
||||
depNodes[propertyKey].push(this.t.cloneNode(innerPath.node));
|
||||
}
|
||||
|
@ -586,7 +595,7 @@ export class ReactivityParser {
|
|||
// e.g. { console.log(count); count = 1 }
|
||||
// this will cause infinite loop
|
||||
// so we eliminate "count" from deps
|
||||
if (assignDepBit & depsBit) {
|
||||
if (assignDepBit & depMask) {
|
||||
// TODO: I think we should throw an error here to indicate the user that there is a loop
|
||||
}
|
||||
|
||||
|
@ -597,37 +606,8 @@ export class ReactivityParser {
|
|||
return idx === i;
|
||||
});
|
||||
|
||||
// deps.forEach(this.usedProperties.add.bind(this.usedProperties));
|
||||
return [depsBit, dependencyNodes];
|
||||
}
|
||||
|
||||
private calDependencyIndexArr = (directDepKey: string) => {
|
||||
// iterate the availableProperties reversely to find the index of the property
|
||||
// cause the availableProperties is in the order of the code
|
||||
const chainedDepKeys = this.findDependency(directDepKey);
|
||||
const depKeyQueue = chainedDepKeys ? [directDepKey, ...chainedDepKeys] : [directDepKey];
|
||||
depKeyQueue.forEach(this.usedProperties.add.bind(this.usedProperties));
|
||||
|
||||
let dep = depKeyQueue.shift();
|
||||
const result: number[] = [];
|
||||
for (let i = this.availableProperties.length - 1; i >= 0; i--) {
|
||||
if (this.availableProperties[i] === dep) {
|
||||
result.push(i);
|
||||
dep = depKeyQueue.shift();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
private findDependency(propertyKey: string) {
|
||||
let currentMap: ReactiveBitMap | undefined = this.reactiveBitMap;
|
||||
do {
|
||||
if (currentMap[propertyKey] !== undefined) {
|
||||
return currentMap[propertyKey];
|
||||
}
|
||||
currentMap = currentMap[PrevMap];
|
||||
} while (currentMap);
|
||||
return null;
|
||||
deps.forEach(this.usedProperties.add.bind(this.usedProperties));
|
||||
return [depMask, dependencyNodes];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,14 +4,14 @@ import type Babel from '@babel/core';
|
|||
export interface DependencyValue<T> {
|
||||
value: T;
|
||||
dynamic: boolean; // to removed
|
||||
depsBit: number; // -> bit
|
||||
depMask: number; // -> bit
|
||||
dependenciesNode: t.ArrayExpression;
|
||||
}
|
||||
|
||||
export interface DependencyProp {
|
||||
value: t.Expression;
|
||||
viewPropMap: Record<string, ViewParticle[]>;
|
||||
depsBit: number;
|
||||
depMask: number;
|
||||
dependenciesNode: t.ArrayExpression;
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ export interface TemplateProp {
|
|||
path: number[];
|
||||
value: t.Expression;
|
||||
dynamic: boolean;
|
||||
depsBit: number;
|
||||
depMask: number;
|
||||
dependenciesNode: t.ArrayExpression;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue