Open switer opened 8 years ago
该模版(vssr)提供了一个同构的前后端组件化架构。这里的同构并不意味着前后端模块能完全共用,vssr 中的同构,仅限于HTML模版的同构,组件化模版文件的渲染能无缝在浏览器端,或是Node端进行。后面会介绍,模版文件的在不同环境运行环境下复用的原理。
vssr
同构
模块
个人观点认为,目前所谓的前后端同构都是由环境分支判断逻辑堆砌而成,并未达到透明/无痛。
通过 vfe 初始化模版:
vfe init node
模版仓库: https://github.com/switer/vfe-init-server-side-render
初始化后会在当前目录生成以下目录结构:
vssr/ ├╴c/ │ ├╴comps/ │ └╴pages/ │ ├╴loaders/ │ └╴tpl-loader.js │ ├╴views/ │ ├╴lib/ │ ├╴index.js │ └╴layout.html │ ├╴server/ │ ├╴load-file.js │ └╴render-page.html │ ├╴gulpfile.js │ ├╴config.js │ └╴server.js
从目录结构上看,与vfe init的基本模版没大的区别:
vfe init
c/pages 架构约定为页面组件的父目录,抽象为页面的控制器。每个页面组件可能包含的文件:.tpl, .js, tpl文件是每个组件所必须的,为后端渲染的入口。js文件是可选的,为组件前端渲染入口:
.tpl
.js
home/ │ ├╴home.tpl │ └╴home.js
在模版声明前端组件:
<div r-component="home"></div>
Real框架会检索r-component属性声明,并以该元素实例化指定的ViewModel,例如下面id为home的VM
r-component
Real.component('home', { // .... })
render-page
/c/pages/$id/$id.tpl
views/layout.html
var renderPage = require('./render-page') app.get('/', function (req, res) { // 渲染 "c/pages/index" 并拼接到 layout 中 var result = renderPage('index', { // data .... }) res.send(result) })
假设 index 页面的模版如下:
<div r-component="p-index"> {% component $id="header" /%} <ul> <!--split--> {{each list as data}} <li> {% component $id="list-item" /%} </li> {{/each}} <!--split--> </ul> </div>
对于给定的页面id,render-page会进行如下图的渲染流程:
步骤2完成后,生成的结果已无文件系统依赖,只需要将依赖的数据注入到页面中,即可放到前端渲染。
按照上面的模版,页面内容经过后端渲染后得到以下结果:
<div r-component="p-index"> <div r-component="c-header">...</div> <ul> ... <li> <div r-component="c-list-item">...</div> </li> ... </ul> </div>
注意标签上的r-component属性,Real框架会根据该属性获取对应的Component类,并实例化,这就是组件的前端反射。
前端反射
假设我们需要在前端进行渲染,例如加载更多的逻辑:
var indexTpl = require('./index.tpl') Real.component('p-index', { ready: function () { var listTpl = indexTpl.split('<!--split-->') var $list = this.$compile(template.render(listTpl, { list: [...] })) this.$el.querySelector('.list').appendChild($list) } })
? 你可能会对 {%component $id="header" /%} 这样组件模版语法如何做到在前端开箱即用而有疑问
{%component $id="header" /%}
对于上面代码,require('./index.tpl') 的时候,Webpack会调用loaders/tpl-loader去加载模版内容,tpl-loader做的是,把在Node端Comps编译的逻辑挪到构建阶段,所以require('./index.tpl')得到的是只包含数据渲染模版语法的内容,直接用模版引擎( ejs/art-template,保持与node端一致)渲染即可。
require('./index.tpl')
Webpack
loaders/tpl-loader
Comps
vfe-init-server-side-render 项目初始化模版
该模版(
vssr
)提供了一个同构的前后端组件化架构。这里的同构
并不意味着前后端模块
能完全共用,vssr 中的同构,仅限于HTML模版的同构,组件化模版文件的渲染能无缝在浏览器端,或是Node端进行。后面会介绍,模版文件的在不同环境运行环境下复用的原理。通过 vfe 初始化模版:
初始化后会在当前目录生成以下目录结构:
从目录结构上看,与
vfe init
的基本模版没大的区别:c/pages 架构约定为页面组件的父目录,抽象为页面的控制器。每个页面组件可能包含的文件:
.tpl
,.js
, tpl文件是每个组件所必须的,为后端渲染的入口。js文件是可选的,为组件前端渲染入口:在模版声明前端组件:
Real框架会检索
r-component
属性声明,并以该元素实例化指定的ViewModel,例如下面id为home的VMUI组件,与页面组件不同的点在于仅限被页面组件使用,粒度相比更小
服务端文件
Webpack构建依赖loader
组件化模版渲染
render-page
是Node端渲染组件化模版的核心模块。根据给定的页面id, 以/c/pages/$id/$id.tpl
作为入口渲染页面模版并递归渲染所依赖的子组件,最后将渲染后的页面内容嵌入views/layout.html
作为最终输出结果:假设 index 页面的模版如下:
对于给定的页面id,
render-page
会进行如下图的渲染流程:步骤2完成后,生成的结果已无文件系统依赖,只需要将依赖的数据注入到页面中,即可放到前端渲染。
按照上面的模版,页面内容经过后端渲染后得到以下结果:
注意标签上的
r-component
属性,Real框架会根据该属性获取对应的Component类,并实例化,这就是组件的前端反射
。假设我们需要在前端进行渲染,例如加载更多的逻辑:
对于上面代码,
require('./index.tpl')
的时候,Webpack
会调用loaders/tpl-loader
去加载模版内容,tpl-loader做的是,把在Node端Comps
编译的逻辑挪到构建阶段,所以require('./index.tpl')
得到的是只包含数据渲染模版语法的内容,直接用模版引擎( ejs/art-template,保持与node端一致)渲染即可。