fix(reactivity): remove used bit
This commit is contained in:
parent
3665d927ae
commit
5fc41b8e5e
|
@ -51,11 +51,11 @@ export function functionalMacroAnalyze(): Visitor {
|
||||||
// watch
|
// watch
|
||||||
if (calleeName === WATCH) {
|
if (calleeName === WATCH) {
|
||||||
const fnNode = extractFnFromMacro(expression, 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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { type NodePath } from '@babel/core';
|
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 { addLifecycle, createComponentNode } from './nodeFactory';
|
||||||
import { variablesAnalyze } from './variablesAnalyze';
|
import { variablesAnalyze } from './variablesAnalyze';
|
||||||
import { functionalMacroAnalyze } from './functionalMacroAnalyze';
|
import { functionalMacroAnalyze } from './functionalMacroAnalyze';
|
||||||
|
@ -7,6 +7,9 @@ import { getFnBodyPath } from '../utils';
|
||||||
import { viewAnalyze } from './viewAnalyze';
|
import { viewAnalyze } from './viewAnalyze';
|
||||||
import { WILL_MOUNT } from '../constants';
|
import { WILL_MOUNT } from '../constants';
|
||||||
import { types as t } from '@openinula/babel-api';
|
import { types as t } from '@openinula/babel-api';
|
||||||
|
import { ViewParticle } from '@openinula/reactivity-parser';
|
||||||
|
import { pruneComponentUnusedBit } from './pruneComponentUnusedBit';
|
||||||
|
|
||||||
const builtinAnalyzers = [variablesAnalyze, functionalMacroAnalyze, viewAnalyze];
|
const builtinAnalyzers = [variablesAnalyze, functionalMacroAnalyze, viewAnalyze];
|
||||||
|
|
||||||
function mergeVisitor(...visitors: Analyzer[]): Visitor {
|
function mergeVisitor(...visitors: Analyzer[]): Visitor {
|
||||||
|
@ -73,6 +76,7 @@ export function analyzeFnComp(
|
||||||
addLifecycle(componentNode, WILL_MOUNT, t.blockStatement(context.unhandledNode));
|
addLifecycle(componentNode, WILL_MOUNT, t.blockStatement(context.unhandledNode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The process of analyzing the component
|
* The process of analyzing the component
|
||||||
* 1. identify the component
|
* 1. identify the component
|
||||||
|
@ -94,5 +98,7 @@ export function analyze(
|
||||||
const root = createComponentNode(fnName, path);
|
const root = createComponentNode(fnName, path);
|
||||||
analyzeFnComp(path, root, { analyzers, htmlTags: options.htmlTags });
|
analyzeFnComp(path, root, { analyzers, htmlTags: options.htmlTags });
|
||||||
|
|
||||||
|
pruneComponentUnusedBit(root);
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,8 @@ export function createComponentNode(
|
||||||
name,
|
name,
|
||||||
children: undefined,
|
children: undefined,
|
||||||
variables: [],
|
variables: [],
|
||||||
|
usedBit: 0,
|
||||||
|
usedPropertySet: parent ? new Set(parent.usedPropertySet) : new Set<string>(),
|
||||||
_reactiveBitMap: parent ? new Map<string, number>(parent._reactiveBitMap) : new Map<string, number>(),
|
_reactiveBitMap: parent ? new Map<string, number>(parent._reactiveBitMap) : new Map<string, number>(),
|
||||||
lifecycle: {},
|
lifecycle: {},
|
||||||
parent,
|
parent,
|
||||||
|
@ -52,6 +54,9 @@ export function addProperty(comp: ComponentNode, name: string, value: t.Expressi
|
||||||
const bit = 1 << idx;
|
const bit = 1 << idx;
|
||||||
const bitmap = depBits ? depBits | bit : bit;
|
const bitmap = depBits ? depBits | bit : bit;
|
||||||
|
|
||||||
|
if (depBits) {
|
||||||
|
comp.usedBit |= depBits;
|
||||||
|
}
|
||||||
comp._reactiveBitMap.set(name, bitmap);
|
comp._reactiveBitMap.set(name, bitmap);
|
||||||
comp.variables.push({ name, value, isComputed: !!depBits, type: 'reactive', depMask: bitmap, level: comp.level });
|
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) {
|
export function addSubComponent(comp: ComponentNode, subComp: ComponentNode) {
|
||||||
|
comp.usedBit |= subComp.usedBit;
|
||||||
comp.variables.push({ ...subComp, type: 'subComp' });
|
comp.variables.push({ ...subComp, type: 'subComp' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,17 +81,21 @@ export function addLifecycle(comp: ComponentNode, lifeCycle: LifeCycle, block: t
|
||||||
export function addWatch(
|
export function addWatch(
|
||||||
comp: ComponentNode,
|
comp: ComponentNode,
|
||||||
callback: NodePath<t.ArrowFunctionExpression> | NodePath<t.FunctionExpression>,
|
callback: NodePath<t.ArrowFunctionExpression> | NodePath<t.FunctionExpression>,
|
||||||
depMask: Bitmap
|
deps: Set<string>,
|
||||||
|
usedBit: Bitmap
|
||||||
) {
|
) {
|
||||||
// if watch not exist, create a new one
|
// if watch not exist, create a new one
|
||||||
if (!comp.watch) {
|
if (!comp.watch) {
|
||||||
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
|
// TODO: Maybe we should merge
|
||||||
comp.usedPropertySet = usedPropertySet;
|
comp.usedPropertySet = usedPropertySet;
|
||||||
|
comp.usedBit |= usedBit;
|
||||||
comp.children = view;
|
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++
|
// ---- Assign deps: count = 1 or count++
|
||||||
let assignDepMask = 0;
|
let assignDepMask = 0;
|
||||||
const depNodes: Record<string, t.Expression[]> = {};
|
const depNodes: Record<string, t.Expression[]> = {};
|
||||||
|
const deps = new Set<string>();
|
||||||
|
|
||||||
const visitor = (innerPath: NodePath<t.Identifier>) => {
|
const visitor = (innerPath: NodePath<t.Identifier>) => {
|
||||||
const propertyKey = innerPath.node.name;
|
const propertyKey = innerPath.node.name;
|
||||||
|
@ -44,6 +45,8 @@ export function getDependenciesFromNode(
|
||||||
assignDepMask |= reactiveBitmap;
|
assignDepMask |= reactiveBitmap;
|
||||||
} else {
|
} else {
|
||||||
depMask |= reactiveBitmap;
|
depMask |= reactiveBitmap;
|
||||||
|
deps.add(propertyKey);
|
||||||
|
|
||||||
if (!depNodes[propertyKey]) depNodes[propertyKey] = [];
|
if (!depNodes[propertyKey]) depNodes[propertyKey] = [];
|
||||||
depNodes[propertyKey].push(t.cloneNode(innerPath.node));
|
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
|
// 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> {
|
export interface ReactiveVariable extends BaseVariable<t.Expression | null> {
|
||||||
type: 'reactive';
|
type: 'reactive';
|
||||||
level: number;
|
level: number;
|
||||||
|
bit?: Bitmap;
|
||||||
/**
|
/**
|
||||||
* indicate the dependency of the variable | the index of the reactive variable
|
* indicate the dependency of the variable | the index of the reactive variable
|
||||||
* i.e.
|
* i.e.
|
||||||
|
@ -69,7 +70,8 @@ export interface ComponentNode<Type = 'comp'> {
|
||||||
/**
|
/**
|
||||||
* The used properties in the component
|
* The used properties in the component
|
||||||
*/
|
*/
|
||||||
usedPropertySet?: Set<string>;
|
usedPropertySet: Set<string>;
|
||||||
|
usedBit: Bitmap;
|
||||||
/**
|
/**
|
||||||
* The map to find the reactive bitmap by name
|
* The map to find the reactive bitmap by name
|
||||||
*/
|
*/
|
||||||
|
@ -93,7 +95,7 @@ export interface ComponentNode<Type = 'comp'> {
|
||||||
* The watch fn in the component
|
* The watch fn in the component
|
||||||
*/
|
*/
|
||||||
watch?: {
|
watch?: {
|
||||||
depMask: Bitmap;
|
depMask?: Bitmap;
|
||||||
callback: NodePath<t.ArrowFunctionExpression> | NodePath<t.FunctionExpression>;
|
callback: NodePath<t.ArrowFunctionExpression> | NodePath<t.FunctionExpression>;
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ export function variablesAnalyze(): Visitor {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
depBits = getDependenciesFromNode(init, ctx);
|
depBits = getDependenciesFromNode(init, ctx)[1];
|
||||||
}
|
}
|
||||||
addProperty(ctx.current, id.node.name, init.node || null, depBits);
|
addProperty(ctx.current, id.node.name, init.node || null, depBits);
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,14 +35,14 @@ export function viewAnalyze(): Visitor {
|
||||||
parseTemplate: false,
|
parseTemplate: false,
|
||||||
});
|
});
|
||||||
// @ts-expect-error TODO: FIX TYPE
|
// @ts-expect-error TODO: FIX TYPE
|
||||||
const [viewParticles, usedPropertySet] = parseReactivity(viewUnits, {
|
const [viewParticles, usedPropertySet, usedBit] = parseReactivity(viewUnits, {
|
||||||
babelApi: getBabelApi(),
|
babelApi: getBabelApi(),
|
||||||
availableProperties: current.availableVariables,
|
availableProperties: current.availableVariables,
|
||||||
depMaskMap: current._reactiveBitMap,
|
depMaskMap: current._reactiveBitMap,
|
||||||
reactivityFuncNames,
|
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 name;
|
||||||
let className;
|
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;
|
||||||
return <input>{count}{doubleCount}</input>;
|
return <input>{count}{doubleCount}</input>;
|
||||||
|
|
|
@ -29,16 +29,25 @@ export function mockAnalyze(code: string, analyzers?: Analyzer[]): ComponentNode
|
||||||
syntaxJSX.default ?? syntaxJSX,
|
syntaxJSX.default ?? syntaxJSX,
|
||||||
function (api): PluginObj {
|
function (api): PluginObj {
|
||||||
register(api);
|
register(api);
|
||||||
|
const seen = new Set();
|
||||||
return {
|
return {
|
||||||
visitor: {
|
visitor: {
|
||||||
FunctionExpression: path => {
|
FunctionExpression: path => {
|
||||||
|
if (seen.has(path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
root = analyze('test', path, { customAnalyzers: analyzers, htmlTags: defaultHTMLTags });
|
root = analyze('test', path, { customAnalyzers: analyzers, htmlTags: defaultHTMLTags });
|
||||||
|
seen.add(path);
|
||||||
if (root) {
|
if (root) {
|
||||||
path.skip();
|
path.skip();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ArrowFunctionExpression: path => {
|
ArrowFunctionExpression: path => {
|
||||||
|
if (seen.has(path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
root = analyze('test', path, { customAnalyzers: analyzers, htmlTags: defaultHTMLTags });
|
root = analyze('test', path, { customAnalyzers: analyzers, htmlTags: defaultHTMLTags });
|
||||||
|
seen.add(path);
|
||||||
if (root) {
|
if (root) {
|
||||||
path.skip();
|
path.skip();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,17 +8,22 @@ import { type ViewParticle, type ReactivityParserConfig } from './types';
|
||||||
* @param config
|
* @param config
|
||||||
* @returns [viewParticles, usedProperties]
|
* @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,
|
// ---- ReactivityParser only accepts one view unit at a time,
|
||||||
// so we loop through the view units and get all the used properties
|
// so we loop through the view units and get all the used properties
|
||||||
const usedProperties = new Set<string>();
|
const usedProperties = new Set<string>();
|
||||||
|
let usedBit = 0;
|
||||||
const dlParticles = viewUnits.map(viewUnit => {
|
const dlParticles = viewUnits.map(viewUnit => {
|
||||||
const parser = new ReactivityParser(config);
|
const parser = new ReactivityParser(config);
|
||||||
const dlParticle = parser.parse(viewUnit);
|
const dlParticle = parser.parse(viewUnit);
|
||||||
parser.usedProperties.forEach(usedProperties.add.bind(usedProperties));
|
parser.usedProperties.forEach(usedProperties.add.bind(usedProperties));
|
||||||
|
usedBit |= parser.usedBit;
|
||||||
return dlParticle;
|
return dlParticle;
|
||||||
});
|
});
|
||||||
return [dlParticles, usedProperties];
|
return [dlParticles, usedProperties, usedBit];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type * from './types';
|
export type * from './types';
|
||||||
|
|
|
@ -54,6 +54,7 @@ export class ReactivityParser {
|
||||||
];
|
];
|
||||||
|
|
||||||
readonly usedProperties = new Set<string>();
|
readonly usedProperties = new Set<string>();
|
||||||
|
usedBit = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Constructor
|
* @brief Constructor
|
||||||
|
@ -328,12 +329,12 @@ export class ReactivityParser {
|
||||||
const keyDep = this.t.isIdentifier(forUnit.key) && forUnit.key.name;
|
const keyDep = this.t.isIdentifier(forUnit.key) && forUnit.key.name;
|
||||||
// ---- Generate an identifierDepMap to track identifiers in item and make them reactive
|
// ---- Generate an identifierDepMap to track identifiers in item and make them reactive
|
||||||
// based on the dependencies from the array
|
// based on the dependencies from the array
|
||||||
this.config.depMaskMap = new Map([
|
// this.config.depMaskMap = new Map([
|
||||||
...this.config.depMaskMap,
|
// ...this.config.depMaskMap,
|
||||||
...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, depMask]),
|
// .map(id => [id, depMask]),
|
||||||
]);
|
// ]);
|
||||||
|
|
||||||
const forParticle: ForParticle = {
|
const forParticle: ForParticle = {
|
||||||
type: 'for',
|
type: 'for',
|
||||||
|
@ -346,7 +347,7 @@ export class ReactivityParser {
|
||||||
children: forUnit.children.map(this.parseViewParticle.bind(this)),
|
children: forUnit.children.map(this.parseViewParticle.bind(this)),
|
||||||
key: forUnit.key,
|
key: forUnit.key,
|
||||||
};
|
};
|
||||||
this.config.identifierDepMap = prevIdentifierDepMap;
|
// this.config.identifierDepMap = prevIdentifierDepMap;
|
||||||
return forParticle;
|
return forParticle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -507,12 +508,14 @@ export class ReactivityParser {
|
||||||
});
|
});
|
||||||
|
|
||||||
deps.forEach(this.usedProperties.add.bind(this.usedProperties));
|
deps.forEach(this.usedProperties.add.bind(this.usedProperties));
|
||||||
|
this.usedBit |= depMask;
|
||||||
return [depMask, dependencyNodes];
|
return [depMask, dependencyNodes];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Generate a dependency node from a dependency identifier,
|
* @brief Generate a dependency node from a dependency identifier,
|
||||||
* loop until the parent node is not a binary expression or a member expression
|
* 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
|
* @param path
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
|
@ -583,6 +586,7 @@ export class ReactivityParser {
|
||||||
const parsedUnit = parser.parse(viewUnit);
|
const parsedUnit = parser.parse(viewUnit);
|
||||||
// ---- Collect used properties
|
// ---- Collect used properties
|
||||||
parser.usedProperties.forEach(this.usedProperties.add.bind(this.usedProperties));
|
parser.usedProperties.forEach(this.usedProperties.add.bind(this.usedProperties));
|
||||||
|
this.usedBit |= parser.usedBit;
|
||||||
return parsedUnit;
|
return parsedUnit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue