fix(reactivity): remove used bit
This commit is contained in:
parent
3665d927ae
commit
5fc41b8e5e
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@ export function createComponentNode(
|
|||
name,
|
||||
children: undefined,
|
||||
variables: [],
|
||||
usedBit: 0,
|
||||
usedPropertySet: parent ? new Set(parent.usedPropertySet) : new Set<string>(),
|
||||
_reactiveBitMap: parent ? new Map<string, number>(parent._reactiveBitMap) : new Map<string, number>(),
|
||||
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<t.ArrowFunctionExpression> | NodePath<t.FunctionExpression>,
|
||||
depMask: Bitmap
|
||||
deps: Set<string>,
|
||||
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<string>) {
|
||||
export function setViewChild(comp: ComponentNode, view: ViewParticle[], usedPropertySet: Set<string>, usedBit: Bitmap) {
|
||||
// TODO: Maybe we should merge
|
||||
comp.usedPropertySet = usedPropertySet;
|
||||
comp.usedBit |= usedBit;
|
||||
comp.children = view;
|
||||
}
|
||||
|
|
|
@ -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<t.ObjectProperty | t.RestElement>, 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<t.ObjectProperty>,
|
||||
{ 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);
|
||||
}
|
|
@ -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<string, number>();
|
||||
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;
|
||||
}
|
|
@ -34,6 +34,7 @@ export function getDependenciesFromNode(
|
|||
// ---- Assign deps: count = 1 or count++
|
||||
let assignDepMask = 0;
|
||||
const depNodes: Record<string, t.Expression[]> = {};
|
||||
const deps = new Set<string>();
|
||||
|
||||
const visitor = (innerPath: NodePath<t.Identifier>) => {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -30,6 +30,7 @@ interface BaseVariable<V> {
|
|||
export interface ReactiveVariable extends BaseVariable<t.Expression | null> {
|
||||
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<Type = 'comp'> {
|
|||
/**
|
||||
* The used properties in the component
|
||||
*/
|
||||
usedPropertySet?: Set<string>;
|
||||
usedPropertySet: Set<string>;
|
||||
usedBit: Bitmap;
|
||||
/**
|
||||
* The map to find the reactive bitmap by name
|
||||
*/
|
||||
|
@ -93,7 +95,7 @@ export interface ComponentNode<Type = 'comp'> {
|
|||
* The watch fn in the component
|
||||
*/
|
||||
watch?: {
|
||||
depMask: Bitmap;
|
||||
depMask?: Bitmap;
|
||||
callback: NodePath<t.ArrowFunctionExpression> | NodePath<t.FunctionExpression>;
|
||||
}[];
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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 <input>{count}{doubleCount}</input>;
|
||||
});
|
||||
return <div className={count}>{doubleCount}</div>;
|
||||
});
|
||||
`);
|
||||
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);
|
||||
});
|
||||
});
|
|
@ -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 <input>{count}{doubleCount}</input>;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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<string>] {
|
||||
export function parseReactivity(
|
||||
viewUnits: ViewUnit[],
|
||||
config: ReactivityParserConfig
|
||||
): [ViewParticle[], Set<string>, 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<string>();
|
||||
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';
|
||||
|
|
|
@ -54,6 +54,7 @@ export class ReactivityParser {
|
|||
];
|
||||
|
||||
readonly usedProperties = new Set<string>();
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue