From 0dcad572f342dfbd3ff1df4bd7b30bfc713b0487 Mon Sep 17 00:00:00 2001 From: Hoikan <30694822+HoikanChan@users.noreply.github.com> Date: Mon, 29 Apr 2024 17:58:19 +0800 Subject: [PATCH] refactor(parse): use bitmap instead of dependency map --- .../src/analyzer/nodeFactory.ts | 19 ++-- .../src/analyzer/reactive/getDependencies.ts | 47 +++----- .../src/analyzer/types.ts | 21 ++-- .../src/analyzer/variablesAnalyze.ts | 6 +- .../src/analyzer/viewAnalyze.ts | 3 +- .../test/analyze/properties.test.ts | 104 ++++-------------- .../analyze/{props.test.ts => props.x.ts} | 0 .../test/analyze/viewAnalyze.test.ts | 17 +-- .../reactivity-parser/src/parser.ts | 91 ++++++++------- .../transpiler/reactivity-parser/src/types.ts | 28 ++--- 10 files changed, 121 insertions(+), 215 deletions(-) rename packages/transpiler/babel-inula-next-core/test/analyze/{props.test.ts => props.x.ts} (100%) diff --git a/packages/transpiler/babel-inula-next-core/src/analyzer/nodeFactory.ts b/packages/transpiler/babel-inula-next-core/src/analyzer/nodeFactory.ts index 0678f030..1008b2d6 100644 --- a/packages/transpiler/babel-inula-next-core/src/analyzer/nodeFactory.ts +++ b/packages/transpiler/babel-inula-next-core/src/analyzer/nodeFactory.ts @@ -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(parent._reactiveBitMap) : new Map(), 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( diff --git a/packages/transpiler/babel-inula-next-core/src/analyzer/reactive/getDependencies.ts b/packages/transpiler/babel-inula-next-core/src/analyzer/reactive/getDependencies.ts index 43d4223c..0013d6c9 100644 --- a/packages/transpiler/babel-inula-next-core/src/analyzer/reactive/getDependencies.ts +++ b/packages/transpiler/babel-inula-next-core/src/analyzer/reactive/getDependencies.ts @@ -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(); + let depsBit = 0; // ---- Assign deps: count = 1 or count++ - const assignDeps = new Set(); + let assignDepBit = 0; const depNodes: Record = {}; const visitor = (innerPath: NodePath) => { 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; -} diff --git a/packages/transpiler/babel-inula-next-core/src/analyzer/types.ts b/packages/transpiler/babel-inula-next-core/src/analyzer/types.ts index 7a6a8a77..0f0c83b2 100644 --- a/packages/transpiler/babel-inula-next-core/src/analyzer/types.ts +++ b/packages/transpiler/babel-inula-next-core/src/analyzer/types.ts @@ -30,7 +30,14 @@ interface BaseVariable { export interface ReactiveVariable extends BaseVariable { 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 { type: 'method'; } -export interface SubCompVariable extends BaseVariable { - 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: Type; name: string; level: number; // The variables defined in the component @@ -66,9 +71,9 @@ export interface ComponentNode { */ usedPropertySet?: Set; /** - * The available props for the component, including the nested props + * The map to find the reactive bitmap by name */ - availableProps: string[]; + _reactiveBitMap: Map; /** * The available variables and props owned by the component */ diff --git a/packages/transpiler/babel-inula-next-core/src/analyzer/variablesAnalyze.ts b/packages/transpiler/babel-inula-next-core/src/analyzer/variablesAnalyze.ts index b5fadde7..46f4bec5 100644 --- a/packages/transpiler/babel-inula-next-core/src/analyzer/variablesAnalyze.ts +++ b/packages/transpiler/babel-inula-next-core/src/analyzer/variablesAnalyze.ts @@ -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); } }); }, diff --git a/packages/transpiler/babel-inula-next-core/src/analyzer/viewAnalyze.ts b/packages/transpiler/babel-inula-next-core/src/analyzer/viewAnalyze.ts index bae240a3..8b618275 100644 --- a/packages/transpiler/babel-inula-next-core/src/analyzer/viewAnalyze.ts +++ b/packages/transpiler/babel-inula-next-core/src/analyzer/viewAnalyze.ts @@ -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, }); diff --git a/packages/transpiler/babel-inula-next-core/test/analyze/properties.test.ts b/packages/transpiler/babel-inula-next-core/test/analyze/properties.test.ts index ae1648b9..73f20bd2 100644 --- a/packages/transpiler/babel-inula-next-core/test/analyze/properties.test.ts +++ b/packages/transpiler/babel-inula-next-core/test/analyze/properties.test.ts @@ -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, - } - `); }); }); diff --git a/packages/transpiler/babel-inula-next-core/test/analyze/props.test.ts b/packages/transpiler/babel-inula-next-core/test/analyze/props.x.ts similarity index 100% rename from packages/transpiler/babel-inula-next-core/test/analyze/props.test.ts rename to packages/transpiler/babel-inula-next-core/test/analyze/props.x.ts diff --git a/packages/transpiler/babel-inula-next-core/test/analyze/viewAnalyze.test.ts b/packages/transpiler/babel-inula-next-core/test/analyze/viewAnalyze.test.ts index d833b187..a8c9a001 100644 --- a/packages/transpiler/babel-inula-next-core/test/analyze/viewAnalyze.test.ts +++ b/packages/transpiler/babel-inula-next-core/test/analyze/viewAnalyze.test.ts @@ -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 {count}{doubleCount}; }); return
{doubleCount2}
; diff --git a/packages/transpiler/reactivity-parser/src/parser.ts b/packages/transpiler/reactivity-parser/src/parser.ts index 23334244..5f534619 100644 --- a/packages/transpiler/reactivity-parser/src/parser.ts +++ b/packages/transpiler/reactivity-parser/src/parser.ts @@ -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; 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(); - const assignDeps = new Set(); + 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 = {}; 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]; diff --git a/packages/transpiler/reactivity-parser/src/types.ts b/packages/transpiler/reactivity-parser/src/types.ts index 7ac6dfb5..202654ce 100644 --- a/packages/transpiler/reactivity-parser/src/types.ts +++ b/packages/transpiler/reactivity-parser/src/types.ts @@ -1,19 +1,17 @@ import { type types as t } from '@babel/core'; import type Babel from '@babel/core'; -import { PrevMap } from '.'; export interface DependencyValue { value: T; dynamic: boolean; // to removed - dependencyIndexArr: number[]; // -> bit + depsBit: number; // -> bit dependenciesNode: t.ArrayExpression; } export interface DependencyProp { value: t.Expression; viewPropMap: Record; - 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; + reactiveBitMap: ReactiveBitMap; identifierDepMap?: Record; 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;