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