• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

baidu / san-ssr / 3824729513

pending completion
3824729513

Pull #163

github

GitHub
Merge e05e440a2 into 5feaebca6
Pull Request #163: chore(deps): bump json5 and tsconfig-paths

1105 of 1107 branches covered (99.82%)

Branch coverage included in aggregate %.

1853 of 1854 relevant lines covered (99.95%)

4190.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/compilers/anode-compiler.ts
1
/**
2
 * 把 ANode 编译成 SSR 代码块
3
 *
4
 * 每个组件的 template 对应于一个根 ANode。从它出发递归地编译整个 template。
5
 * 这里要做的就是区分 ANode 类型,做不同的编译。
6
 */
7
import assert from 'assert'
8
import { camelCase } from 'lodash'
9
import { ANode, AIfNode, AForNode, ASlotNode, AFragmentNode, AText, ADynamicNode, AElement, StringLiteral } from 'san'
10
import { ComponentInfo } from '../models/component-info'
11
import { ElementCompiler } from './element-compiler'
12
import { getANodePropByName } from '../ast/san-ast-util'
13
import * as TypeGuards from '../ast/san-ast-type-guards'
14
import { IDGenerator } from '../utils/id-generator'
15
import {
16
    JSONStringify, RegexpReplace, Statement, SlotRendererDefinition, ElseIf, Else, MapAssign, Foreach, If, MapLiteral,
17
    ComponentRendererReference, FunctionCall, SlotRenderCall, Expression, GetRootCtxCall, ComponentReferenceLiteral,
18
    ComponentClassReference,
19
    VariableDefinition,
20
    ConditionalExpression,
21
    Typeof,
22
    AssignmentStatement
23
} from '../ast/renderer-ast-dfn'
24
import {
25
    CTX_DATA, createHTMLExpressionAppend, createHTMLLiteralAppend, L, I, ASSIGN, STATEMENT, UNARY, DEF, BINARY, RETURN
26
} from '../ast/renderer-ast-util'
27
import { sanExpr, OutputType } from './san-expr-compiler'
28
import type { RenderOptions } from './renderer-options'
29

30
/**
31
 * ANode 编译
32
 *
33
 * 负责单个 ComponentClass 的编译,每个 ANodeCompiler 对应于一个 ComponentInfo。
34
 */
