-
Notifications
You must be signed in to change notification settings - Fork 20
/
component-info.ts
184 lines (164 loc) · 6.22 KB
/
component-info.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/**
* 组件信息
*
* 概念:每个 San 组件都对应一个 ComponentInfo。
*
* 关系:通常通过 src/parsers 下的解析器从 SSR 的输入得到 SanSourceFile。
* 一个 SanSourceFile 中可能包含若干个 ComponentInfo。
*
* 类型:对于 TS 源码输入,解析得到的是 TypedComponentInfo,
* 对于其他输入,解析得到的是 JSComponentInfo。
*/
import type { SanComponentConfig, ANode } from 'san'
import { FunctionDefinition } from '../ast/renderer-ast-dfn'
import { parseAndNormalizeTemplate } from '../parsers/parse-template'
import type { ClassDeclaration } from 'ts-morph'
import { Node } from 'estree'
import { visitANodeRecursively } from '../ast/san-ast-util'
import { ComponentReference, DynamicComponentReference } from './component-reference'
import { getObjectLiteralPropertyKeys } from '../ast/ts-ast-util'
import { assertObjectExpression, getLiteralValue, getPropertiesFromObject, getStringArrayValue } from '../ast/js-ast-util'
import type { RenderOptions } from '../compilers/renderer-options'
import { RendererCompiler } from '../compilers/renderer-compiler'
import { ComponentClass } from './component'
export type TagName = string
type TrimWhitespace = 'none' | 'blank' | 'all' | undefined
/**
* 所有类型的 ComponentInfo,都需要实现如下接口
*/
export interface ComponentInfo {
id: string,
root: ANode,
childComponents: Map<TagName, ComponentReference>
hasMethod (name: string): boolean
getComputedNames (): string[]
getFilterNames (): string[]
hasDynamicComponent (): boolean
compileToRenderer (options: RenderOptions): FunctionDefinition
}
/**
* 提供一个组件信息的封装,包括:
* - computed name 列表、filters name 列表、root ANode
* - 从 TypeScript 来的还有 sourceFile、classDeclaration 等
*
* 注意:
* - 这里只是存数据,它的创建由具体 parser 负责
* - 这个工具类只为了方便实现具体的 ComponentInfo,不暴露作为接口
*/
abstract class ComponentInfoImpl<R extends ComponentReference = ComponentReference> {
constructor (
/**
* 见 component-reference.ts 的说明
*/
public readonly id: string,
public readonly root: ANode,
public readonly childComponents: Map<TagName, R>
) {}
abstract hasMethod (name: string): boolean
abstract getComputedNames (): string[]
abstract getFilterNames (): string[]
hasDynamicComponent (): boolean {
let found = false
visitANodeRecursively(this.root, (node) => {
if (node.directives && node.directives.is) found = true
})
return found
}
compileToRenderer (options: RenderOptions): FunctionDefinition {
return new RendererCompiler(options).compileToRenderer(this)
}
}
export class DynamicComponentInfo extends ComponentInfoImpl<DynamicComponentReference> implements ComponentInfo {
/**
* 归一化的 proto。
*
* 确保 computed 等属性都出现在 proto 上,
* 用于 compileToRenderer() 和 compileToSource()
*/
public readonly proto: SanComponentConfig<{}, {}>
constructor (
id: string,
root: ANode,
childComponents: Map<TagName, DynamicComponentReference>,
public readonly componentClass: ComponentClass
) {
super(id, root, childComponents)
this.proto = Object.assign(componentClass.prototype, componentClass)
}
hasMethod (name: string) {
return !!this.proto[name]
}
getComputedNames () {
return Object.keys(this.proto.computed || {})
}
getFilterNames () {
return Object.keys(this.proto.filters || {})
}
}
export class JSComponentInfo extends ComponentInfoImpl<ComponentReference> {
public readonly className: string
public readonly sourceCode: string
public readonly isRawObject: boolean
private readonly properties: Map<string, Node>
constructor (
id: string,
className: string,
properties: Map<string, Node>,
sourceCode: string,
isRawObject: boolean = false
) {
const template = properties.has('template') ? getLiteralValue(properties.get('template')!) as string : ''
const trimWhitespace: TrimWhitespace = properties.has('trimWhitespace') ? getLiteralValue(properties.get('trimWhitespace')!) : undefined
const delimiters = properties.has('delimiters') ? getStringArrayValue(properties.get('delimiters')!) as [string, string] : undefined
const root = parseAndNormalizeTemplate(template, { trimWhitespace, delimiters })
super(id, root, new Map())
this.className = className
this.properties = properties
this.sourceCode = sourceCode
this.isRawObject = isRawObject
}
hasMethod (name: string) {
return this.properties.has(name)
}
getComputedNames (): string[] {
return this.getObjectPropertyKeys('computed')
}
getFilterNames () {
return this.getObjectPropertyKeys('filters')
}
* getComponentsDelcarations (): Generator<[string, Node]> {
const expr = this.properties.get('components')
if (!expr) return
assertObjectExpression(expr)
yield * getPropertiesFromObject(expr)
}
private getObjectPropertyKeys (propertyName: string) {
const obj = this.properties.get(propertyName)
if (!obj) return []
assertObjectExpression(obj)
return [...getPropertiesFromObject(obj)].map(([key]) => key)
}
}
export class TypedComponentInfo extends ComponentInfoImpl implements ComponentInfo {
private computedNames: string[]
private filterNames: string[]
constructor (
id: string,
root: ANode,
childComponents: Map<TagName, ComponentReference>,
public readonly classDeclaration: ClassDeclaration
) {
super(id, root, childComponents)
this.computedNames = getObjectLiteralPropertyKeys(this.classDeclaration, 'computed')
this.filterNames = getObjectLiteralPropertyKeys(this.classDeclaration, 'filters')
}
hasMethod (name: string) {
return !!this.classDeclaration.getMethod(name)
}
getComputedNames () {
return this.computedNames
}
getFilterNames () {
return this.filterNames
}
}