diff --git a/packages/transpiler/babel-inula-next-core/src/analyzer/functionalMacroAnalyze.ts b/packages/transpiler/babel-inula-next-core/src/analyzer/functionalMacroAnalyze.ts index 6feb09ee..a9e07350 100644 --- a/packages/transpiler/babel-inula-next-core/src/analyzer/functionalMacroAnalyze.ts +++ b/packages/transpiler/babel-inula-next-core/src/analyzer/functionalMacroAnalyze.ts @@ -51,11 +51,11 @@ export function functionalMacroAnalyze(): Visitor { // watch if (calleeName === WATCH) { const fnNode = extractFnFromMacro(expression, WATCH); - const deps = getWatchDeps(expression); + const depsPath = getWatchDeps(expression); - const depMask = getDependenciesFromNode(deps ?? fnNode, ctx); + const [deps, depMask] = getDependenciesFromNode(depsPath ?? fnNode, ctx); - addWatch(ctx.current, fnNode, depMask); + addWatch(ctx.current, fnNode, deps, depMask); return; } } diff --git a/packages/transpiler/babel-inula-next-core/src/analyzer/index.ts b/packages/transpiler/babel-inula-next-core/src/analyzer/index.ts index 0b14b3d3..63f4ac70 100644 --- a/packages/transpiler/babel-inula-next-core/src/analyzer/index.ts +++ b/packages/transpiler/babel-inula-next-core/src/analyzer/index.ts @@ -1,5 +1,5 @@ import { type NodePath } from '@babel/core'; -import { AnalyzeContext, Analyzer, ComponentNode, Visitor } from './types'; +import { AnalyzeContext, Analyzer, Bitmap, ComponentNode, Visitor } from './types'; import { addLifecycle, createComponentNode } from './nodeFactory'; import { variablesAnalyze } from './variablesAnalyze'; import { functionalMacroAnalyze } from './functionalMacroAnalyze'; @@ -7,6 +7,9 @@ import { getFnBodyPath } from '../utils'; import { viewAnalyze } from './viewAnalyze'; import { WILL_MOUNT } from '../constants'; import { types as t } from '@openinula/babel-api'; +import { ViewParticle } from '@openinula/reactivity-parser'; +import { pruneComponentUnusedBit } from './pruneComponentUnusedBit'; + const builtinAnalyzers = [variablesAnalyze, functionalMacroAnalyze, viewAnalyze]; function mergeVisitor(...visitors: Analyzer[]): Visitor { @@ -73,6 +76,7 @@ export function analyzeFnComp( addLifecycle(componentNode, WILL_MOUNT, t.blockStatement(context.unhandledNode)); } } + /** * The process of analyzing the component * 1. identify the component @@ -94,5 +98,7 @@ export function analyze( const root = createComponentNode(fnName, path); analyzeFnComp(path, root, { analyzers, htmlTags: options.htmlTags }); + pruneComponentUnusedBit(root); + return root; } 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 bf957d3d..bacd3695 100644 --- a/packages/transpiler/babel-inula-next-core/src/analyzer/nodeFactory.ts +++ b/packages/transpiler/babel-inula-next-core/src/analyzer/nodeFactory.ts @@ -29,6 +29,8 @@ export function createComponentNode( name, children: undefined, variables: [], + usedBit: 0, + usedPropertySet: parent ? new Set(parent.usedPropertySet) : new Set(), _reactiveBitMap: parent ? new Map(parent._reactiveBitMap) : new Map(), lifecycle: {}, parent, @@ -52,6 +54,9 @@ export function addProperty(comp: ComponentNode, name: string, value: t.Expressi const bit = 1 << idx; const bitmap = depBits ? depBits | bit : bit; + if (depBits) { + comp.usedBit |= depBits; + } comp._reactiveBitMap.set(name, bitmap); comp.variables.push({ name, value, isComputed: !!depBits, type: 'reactive', depMask: bitmap, level: comp.level }); } @@ -61,6 +66,7 @@ export function addMethod(comp: ComponentNode, name: string, value: FunctionalEx } export function addSubComponent(comp: ComponentNode, subComp: ComponentNode) { + comp.usedBit |= subComp.usedBit; comp.variables.push({ ...subComp, type: 'subComp' }); } @@ -75,17 +81,21 @@ export function addLifecycle(comp: ComponentNode, lifeCycle: LifeCycle, block: t export function addWatch( comp: ComponentNode, callback: NodePath | NodePath, - depMask: Bitmap + deps: Set, + usedBit: Bitmap ) { // if watch not exist, create a new one if (!comp.watch) { comp.watch = []; } - comp.watch.push({ callback, depMask }); + comp.usedPropertySet = new Set([...comp.usedPropertySet, ...deps]); + comp.usedBit |= usedBit; + comp.watch.push({ callback }); } -export function setViewChild(comp: ComponentNode, view: ViewParticle[], usedPropertySet: Set) { +export function setViewChild(comp: ComponentNode, view: ViewParticle[], usedPropertySet: Set, usedBit: Bitmap) { // TODO: Maybe we should merge comp.usedPropertySet = usedPropertySet; + comp.usedBit |= usedBit; comp.children = view; } diff --git a/packages/transpiler/babel-inula-next-core/src/analyzer/propsAnalyze.ts b/packages/transpiler/babel-inula-next-core/src/analyzer/propsAnalyze.ts deleted file mode 100644 index 93c2f112..00000000 --- a/packages/transpiler/babel-inula-next-core/src/analyzer/propsAnalyze.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { type NodePath } from '@babel/core'; -import { AnalyzeContext, Visitor } from './types'; -import { PropType } from '../constants'; -import { types as t } from '@openinula/babel-api'; - -export interface Prop { - name: string; - type: PropType; - alias: string | null; - default: t.Expression | null; - nestedProps: string[] | null; - nestedRelationship: t.ObjectPattern | t.ArrayPattern | null; -} - -/** - * Analyze the props deconstructing in the function component - * 1. meet identifier, just collect the name - * 2. has alias, collect the alias name - * 3. has default value, collect the default value - * 4. has rest element, collect the rest element - * 5. nested destructuring, the e2e goal: - * ```js - * function(prop1, prop2: [p20, p21]) {} - * // transform into - * function({ prop1, prop2: [p20, p21] }) { - * let p20, p21 - * watch(() => { - * [p20, p21] = prop2 - * }) - * } - * ``` - */ -export function propsAnalyze(): Visitor { - return { - Prop(path: NodePath, ctx) { - if (path.isObjectProperty()) { - // --- normal property --- - const key = path.node.key; - const value = path.node.value; - if (t.isIdentifier(key) || t.isStringLiteral(key)) { - const name = t.isIdentifier(key) ? key.name : key.value; - analyzeSingleProp(value, name, path, ctx); - return; - } - - throw Error(`Unsupported key type in object destructuring: ${key.type}`); - } else { - // --- rest element --- - const arg = path.get('argument'); - if (!Array.isArray(arg) && arg.isIdentifier()) { - addProp(ctx.current, PropType.REST, arg.node.name); - } - } - }, - }; -} - -function analyzeSingleProp( - value: t.ObjectProperty['value'], - key: string, - path: NodePath, - { current }: AnalyzeContext -) { - let defaultVal: t.Expression | null = null; - let alias: string | null = null; - const nestedProps: string[] | null = []; - let nestedRelationship: t.ObjectPattern | t.ArrayPattern | null = null; - if (t.isIdentifier(value)) { - // 1. handle alias without default value - // handle alias without default value - if (key !== value.name) { - alias = value.name; - } - } else if (t.isAssignmentPattern(value)) { - // 2. handle default value case - const assignedName = value.left; - defaultVal = value.right; - if (t.isIdentifier(assignedName)) { - if (assignedName.name !== key) { - // handle alias in default value case - alias = assignedName.name; - } - } else { - throw Error(`Unsupported assignment type in object destructuring: ${assignedName.type}`); - } - } else if (t.isObjectPattern(value) || t.isArrayPattern(value)) { - // 3. nested destructuring - // we should collect the identifier that can be used in the function body as the prop - // e.g. function ({prop1, prop2: [p20X, {p211, p212: p212X}]} - // we should collect prop1, p20X, p211, p212X - path.get('value').traverse({ - Identifier(path) { - // judge if the identifier is a prop - // 1. is the key of the object property and doesn't have alias - // 2. is the item of the array pattern and doesn't have alias - // 3. is alias of the object property - const parentPath = path.parentPath; - if (parentPath.isObjectProperty() && path.parentKey === 'value') { - // collect alias of the object property - nestedProps.push(path.node.name); - } else if ( - parentPath.isArrayPattern() || - parentPath.isObjectPattern() || - parentPath.isRestElement() || - (parentPath.isAssignmentPattern() && path.key === 'left') - ) { - // collect the key of the object property or the item of the array pattern - nestedProps.push(path.node.name); - } - }, - }); - nestedRelationship = value; - } - addProp(current, PropType.SINGLE, key, defaultVal, alias, nestedProps, nestedRelationship); -} diff --git a/packages/transpiler/babel-inula-next-core/src/analyzer/pruneComponentUnusedBit.ts b/packages/transpiler/babel-inula-next-core/src/analyzer/pruneComponentUnusedBit.ts new file mode 100644 index 00000000..2bd1737b --- /dev/null +++ b/packages/transpiler/babel-inula-next-core/src/analyzer/pruneComponentUnusedBit.ts @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2024 Huawei Technologies Co.,Ltd. + * + * openInula is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +import { Bitmap, ComponentNode } from './types'; +import { ViewParticle } from '@openinula/reactivity-parser'; + +/** + * To prune the bitmap of unused properties + * etc.: + * ```js + * let a = 1; // 0b001 + * let b = 2; // 0b010 b is not used*, and should be pruned + * let c = 3; // 0b100 -> 0b010(cause bit of b is pruned) + * ``` + * @param root + * @param index + */ +export function pruneComponentUnusedBit(comp: ComponentNode<'comp'> | ComponentNode<'subComp'>, index = 1) { + // dfs the component tree + // To store the bitmap of the properties + const bitMap = new Map(); + const bitPositionToRemove: number[] = []; + comp.variables.forEach(v => { + if (v.type === 'reactive') { + // get the origin bit, computed should keep the highest bit, etc. 0b0111 -> 0b0100 + const originBit = keepHighestBit(v.depMask); + if ((comp.usedBit & originBit) !== 0) { + v.bit = 1 << index; + bitMap.set(v.name, v.bit); + if (v.isComputed) { + v.depMask = pruneBitmap(v.depMask, bitPositionToRemove); + } + } else { + bitPositionToRemove.push(index); + } + index++; + } else if (v.type === 'subComp') { + pruneComponentUnusedBit(v, index); + } + }); + + comp.watch?.forEach(watch => { + if (!watch.depMask) { + return; + } + watch.depMask = pruneBitmap(watch.depMask, bitPositionToRemove); + }); + + // handle children + if (comp.children) { + comp.children.forEach(child => { + if (child.type === 'comp') { + pruneComponentUnusedBit(child as ComponentNode<'comp'>, index); + } else { + pruneViewParticleUnusedBit(child as ViewParticle, bitPositionToRemove); + } + }); + } +} + +function pruneBitmap(depMask: Bitmap, bitPositionToRemove: number[]) { + // turn the bitmap to binary string + const binary = depMask.toString(2); + // remove the bit + binary + .split('') + .reverse() + .filter((bit, index) => { + return !bitPositionToRemove.includes(index); + }) + .reverse() + .join(''); + + return parseInt(binary, 2); +} + +function pruneViewParticleUnusedBit(particle: ViewParticle, bitPositionToRemove: number[]) { + // dfs the view particle to prune the bitmap + const stack: ViewParticle[] = [particle]; + while (stack.length) { + const node = stack.pop()! as ViewParticle; + if (node.type === 'template') { + node.props.forEach(prop => { + prop.depMask = pruneBitmap(prop.depMask, bitPositionToRemove); + }); + stack.push(node.template); + } else if (node.type === 'html') { + for (const key in node.props) { + node.props[key].depMask = pruneBitmap(node.props[key].depMask, bitPositionToRemove); + } + stack.push(...node.children); + } else if (node.type === 'text') { + node.content.depMask = pruneBitmap(node.content.depMask, bitPositionToRemove); + } else if (node.type === 'for') { + node.array.depMask = pruneBitmap(node.array.depMask, bitPositionToRemove); + stack.push(...node.children); + } else if (node.type === 'if') { + node.branches.forEach(branch => { + branch.condition.depMask = pruneBitmap(branch.condition.depMask, bitPositionToRemove); + stack.push(...branch.children); + }); + } else if (node.type === 'env') { + for (const key in node.props) { + node.props[key].depMask = pruneBitmap(node.props[key].depMask, bitPositionToRemove); + } + stack.push(...node.children); + } else if (node.type === 'exp') { + node.content.depMask = pruneBitmap(node.content.depMask, bitPositionToRemove); + for (const key in node.props) { + node.props[key].depMask = pruneBitmap(node.props[key].depMask, bitPositionToRemove); + } + } + } +} + +function keepHighestBit(bitmap: number) { + // 获取二进制数的长度 + const length = bitmap.toString(2).length; + + // 创建掩码 + const mask = 1 << (length - 1); + + // 使用按位与运算符只保留最高位 + return bitmap & mask; +} + +function removeBit(bitmap: number, bitPosition: number) { + // 创建掩码,将目标位右边的位设置为 1,其他位设置为 0 + const rightMask = (1 << (bitPosition - 1)) - 1; + + // 创建掩码,将目标位左边的位设置为 1,其他位设置为 0 + const leftMask = ~rightMask << 1; + + // 提取右部分 + const rightPart = bitmap & rightMask; + + // 提取左部分并右移一位 + const leftPart = (bitmap & leftMask) >> 1; + + // 组合左部分和右部分 + return leftPart | rightPart; +} 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 6e103bfd..19dbcf16 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 @@ -34,6 +34,7 @@ export function getDependenciesFromNode( // ---- Assign deps: count = 1 or count++ let assignDepMask = 0; const depNodes: Record = {}; + const deps = new Set(); const visitor = (innerPath: NodePath) => { const propertyKey = innerPath.node.name; @@ -44,6 +45,8 @@ export function getDependenciesFromNode( assignDepMask |= reactiveBitmap; } else { depMask |= reactiveBitmap; + deps.add(propertyKey); + if (!depNodes[propertyKey]) depNodes[propertyKey] = []; depNodes[propertyKey].push(t.cloneNode(innerPath.node)); } @@ -64,7 +67,7 @@ export function getDependenciesFromNode( // TODO: I think we should throw an error here to indicate the user that there is a loop } - return depMask; + return [deps, depMask] as const; } /** 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 52902c7b..b87ad2af 100644 --- a/packages/transpiler/babel-inula-next-core/src/analyzer/types.ts +++ b/packages/transpiler/babel-inula-next-core/src/analyzer/types.ts @@ -30,6 +30,7 @@ interface BaseVariable { export interface ReactiveVariable extends BaseVariable { type: 'reactive'; level: number; + bit?: Bitmap; /** * indicate the dependency of the variable | the index of the reactive variable * i.e. @@ -69,7 +70,8 @@ export interface ComponentNode { /** * The used properties in the component */ - usedPropertySet?: Set; + usedPropertySet: Set; + usedBit: Bitmap; /** * The map to find the reactive bitmap by name */ @@ -93,7 +95,7 @@ export interface ComponentNode { * The watch fn in the component */ watch?: { - depMask: Bitmap; + depMask?: Bitmap; callback: NodePath | NodePath; }[]; } 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 a42dccc9..f3639300 100644 --- a/packages/transpiler/babel-inula-next-core/src/analyzer/variablesAnalyze.ts +++ b/packages/transpiler/babel-inula-next-core/src/analyzer/variablesAnalyze.ts @@ -68,7 +68,7 @@ export function variablesAnalyze(): Visitor { return; } - depBits = getDependenciesFromNode(init, ctx); + depBits = getDependenciesFromNode(init, ctx)[1]; } 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 179ea8fd..be6edb79 100644 --- a/packages/transpiler/babel-inula-next-core/src/analyzer/viewAnalyze.ts +++ b/packages/transpiler/babel-inula-next-core/src/analyzer/viewAnalyze.ts @@ -35,14 +35,14 @@ export function viewAnalyze(): Visitor { parseTemplate: false, }); // @ts-expect-error TODO: FIX TYPE - const [viewParticles, usedPropertySet] = parseReactivity(viewUnits, { + const [viewParticles, usedPropertySet, usedBit] = parseReactivity(viewUnits, { babelApi: getBabelApi(), availableProperties: current.availableVariables, depMaskMap: current._reactiveBitMap, reactivityFuncNames, }); - setViewChild(current, viewParticles, usedPropertySet); + setViewChild(current, viewParticles, usedPropertySet, usedBit); } }, }; diff --git a/packages/transpiler/babel-inula-next-core/test/analyze/pruneBitmap.test.ts b/packages/transpiler/babel-inula-next-core/test/analyze/pruneBitmap.test.ts new file mode 100644 index 00000000..dd17edbe --- /dev/null +++ b/packages/transpiler/babel-inula-next-core/test/analyze/pruneBitmap.test.ts @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024 Huawei Technologies Co.,Ltd. + * + * openInula is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +import { variablesAnalyze } from '../../src/analyzer/variablesAnalyze'; +import { ComponentNode } from '../../src/analyzer/types'; +import { viewAnalyze } from '../../src/analyzer/viewAnalyze'; +import { functionalMacroAnalyze } from '../../src/analyzer/functionalMacroAnalyze'; +import { genCode, mockAnalyze } from '../mock'; +import { describe, expect, it } from 'vitest'; + +const analyze = (code: string) => mockAnalyze(code, [variablesAnalyze, viewAnalyze, functionalMacroAnalyze]); +describe('prune unused bit', () => { + it('should work', () => { + const root = analyze(/*js*/ ` + Component(({}) => { + let name; + let className; // unused + let className1; // unused + let className2; // unused + let count = name; // 1 + let doubleCount = count * 2; // 2 + const Input = Component(() => { + let count3 = 1; + let count2 = 1; + let count = 1; + return {count}{doubleCount}; + }); + return
{doubleCount}
; + }); + `); + const div = root.children![0] as any; + expect(div.children[0].content.depMask).toEqual(0b111); + expect(div.props.className.depMask).toEqual(0b11); + + // @ts-expect-error ignore ts here + const InputCompNode = root.variables[4] as ComponentNode; + // it's the {count} + expect(inputFirstExp.content.depMask).toEqual(0b10000); + // it's the {doubleCount} + expect(inputSecondExp.content.depMask).toEqual(0b1101); + }); +}); 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 6923e45b..d0e4e075 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 @@ -12,8 +12,8 @@ describe('viewAnalyze', () => { let name; let className; let count = name; // 1 - let doubleCount = count* 2; // 2 - let doubleCount2 = doubleCount* 2; // 4 + let doubleCount = count * 2; // 2 + let doubleCount2 = doubleCount * 2; // 4 const Input = Component(() => { let count = 1; return {count}{doubleCount}; diff --git a/packages/transpiler/babel-inula-next-core/test/mock.ts b/packages/transpiler/babel-inula-next-core/test/mock.ts index 5a0c09bd..596ebccf 100644 --- a/packages/transpiler/babel-inula-next-core/test/mock.ts +++ b/packages/transpiler/babel-inula-next-core/test/mock.ts @@ -29,16 +29,25 @@ export function mockAnalyze(code: string, analyzers?: Analyzer[]): ComponentNode syntaxJSX.default ?? syntaxJSX, function (api): PluginObj { register(api); + const seen = new Set(); return { visitor: { FunctionExpression: path => { + if (seen.has(path)) { + return; + } root = analyze('test', path, { customAnalyzers: analyzers, htmlTags: defaultHTMLTags }); + seen.add(path); if (root) { path.skip(); } }, ArrowFunctionExpression: path => { + if (seen.has(path)) { + return; + } root = analyze('test', path, { customAnalyzers: analyzers, htmlTags: defaultHTMLTags }); + seen.add(path); if (root) { path.skip(); } diff --git a/packages/transpiler/reactivity-parser/src/index.ts b/packages/transpiler/reactivity-parser/src/index.ts index 1b7ef8cf..4aafcded 100644 --- a/packages/transpiler/reactivity-parser/src/index.ts +++ b/packages/transpiler/reactivity-parser/src/index.ts @@ -8,17 +8,22 @@ import { type ViewParticle, type ReactivityParserConfig } from './types'; * @param config * @returns [viewParticles, usedProperties] */ -export function parseReactivity(viewUnits: ViewUnit[], config: ReactivityParserConfig): [ViewParticle[], Set] { +export function parseReactivity( + viewUnits: ViewUnit[], + config: ReactivityParserConfig +): [ViewParticle[], Set, number] { // ---- ReactivityParser only accepts one view unit at a time, // so we loop through the view units and get all the used properties const usedProperties = new Set(); + let usedBit = 0; const dlParticles = viewUnits.map(viewUnit => { const parser = new ReactivityParser(config); const dlParticle = parser.parse(viewUnit); parser.usedProperties.forEach(usedProperties.add.bind(usedProperties)); + usedBit |= parser.usedBit; return dlParticle; }); - return [dlParticles, usedProperties]; + return [dlParticles, usedProperties, usedBit]; } export type * from './types'; diff --git a/packages/transpiler/reactivity-parser/src/parser.ts b/packages/transpiler/reactivity-parser/src/parser.ts index e4446807..f17f1cc6 100644 --- a/packages/transpiler/reactivity-parser/src/parser.ts +++ b/packages/transpiler/reactivity-parser/src/parser.ts @@ -54,6 +54,7 @@ export class ReactivityParser { ]; readonly usedProperties = new Set(); + usedBit = 0; /** * @brief Constructor @@ -328,12 +329,12 @@ export class ReactivityParser { const keyDep = this.t.isIdentifier(forUnit.key) && forUnit.key.name; // ---- Generate an identifierDepMap to track identifiers in item and make them reactive // based on the dependencies from the array - this.config.depMaskMap = new Map([ - ...this.config.depMaskMap, - ...this.getIdentifiers(this.t.assignmentExpression('=', forUnit.item, this.t.objectExpression([]))) - .filter(id => !keyDep || id !== keyDep) - .map(id => [id, depMask]), - ]); + // this.config.depMaskMap = new Map([ + // ...this.config.depMaskMap, + // ...this.getIdentifiers(this.t.assignmentExpression('=', forUnit.item, this.t.objectExpression([]))) + // .filter(id => !keyDep || id !== keyDep) + // .map(id => [id, depMask]), + // ]); const forParticle: ForParticle = { type: 'for', @@ -346,7 +347,7 @@ export class ReactivityParser { children: forUnit.children.map(this.parseViewParticle.bind(this)), key: forUnit.key, }; - this.config.identifierDepMap = prevIdentifierDepMap; + // this.config.identifierDepMap = prevIdentifierDepMap; return forParticle; } @@ -507,12 +508,14 @@ export class ReactivityParser { }); deps.forEach(this.usedProperties.add.bind(this.usedProperties)); + this.usedBit |= depMask; return [depMask, dependencyNodes]; } /** * @brief Generate a dependency node from a dependency identifier, * loop until the parent node is not a binary expression or a member expression + * And turn the member expression into an optional member expression, like info.name -> info?.name * @param path * @returns */ @@ -583,6 +586,7 @@ export class ReactivityParser { const parsedUnit = parser.parse(viewUnit); // ---- Collect used properties parser.usedProperties.forEach(this.usedProperties.add.bind(this.usedProperties)); + this.usedBit |= parser.usedBit; return parsedUnit; }