Vue3 源码头脑风暴之 6 ☞compiler-ssr
文章目录
stb-vue-next 完全拷贝于 vue-next ,主要目的用于学习。
声明 :vue-next compiler-ssr 服务端渲染模块,相关的所有测试代码均在
/js/vue/
目录下面。更新日志&Todos :
[2021-01-04 20:11:43] 创建
[2021-01-08 10:11:47] 完成
模块初始化:feat(init): compiler-ssr · gcclll/stb-vue-next@dff9d31 · GitHub
脑图:
Tip
ssr 不处理 v-on/v-once/v-cloak 三个指令
v-show 转成
exp ? 'null' : {display: 'none'}
合并到style
<Suspense>
组件,会将所有 children 放到一个await
函数里面执行<Teleport>
作用?
05db578 compiler-ssr init
feat(init): compiler-ssr -> compile function · gcclll/stb-vue-next@05db578 · GitHub
源码:
|
|
沿用 compiler-core 的三个阶段(parse -> transform -> generate),另加上 SSR 渲染相 关的选项和其对应的 transforms 函数。
ssr text testing
|
|
codegenNode: null >>> render function return function ssrRender(_ctx, _push, _parent, _attrs) { null } undefined
结果显示没有 codegenNode, ssrRender 函数体内也就啥都没有。
FIX: 应该需要需要实现 ssrCodegenTransform
。
后面的所有测试都会依赖下面的正则(官方用例中的代码):
/_push\(\`<div\${\s*_ssrRenderAttrs\(_attrs\)\s*}>([^]*)<\/div>\`\)/
54ad7e2 coding ssrCodegenTransform function
feat(add): compiler-ssr-> ssrCodegenTransform function · gcclll/stb-vue-next@54ad7e2 · GitHub
生成 ssr codegenNode 的 transform 函数。
大致流程和 compiler-core 差不多。
创建上下文 context =
createSSRTransformContext(ast, options)
options.ssrCssVars 样式变量处理
如果多个且至少有一个为非文本节点,需要用到 fragment
processChildren
递归处理所有孩子节点,生成codegenNode
, 所以这里是 核心helpers 合并
|
|
>>> ast.children [ { type: 2, content: 'foo', loc: { start: [Object], end: [Object], source: 'foo' } } ] >>> ast.codegenNode.body [ { type: 14, loc: { source: '', start: [Object], end: [Object] }, callee: '_push', arguments: [ [Object] ] } ] >>> code return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`foo`) } undefined
Bug1: body 里面没东西, fix: body null · gcclll/stb-vue-next@f6d22c1 · GitHub
Bug2: div
没有被解析到,因为没有实现 ssrTransformElement 所有这里要先实现它,
测试用例中默认是 <div>${src}</div>
包起来的。
因为测试函数
getCompiledSSRString
中会将 src 用<div>
包裹起来,所以需要先实 现 div 的解析,即NodeTypes.ELEMENT
类型解析。
561d41b ELEMENT: ssrTransformElement
feat(add): ssr->ssrTransformElement · gcclll/stb-vue-next@561d41b · GitHub
新增两个函数实现:
ssrProcessElement
处理标签ssrPostTransformElement
ELEMENT 的转换函数
|
|
>>> code return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div>foo</div>`) } undefined
还是没有 _ssrRenderAttrs
匹配失败,与期待结果还差一步:属性解析。
feat(add): directives and node transforms from compiler-core · gcclll/stb-vue-next@dc15719 · GitHub
ea6bb01 add ssrInjectFallthroughAttrs 注入属性
feat(add): ssr-> add ssrInjectFallthroughAttrs · gcclll/stb-vue-next@ea6bb01 · GitHub
|
|
这个函数是用来将 render 函数的 attrs
参数处理成 v-bind
指令。
前提条件:
必须要有 parent 父元素,即 ROOT 节点不会处理
且 parent 必须是 ROOT 节点,即
attrs
会注入到第一个最外层的元素上比如:实例中的
<div>${src}</div>
, render 函数中的attrs
会被注入到这个div
上,这也是_ssrRenderAttrs
的由来。
测试:
|
|
>>> code return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div>foo</div>`) } >>> ast.children[0].props [ { type: 7, name: 'bind', arg: undefined, exp: { type: 4, loc: [Object], content: '_attrs', isStatic: false, constType: 0 }, modifiers: [], loc: { source: '', start: [Object], end: [Object] } } ] undefined
虽然结果还没达预期,但是上面结果显示已经有属性了,那么接下来就是要处理这个属性了,
这个在 ssrTransformElement
中处理。
7d20acd ELEMENT: ssrTransformElement>v-bind
feat(add): ssr->element:v-bind · gcclll/stb-vue-next@7d20acd · GitHub
新增处理代码:
|
|
因为在上一节中将 attrs
注册为了 v-bind 属性,因此在 transform element 中就有
Props 需要处理了, ssrRenderAttrs
就是在这里增加了 SSR_RENDER_ATTRS
。
|
|
>>> code const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${_ssrRenderAttrs(_attrs)}>foo</div>`) } undefined
到这里算是能满足测试用例中的正则要求了。
_attrs 注入逻辑脑图:
f6d22c1 TEXT 节点类型解析
fix: body null · gcclll/stb-vue-next@f6d22c1 · GitHub
新增 pushStringPart
函数的实现,用来处理 NodeTypes.TEXT
节点类型。
|
|
测试:
|
|
>>> 静态文本 const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${_ssrRenderAttrs(_attrs)}>foo</div>`) } >>> 静态文本,含反斜杠 const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${_ssrRenderAttrs(_attrs)}>\\\$foo</div>`) } >>> 静态文本,< 等符号的 const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${_ssrRenderAttrs(_attrs)}><foo></div>`) } >>> 静态文本,元素嵌套 const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${_ssrRenderAttrs(_attrs)}><div><span>hello</span><span>bye</span></div></div>`) } undefined
8f09472 INTERPOLATION 插值处理
feat(add): ssr->interpolation · gcclll/stb-vue-next@8f09472 · GitHub
增加代码:
|
|
测试:
|
|
>>> 插值处理 const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${_ssrRenderAttrs(_attrs)}>\`\${foo}\`</div>`) } >>> 插值处理,元素嵌套 const { ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${ _ssrRenderAttrs(_attrs) }><div><span>${ _ssrInterpolate(_ctx.foo) } bar</span><span>baz ${ _ssrInterpolate(_ctx.qux) }</span></div></div>`) } undefined
第一个并非直接的差值,而是字符串形式,所以并没有当做插值处理。
后面的差值调用 _ssrInterpolate(_ctx.foo)
处理
ssrTransformElement 续
954a9ee static class 属性处理
feat(add): ssr->static class attr · gcclll/stb-vue-next@954a9ee · GitHub
静态 class 属性处理:
|
|
class 处理部分:
|
|
等于是将 class="bar"
原样添加到 openTag 中了,只不过这里对值用 escapeHtml
处
理了一下。
匹配: const escapeRE = /["'&<>]/
替换成对应的
char | value |
---|---|
" | " |
& | & |
' | ' |
< | < |
> | > |
如下测试:
|
|
>>> static class const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${_ssrRenderAttrs(_attrs)}><div class="bar"></div><p class="foo>"></p></div>`) } >>> ref/key 属性会被忽略,不论静态还是动态 const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${_ssrRenderAttrs(_attrs)}><div></div></div>`) } >>> ref/key 属性会被忽略,不论静态还是动态 const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${_ssrRenderAttrs(_attrs)}><div></div></div>`) } undefined
c28d528 dynamic class 属性处理
feat(add): ssr->dynamic class · gcclll/stb-vue-next@c28d528 · GitHub
当既有 static 也有 dynamic class 时需要进行合并,且是将 static 往 dynamic 上进行 合并,最后成为动态的 class。
新增处理逻辑:
|
|
如果也有静态属性的时候,将两者合并,需要用到两个函数:
|
|
mergeCall: 将静态 class 合并到动态 class 上 removeStaticBinding: 删除原来的静态 class 属性
测试:
|
|
>>> dynamic class const { ssrRenderClass: _ssrRenderClass, ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${ _ssrRenderAttrs(_attrs) }><div class="${ _ssrRenderClass(_ctx.bar) }"></div></div>`) } >>> static class const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${_ssrRenderAttrs(_attrs)}><div class="foo"></div></div>`) } >>> static + dynamic class const { ssrRenderClass: _ssrRenderClass, ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${ _ssrRenderAttrs(_attrs) }><div class="${ _ssrRenderClass([_ctx.bar, "foo"]) }"></div></div>`) } undefined
逻辑脑图:
ca39229 style 属性处理
feat(add): ssr->style prop · gcclll/stb-vue-next@ca39229 · GitHub
新增处理代码:
|
|
|
|
>>> static style const { ssrRenderStyle: _ssrRenderStyle, ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${ _ssrRenderAttrs(_attrs) }><div style="${ _ssrRenderStyle({"color":"red"}) }"></div></div>`) } >>> dynamic style const { ssrRenderStyle: _ssrRenderStyle, ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${ _ssrRenderAttrs(_attrs) }><div style="${ _ssrRenderStyle(_ctx.bar) }"></div></div>`) } >>> dynamic + static style const { ssrRenderStyle: _ssrRenderStyle, ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${ _ssrRenderAttrs(_attrs) }><div style="${ _ssrRenderStyle([_ctx.bar, {"color":"red"}]) }"></div></div>`) } undefined
dfd4fb9 v-html 指令处理
feat(add): ssr->v-html directive · gcclll/stb-vue-next@dfd4fb9 · GitHub
这个处理在 ssrTransformElement
中只需要增加一行代码就OK,但是需要结合
ssrProcessElement
来进行处理。
|
|
ssrProcessElement 中会对 rawChildrenMap 进行处理:
|
|
测试:
|
|
直接进行值替换。
678e98a v-text 指令处理
feat(add): ssr->v-text directive · gcclll/stb-vue-next@678e98a · GitHub
这里是用插值方式来处理了 v-text :
|
|
测试:
|
|
const { ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${ _ssrRenderAttrs(_attrs) }><div>${ _ssrInterpolate(_ctx.foo) }</div></div>`) } undefined
0472dfd v-slot 指令错误
feat(add): ssr->v-slot directive · gcclll/stb-vue-next@0472dfd · GitHub
由于指令不能应用于非 component 或 template 组件上,所以这里无法适用。
45e78e1 v-bind 指令
feat(add): ssr->v-bind · gcclll/stb-vue-next@45e78e1 · GitHub
下面的测试为综合情况测试,包含大部分使用情况。
v-bind:arg(non-boolean)
v-bind:[arg] 动态参数处理
v-bind:[arg] + v-bind 混合方式
style + :style
class + :class
v-on 会被忽略
key/ref 无论静态动态都会被忽略
|
|
const { mergeProps: _mergeProps } = require("vue") const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${ _ssrRenderAttrs(_attrs) }><div${ _ssrRenderAttrs(_mergeProps({ style: [{"color":"red"}, _ctx.baz], class: ["foo", _ctx.bar], [_ctx.key || ""]: _ctx.value, id: _ctx.id }, _ctx.obj, { key: 1, ref: _ctx.el })) }></div></div>`) } undefined
key, ref 为什么没有忽略???
value on textarea
e79e343 static value
feat(add): ssr->static value on textarea · gcclll/stb-vue-next@e79e343 · GitHub
静态 value 处理很简单,直接当做子节点替换。
|
|
测试
|
|
>>> static value on textarea const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${_ssrRenderAttrs(_attrs)}><textarea>fo>o</textarea></div>`) } undefined
dynamic value
处理代码:
|
|
当做插值类型处理,作为孩子节点。
|
|
const { ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${ _ssrRenderAttrs(_attrs) }><textarea>${ _ssrInterpolate(_ctx.foo) }</textarea></div>`) } undefined
cdd8fd0 dynamic arg 动态参数
feat(add): ssr->dynamic arg on textarea · gcclll/stb-vue-next@cdd8fd0 · GitHub
|
|
在包含动态参数的时候,并不能确定最终参数名就是 value
所以需要做些特殊处理。
|
|
const { mergeProps: _mergeProps } = require("vue") const { ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { let _temp0 _push(`<textarea${ _ssrRenderAttrs(_temp0 = _mergeProps(_ctx.obj, _attrs), "textarea") }>${ _ssrInterpolate(("value" in _temp0) ? _temp0.value : "fallback") }</textarea>`) } undefined
等于先将所有属性合并起来,在运行时决定是否有 value
属性,如果存在就使用这个值
内容填充 <textarea>
孩子节点,否则直接使用原来的孩子节点内容(如: "fallback"
)
源码处理中有两个前提条件,才会这样处理
没有孩子节点
或者孩子节点不是插值类型
即如果有插值类型的孩子节点,是不会进行如上的处理的,看下面的实例:
|
|
const { mergeProps: _mergeProps } = require("vue") const { ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<textarea${ _ssrRenderAttrs(_mergeProps(_ctx.obj, _attrs), "textarea") }>${ _ssrInterpolate(_ctx.foo) }</textarea>`) } undefined
结果如上 ↑。
b97d467 input + boolean attr
feat(add): ssr->v-bind boolean on input · gcclll/stb-vue-next@b97d467 · GitHub
|
|
>>> input const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${_ssrRenderAttrs(_attrs)}><input></div>`) } >>> input with v-bind:arg(boolean) const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${ _ssrRenderAttrs(_attrs) }><input type="checkbox"${ (_ctx.checked) ? " checked" : "" }></div>`) } undefined
TODO 对于 v-bind + v-model 的结合使用,需要实现
ssrTransformModel
函数,这里暂时不做处理。
TODO e58d062 dynamic key attr
893681b v-model transform
feat(add): ssr->v-model, text type · gcclll/stb-vue-next@893681b · GitHub
|
|
脑图:
7502230 input type: radio
feat(add): ssr->v-model, type radio · gcclll/stb-vue-next@7502230 · GitHub
v-model 根据 model表达式的值和 value
属性的值,判断最终转成 checked
属性
(<input type="radio" checked>
)。
|
|
测试:
|
|
const { ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${ _ssrRenderAttrs(_attrs) }><input type="radio" value="foo"${ (_ssrLooseEqual(_ctx.bar, "foo")) ? " checked" : "" }></div>`) } undefined
input type: checkbox
类型为 checkbox 的时候要检查 true-value/false-value
属性。
880eaf3 with true/false-value
feat(add): ssr->v-model, type checkbox with true/false-value · gcclll/stb-vue-next@880eaf3 · GitHub
|
|
如果存在 true-value/false-value
的时候,检测条件就是这两个值的比较结果,只有这
两个值相等的情况下才会 checked
。
|
|
>>> v-bind true-value/false-value const { ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${ _ssrRenderAttrs(_attrs) }><input type="checkbox"${ (_ssrLooseEqual(_ctx.baz, _ctx.foo)) ? " checked" : "" }></div>`) } >>> static true-value/false-value const { ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${ _ssrRenderAttrs(_attrs) }><input type="checkbox"${ (_ssrLooseEqual(_ctx.baz, "foo")) ? " checked" : "" }></div>`) } >>> true-value/false-value 只有其中一个 const { ssrLooseContain: _ssrLooseContain, ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${ _ssrRenderAttrs(_attrs) }><input type="checkbox"${ ((Array.isArray(_ctx.baz)) ? _ssrLooseContain(_ctx.baz, null) : _ctx.baz) ? " checked" : "" }></div>`) } undefined
❓ 为什么
false-value
还在?FIX: fix: false.value -> false-value · gcclll/stb-vue-next@e0fc173 · GitHub
从结果看,貌似 false-value
并没有被用到,用到的只有 true-value
去和
v-model
表达式值进行比较。
59b1577 without true/false-value
|
|
const { ssrLooseContain: _ssrLooseContain, ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${ _ssrRenderAttrs(_attrs) }><input type="checkbox" value="foo"${ ((Array.isArray(_ctx.bar)) ? _ssrLooseContain(_ctx.bar, "foo") : _ctx.bar) ? " checked" : "" }></div>`) } undefined
_ssrLooseContain(_ctx.bar, "foo")
简单的数组找值操作:
|
|
相当于,如果 v-model="bar"
的值 bar 是个数组,只需要其中有一个满足条件就会
checked
,这也是经常使用到的方式,将一组数据保存到一个数组里面,然后对应一组
checkboxs
用来控制这些组件的选中未选中状态。
|
|
就如上面的例子,只要 checkedBoxes
里面的值发生改变,就会触发 checkbox
状态更
新,且只有在数组内的值与当前 checkbox
的 value 属性值相等就会被选中,反之不会
被选中。
a0d4a40 input type: file 时不能用 v-model
feat(add): ssr->v-model, type file with v-model error · gcclll/stb-vue-next@a0d4a40 · GitHub
|
|
v-model cannot be used on file inputs since they are read-only. Use a v-on:change listener instead. undefined
d7309be v-model on textarea
feat(add): ssr->v-model on textarea · gcclll/stb-vue-next@d7309be · GitHub
当做插值处理,替换成孩子节点。
|
|
const { ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${ _ssrRenderAttrs(_attrs) }><textarea>${ _ssrInterpolate(_ctx.foo) }</textarea></div>`) } undefined
169027e v-show transform
feat(add): ssr->v-show · gcclll/stb-vue-next@169027e · GitHub
v-show 指令的处理相对简单,根据指令表达式值,创建一个三元条件表达式,利用
display:none
属性隐藏元素(非删除操作)。
|
|
测试:
|
|
>>> basic 作为根节点 const { mergeProps: _mergeProps } = require("vue") const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${_ssrRenderAttrs(_mergeProps({ style: (_ctx.foo) ? null : { display: "none" } }, _attrs))}></div>`) } >>> basic 非根节点 const { ssrRenderStyle: _ssrRenderStyle, ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${ _ssrRenderAttrs(_attrs) }><div style="${ _ssrRenderStyle((_ctx.foo) ? null : { display: "none" }) }"></div></div>`) } >>> basic 非根节点,包含静态和动态 style const { ssrRenderStyle: _ssrRenderStyle, ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<div${ _ssrRenderAttrs(_attrs) }><div style="${ _ssrRenderStyle([ (_ctx.foo) ? null : { display: "none" }, {"color":"red"}, _ctx.bar ]) }"></div></div>`) } undefined
5bf3644 v-if transform
feat(add): ssr->v-if · gcclll/stb-vue-next@5bf3644 · GitHub
ssrTransformIf 也是直接使用了 compiler-core: transformIf 进行处理。
fix: ssr->template v-if no ] · gcclll/stb-vue-next@094d5c0 · GitHub
|
|
剩下的 ssr 的部分,需要用到 ssrProcessIf()
进行单独处理。
|
|
{ type: 10, loc: { start: { column: 1, line: 1, offset: 0 }, end: { column: 23, line: 1, offset: 22 }, source: '<div v-if="foo"></div>' }, condition: { type: 4, content: '_ctx.foo', isStatic: false, constType: 0, loc: { start: [Object], end: [Object], source: 'foo' } }, children: [ { type: 1, ns: 0, tag: 'div', tagType: 0, props: [Array], isSelfClosing: false, children: [], loc: [Object], codegenNode: undefined, ssrCodegenNode: [Object] } ], userKey: undefined } const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { } undefined
啥也没有?
但是从 ast.children[0].branches[0] 结果看确实被 core 正确处理了
所以还是需要实现 ssrProcessIf()
并且在 ssrCodegenTransform->processChildren
增加 NodeTypes.IF
分支处理。
加上 ssrProcessIf
再测试一遍(测试均来自官方测试用例 ssrVIf.spec.ts 其他同):
|
|
>>> basic const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { if (_ctx.foo) { _push(`<div${_ssrRenderAttrs(_attrs)}></div>`) } else { _push(`<!---->`) } } >>> with nested content const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { if (_ctx.foo) { _push(`<div${_ssrRenderAttrs(_attrs)}>hello<span>ok</span></div>`) } else { _push(`<!---->`) } } >>> v-if + v-else const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { if (_ctx.foo) { _push(`<div${_ssrRenderAttrs(_attrs)}></div>`) } else { _push(`<span${_ssrRenderAttrs(_attrs)}></span>`) } } >>> v-if + v-else-if const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { if (_ctx.foo) { _push(`<div${_ssrRenderAttrs(_attrs)}></div>`) } else if (_ctx.bar) { _push(`<span${_ssrRenderAttrs(_attrs)}></span>`) } else { _push(`<!---->`) } } >>> v-if + v-else-if + v-else const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { if (_ctx.foo) { _push(`<div${_ssrRenderAttrs(_attrs)}></div>`) } else if (_ctx.bar) { _push(`<span${_ssrRenderAttrs(_attrs)}></span>`) } else { _push(`<span${_ssrRenderAttrs(_attrs)}></span>`) } } >>> <template v-if>(text) return function ssrRender(_ctx, _push, _parent, _attrs) { if (_ctx.foo) { _push(`<!--[-->hello<!--]-->`) } else { _push(`<!---->`) } } >>> <template v-if>(single element) const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { if (_ctx.foo) { _push(`<div${_ssrRenderAttrs(_attrs)}>hi</div>`) } else { _push(`<!---->`) } } >>> <template v-if>(multiple element) return function ssrRender(_ctx, _push, _parent, _attrs) { if (_ctx.foo) { _push(`<!--[--><div>hi</div><div>hi</div><div>ho</div><!--]-->`) } else { _push(`<!---->`) } } >>> <template v-if> + normal v-else const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { if (_ctx.foo) { _push(`<div${_ssrRenderAttrs(_attrs)}>hi</div>`) } else { _push(`<div${_ssrRenderAttrs(_attrs)}></div>`) } } undefined
为什么是 if(){}else{}
???
这个要追溯到 compiler-core: codegen.ts 里面对 ssr 环境下的 if
指令的处理代码:
|
|
这个 genIfStatement
就是用来生成 if...else
代码的。
脑图:
所以 ssr v-if 处理大致流程简单分两步:
core: transformIf 得到 node.branches
ssrProcessIf 处理,生成 if -> else if -> else
4839090 v-for transform
fix: ssr->v-for add transform · gcclll/stb-vue-next@4839090 · GitHub
主要还是借助了 compiler-core: transformFor 处理逻辑,加上 ssrProcessFor 加工处理 了下。
|
|
>>> basic const { ssrRenderList: _ssrRenderList } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<!--[-->`) _ssrRenderList(_ctx.list, (i) => { _push(`<div></div>`) }) _push(`<!--]-->`) } >>> nested content const { ssrRenderList: _ssrRenderList } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<!--[-->`) _ssrRenderList(_ctx.list, (i) => { _push(`<div>foo<span>bar</span></div>`) }) _push(`<!--]-->`) } >>> nested v-for const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<!--[-->`) _ssrRenderList(_ctx.list, (row, i) => { _push(`<div><!--[-->`) _ssrRenderList(row, (j) => { _push(`<div>${ _ssrInterpolate(i) },${ _ssrInterpolate(j) }</div>`) }) _push(`<!--]--></div>`) }) _push(`<!--]-->`) } >>> template v-for(text) const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<!--[-->`) _ssrRenderList(_ctx.list, (i) => { _push(`<!--[-->${_ssrInterpolate(i)}<!--]-->`) }) _push(`<!--]-->`) } >>> template v-for (single element) const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<!--[-->`) _ssrRenderList(_ctx.list, (i) => { _push(`<span>${_ssrInterpolate(i)}</span>`) }) _push(`<!--]-->`) } >>> template v-for (multi element) const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<!--[-->`) _ssrRenderList(_ctx.list, (i) => { _push(`<!--[--><span>${ _ssrInterpolate(i) }</span><span>${ _ssrInterpolate(i + 1) }</span><!--]-->`) }) _push(`<!--]-->`) } >>> render loop args should not be prefixed > render loop 循环回调的参数不应该加前缀 const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<!--[-->`) _ssrRenderList(_ctx.list, ({ foo }, index) => { _push(`<div>${_ssrInterpolate(foo + _ctx.bar + index)}</div>`) }) _push(`<!--]-->`) } undefined
代码:
|
|
v-for 处理除了 tranformFor
剩下的处理都在这个 ssrProcessFor
里面了。
8036837 其他不需要处理的情况
fix: ssr->add other useless cases in process children · gcclll/stb-vue-next@8036837 · GitHub
比如:
IF_BRANCH
在ssrProcessIf
中被处理了TEXT_CALL
和COMPOUND_EXPRESSION
在 ssr 中不会被用到COMMENT
注释简单的还原处理即可
TODO 16833be component transform
fix: ssr->component transform · gcclll/stb-vue-next@16833be · GitHub
|
|
>>> basic const { resolveComponent: _resolveComponent, mergeProps: _mergeProps } = require("vue") const { ssrRenderComponent: _ssrRenderComponent } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { const _component_foo = _resolveComponent("foo") _push(_ssrRenderComponent(_component_foo, _mergeProps({ id: "a", prop: _ctx.b }, _attrs), null, _parent)) } >>> 动态组件 is const { resolveDynamicComponent: _resolveDynamicComponent, mergeProps: _mergeProps, createVNode: _createVNode } = require("vue") const { ssrRenderVNode: _ssrRenderVNode } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent("foo"), _mergeProps({ prop: "b" }, _attrs), null), _parent) } >>> 动态组件 :is const { resolveDynamicComponent: _resolveDynamicComponent, mergeProps: _mergeProps, createVNode: _createVNode } = require("vue") const { ssrRenderVNode: _ssrRenderVNode } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.foo), _mergeProps({ prop: "b" }, _attrs), null), _parent) } undefined
cdba013 component slot outlet
feat(add): ssr->slot outlet transform · gcclll/stb-vue-next@cdba013 · GitHub
<slot></slot>
插槽处理。
处理逻辑:
transform 阶段 -> ssrTransformSlotOutlet
这里还只是创建了 ssrCodegenNode 并没有实际创建 render 函数
1
_ssrRenderSlot(_ctx.$slots, slotName, slotProps, fallback, _push, _parent)
codegen 处理 -> ssrCodgenTransform
这个阶段是扩展 1 中的第四个参数,也就是 fallback,检测
<slot></slot>
下是不 是有孩子节点,如果有当做 fallback 处理,替换node.ssrCodegenNode.arguments[3]
的值。
|
|
>>> basic const { ssrRenderSlot: _ssrRenderSlot } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent) } >>> with named const { ssrRenderSlot: _ssrRenderSlot } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _ssrRenderSlot(_ctx.$slots, "foo", {}, null, _push, _parent) } >>> with dynamic named const { ssrRenderSlot: _ssrRenderSlot } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _ssrRenderSlot(_ctx.$slots, _ctx.bar.baz, {}, null, _push, _parent) } >>> with props and fallback const { ssrRenderSlot: _ssrRenderSlot, ssrInterpolate: _ssrInterpolate } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _ssrRenderSlot(_ctx.$slots, "foo", { p1: 1, bar: "2" }, () => { _push(`some ${_ssrInterpolate(_ctx.fallback)} content`) }, _push, _parent) } >>> with fallback const { ssrRenderSlot: _ssrRenderSlot, ssrInterpolate: _ssrInterpolate } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _ssrRenderSlot(_ctx.$slots, "default", {}, () => { _push(`some ${_ssrInterpolate(_ctx.fallback)} content`) }, _push, _parent) } undefined
ssrRenderSlot 函数实现:
|
|
fallback 用途:在没有任何 slot template 时候会默认用 fallback 里的内容来渲染这个 slot。
如:
|
|
359f856 suspense 内置组件
feat(add): ssr->slot suspense component · gcclll/stb-vue-next@359f856 · GitHub
|
|
>>> implicit default const { resolveComponent: _resolveComponent, withCtx: _withCtx } = require("vue") const { ssrRenderComponent: _ssrRenderComponent, ssrRenderSuspense: _ssrRenderSuspense } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { const _component_foo = _resolveComponent("foo") _ssrRenderSuspense(_push, { default: () => { _push(_ssrRenderComponent(_component_foo, null, null, _parent)) }, _: 1 /* STABLE */ }) } >>> explicit slots const { resolveComponent: _resolveComponent, withCtx: _withCtx } = require("vue") const { ssrRenderComponent: _ssrRenderComponent, ssrRenderSuspense: _ssrRenderSuspense } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { const _component_foo = _resolveComponent("foo") _ssrRenderSuspense(_push, { default: () => { _push(_ssrRenderComponent(_component_foo, null, null, _parent)) }, fallback: () => { _push(` loading... `) }, _: 1 /* STABLE */ }) } undefined
ssrRenderSuspense 实际上只是一个 await 异步函数封装:
|
|
最终将 SUSPENSE
中的组件异步渲染。
e27a5e4 teleport 内置组件
feat(add): ssr->teleport component · gcclll/stb-vue-next@e27a5e4 · GitHub
作用❓
|
|
>>> basic const { ssrRenderTeleport: _ssrRenderTeleport } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _ssrRenderTeleport(_push, (_push) => { _push(`<div></div>`) }, _ctx.target, false, _parent) } >>> disabled prop const { ssrRenderTeleport: _ssrRenderTeleport } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _ssrRenderTeleport(_push, (_push) => { _push(`<div></div>`) }, _ctx.target, true, _parent) } >>> disabled prop with value const { ssrRenderTeleport: _ssrRenderTeleport } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _ssrRenderTeleport(_push, (_push) => { _push(`<div></div>`) }, _ctx.target, _ctx.foo, _parent) } undefined
ssrRenderTeleport :
|
|
e0fc173 transition group transform
fix: false.value -> false-value · gcclll/stb-vue-next@e0fc173 · GitHub
|
|
>>> basic const { ssrRenderList: _ssrRenderList } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<!--[-->`) _ssrRenderList(_ctx.list, (i) => { _push(`<div></div>`) }) _push(`<!--]-->`) } >>> with static tag const { ssrRenderList: _ssrRenderList } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<ul>`) _ssrRenderList(_ctx.list, (i) => { _push(`<div></div>`) }) _push(`</ul>`) } >>> with dynamic tag const { ssrRenderList: _ssrRenderList } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<${_ctx.someTag}>`) _ssrRenderList(_ctx.list, (i) => { _push(`<div></div>`) }) _push(`</${_ctx.someTag}>`) } >>> with multi fragments children const { ssrRenderList: _ssrRenderList } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { _push(`<!--[-->`) _ssrRenderList(10, (i) => { _push(`<div></div>`) }) _ssrRenderList(10, (i) => { _push(`<div></div>`) }) if (_ctx.ok) { _push(`<div>ok</div>`) } else { _push(`<!---->`) } _push(`<!--]-->`) } undefined
ssrRenderList:
|
|
也就是将 children 直接调用 renderItem 渲染出来,那这个跟动画有什么关系呢❓
c8e1d56 ssrCssVars inject
feat(add): ssr->ssrCssVars inject · gcclll/stb-vue-next@c8e1d56 · GitHub
|
|
>>> basic const { mergeProps: _mergeProps } = require("vue") const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { const _cssVars = { style: { color: _ctx.color }} _push(`<div${_ssrRenderAttrs(_mergeProps(_attrs, _cssVars))}></div>`) } >>> fragment const { ssrRenderAttrs: _ssrRenderAttrs } = require("@vue/server-renderer") return function ssrRender(_ctx, _push, _parent, _attrs) { const _cssVars = { style: { color: _ctx.color }} _push(`<!--[--><div${ _ssrRenderAttrs(_cssVars) }></div><div${ _ssrRenderAttrs(_cssVars) }></div><div${ _ssrRenderAttrs(_cssVars) }><p></p></div><!--]-->`) } undefined
cssVars 属性会注入到每个外层同级元素上。
总结
ELEMENT 解析
v-html:
<div v-html="foo"/>
=><div>${foo}</div>
v-text:
<div v-text="foo"/>
=><div>${_ssrInterpolate(_ctx.foo)}</div>
v-slot: 只能用在
<template>
和<component>
或用户组件上v-on: ssr 中不处理
v-bind: 忽略 ref和key 属性,class 合并成动态 class 属性(style 也一样)
<div class="foo" :class="bar"/>
> ~<div class
"${_ssrRenderCalss([_ctx.bar, 'foo'])}"></div>~
input: radio
<input type="radio" value="foo" v-model="bar">
=>
1
<input type="radio" value="${(_ssrLooseEqual(_ctx.bar, 'foo')) ? 'checked' : ''}">
input: checkbox, 详情->
有关属性: value, true-value, false-value, v-model
如果使用 true/false-value 配套,则只支持 v-model 绑定单个属性值。
如果单独使用 value ,则 v-model 支持绑定一个数组,只要当前 checkbox 的 value 值在该数组里面,则为 checked 状态,否则非选中状态。
input: file 不支持,如果没有 type 属性,默认为
text
ssrInjectFallthroughAttrs ,将 ssrRender 函数的 _attrs 参数作为属性注入到最外 层的元素上。
INTEROLATION: 插值调用
_ssrInterpolate(_ctx.foo)
处理v-show 指令处理,就是在元素上增加一个
style = { display: 'none' }
来切换元 素显示隐藏v-if 先调用 compiler-core 的 transformIf 解析出 node.branches,然后使用 ssr 端的 processIf 处理成
if (condition) {} else if () {} else {}
语句,而不是 非 ssr 情况下的三元表达式(?:
)v-for 指令使用
_ssrRenderList(_ctx.list, (row, i) => {...})
<slot/>
标签的处理1 2 3 4 5 6 7
// `<slot name="foo" :p1="1" bar="2" >some {{ fallback }} content</slot> _ssrRenderSlot(_ctx.$slots, "foo", { p1: 1, bar: "2" }, () => { _push(`some ${_ssrInterpolate(_ctx.fallback)} content`) }, _push, _parent)
<Suspense/>
内置组件,内部处理其实等于将 children 用一个await
函数包裹 起来了,成为异步操作。<Teleport/>
内置组件,需要制定to="target"
支持
disabled
属性1 2 3 4
// <teleport :to="target" :disabled="foo"><div/></teleport> _ssrRenderTeleport(_push, (_push) => { _push(`<div></div>`) }, _ctx.target, _ctx.foo, _parent)
ssr css vars 简单在元素上注入
style = { color }
属性