switer / switer.github.io

Personal homepage
https://switer.github.io
5 stars 0 forks source link

Node.js server-side render with Real + Comps + ejs/artTemplate #29

Open switer opened 8 years ago

switer commented 8 years ago

vfe-init-server-side-render 项目初始化模版

该模版(vssr)提供了一个同构的前后端组件化架构。这里的同构并不意味着前后端模块能完全共用,vssr 中的同构,仅限于HTML模版的同构,组件化模版文件的渲染能无缝在浏览器端,或是Node端进行。后面会介绍,模版文件的在不同环境运行环境下复用的原理。

个人观点认为,目前所谓的前后端同构都是由环境分支判断逻辑堆砌而成,并未达到透明/无痛。

通过 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的基本模版没大的区别:

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会进行如下图的渲染流程:

vfe-server-side-render

步骤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" /%} 这样组件模版语法如何做到在前端开箱即用而有疑问

对于上面代码,require('./index.tpl') 的时候,Webpack会调用loaders/tpl-loader去加载模版内容,tpl-loader做的是,把在Node端Comps编译的逻辑挪到构建阶段,所以require('./index.tpl')得到的是只包含数据渲染模版语法的内容,直接用模版引擎( ejs/art-template,保持与node端一致)渲染即可。