-
Notifications
You must be signed in to change notification settings - Fork 20
/
renderer-compiler.ts
188 lines (167 loc) · 7.02 KB
/
renderer-compiler.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
185
186
187
188
/**
* 把组件(ComponentInfo)编译成 renderer 函数(render AST 形式)
*
* 每个 ComponentInfo 对应于一个 San 组件定义,对应一个 SSR 的 renderer 函数。
* 这个函数接受数据,返回 HTML。
*/
import { ANodeCompiler } from './anode-compiler'
import { ComponentInfo } from '../models/component-info'
import { RenderOptions } from './renderer-options'
import { FunctionDefinition, ComputedCall, Foreach, FunctionCall, MapLiteral, If, CreateComponentInstance, ImportHelper, ComponentReferenceLiteral, ConditionalExpression, BinaryExpression } from '../ast/renderer-ast-dfn'
import { EMPTY_MAP, STATEMENT, NEW, BINARY, ASSIGN, DEF, RETURN, createDefaultValue, L, I, NULL, UNDEFINED, createTryStatement, createDefineWithDefaultValue } from '../ast/renderer-ast-util'
import { IDGenerator } from '../utils/id-generator'
import { mergeLiteralAdd } from '../optimizers/merge-literal-add'
/**
* 每个 ComponentClass 对应一个 Render 函数,由 RendererCompiler 生成。
*/
export class RendererCompiler {
private id = new IDGenerator()
constructor (
private options: RenderOptions
) {}
/**
* 把 ComponentInfo 编译成函数源码,返回 Renderer 函数的 AST
*/
public compileToRenderer (componentInfo: ComponentInfo) {
const args = [
DEF('data'),
// 参数太多了,后续要增加的参数统一收敛到这里
DEF('info', L({}))
]
const fn = new FunctionDefinition(this.options.functionName || '', args,
this.compileComponentRendererBody(componentInfo)
)
mergeLiteralAdd(fn)
return fn
}
private compileComponentRendererBody (info: ComponentInfo) {
const body = []
// 没有 ANode 的组件,比如 load-success 样例
if (!info.root) {
body.push(RETURN(L('')))
return body
}
// get params from info
body.push(createDefineWithDefaultValue('noDataOutput', BINARY(I('info'), '.', I('noDataOutput')), L(false)))
body.push(createDefineWithDefaultValue('parentCtx', BINARY(I('info'), '.', I('parentCtx')), NULL))
body.push(createDefineWithDefaultValue('tagName', BINARY(I('info'), '.', I('tagName')), L('div')))
body.push(createDefineWithDefaultValue('slots', BINARY(I('info'), '.', I('slots')), EMPTY_MAP))
if (this.options.useProvidedComponentClass) {
body.push(DEF('ComponentClass', new BinaryExpression(I('info'), '.', I('ComponentClass'))))
}
// helper
body.push(new ImportHelper('_'))
body.push(new ImportHelper('SanSSRData'))
// context
body.push(this.compileGenInstance(info))
body.push(...this.compileContext(info))
// instance preraration
if (info.hasMethod('initData')) {
body.push(...this.emitInitData())
}
// calc computed
for (const name of info.getComputedNames()) {
body.push(ASSIGN(BINARY(I('data'), '[]', L(name)), new ComputedCall(name)))
}
// call inited
if (info.hasMethod('inited')) {
body.push(createTryStatement(
[STATEMENT(new FunctionCall(BINARY(I('instance'), '.', I('inited')), []))],
I('e'),
[STATEMENT(new FunctionCall(BINARY(I('_'), '.', I('handleError')), [
I('e'),
I('instance'),
L('hook:inited')
]))]
))
}
body.push(ASSIGN(
BINARY(I('instance'), '.', BINARY(I('lifeCycle'), '.', I('inited'))),
I('true')
))
body.push(DEF('html', L('')))
body.push(ASSIGN(I('parentCtx'), I('ctx')))
const aNodeCompiler = new ANodeCompiler(info, !!this.options.ssrOnly, this.id, this.options.useProvidedComponentClass)
body.push(...aNodeCompiler.compile(info.root, true))
body.push(RETURN(I('html')))
return body
}
private compileGenInstance (info: ComponentInfo) {
if (this.options.useProvidedComponentClass) {
return DEF('instance', new FunctionCall(
BINARY(I('Object'), '.', I('create')),
[BINARY(I('ComponentClass'), '.', I('prototype'))]
))
}
return DEF('instance', new CreateComponentInstance(info))
}
private compileContext (info: ComponentInfo) {
const refs = info.hasDynamicComponent()
? new MapLiteral([...info.childComponents.entries()].map(([key, val]) => [L(key), new ComponentReferenceLiteral(val)]))
: EMPTY_MAP
return [
ASSIGN(
BINARY(I('instance'), '.', I('data')),
NEW(I('SanSSRData'), [I('data'), I('instance')])
),
ASSIGN(
BINARY(I('instance'), '.', I('sourceSlots')),
new FunctionCall(
BINARY(I('_'), '.', I('mergeChildSlots')),
[I('slots')]
)
),
ASSIGN(
BINARY(I('instance'), '.', I('lifeCycle')),
new MapLiteral([
[I('compiled'), I('true')],
[I('inited'), I('false')]
])
),
new If(
I('parentCtx'), [ASSIGN(
BINARY(I('instance'), '.', I('parentComponent')),
BINARY(I('parentCtx'), '.', I('instance'))
)]
),
DEF('refs', refs),
// 组件级别的 context
DEF('ctx', new MapLiteral([
I('instance'),
I('slots'),
I('data'),
I('parentCtx'),
I('refs'),
// 单次渲染级别的 context
// 从最外层一直传下来的,上面可以绑 customRequirePath 等方法
[I('context'), BINARY(I('parentCtx'), '&&', BINARY(I('parentCtx'), '.', I('context')))]
]))
]
}
/**
* 产出 initData() 的函数调用
*
* 注意即使对于 JSComponentInfo,也不能在编译期调用 initData。
* 因为字面量是无法表示嵌套关系的,详细讨论见:
* https://github.com/baidu/san-ssr/issues/99
*/
private emitInitData () {
const item = BINARY(BINARY(I('ctx'), '.', I('data')), '[]', I('key'))
return [
DEF('initData', undefined),
createTryStatement(
[ASSIGN(I('initData'), new FunctionCall(BINARY(I('instance'), '.', I('initData')), []))],
I('e'),
[STATEMENT(new FunctionCall(BINARY(I('_'), '.', I('handleError')), [
I('e'),
I('instance'),
L('initData')
]))]
),
createDefaultValue(I('initData'), new MapLiteral([])),
new Foreach(I('key'), I('value'), I('initData'), [
ASSIGN(item, new ConditionalExpression(BINARY(item, '!==', UNDEFINED), item, I('value')))
])
]
}
}