feat: viewNode as child
This commit is contained in:
parent
f15b7d1a14
commit
be4456f225
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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 { NodePath } from '@babel/core';
|
||||
import { LifeCycle, Visitor } from './types';
|
||||
import { addLifecycle, addWatch } from './nodeFactory';
|
||||
import * as t from '@babel/types';
|
||||
import { ON_MOUNT, ON_UNMOUNT, WATCH, WILL_MOUNT, WILL_UNMOUNT } from '../constants';
|
||||
import { extractFnFromMacro, getFnBody } from '../utils';
|
||||
|
||||
function isLifeCycleName(name: string): name is LifeCycle {
|
||||
return [WILL_MOUNT, ON_MOUNT, WILL_UNMOUNT, ON_UNMOUNT].includes(name);
|
||||
}
|
||||
/**
|
||||
* Analyze the functional macro in the function component
|
||||
* 1. lifecycle
|
||||
* 1. willMount
|
||||
* 2. onMount
|
||||
* 3. willUnMount
|
||||
* 4. onUnmount
|
||||
* 2. watch
|
||||
*/
|
||||
export function functionalMacroAnalyze(): Visitor {
|
||||
return {
|
||||
ExpressionStatement(path: NodePath<t.ExpressionStatement>, ctx) {
|
||||
const expression = path.get('expression');
|
||||
if (expression.isCallExpression()) {
|
||||
const callee = expression.get('callee');
|
||||
if (callee.isIdentifier()) {
|
||||
const calleeName = callee.node.name;
|
||||
// lifecycle
|
||||
if (isLifeCycleName(calleeName)) {
|
||||
const fnNode = extractFnFromMacro(expression, calleeName);
|
||||
addLifecycle(ctx.current, calleeName, getFnBody(fnNode).node);
|
||||
return;
|
||||
}
|
||||
|
||||
// watch
|
||||
if (calleeName === WATCH) {
|
||||
const fnNode = extractFnFromMacro(expression, WATCH);
|
||||
const deps = getWatchDeps(expression);
|
||||
addWatch(ctx.current, fnNode, deps);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.unhandledNode.push(path.node);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getWatchDeps(callExpression: NodePath<t.CallExpression>) {
|
||||
const args = callExpression.get('arguments');
|
||||
if (!args[1]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let deps: null | NodePath<t.ArrayExpression> = null;
|
||||
if (args[1].isArrayExpression()) {
|
||||
deps = args[1];
|
||||
} else {
|
||||
console.error('watch deps should be an array expression');
|
||||
}
|
||||
return deps;
|
||||
}
|
|
@ -1,11 +1,14 @@
|
|||
import { type types as t, type NodePath } from '@babel/core';
|
||||
import { propsAnalyze } from './propsAnalyze';
|
||||
import { AnalyzeContext, Analyzer, ComponentNode, CondNode, Visitor } from './types';
|
||||
import { createComponentNode } from './nodeFactory';
|
||||
import { addLifecycle, createComponentNode } from './nodeFactory';
|
||||
import { propertiesAnalyze } from './propertiesAnalyze';
|
||||
import { lifeCycleAnalyze } from './lifeCycleAnalyze';
|
||||
import { functionalMacroAnalyze } from './functionalMacroAnalyze';
|
||||
import { getFnBody } from '../utils';
|
||||
const builtinAnalyzers = [propsAnalyze, propertiesAnalyze, lifeCycleAnalyze];
|
||||
import { viewAnalyze } from './viewAnalyze';
|
||||
import { WILL_MOUNT } from '../constants';
|
||||
import { types } from '../babelTypes';
|
||||
const builtinAnalyzers = [propsAnalyze, propertiesAnalyze, functionalMacroAnalyze, viewAnalyze];
|
||||
|
||||
export function isCondNode(node: any): node is CondNode {
|
||||
return node && node.type === 'cond';
|
||||
|
@ -13,22 +16,7 @@ export function isCondNode(node: any): node is CondNode {
|
|||
|
||||
function mergeVisitor(...visitors: Analyzer[]): Visitor {
|
||||
return visitors.reduce<Visitor<AnalyzeContext>>((acc, cur) => {
|
||||
const visitor = cur();
|
||||
const visitorKeys = Object.keys(visitor) as (keyof Visitor)[];
|
||||
for (const key of visitorKeys) {
|
||||
if (acc[key]) {
|
||||
// if already exist, merge the visitor function
|
||||
const original = acc[key]!;
|
||||
acc[key] = (path: any, ctx) => {
|
||||
original(path, ctx);
|
||||
visitor[key]?.(path, ctx);
|
||||
};
|
||||
} else {
|
||||
// @ts-expect-error key is a valid key, no idea why it's not working
|
||||
acc[key] = visitor[key];
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
return { ...acc, ...cur() };
|
||||
}, {});
|
||||
}
|
||||
|
||||
|
@ -45,6 +33,7 @@ export function analyzeFnComp(
|
|||
current: componentNode,
|
||||
htmlTags,
|
||||
analyzers,
|
||||
unhandledNode: [],
|
||||
traverse: (path: NodePath<t.Statement>, ctx: AnalyzeContext) => {
|
||||
path.traverse(visitor, ctx);
|
||||
},
|
||||
|
@ -71,14 +60,23 @@ export function analyzeFnComp(
|
|||
|
||||
const type = p.node.type;
|
||||
|
||||
// TODO: More type safe way to handle this
|
||||
visitor[type]?.(p as unknown as any, context);
|
||||
const visit = visitor[type];
|
||||
if (visit) {
|
||||
// TODO: More type safe way to handle this
|
||||
visit(p as unknown as any, context);
|
||||
} else {
|
||||
context.unhandledNode.push(p.node);
|
||||
}
|
||||
|
||||
if (p.isReturnStatement()) {
|
||||
visitor.ReturnStatement?.(p, context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (context.unhandledNode.length) {
|
||||
addLifecycle(componentNode, WILL_MOUNT, types.blockStatement(context.unhandledNode));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* The process of analyzing the component
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* 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 { NodePath } from '@babel/core';
|
||||
import { LifeCycle, Visitor } from './types';
|
||||
import { addLifecycle } from './nodeFactory';
|
||||
import * as t from '@babel/types';
|
||||
import { ON_MOUNT, ON_UNMOUNT, WILL_MOUNT, WILL_UNMOUNT } from '../constants';
|
||||
import { extractFnFromMacro, getFnBody } from '../utils';
|
||||
|
||||
function isLifeCycleName(name: string): name is LifeCycle {
|
||||
return [WILL_MOUNT, ON_MOUNT, WILL_UNMOUNT, ON_UNMOUNT].includes(name);
|
||||
}
|
||||
/**
|
||||
* Analyze the lifeCycle in the function component
|
||||
* 1. willMount
|
||||
* 2. onMount
|
||||
* 3. willUnMount
|
||||
* 4. onUnmount
|
||||
*/
|
||||
export function lifeCycleAnalyze(): Visitor {
|
||||
return {
|
||||
ExpressionStatement(path: NodePath<t.ExpressionStatement>, ctx) {
|
||||
const expression = path.get('expression');
|
||||
if (expression.isCallExpression()) {
|
||||
const callee = expression.get('callee');
|
||||
if (callee.isIdentifier()) {
|
||||
const lifeCycleName = callee.node.name;
|
||||
if (isLifeCycleName(lifeCycleName)) {
|
||||
const fnNode = extractFnFromMacro(expression, lifeCycleName);
|
||||
addLifecycle(ctx.current, lifeCycleName, getFnBody(fnNode));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
|
@ -14,12 +14,13 @@
|
|||
*/
|
||||
|
||||
import { NodePath, type types as t } from '@babel/core';
|
||||
import { Branch, ComponentNode, CondNode, InulaNode, JSX, JSXNode, LifeCycle, SubCompNode } from './types';
|
||||
import { ComponentNode, FunctionalExpression, LifeCycle, ViewNode } from './types';
|
||||
import { PropType } from '../constants';
|
||||
import { ViewParticle } from '@openinula/reactivity-parser';
|
||||
|
||||
export function createComponentNode(
|
||||
name: string,
|
||||
fnNode: NodePath<t.FunctionExpression | t.ArrowFunctionExpression>,
|
||||
fnNode: NodePath<FunctionalExpression>,
|
||||
parent?: ComponentNode
|
||||
): ComponentNode {
|
||||
const comp: ComponentNode = {
|
||||
|
@ -27,12 +28,12 @@ export function createComponentNode(
|
|||
name,
|
||||
props: [],
|
||||
child: undefined,
|
||||
properties: [],
|
||||
variables: [],
|
||||
dependencyMap: {},
|
||||
reactiveMap: {},
|
||||
lifecycle: {},
|
||||
parent,
|
||||
// fnBody,
|
||||
fnNode,
|
||||
get availableProps() {
|
||||
return comp.props
|
||||
.map(({ name, nestedProps, alias }) => {
|
||||
|
@ -41,11 +42,11 @@ export function createComponentNode(
|
|||
})
|
||||
.flat();
|
||||
},
|
||||
get ownAvailableProperties() {
|
||||
return [...comp.properties.filter(p => !p.isMethod).map(({ name }) => name), ...comp.availableProps];
|
||||
get ownAvailableVariables() {
|
||||
return [...comp.variables.filter(p => p.type === 'reactive').map(({ name }) => name), ...comp.availableProps];
|
||||
},
|
||||
get availableProperties() {
|
||||
return [...comp.ownAvailableProperties, ...(comp.parent ? comp.parent.availableProperties : [])];
|
||||
get availableVariables() {
|
||||
return [...comp.ownAvailableVariables, ...(comp.parent ? comp.parent.availableVariables : [])];
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -53,15 +54,15 @@ export function createComponentNode(
|
|||
}
|
||||
|
||||
export function addProperty(comp: ComponentNode, name: string, value: t.Expression | null, isComputed: boolean) {
|
||||
comp.properties.push({ name, value, isComputed, isMethod: false });
|
||||
comp.variables.push({ name, value, isComputed, type: 'reactive' });
|
||||
}
|
||||
|
||||
export function addMethod(comp: ComponentNode, name: string, value: t.Expression | null) {
|
||||
comp.properties.push({ name, value, isComputed: false, isMethod: true });
|
||||
export function addMethod(comp: ComponentNode, name: string, value: FunctionalExpression) {
|
||||
comp.variables.push({ name, value, type: 'method' });
|
||||
}
|
||||
|
||||
export function addSubComponent(comp: ComponentNode, subComp: ComponentNode, isComputed: boolean) {
|
||||
comp.properties.push({ name: subComp.name, value: subComp, isSubComp: true, isComputed, isMethod: false });
|
||||
export function addSubComponent(comp: ComponentNode, subComp: ComponentNode) {
|
||||
comp.variables.push({ name: subComp.name, value: subComp, type: 'subComp' });
|
||||
}
|
||||
|
||||
export function addProp(
|
||||
|
@ -76,7 +77,7 @@ export function addProp(
|
|||
comp.props.push({ name: key, type, default: defaultVal, alias, nestedProps, nestedRelationship });
|
||||
}
|
||||
|
||||
export function addLifecycle(comp: ComponentNode, lifeCycle: LifeCycle, block: NodePath<t.BlockStatement>) {
|
||||
export function addLifecycle(comp: ComponentNode, lifeCycle: LifeCycle, block: t.BlockStatement) {
|
||||
const compLifecycle = comp.lifecycle;
|
||||
if (!compLifecycle[lifeCycle]) {
|
||||
compLifecycle[lifeCycle] = [];
|
||||
|
@ -96,28 +97,10 @@ export function addWatch(
|
|||
comp.watch.push({ callback, deps });
|
||||
}
|
||||
|
||||
export function createJSXNode(parent: ComponentNode, content: NodePath<JSX>): JSXNode {
|
||||
return {
|
||||
type: 'jsx',
|
||||
parent,
|
||||
child: content,
|
||||
};
|
||||
}
|
||||
|
||||
export function createCondNode(parent: ComponentNode, child: InulaNode, branches: Branch[]): CondNode {
|
||||
return {
|
||||
type: 'cond',
|
||||
branches,
|
||||
child,
|
||||
parent,
|
||||
};
|
||||
}
|
||||
|
||||
export function createSubCompNode(name: string, parent: ComponentNode, child: JSX): SubCompNode {
|
||||
return {
|
||||
type: 'subComp',
|
||||
name,
|
||||
parent,
|
||||
child,
|
||||
export function setViewChild(comp: ComponentNode, view: ViewParticle[], usedPropertySet: Set<string>) {
|
||||
const viewNode: ViewNode = {
|
||||
content: view,
|
||||
usedPropertySet,
|
||||
};
|
||||
comp.child = viewNode;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
*/
|
||||
|
||||
import { AnalyzeContext, Visitor } from './types';
|
||||
import { addMethod, addProperty, createComponentNode } from './nodeFactory';
|
||||
import { addMethod, addProperty, addSubComponent, createComponentNode } from './nodeFactory';
|
||||
import { isValidPath } from './utils';
|
||||
import { type types as t, type NodePath } from '@babel/core';
|
||||
import { reactivityFuncNames } from '../const';
|
||||
|
@ -45,11 +45,12 @@ export function propertiesAnalyze(): Visitor {
|
|||
const init = declaration.get('init');
|
||||
let deps: string[] | null = null;
|
||||
if (isValidPath(init)) {
|
||||
// the property is a method
|
||||
// handle the method
|
||||
if (init.isArrowFunctionExpression() || init.isFunctionExpression()) {
|
||||
addMethod(ctx.current, id.node.name, init.node);
|
||||
return;
|
||||
}
|
||||
// handle the sub component
|
||||
// Should like Component(() => {})
|
||||
if (
|
||||
init.isCallExpression() &&
|
||||
|
@ -64,9 +65,10 @@ export function propertiesAnalyze(): Visitor {
|
|||
|
||||
analyzeFnComp(fnNode, subComponent, ctx);
|
||||
deps = getDependenciesFromNode(id.node.name, init, ctx);
|
||||
addProperty(ctx.current, id.node.name, subComponent, !!deps?.length);
|
||||
addSubComponent(ctx.current, subComponent);
|
||||
return;
|
||||
}
|
||||
|
||||
deps = getDependenciesFromNode(id.node.name, init, ctx);
|
||||
}
|
||||
addProperty(ctx.current, id.node.name, init.node || null, !!deps?.length);
|
||||
|
@ -111,7 +113,7 @@ function getDependenciesFromNode(
|
|||
const propertyKey = innerPath.node.name;
|
||||
if (isAssignmentExpressionLeft(innerPath) || isAssignmentFunction(innerPath)) {
|
||||
assignDeps.add(propertyKey);
|
||||
} else if (current.availableProperties.includes(propertyKey)) {
|
||||
} else if (current.availableVariables.includes(propertyKey)) {
|
||||
deps.add(propertyKey);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -14,32 +14,36 @@
|
|||
*/
|
||||
|
||||
import { type NodePath, types as t } from '@babel/core';
|
||||
import { Node } from '@babel/traverse';
|
||||
import { ON_MOUNT, ON_UNMOUNT, PropType, WILL_MOUNT, WILL_UNMOUNT } from '../constants';
|
||||
import { ViewParticle } from '@openinula/reactivity-parser';
|
||||
|
||||
// --- Node shape ---
|
||||
export type InulaNode = ComponentNode | CondNode | JSXNode;
|
||||
export type JSX = t.JSXElement | t.JSXFragment;
|
||||
export type LifeCycle = typeof WILL_MOUNT | typeof ON_MOUNT | typeof WILL_UNMOUNT | typeof ON_UNMOUNT;
|
||||
type defaultVal = any | null;
|
||||
type Bitmap = number;
|
||||
interface BaseProperty<V> {
|
||||
|
||||
export type FunctionalExpression = t.FunctionExpression | t.ArrowFunctionExpression;
|
||||
interface BaseVariable<V> {
|
||||
name: string;
|
||||
value: V;
|
||||
// need a flag for computed to gen a getter
|
||||
// watch is a static computed
|
||||
isComputed: boolean;
|
||||
isMethod: boolean;
|
||||
}
|
||||
interface Property extends BaseProperty<t.Expression | null> {
|
||||
export interface ReactiveVariable extends BaseVariable<t.Expression | null> {
|
||||
type: 'reactive';
|
||||
// indicate the value is a state or computed or watch
|
||||
listeners?: string[];
|
||||
bitmap?: Bitmap;
|
||||
// need a flag for computed to gen a getter
|
||||
// watch is a static computed
|
||||
isComputed: boolean;
|
||||
}
|
||||
interface SubCompProperty extends BaseProperty<ComponentNode> {
|
||||
isSubComp: true;
|
||||
|
||||
export interface MethodVariable extends BaseVariable<FunctionalExpression> {
|
||||
type: 'method';
|
||||
}
|
||||
interface Prop {
|
||||
export interface SubCompVariable extends BaseVariable<ComponentNode> {
|
||||
type: 'subComp';
|
||||
}
|
||||
|
||||
export type Variable = ReactiveVariable | MethodVariable | SubCompVariable;
|
||||
export interface Prop {
|
||||
name: string;
|
||||
type: PropType;
|
||||
alias: string | null;
|
||||
|
@ -51,66 +55,45 @@ export interface ComponentNode {
|
|||
type: 'comp';
|
||||
name: string;
|
||||
props: Prop[];
|
||||
// A properties could be a state or computed
|
||||
properties: (Property | SubCompProperty)[];
|
||||
// The variables defined in the component
|
||||
variables: Variable[];
|
||||
/**
|
||||
* The available props for the component, including the nested props
|
||||
*/
|
||||
availableProps: string[];
|
||||
/**
|
||||
* The available properties for the component
|
||||
* The available variables and props owned by the component
|
||||
*/
|
||||
ownAvailableProperties: string[];
|
||||
availableProperties: string[];
|
||||
ownAvailableVariables: string[];
|
||||
/**
|
||||
* The available variables and props for the component and its parent
|
||||
*/
|
||||
availableVariables: string[];
|
||||
/**
|
||||
* The map to find the dependencies
|
||||
*/
|
||||
dependencyMap: {
|
||||
[key: string]: string[];
|
||||
};
|
||||
child?: InulaNode;
|
||||
child?: ComponentNode | ViewNode;
|
||||
parent?: ComponentNode;
|
||||
/**
|
||||
* The function body of the fn component code
|
||||
*/
|
||||
fnBody: NodePath<t.Statement>[];
|
||||
fnNode: NodePath<FunctionalExpression>;
|
||||
/**
|
||||
* The map to find the state
|
||||
*/
|
||||
reactiveMap: Record<string, Bitmap>;
|
||||
lifecycle: Partial<Record<LifeCycle, NodePath<t.Statement>[]>>;
|
||||
lifecycle: Partial<Record<LifeCycle, t.Statement[]>>;
|
||||
watch?: {
|
||||
deps: NodePath<t.ArrayExpression> | null;
|
||||
callback: NodePath<t.ArrowFunctionExpression> | NodePath<t.FunctionExpression>;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface SubCompNode {
|
||||
type: 'subComp';
|
||||
name: string;
|
||||
parent: ComponentNode;
|
||||
child: JSX;
|
||||
}
|
||||
|
||||
export interface JSXNode {
|
||||
type: 'jsx';
|
||||
parent: ComponentNode;
|
||||
child: NodePath<JSX>;
|
||||
}
|
||||
|
||||
export interface CondNode {
|
||||
type: 'cond';
|
||||
branches: Branch[];
|
||||
parent: ComponentNode;
|
||||
/**
|
||||
* The default branch
|
||||
*/
|
||||
child: InulaNode;
|
||||
}
|
||||
|
||||
export interface Branch {
|
||||
conditions: NodePath<t.Expression>[];
|
||||
content: InulaNode;
|
||||
export interface ViewNode {
|
||||
content: ViewParticle[];
|
||||
usedPropertySet: Set<string>;
|
||||
}
|
||||
|
||||
export interface AnalyzeContext {
|
||||
|
@ -119,10 +102,11 @@ export interface AnalyzeContext {
|
|||
analyzers: Analyzer[];
|
||||
htmlTags: string[];
|
||||
traverse: (p: NodePath<t.Statement>, ctx: AnalyzeContext) => void;
|
||||
unhandledNode: t.Statement[];
|
||||
}
|
||||
|
||||
export type Visitor<S = AnalyzeContext> = {
|
||||
[Type in Node['type']]?: (path: NodePath<Extract<Node, { type: Type }>>, state: S) => void;
|
||||
[Type in t.Statement['type']]?: (path: NodePath<Extract<t.Statement, { type: Type }>>, state: S) => void;
|
||||
} & {
|
||||
Prop?: (path: NodePath<t.ObjectProperty | t.RestElement>, state: S) => void;
|
||||
};
|
||||
|
|
|
@ -19,6 +19,7 @@ import { parseView as parseJSX } from 'jsx-view-parser';
|
|||
import { getBabelApi } from '../babelTypes';
|
||||
import { parseReactivity } from '@openinula/reactivity-parser';
|
||||
import { reactivityFuncNames } from '../const';
|
||||
import { setViewChild } from './nodeFactory';
|
||||
|
||||
/**
|
||||
* Analyze the watch in the function component
|
||||
|
@ -35,10 +36,12 @@ export function viewAnalyze(): Visitor {
|
|||
});
|
||||
const [viewParticles, usedPropertySet] = parseReactivity(viewUnits, {
|
||||
babelApi: getBabelApi(),
|
||||
availableProperties: current.availableProperties,
|
||||
availableProperties: current.availableVariables,
|
||||
dependencyMap: current.dependencyMap,
|
||||
reactivityFuncNames,
|
||||
});
|
||||
|
||||
setViewChild(current, viewParticles, usedPropertySet);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* 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 { NodePath } from '@babel/core';
|
||||
import { Visitor } from './types';
|
||||
import { addWatch } from './nodeFactory';
|
||||
import * as t from '@babel/types';
|
||||
import { WATCH } from '../constants';
|
||||
import { extractFnFromMacro } from '../utils';
|
||||
|
||||
/**
|
||||
* Analyze the watch in the function component
|
||||
*/
|
||||
export function watchAnalyze(): Visitor {
|
||||
return {
|
||||
ExpressionStatement(path: NodePath<t.ExpressionStatement>, ctx) {
|
||||
const callExpression = path.get('expression');
|
||||
if (callExpression.isCallExpression()) {
|
||||
const callee = callExpression.get('callee');
|
||||
if (callee.isIdentifier() && callee.node.name === WATCH) {
|
||||
const fnNode = extractFnFromMacro(callExpression, WATCH);
|
||||
const deps = getWatchDeps(callExpression);
|
||||
addWatch(ctx.current, fnNode, deps);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getWatchDeps(callExpression: NodePath<t.CallExpression>) {
|
||||
const args = callExpression.get('arguments');
|
||||
if (!args[1]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let deps: null | NodePath<t.ArrayExpression> = null;
|
||||
if (args[1].isArrayExpression()) {
|
||||
deps = args[1];
|
||||
} else {
|
||||
console.error('watch deps should be an array expression');
|
||||
}
|
||||
return deps;
|
||||
}
|
|
@ -15,12 +15,12 @@
|
|||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { genCode, mockAnalyze } from '../mock';
|
||||
import { lifeCycleAnalyze } from '../../src/analyze/lifeCycleAnalyze';
|
||||
import { functionalMacroAnalyze } from '../../src/analyze/functionalMacroAnalyze';
|
||||
import { types } from '../../src/babelTypes';
|
||||
import { type NodePath, type types as t } from '@babel/core';
|
||||
|
||||
const analyze = (code: string) => mockAnalyze(code, [lifeCycleAnalyze]);
|
||||
const combine = (body: NodePath<t.Statement>[]) => types.program(body.map(path => path.node));
|
||||
const analyze = (code: string) => mockAnalyze(code, [functionalMacroAnalyze]);
|
||||
const combine = (body: t.Statement[]) => types.program(body);
|
||||
|
||||
describe('analyze lifeCycle', () => {
|
||||
it('should collect will mount', () => {
|
||||
|
|
|
@ -29,7 +29,7 @@ describe('analyze properties', () => {
|
|||
let bar = 1;
|
||||
})
|
||||
`);
|
||||
expect(root.properties.length).toBe(2);
|
||||
expect(root.variables.length).toBe(2);
|
||||
});
|
||||
|
||||
describe('state dependency', () => {
|
||||
|
@ -40,11 +40,11 @@ describe('analyze properties', () => {
|
|||
let bar = foo;
|
||||
})
|
||||
`);
|
||||
expect(root.properties.length).toBe(2);
|
||||
expect(root.properties[0].isComputed).toBe(false);
|
||||
expect(genCode(root.properties[0].value)).toBe('1');
|
||||
expect(root.properties[1].isComputed).toBe(true);
|
||||
expect(genCode(root.properties[1].value)).toBe('foo');
|
||||
expect(root.variables.length).toBe(2);
|
||||
expect(root.variables[0].isComputed).toBe(false);
|
||||
expect(genCode(root.variables[0].value)).toBe('1');
|
||||
expect(root.variables[1].isComputed).toBe(true);
|
||||
expect(genCode(root.variables[1].value)).toBe('foo');
|
||||
expect(root.dependencyMap).toEqual({ bar: ['foo'] });
|
||||
});
|
||||
|
||||
|
@ -57,9 +57,9 @@ describe('analyze properties', () => {
|
|||
let bar = { foo: foo ? a : b };
|
||||
})
|
||||
`);
|
||||
expect(root.properties.length).toBe(4);
|
||||
expect(root.properties[3].isComputed).toBe(true);
|
||||
expect(genCode(root.properties[3].value)).toMatchInlineSnapshot(`
|
||||
expect(root.variables.length).toBe(4);
|
||||
expect(root.variables[3].isComputed).toBe(true);
|
||||
expect(genCode(root.variables[3].value)).toMatchInlineSnapshot(`
|
||||
"{
|
||||
foo: foo ? a : b
|
||||
}"
|
||||
|
@ -73,8 +73,8 @@ describe('analyze properties', () => {
|
|||
let bar = foo;
|
||||
})
|
||||
`);
|
||||
expect(root.properties.length).toBe(1);
|
||||
expect(root.properties[0].isComputed).toBe(true);
|
||||
expect(root.variables.length).toBe(1);
|
||||
expect(root.variables[0].isComputed).toBe(true);
|
||||
expect(root.dependencyMap).toEqual({ bar: ['foo'] });
|
||||
});
|
||||
|
||||
|
@ -84,8 +84,8 @@ describe('analyze properties', () => {
|
|||
let bar = [foo1, first, last];
|
||||
})
|
||||
`);
|
||||
expect(root.properties.length).toBe(1);
|
||||
expect(root.properties[0].isComputed).toBe(true);
|
||||
expect(root.variables.length).toBe(1);
|
||||
expect(root.variables[0].isComputed).toBe(true);
|
||||
expect(root.dependencyMap).toEqual({ bar: ['foo1', 'first', 'last'] });
|
||||
});
|
||||
|
||||
|
@ -96,8 +96,8 @@ describe('analyze properties', () => {
|
|||
let bar = cond ? count : window.innerWidth;
|
||||
})
|
||||
`);
|
||||
expect(root.properties.length).toBe(1);
|
||||
expect(root.properties[0].isComputed).toBe(false);
|
||||
expect(root.variables.length).toBe(1);
|
||||
expect(root.variables[0].isComputed).toBe(false);
|
||||
expect(root.dependencyMap).toEqual({});
|
||||
});
|
||||
});
|
||||
|
@ -112,9 +112,9 @@ describe('analyze properties', () => {
|
|||
});
|
||||
})
|
||||
`);
|
||||
expect(root.properties.length).toBe(2);
|
||||
expect(root.variables.length).toBe(2);
|
||||
expect(root.dependencyMap).toEqual({ Sub: ['foo'] });
|
||||
expect((root.properties[1].value as ComponentNode).dependencyMap).toMatchInlineSnapshot(`
|
||||
expect((root.variables[1].value as ComponentNode).dependencyMap).toMatchInlineSnapshot(`
|
||||
{
|
||||
"bar": [
|
||||
"foo",
|
||||
|
@ -135,9 +135,9 @@ describe('analyze properties', () => {
|
|||
function onInput() {}
|
||||
})
|
||||
`);
|
||||
expect(root.properties.map(p => p.name)).toEqual(['foo', 'onClick', 'onHover', 'onInput']);
|
||||
expect(root.properties[1].isMethod).toBe(true);
|
||||
expect(root.properties[2].isMethod).toBe(true);
|
||||
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('{}');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { propsAnalyze } from '../../src/analyze/propsAnalyze';
|
||||
import { watchAnalyze } from '../../src/analyze/watchAnalyze';
|
||||
import { functionalMacroAnalyze } from '../../src/analyze/functionalMacroAnalyze';
|
||||
import { genCode, mockAnalyze } from '../mock';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
const analyze = (code: string) => mockAnalyze(code, [watchAnalyze]);
|
||||
const analyze = (code: string) => mockAnalyze(code, [functionalMacroAnalyze]);
|
||||
|
||||
describe('watchAnalyze', () => {
|
||||
it('should analyze watch expressions', () => {
|
||||
|
|
Loading…
Reference in New Issue