refactor(parse): use bitmap instead of dependency map
This commit is contained in:
parent
f32da0e9c7
commit
0dcad572f3
|
@ -16,7 +16,7 @@
|
|||
import { NodePath, type types as t } from '@babel/core';
|
||||
import { ComponentNode, FunctionalExpression, LifeCycle, ReactiveVariable } from './types';
|
||||
import { PropType } from '../constants';
|
||||
import { ViewParticle, PrevMap } from '@openinula/reactivity-parser';
|
||||
import { ViewParticle } from '@openinula/reactivity-parser';
|
||||
|
||||
export function createComponentNode(
|
||||
name: string,
|
||||
|
@ -29,7 +29,7 @@ export function createComponentNode(
|
|||
name,
|
||||
children: undefined,
|
||||
variables: [],
|
||||
dependencyMap: parent ? { [PrevMap]: parent.dependencyMap } : {},
|
||||
_reactiveBitMap: parent ? new Map<string, number>(parent._reactiveBitMap) : new Map<string, number>(),
|
||||
lifecycle: {},
|
||||
parent,
|
||||
fnNode,
|
||||
|
@ -46,11 +46,14 @@ export function createComponentNode(
|
|||
return comp;
|
||||
}
|
||||
|
||||
export function addProperty(comp: ComponentNode, name: string, value: t.Expression | null, deps: string[] | null) {
|
||||
comp.variables.push({ name, value, isComputed: !!deps?.length, type: 'reactive', deps });
|
||||
if (comp.dependencyMap[name] === undefined) {
|
||||
comp.dependencyMap[name] = null;
|
||||
}
|
||||
export function addProperty(comp: ComponentNode, name: string, value: t.Expression | null, depBits: number) {
|
||||
// The index of the variable in the availableVariables
|
||||
const idx = comp.availableVariables.length;
|
||||
const bit = 1 << idx;
|
||||
const bitmap = depBits ? depBits | bit : bit;
|
||||
|
||||
comp._reactiveBitMap.set(name, bitmap);
|
||||
comp.variables.push({ name, value, isComputed: !!depBits, type: 'reactive', bitmap, level: comp.level });
|
||||
}
|
||||
|
||||
export function addMethod(comp: ComponentNode, name: string, value: FunctionalExpression) {
|
||||
|
@ -58,7 +61,7 @@ export function addMethod(comp: ComponentNode, name: string, value: FunctionalEx
|
|||
}
|
||||
|
||||
export function addSubComponent(comp: ComponentNode, subComp: ComponentNode) {
|
||||
comp.variables.push({ name: subComp.name, value: subComp, type: 'subComp' });
|
||||
comp.variables.push({ ...subComp, type: 'subComp' });
|
||||
}
|
||||
|
||||
export function addProp(
|
||||
|
|
|
@ -14,10 +14,9 @@
|
|||
*/
|
||||
|
||||
import type { NodePath } from '@babel/core';
|
||||
import { AnalyzeContext, DependencyMap } from '../types';
|
||||
import { AnalyzeContext } from '../types';
|
||||
import { types as t } from '@openinula/babel-api';
|
||||
import { reactivityFuncNames } from '../../const';
|
||||
import { PrevMap } from '@openinula/reactivity-parser';
|
||||
|
||||
/**
|
||||
* @brief Get all valid dependencies of a babel path
|
||||
|
@ -32,20 +31,23 @@ export function getDependenciesFromNode(
|
|||
{ current }: AnalyzeContext
|
||||
) {
|
||||
// ---- Deps: console.log(count)
|
||||
const deps = new Set<string>();
|
||||
let depsBit = 0;
|
||||
// ---- Assign deps: count = 1 or count++
|
||||
const assignDeps = new Set<string>();
|
||||
let assignDepBit = 0;
|
||||
const depNodes: Record<string, t.Expression[]> = {};
|
||||
|
||||
const visitor = (innerPath: NodePath<t.Identifier>) => {
|
||||
const propertyKey = innerPath.node.name;
|
||||
if (isAssignmentExpressionLeft(innerPath) || isAssignmentFunction(innerPath)) {
|
||||
assignDeps.add(propertyKey);
|
||||
} else if (current.availableVariables.includes(propertyKey)) {
|
||||
deps.add(propertyKey);
|
||||
findDependency(current.dependencyMap, propertyKey)?.forEach(deps.add.bind(deps));
|
||||
if (!depNodes[propertyKey]) depNodes[propertyKey] = [];
|
||||
depNodes[propertyKey].push(t.cloneNode(innerPath.node));
|
||||
const reactiveBitmap = current._reactiveBitMap.get(propertyKey);
|
||||
|
||||
if (reactiveBitmap !== undefined) {
|
||||
if (isAssignmentExpressionLeft(innerPath) || isAssignmentFunction(innerPath)) {
|
||||
assignDepBit |= reactiveBitmap;
|
||||
} else {
|
||||
depsBit |= reactiveBitmap;
|
||||
if (!depNodes[propertyKey]) depNodes[propertyKey] = [];
|
||||
depNodes[propertyKey].push(t.cloneNode(innerPath.node));
|
||||
}
|
||||
}
|
||||
};
|
||||
if (path.isIdentifier()) {
|
||||
|
@ -59,16 +61,11 @@ export function getDependenciesFromNode(
|
|||
// e.g. { console.log(count); count = 1 }
|
||||
// this will cause infinite loop
|
||||
// so we eliminate "count" from deps
|
||||
assignDeps.forEach(dep => {
|
||||
deps.delete(dep);
|
||||
});
|
||||
|
||||
const depArr = [...deps];
|
||||
if (deps.size > 0) {
|
||||
current.dependencyMap[propertyKey] = depArr;
|
||||
if (assignDepBit & depsBit) {
|
||||
// TODO: I think we should throw an error here to indicate the user that there is a loop
|
||||
}
|
||||
|
||||
return depArr;
|
||||
return depsBit;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -110,15 +107,3 @@ function isAssignmentFunction(innerPath: NodePath): boolean {
|
|||
reactivityFuncNames.includes((parentPath.get('callee').node as t.Identifier).name)
|
||||
);
|
||||
}
|
||||
|
||||
function findDependency(dependencyMap: DependencyMap, propertyKey: string) {
|
||||
let currentMap: DependencyMap | undefined = dependencyMap;
|
||||
do {
|
||||
if (currentMap[propertyKey] !== undefined) {
|
||||
return currentMap[propertyKey];
|
||||
}
|
||||
// trace back to the previous map
|
||||
currentMap = currentMap[PrevMap];
|
||||
} while (currentMap);
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -30,7 +30,14 @@ interface BaseVariable<V> {
|
|||
export interface ReactiveVariable extends BaseVariable<t.Expression | null> {
|
||||
type: 'reactive';
|
||||
level: number;
|
||||
bitmap?: Bitmap;
|
||||
/**
|
||||
* indicate the dependency of the variable | the index of the reactive variable
|
||||
* i.e.
|
||||
* let name = 'John'; // name's bitmap is 0x0001
|
||||
* let age = 18; // age's bitmap is 0x0010
|
||||
* let greeting = `Hello, ${name}`; // greeting's bitmap is 0x0101
|
||||
*/
|
||||
bitmap: Bitmap;
|
||||
// need a flag for computed to gen a getter
|
||||
// watch is a static computed
|
||||
isComputed: boolean;
|
||||
|
@ -40,9 +47,7 @@ export interface MethodVariable extends BaseVariable<FunctionalExpression> {
|
|||
type: 'method';
|
||||
}
|
||||
|
||||
export interface SubCompVariable extends BaseVariable<ComponentNode> {
|
||||
type: 'subComp';
|
||||
}
|
||||
export type SubCompVariable = ComponentNode<'subComp'>;
|
||||
|
||||
export type Variable = ReactiveVariable | MethodVariable | SubCompVariable;
|
||||
|
||||
|
@ -55,8 +60,8 @@ export interface Prop {
|
|||
nestedRelationship: t.ObjectPattern | t.ArrayPattern | null;
|
||||
}
|
||||
|
||||
export interface ComponentNode {
|
||||
type: 'comp';
|
||||
export interface ComponentNode<Type = 'comp'> {
|
||||
type: Type;
|
||||
name: string;
|
||||
level: number;
|
||||
// The variables defined in the component
|
||||
|
@ -66,9 +71,9 @@ export interface ComponentNode {
|
|||
*/
|
||||
usedPropertySet?: Set<string>;
|
||||
/**
|
||||
* The available props for the component, including the nested props
|
||||
* The map to find the reactive bitmap by name
|
||||
*/
|
||||
availableProps: string[];
|
||||
_reactiveBitMap: Map<string, Bitmap>;
|
||||
/**
|
||||
* The available variables and props owned by the component
|
||||
*/
|
||||
|
|
|
@ -43,7 +43,7 @@ export function variablesAnalyze(): Visitor {
|
|||
} else if (id.isIdentifier()) {
|
||||
// --- properties: the state / computed / plain properties / methods ---
|
||||
const init = declaration.get('init');
|
||||
let deps: string[] | null = null;
|
||||
let depBits = 0;
|
||||
if (isValidPath(init)) {
|
||||
// handle the method
|
||||
if (init.isArrowFunctionExpression() || init.isFunctionExpression()) {
|
||||
|
@ -68,9 +68,9 @@ export function variablesAnalyze(): Visitor {
|
|||
return;
|
||||
}
|
||||
|
||||
deps = getDependenciesFromNode(id.node.name, init, ctx);
|
||||
depBits = getDependenciesFromNode(id.node.name, init, ctx);
|
||||
}
|
||||
addProperty(ctx.current, id.node.name, init.node || null, deps);
|
||||
addProperty(ctx.current, id.node.name, init.node || null, depBits);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
@ -34,10 +34,11 @@ export function viewAnalyze(): Visitor {
|
|||
htmlTags,
|
||||
parseTemplate: false,
|
||||
});
|
||||
// @ts-expect-error TODO: FIX TYPE
|
||||
const [viewParticles, usedPropertySet] = parseReactivity(viewUnits, {
|
||||
babelApi: getBabelApi(),
|
||||
availableProperties: current.availableVariables,
|
||||
dependencyMap: current.dependencyMap,
|
||||
reactiveBitMap: current._reactiveBitMap,
|
||||
reactivityFuncNames,
|
||||
});
|
||||
|
||||
|
|
|
@ -16,10 +16,9 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { genCode, mockAnalyze } from '../mock';
|
||||
import { variablesAnalyze } from '../../src/analyzer/variablesAnalyze';
|
||||
import { propsAnalyze } from '../../src/analyzer/propsAnalyze';
|
||||
import { ComponentNode, ReactiveVariable } from '../../src/analyzer/types';
|
||||
import { ReactiveVariable, SubCompVariable } from '../../src/analyzer/types';
|
||||
|
||||
const analyze = (code: string) => mockAnalyze(code, [propsAnalyze, variablesAnalyze]);
|
||||
const analyze = (code: string) => mockAnalyze(code, [variablesAnalyze]);
|
||||
|
||||
describe('analyze properties', () => {
|
||||
it('should work', () => {
|
||||
|
@ -48,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(root.dependencyMap).toEqual({ bar: ['foo'], foo: null });
|
||||
expect(barVar.bitmap).toEqual(0b11);
|
||||
});
|
||||
|
||||
it('should analyze dependency from state in different shape', () => {
|
||||
|
@ -69,10 +68,11 @@ describe('analyze properties', () => {
|
|||
foo: foo ? a : b
|
||||
}"
|
||||
`);
|
||||
expect(root.dependencyMap).toEqual({ bar: ['foo', 'a', 'b'], foo: null, a: null, b: null });
|
||||
expect(barVar.bitmap).toEqual(0b1111);
|
||||
});
|
||||
|
||||
it('should analyze dependency from props', () => {
|
||||
// TODO:MOVE TO PROPS PLUGIN TEST
|
||||
it.skip('should analyze dependency from props', () => {
|
||||
const root = analyze(`
|
||||
Component(({ foo }) => {
|
||||
let bar = foo;
|
||||
|
@ -82,10 +82,10 @@ describe('analyze properties', () => {
|
|||
|
||||
const barVar = root.variables[0] as ReactiveVariable;
|
||||
expect(barVar.isComputed).toBe(true);
|
||||
expect(root.dependencyMap).toEqual({ bar: ['foo'] });
|
||||
});
|
||||
|
||||
it('should analyze dependency from nested props', () => {
|
||||
// TODO:MOVE TO PROPS PLUGIN TEST
|
||||
it.skip('should analyze dependency from nested props', () => {
|
||||
const root = analyze(`
|
||||
Component(({ foo: foo1, name: [first, last] }) => {
|
||||
let bar = [foo1, first, last];
|
||||
|
@ -94,6 +94,7 @@ describe('analyze properties', () => {
|
|||
expect(root.variables.length).toBe(1);
|
||||
const barVar = root.variables[0] as ReactiveVariable;
|
||||
expect(barVar.isComputed).toBe(true);
|
||||
// @ts-expect-error ignore ts here
|
||||
expect(root.dependencyMap).toEqual({ bar: ['foo1', 'first', 'last'] });
|
||||
});
|
||||
|
||||
|
@ -107,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(root.dependencyMap).toEqual({ bar: null });
|
||||
expect(barVar.bitmap).toEqual(0b1);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -122,22 +123,14 @@ describe('analyze properties', () => {
|
|||
})
|
||||
`);
|
||||
expect(root.variables.length).toBe(2);
|
||||
expect(root.dependencyMap).toEqual({ foo: null });
|
||||
expect((root.variables[1].value as ComponentNode).dependencyMap).toMatchInlineSnapshot(`
|
||||
{
|
||||
"bar": [
|
||||
"foo",
|
||||
],
|
||||
Symbol(prevMap): {
|
||||
"foo": null,
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(root.availableVariables[0].bitmap).toEqual(0b1);
|
||||
expect((root.variables[1] as SubCompVariable).ownAvailableVariables[0].bitmap).toBe(0b11);
|
||||
});
|
||||
|
||||
it('should analyze dependency in parent', () => {
|
||||
const root = analyze(`
|
||||
Component(({lastName}) => {
|
||||
Component(() => {
|
||||
let lastName;
|
||||
let parentFirstName = 'sheldon';
|
||||
const parentName = parentFirstName + lastName;
|
||||
const Son = Component(() => {
|
||||
|
@ -149,63 +142,15 @@ describe('analyze properties', () => {
|
|||
});
|
||||
})
|
||||
`);
|
||||
const sonNode = root.variables[2].value as ComponentNode;
|
||||
expect(sonNode.dependencyMap).toMatchInlineSnapshot(`
|
||||
{
|
||||
"middleName": [
|
||||
"parentName",
|
||||
"parentFirstName",
|
||||
"lastName",
|
||||
],
|
||||
"name": [
|
||||
"middleName",
|
||||
"parentName",
|
||||
"parentFirstName",
|
||||
"lastName",
|
||||
],
|
||||
Symbol(prevMap): {
|
||||
"parentFirstName": null,
|
||||
"parentName": [
|
||||
"parentFirstName",
|
||||
"lastName",
|
||||
],
|
||||
},
|
||||
}
|
||||
`);
|
||||
const grandSonNode = sonNode.variables[2].value as ComponentNode;
|
||||
expect(grandSonNode.dependencyMap).toMatchInlineSnapshot(`
|
||||
{
|
||||
"grandSonName": [
|
||||
"lastName",
|
||||
],
|
||||
Symbol(prevMap): {
|
||||
"middleName": [
|
||||
"parentName",
|
||||
"parentFirstName",
|
||||
"lastName",
|
||||
],
|
||||
"name": [
|
||||
"middleName",
|
||||
"parentName",
|
||||
"parentFirstName",
|
||||
"lastName",
|
||||
],
|
||||
Symbol(prevMap): {
|
||||
"parentFirstName": null,
|
||||
"parentName": [
|
||||
"parentFirstName",
|
||||
"lastName",
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
const sonNode = root.variables[3] as SubCompVariable;
|
||||
// Son > middleName
|
||||
expect(sonNode.ownAvailableVariables[0].bitmap).toBe(0b1111);
|
||||
// Son > name
|
||||
expect(sonNode.ownAvailableVariables[1].bitmap).toBe(0b11111);
|
||||
const grandSonNode = sonNode.variables[2] as SubCompVariable;
|
||||
// GrandSon > grandSonName
|
||||
expect(grandSonNode.ownAvailableVariables[0].bitmap).toBe(0b100001);
|
||||
});
|
||||
// SubscriptionTree
|
||||
// const SubscriptionTree = {
|
||||
// lastName: ['parentName','son:middleName','son:name','son,grandSon:grandSonName'],
|
||||
//
|
||||
// }
|
||||
});
|
||||
|
||||
it('should collect method', () => {
|
||||
|
@ -222,10 +167,5 @@ describe('analyze properties', () => {
|
|||
expect(root.variables.map(p => p.name)).toEqual(['foo', 'onClick', 'onHover', 'onInput']);
|
||||
expect(root.variables[1].type).toBe('method');
|
||||
expect(root.variables[2].type).toBe('method');
|
||||
expect(root.dependencyMap).toMatchInlineSnapshot(`
|
||||
{
|
||||
"foo": null,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,27 +5,18 @@ import { viewAnalyze } from '../../src/analyzer/viewAnalyze';
|
|||
import { genCode, mockAnalyze } from '../mock';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
const analyze = (code: string) => mockAnalyze(code, [propsAnalyze, variablesAnalyze, viewAnalyze]);
|
||||
const analyze = (code: string) => mockAnalyze(code, [variablesAnalyze, viewAnalyze]);
|
||||
describe('viewAnalyze', () => {
|
||||
it('should analyze view', () => {
|
||||
const root = analyze(/*js*/ `
|
||||
Component(({name ,className}) => {
|
||||
Component(({}) => {
|
||||
let name;
|
||||
let className;
|
||||
let count = name; // 1
|
||||
let doubleCount = count* 2; // 2
|
||||
let doubleCount2 = doubleCount* 2; // 4
|
||||
const Input = Component(() => {
|
||||
let count = 1;
|
||||
watch(() => {
|
||||
if (doubleCount2 > 10) {
|
||||
count++;
|
||||
}
|
||||
console.log(doubleCount2);
|
||||
});
|
||||
const update = changed => {
|
||||
if (changed & 0x1011) {
|
||||
node1.update(_$this0.count, _$this0.doubleCount);
|
||||
}
|
||||
};
|
||||
return <input>{count}{doubleCount}</input>;
|
||||
});
|
||||
return <div className={className + count}>{doubleCount2}</div>;
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
type ForParticle,
|
||||
type IfParticle,
|
||||
type EnvParticle,
|
||||
DependencyMap,
|
||||
ReactiveBitMap,
|
||||
} from './types';
|
||||
import { type NodePath, type types as t, type traverse } from '@babel/core';
|
||||
import {
|
||||
|
@ -36,7 +36,7 @@ export class ReactivityParser {
|
|||
private readonly traverse: typeof traverse;
|
||||
private readonly availableProperties: string[];
|
||||
private readonly availableIdentifiers?: string[];
|
||||
private readonly dependencyMap: DependencyMap;
|
||||
private readonly reactiveBitMap: ReactiveBitMap;
|
||||
private readonly identifierDepMap: Record<string, string[]>;
|
||||
private readonly dependencyParseType;
|
||||
private readonly reactivityFuncNames;
|
||||
|
@ -70,7 +70,7 @@ export class ReactivityParser {
|
|||
this.traverse = config.babelApi.traverse;
|
||||
this.availableProperties = config.availableProperties;
|
||||
this.availableIdentifiers = config.availableIdentifiers;
|
||||
this.dependencyMap = config.dependencyMap;
|
||||
this.reactiveBitMap = config.reactiveBitMap;
|
||||
this.identifierDepMap = config.identifierDepMap ?? {};
|
||||
this.dependencyParseType = config.dependencyParseType ?? 'property';
|
||||
this.reactivityFuncNames = config.reactivityFuncNames ?? [];
|
||||
|
@ -123,8 +123,8 @@ export class ReactivityParser {
|
|||
* @brief Generate a template
|
||||
* There'll be a situation where the tag is dynamic, e.g. tag(this.htmlTag),
|
||||
* which we can't generate a template string for it, so we'll wrap it in an ExpParticle in parseHTML() section
|
||||
* @param htmlUnit
|
||||
* @returns template string
|
||||
* @param unit
|
||||
*/
|
||||
private generateTemplate(unit: HTMLUnit): HTMLParticle {
|
||||
const staticProps = this.filterTemplateProps(
|
||||
|
@ -240,7 +240,7 @@ export class ReactivityParser {
|
|||
key: 'value',
|
||||
path: [...path, idx],
|
||||
value: child.content,
|
||||
dependencyIndexArr: [],
|
||||
depsBit: [],
|
||||
dependenciesNode: this.t.arrayExpression([]),
|
||||
dynamic: false,
|
||||
});
|
||||
|
@ -280,7 +280,7 @@ export class ReactivityParser {
|
|||
* @returns ExpParticle | HTMLParticle
|
||||
*/
|
||||
private parseHTML(htmlUnit: HTMLUnit): ExpParticle | HTMLParticle {
|
||||
const { dependencyIndexArr, dependenciesNode, dynamic } = this.getDependencies(htmlUnit.tag);
|
||||
const { depsBit, dependenciesNode } = this.getDependencies(htmlUnit.tag);
|
||||
|
||||
const innerHTMLParticle: HTMLParticle = {
|
||||
type: 'html',
|
||||
|
@ -295,9 +295,6 @@ 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 {
|
||||
|
@ -307,9 +304,8 @@ export class ReactivityParser {
|
|||
viewPropMap: {
|
||||
[id]: [innerHTMLParticle],
|
||||
},
|
||||
dependencyIndexArr,
|
||||
depsBit,
|
||||
dependenciesNode,
|
||||
dynamic,
|
||||
},
|
||||
props: {},
|
||||
};
|
||||
|
@ -324,7 +320,7 @@ export class ReactivityParser {
|
|||
* @returns CompParticle | ExpParticle
|
||||
*/
|
||||
private parseComp(compUnit: CompUnit): CompParticle | ExpParticle {
|
||||
const { dependencyIndexArr, dependenciesNode, dynamic } = this.getDependencies(compUnit.tag);
|
||||
const { depsBit, dependenciesNode } = this.getDependencies(compUnit.tag);
|
||||
|
||||
const compParticle: CompParticle = {
|
||||
type: 'comp',
|
||||
|
@ -348,7 +344,7 @@ export class ReactivityParser {
|
|||
viewPropMap: {
|
||||
[id]: [compParticle],
|
||||
},
|
||||
dependencyIndexArr,
|
||||
depsBit,
|
||||
dependenciesNode,
|
||||
dynamic,
|
||||
},
|
||||
|
@ -364,7 +360,7 @@ export class ReactivityParser {
|
|||
* @returns ForParticle
|
||||
*/
|
||||
private parseFor(forUnit: ForUnit): ForParticle {
|
||||
const { dependencyIndexArr, dependenciesNode, dynamic } = this.getDependencies(forUnit.array);
|
||||
const { depsBit, 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,
|
||||
|
@ -375,7 +371,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, dependencyIndexArr.map(n => this.availableProperties[n])])
|
||||
.map(id => [id, depsBit.map(n => this.availableProperties[n])])
|
||||
);
|
||||
|
||||
const forParticle: ForParticle = {
|
||||
|
@ -384,7 +380,7 @@ export class ReactivityParser {
|
|||
array: {
|
||||
value: forUnit.array,
|
||||
dynamic,
|
||||
dependencyIndexArr,
|
||||
depsBit,
|
||||
dependenciesNode,
|
||||
},
|
||||
children: forUnit.children.map(this.parseViewParticle.bind(this)),
|
||||
|
@ -474,30 +470,24 @@ export class ReactivityParser {
|
|||
* @returns dependency index array
|
||||
*/
|
||||
private getDependencies(node: t.Expression | t.Statement): {
|
||||
dynamic: boolean;
|
||||
dependencyIndexArr: number[];
|
||||
depsBit: number;
|
||||
dependenciesNode: t.ArrayExpression;
|
||||
} {
|
||||
if (this.t.isFunctionExpression(node) || this.t.isArrowFunctionExpression(node)) {
|
||||
return {
|
||||
dynamic: false,
|
||||
dependencyIndexArr: [],
|
||||
depsBit: 0,
|
||||
dependenciesNode: this.t.arrayExpression([]),
|
||||
};
|
||||
}
|
||||
// ---- Both id and prop deps need to be calculated because
|
||||
// id is for snippet update, prop is normal update
|
||||
// in a snippet, the depsNode should be both id and prop
|
||||
const [directPropertyDeps, propertyDepNodes] = this.getPropertyDependencies(node);
|
||||
const directDependencies = directPropertyDeps;
|
||||
const identifierMapDependencies = this.getIdentifierMapDependencies(node);
|
||||
const deps = [...new Set([...directDependencies, ...identifierMapDependencies])];
|
||||
const [deps, propertyDepNodes] = this.getPropertyDependencies(node);
|
||||
|
||||
const depNodes = [...propertyDepNodes] as t.Expression[];
|
||||
|
||||
return {
|
||||
dynamic: depNodes.length > 0 || deps.length > 0,
|
||||
dependencyIndexArr: deps,
|
||||
depsBit: deps,
|
||||
dependenciesNode: this.t.arrayExpression(depNodes),
|
||||
};
|
||||
}
|
||||
|
@ -534,7 +524,7 @@ export class ReactivityParser {
|
|||
!this.isMemberInManualFunction(innerPath)
|
||||
) {
|
||||
deps.add(idName);
|
||||
this.dependencyMap[idName]?.forEach(deps.add.bind(deps));
|
||||
this.reactiveBitMap[idName]?.forEach(deps.add.bind(deps));
|
||||
if (!depNodes[idName]) depNodes[idName] = [];
|
||||
depNodes[idName].push(this.geneDependencyNode(innerPath));
|
||||
}
|
||||
|
@ -567,34 +557,39 @@ export class ReactivityParser {
|
|||
* @param node
|
||||
* @returns dependency index array
|
||||
*/
|
||||
private getPropertyDependencies(node: t.Expression | t.Statement): [number[], t.Node[]] {
|
||||
const deps = new Set<string>();
|
||||
const assignDeps = new Set<string>();
|
||||
private getPropertyDependencies(node: t.Expression | t.Statement): [number, t.Node[]] {
|
||||
// ---- Deps: console.log(count)
|
||||
let depsBit = 0;
|
||||
// ---- Assign deps: count = 1 or count++
|
||||
let assignDepBit = 0;
|
||||
const depNodes: Record<string, t.Node[]> = {};
|
||||
|
||||
const wrappedNode = this.valueWrapper(node);
|
||||
this.traverse(wrappedNode, {
|
||||
Identifier: innerPath => {
|
||||
const propertyKey = innerPath.node.name;
|
||||
const reactiveBitmap = this.reactiveBitMap.get(propertyKey);
|
||||
|
||||
if (this.isAssignmentExpressionLeft(innerPath) || this.isAssignmentFunction(innerPath)) {
|
||||
assignDeps.add(propertyKey);
|
||||
} else if (
|
||||
this.availableProperties.includes(propertyKey) &&
|
||||
!this.isMemberInEscapeFunction(innerPath) &&
|
||||
!this.isMemberInManualFunction(innerPath)
|
||||
) {
|
||||
deps.add(propertyKey);
|
||||
if (!depNodes[propertyKey]) depNodes[propertyKey] = [];
|
||||
depNodes[propertyKey].push(this.geneDependencyNode(innerPath));
|
||||
if (reactiveBitmap !== undefined) {
|
||||
if (this.isAssignmentExpressionLeft(innerPath) || this.isAssignmentFunction(innerPath)) {
|
||||
assignDepBit |= reactiveBitmap;
|
||||
} else {
|
||||
depsBit |= reactiveBitmap;
|
||||
if (!depNodes[propertyKey]) depNodes[propertyKey] = [];
|
||||
depNodes[propertyKey].push(this.t.cloneNode(innerPath.node));
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
const dependencyIdxArr = deduplicate([...deps].map(this.calDependencyIndexArr).flat());
|
||||
assignDeps.forEach(dep => {
|
||||
deps.delete(dep);
|
||||
delete depNodes[dep];
|
||||
});
|
||||
|
||||
// ---- Eliminate deps that are assigned in the same method
|
||||
// e.g. { console.log(count); count = 1 }
|
||||
// this will cause infinite loop
|
||||
// so we eliminate "count" from deps
|
||||
if (assignDepBit & depsBit) {
|
||||
// TODO: I think we should throw an error here to indicate the user that there is a loop
|
||||
}
|
||||
|
||||
let dependencyNodes = Object.values(depNodes).flat();
|
||||
// ---- deduplicate the dependency nodes
|
||||
dependencyNodes = dependencyNodes.filter((n, i) => {
|
||||
|
@ -602,8 +597,8 @@ export class ReactivityParser {
|
|||
return idx === i;
|
||||
});
|
||||
|
||||
deps.forEach(this.usedProperties.add.bind(this.usedProperties));
|
||||
return [dependencyIdxArr, dependencyNodes];
|
||||
// deps.forEach(this.usedProperties.add.bind(this.usedProperties));
|
||||
return [depsBit, dependencyNodes];
|
||||
}
|
||||
|
||||
private calDependencyIndexArr = (directDepKey: string) => {
|
||||
|
@ -625,7 +620,7 @@ export class ReactivityParser {
|
|||
};
|
||||
|
||||
private findDependency(propertyKey: string) {
|
||||
let currentMap: DependencyMap | undefined = this.dependencyMap;
|
||||
let currentMap: ReactiveBitMap | undefined = this.reactiveBitMap;
|
||||
do {
|
||||
if (currentMap[propertyKey] !== undefined) {
|
||||
return currentMap[propertyKey];
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
import { type types as t } from '@babel/core';
|
||||
import type Babel from '@babel/core';
|
||||
import { PrevMap } from '.';
|
||||
|
||||
export interface DependencyValue<T> {
|
||||
value: T;
|
||||
dynamic: boolean; // to removed
|
||||
dependencyIndexArr: number[]; // -> bit
|
||||
depsBit: number; // -> bit
|
||||
dependenciesNode: t.ArrayExpression;
|
||||
}
|
||||
|
||||
export interface DependencyProp {
|
||||
value: t.Expression;
|
||||
viewPropMap: Record<string, ViewParticle[]>;
|
||||
dynamic: boolean;
|
||||
dependencyIndexArr: number[];
|
||||
depsBit: number;
|
||||
dependenciesNode: t.ArrayExpression;
|
||||
}
|
||||
|
||||
|
@ -23,7 +21,7 @@ export interface TemplateProp {
|
|||
path: number[];
|
||||
value: t.Expression;
|
||||
dynamic: boolean;
|
||||
dependencyIndexArr: number[];
|
||||
depsBit: number;
|
||||
dependenciesNode: t.ArrayExpression;
|
||||
}
|
||||
|
||||
|
@ -128,25 +126,13 @@ export interface ReactivityParserConfig {
|
|||
babelApi: typeof Babel;
|
||||
availableProperties: string[];
|
||||
availableIdentifiers?: string[];
|
||||
dependencyMap: Record<string, string[] | null>;
|
||||
reactiveBitMap: ReactiveBitMap;
|
||||
identifierDepMap?: Record<string, string[]>;
|
||||
dependencyParseType?: 'property' | 'identifier';
|
||||
parseTemplate?: boolean;
|
||||
reactivityFuncNames?: string[];
|
||||
}
|
||||
|
||||
export interface DependencyMap {
|
||||
/**
|
||||
* key is the variable name, value is the dependencies
|
||||
* i.e. {
|
||||
* count: ['flag'],
|
||||
* state1: ['count', 'flag'],
|
||||
* state2: ['count', 'flag', 'state1'],
|
||||
* state3: ['count', 'flag', 'state1', 'state2'],
|
||||
* state4: ['count', 'flag', 'state1', 'state2', 'state3'],
|
||||
* }
|
||||
*/
|
||||
[key: string]: string[] | null;
|
||||
|
||||
[PrevMap]?: DependencyMap;
|
||||
}
|
||||
// TODO: unify with the types in babel-inula-next-core
|
||||
type Bitmap = number;
|
||||
export type ReactiveBitMap = Map<string, Bitmap>;
|
||||
|
|
Loading…
Reference in New Issue