35
export class ANodeCompiler {
36
    private elementCompiler: ElementCompiler
37
    private inScript = false
781✔
38

39
    /**
40
     * @param componentInfo 要被编译的节点所在组件的信息
41
     * @param ssrOnly san-ssr 当做模板引擎来使用(产出 HTML 更简单,但无法反解)
42
     * @param id 抗冲突变量名产生器
43
     */
44
    constructor (
45
        private componentInfo: ComponentInfo,
46
        private ssrOnly: boolean,
47
        private id: IDGenerator,
48
        private useProvidedComponentClass: RenderOptions['useProvidedComponentClass'] = false
764✔
49
    ) {
50
        this.elementCompiler = new ElementCompiler(this, this.id)
781✔
51
    }
52

53
    compile (aNode: ANode, isRootElement: boolean): Generator<Statement> {
54
        if (TypeGuards.isATextNode(aNode)) return this.compileText(aNode)
3,415✔
55
        if (TypeGuards.isAIfNode(aNode)) return this.compileIf(aNode, isRootElement)
2,360✔
56
        if (TypeGuards.isAForNode(aNode)) return this.compileFor(aNode)
2,251✔
57
        if (TypeGuards.isASlotNode(aNode)) return this.compileSlot(aNode)
2,142✔
58
        if (TypeGuards.isAFragmentNode(aNode) && aNode.tagName === 'template') return this.compileTemplate(aNode)
1,996✔
59
        if (TypeGuards.isAFragmentNode(aNode) && aNode.tagName === 'fragment') return this.compileFragment(aNode)
1,957✔
60
        if (TypeGuards.isADynamicNode(aNode)) return this.compileDynamic(aNode, isRootElement)
1,930✔
61

62
        const childComponentReference = this.generateRef(aNode)
1,911✔
63
        if (childComponentReference) {
1,911✔
64
            return this.compileComponent(aNode, childComponentReference, isRootElement)
278✔
65
        }
66
        return this.compileElement(aNode, undefined, isRootElement)
1,633✔
67
    }
68

69
    private generateRef (aNode: ANode) {
70
        if (
1,911✔
71
            !TypeGuards.isATextNode(aNode) &&
5,727✔
72
            aNode.tagName &&
73
            this.componentInfo.childComponents.has(aNode.tagName)
74
        ) {
75
            return new ComponentReferenceLiteral(this.componentInfo.childComponents.get(aNode.tagName)!)
278✔
76
        }
77
    }
78

79
    private * compileText (aNode: AText): Generator<Statement> {
80
        const shouldEmitComment = (
1,055✔
81
            TypeGuards.isExprTextNode(aNode.textExpr) ||
82
            TypeGuards.isExprInterpNode(aNode.textExpr)
83
        ) && aNode.textExpr.original && !this.ssrOnly && !this.inScript
84
        const outputType = this.inScript ? OutputType.HTML : OutputType.ESCAPE_HTML
1,055✔
85
        if (shouldEmitComment) yield createHTMLLiteralAppend('<!--s-text-->')
1,055✔
86
        yield createHTMLExpressionAppend(sanExpr(aNode.textExpr, outputType))
1,055✔
87
        if (shouldEmitComment) yield createHTMLLiteralAppend('<!--/s-text-->')
1,055✔
88
    }
89

90
    private * compileTemplate (aNode: AFragmentNode) {
91
        // if、for 等区块 wrap,只渲染内容。
92
        // 注意:<template> 为组件根节点时,tagName=null, isATemplateNode=false
93
        yield * this.elementCompiler.inner(aNode)
39✔
94
    }
95

96
    private * compileFragment (aNode: AFragmentNode) {
97
        if (TypeGuards.isATextNode(aNode.children[0]) && !this.ssrOnly && !this.inScript) {
27✔
98
            yield createHTMLLiteralAppend('<!--s-frag-->')
15✔
99
        }
100
        yield * this.elementCompiler.inner(aNode)
27✔
101
        if (TypeGuards.isATextNode(aNode.children[aNode.children.length - 1]) && !this.ssrOnly && !this.inScript) {
27✔
102
            yield createHTMLLiteralAppend('<!--/s-frag-->')
15✔
103
        }
104
    }
105

106
    private * compileDynamic (aNode: ADynamicNode, isRootElement: boolean) {
107
        const dynamicTagName = this.id.next('dynamicTagName')
19✔
108
        yield DEF(dynamicTagName, sanExpr(aNode.directives.is.value))
19✔
109
        const refs = BINARY(I('ctx'), '.', I('refs'))
19✔
110

111
        // 这里会对 aNode 编译两次,期间一定不能有对 aNode 的修改,否则第二次会有问题
112
        yield new If(
19✔
113
            BINARY(refs, '[]', I(dynamicTagName)),
114
            this.compileComponent(aNode, BINARY(refs, '[]', I(dynamicTagName)), isRootElement, dynamicTagName)
115
        )
116
        yield new Else(this.compileElement(aNode, dynamicTagName, isRootElement))
19✔
117
    }
118

119
    private * compileIf (aNode: AIfNode, isRootElement: boolean): Generator<Statement> {
120
        const ifDirective = aNode.directives.if
109✔
121

122
        // 动态节点 s-is 的子节点,会被编译两次。期间不能被修改。
123
        // 这里复制一份。
124
        const aNodeWithoutIf = Object.assign({}, aNode)
109✔
125
        aNodeWithoutIf.directives = Object.assign({}, aNode.directives)
109✔
126

127
        // 防止重新进入 compileIf:删掉 if 指令,再递归进入当前 aNode
128
        // @ts-ignore
129
        delete aNodeWithoutIf.directives.if
109✔
130

131
        yield new If(sanExpr(ifDirective.value), this.compile(aNodeWithoutIf, isRootElement))
109✔
132

133
        for (const elseANode of aNode.elses || []) {
109✔
134
            const elifDirective = elseANode.directives.elif
70✔
135
            const body = this.compile(elseANode, isRootElement)
70✔
136
            yield elifDirective ? new ElseIf(sanExpr(elifDirective.value), body) : new Else(body)
70✔
137
        }
138
    }
139

140
    private * compileFor (aNode: AForNode): Generator<Statement> {
141
        const { id } = this
109✔
142
        const forElementANode = {
109✔
143
            children: aNode.children,
144
            props: aNode.props,
145
            events: aNode.events,
146
            tagName: aNode.tagName,
147
            directives: { ...aNode.directives }
148
        }
149
        // 防止重新进入 compileFor
150
        // @ts-ignore
151
        delete forElementANode.directives.for
109✔
152

153
        const { item, index, value } = aNode.directives.for
109✔
154
        const key = I(id.next('key'))
109✔
155
        const val = I(id.next('val'))
109✔
156
        const list = id.next('list')
109✔
157

158
        yield DEF(list, sanExpr(value))
109✔
159
        yield new Foreach(key, val, I(list), [
109✔
160
            ...index ? [ASSIGN(BINARY(CTX_DATA, '[]', L(index)), key)] : [],
109✔
161
            ASSIGN(BINARY(CTX_DATA, '[]', L(item!)), val),
162
            ...this.compile(forElementANode, false)
163
        ])
164
    }
165

166
    private * compileSlot (aNode: ASlotNode): Generator<Statement> {
167
        const { id } = this
146✔
168
        assert(!this.inScript, '<slot> is not allowed inside <script>')
146✔
169

170
        const defaultRender = I(id.next('defaultRender'))
146✔
171
        yield DEF(defaultRender.name, this.compileSlotRenderer(aNode.children))
146✔
172

173
        const slotData = I(id.next('slotData'))
146✔
174
        yield DEF(slotData.name, new MapLiteral([]))
146✔
175
        if (aNode.directives.bind) {
146✔
176
            yield STATEMENT(new MapAssign(slotData, [sanExpr(aNode.directives.bind.value)]))
3✔
177
        }
178

179
        const props = aNode.vars || []
146✔
180
        if (props.length) {
146✔
181
            yield STATEMENT(new MapAssign(
26✔
182
                slotData,
183
                [new MapLiteral(props.map(prop => [L(prop.name), sanExpr(prop.expr)]))]
72✔
184
            ))
185
        }
186

187
        const slotName = I(id.next('slotName'))
146✔
188
        const render = I(id.next('render'))
146✔
189

190
        const nameProp = getANodePropByName(aNode, 'name')
146✔
191
        yield DEF(slotName.name, nameProp ? sanExpr(nameProp.expr) : L(''))
146✔
192
        yield (DEF(render.name, BINARY(
146✔
193
            BINARY(BINARY(I('ctx'), '.', I('slots')), '[]', slotName),
194
            '||',
195
            defaultRender
196
        )))
197
        yield createHTMLExpressionAppend(new SlotRenderCall(render, [I('parentCtx'), slotData]))
146✔
198
    }
199

200
    private * compileElement (
201
        aNode: AElement,
202
        dynamicTagName: string | undefined = undefined,
1,633✔
203
        isRootElement: boolean
204
    ): Generator<Statement> {
205
        yield * this.elementCompiler.tagStart(aNode, dynamicTagName)
1,652✔
206
        if (aNode.tagName === 'script') this.inScript = true
1,652✔
207
        if (isRootElement && !this.ssrOnly && !this.inScript) {
1,652✔
208
            yield new If(UNARY('!', I('noDataOutput')), this.createDataComment())
733✔
209
        }
210

211
        yield * this.elementCompiler.inner(aNode)
1,652✔
212
        this.inScript = false
1,652✔
213
        yield * this.elementCompiler.tagEnd(aNode, dynamicTagName)
1,652✔
214
    }
215

216
    private createDataComment () {
217
        const dataExpr = BINARY(new GetRootCtxCall([I('ctx')]), '.', I('data'))
733✔
218
        const outputDataExpr = BINARY(I('info'), '.', I('outputData'))
733✔
219
        return [
733✔
220
            new VariableDefinition('data', dataExpr),
221
            new If(outputDataExpr, [
222
                new AssignmentStatement(
223
                    I('data'),
224
                    new ConditionalExpression(
225
                        BINARY(new Typeof(outputDataExpr), '===', L('function')),
226
                        new FunctionCall(outputDataExpr, [dataExpr]),
227
                        outputDataExpr
228
                    )
229
                )
230
            ]),
231
            createHTMLLiteralAppend('<!--s-data:'),
232
            createHTMLExpressionAppend(new RegexpReplace(new JSONStringify(I('data')), '(?<=-)-', L('\\-'))),
233
            createHTMLLiteralAppend('-->')
234
        ]
235
    }
236

237
    private * compileComponent (
238
        aNode: AElement,
239
        ref: Expression,
240
        isRootElement: boolean,
241
        dynamicTagName: string | undefined = undefined
278✔
242
    ) {
243
        assert(!this.inScript, 'component reference is not allowed inside <script>')
297✔
244

245
        // slot
246
        const defaultSlotContents: ANode[] = []
297✔
247
        const namedSlotContents = new Map()
297✔
248

249
        // nodes without children (like pATextNode) has been taken over by other methods
250
        for (const child of aNode.children!) {
297✔
251
            const slotBind = !TypeGuards.isATextNode(child) && getANodePropByName(child, 'slot')
182✔
252
            if (slotBind) {
182✔
253
                const slotName = (slotBind.expr as StringLiteral).value
79✔
254
                if (!namedSlotContents.has(slotName)) {
79✔
255
                    namedSlotContents.set(slotName, { children: [], prop: slotBind })
50✔
256
                }
257
                namedSlotContents.get(slotName).children.push(child)
79✔
258
            } else {
259
                defaultSlotContents.push(child)
103✔
260
            }
261
        }
262

263
        const childSlots = I(this.id.next('childSlots'))
297✔
264
        yield DEF(childSlots.name, new MapLiteral([]))
297✔
265
        if (defaultSlotContents.length) {
297✔
266
            yield ASSIGN(
85✔
267
                BINARY(childSlots, '[]', L('')),
268
                this.compileSlotRenderer(defaultSlotContents)
269
            )
270
        }
271

272
        for (const sourceSlotCode of namedSlotContents.values()) {
297✔
273
            yield ASSIGN(
50✔
274
                BINARY(childSlots, '[]', sanExpr(sourceSlotCode.prop.expr)),
275
                this.compileSlotRenderer(sourceSlotCode.children)
276
            )
277
        }
278

279
        // data output
280
        const ndo = isRootElement ? I('noDataOutput') : L(true)
297✔
281

282
        // child component class
283
        let ChildComponentClassName = ''
297✔
284
        if (this.useProvidedComponentClass) {
297✔
285
            ChildComponentClassName = this.id.next('ChildComponentClass')
16✔
286
            yield DEF(
16✔
287
                ChildComponentClassName,
288
                new ComponentClassReference(ref, dynamicTagName ? I(dynamicTagName) : L(aNode.tagName))
16✔
289
            )
290
        }
291

292
        // get and call renderer
293
        const mapItems = [
297✔
294
            [I('noDataOutput'), ndo],
295
            [I('parentCtx'), I('parentCtx')],
296
            [I('tagName'), L(aNode.tagName)],
297
            [I('slots'), childSlots]
298
        ] as ConstructorParameters<typeof MapLiteral>[0]
299
        if (this.useProvidedComponentClass) {
297✔
300
            assert(ChildComponentClassName !== '')
16✔
301
            mapItems.push([I('ComponentClass'), I(ChildComponentClassName)])
16✔
302
        }
303

304
        const args = [this.childRenderData(aNode), new MapLiteral(mapItems)]
297✔
305
        const childRenderCall = new FunctionCall(
297✔
306
            new ComponentRendererReference(ref, L(aNode.tagName)),
307
            args
308
        )
309
        yield createHTMLExpressionAppend(childRenderCall)
297✔
310
    }
311

312
    private compileSlotRenderer (content: ANode[]) {
313
        const args = [DEF('parentCtx'), DEF('data')]
281✔
314
        const body: Statement[] = []
281✔
315
        if (content.length) {
281✔
316
            const shouldEmitComment = emitComment(content)
169✔
317
            body.push(DEF('html', L('')))
169✔
318

319
            if (shouldEmitComment) {
169✔
320
                body.push(createHTMLLiteralAppend('<!--s-slot-->'))
30✔
321
            }
322

323
            const compData = this.id.next('compData')
169✔
324
            body.push(DEF(compData, CTX_DATA))
169✔
325
            body.push(STATEMENT(new MapAssign(CTX_DATA, [I('data')])))
169✔
326

327
            for (const child of content) body.push(...this.compile(child, false))
216✔
328

329
            if (shouldEmitComment) {
169✔
330
                body.push(createHTMLLiteralAppend('<!--/s-slot-->'))
30✔
331
            }
332

333
            body.push(ASSIGN(CTX_DATA, I(compData)))
169✔
334
            body.push(RETURN(I('html')))
169✔
335
        } else {
336
            body.push(RETURN(L('')))
112✔
337
        }
338
        return new SlotRendererDefinition('', args, body)
281✔
339

340
        // 第一个或最后一个,不为空的节点为 text node
341
        function emitComment (content: ANode[]) {
342
            let i = 0
169✔
343
            while (i < content.length) {
169✔
344
                const c = content[i]
172✔
345
                if (!TypeGuards.isATextNode(c)) {
172✔
346
                    break
141✔
347
                }
348

349
                const textExprValue = TypeGuards.isExprInterpNode(c.textExpr)
31✔
350
                    ? (c.textExpr.expr as StringLiteral).value : c.textExpr.value
351
                if (!textExprValue || textExprValue.trim() !== '') {
31✔
352
                    return true
28✔
353
                }
354
                i++
3✔
355
            }
356
            let j = content.length - 1
141✔
357
            while (j > i) {
141✔
358
                const c = content[j]
21✔
359
                if (!TypeGuards.isATextNode(c)) {
21✔
360
                    break
16✔
361
                }
362

363
                const textExprValue = TypeGuards.isExprInterpNode(c.textExpr)
5✔
364
                    ? (c.textExpr.expr as StringLiteral).value : c.textExpr.value
365
                if (!textExprValue || textExprValue.trim() !== '') {
5✔
366
                    return true
2✔
367
                }
368
                j--
3✔
369
            }
370
            return false
139✔
371
        }
372
    }
373

374
    private childRenderData (aNode: AElement) {
375
        const propData = new MapLiteral(
297✔
376
            aNode.props.map(prop => [L(camelCase(prop.name)), sanExpr(prop.expr)])
372✔
377
        )
378
        const bindDirective = aNode.directives.bind
297✔
379
        return bindDirective
297✔
380
            ? new MapAssign(
381
                BINARY(sanExpr(bindDirective.value), '||', new MapLiteral([])),
382
                [propData]
383
            )
384
            : propData
385
    }
386
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc