美文网首页
Vue SSR初探(1)生成HTML

Vue SSR初探(1)生成HTML

作者: 浩神 | 来源:发表于2018-07-27 10:30 被阅读30次

Step1. 使用vue-server-renderer生成html片段

vue官方提供了vue-server-renderer这个库,它能将vue实例初步渲染,生成html,效果如下:

const Vue = require('vue')
const renderer = require('vue-server-renderer').createRenderer()

const app = new Vue({
    template: '<div>Hello {{name}}!</div>',
    data() {
        return {
            name: 'world'
        }
    }
})

renderer.renderToString(app, (err, html) => {
    if(err) throw err
    console.log(html)
})
image.png
注意到vue将data应用到template上,渲染出html。
线上代码: https://github.com/boomler/vue-ssr-demo/tree/master/ssr01

Step2: 使用html模板和Express

上一步只生成了html片段,但是不能生成完整的HTML文档,下面我们想办法生成一个完整的html并能处理请求。
新建index.template.html,内容如下:

<html lang="en">
<head>
    <title>hello</title>
</head>
<body>
<h1>Message from US: </h1>
<!--vue-ssr-outlet-->
</body>
</html>
文件结构

这次我们在createRenderer方法中传入template字段,为ssr指定一个html模板:

// index.js
const fs = require('fs');
const Vue = require('vue');
const server = require('express')();
const renderer = require('vue-server-renderer').createRenderer({
    template: fs.readFileSync(__dirname + '/index.template.html', 'utf-8')
});

server.get('*', (req, res) => {
    const app = new Vue({
        template: `<div>Hello {{name}}!</div>`,
        data: {
            name: 'World'
        }
    });

    renderer.renderToString(app, (err, html) => {
        if (err) {
            res.status(500).end('Internal Server Error')
            return
        }
        res.end(html)
    });
});

server.listen(8000, () => {
    console.log('listening on 8000')
});
vue-ssr-outlet会被替换成生成的HTML

这个时候我们就完成了第一步,初次访问页面的时候返回渲染好的html
线上代码: https://github.com/boomler/vue-ssr-demo/tree/master/ssr02

Step 3 根据url返回相应HTML

这一部分讨论如何使用vue-router
假设我们正在做一个博客网站,我们有三个页面:

  • 文章列表(/articles
  • 文章详情 (/article-detail)
  • 个人页面(/me)
    那么我们访问 XXX.com/articles我们希望拿到渲染好的文章列表页面,访问/me拿到个人信息页面。我们怎么根据路由生成特定的HTML?

使用vue-router

先增加一个路由文件:

// router.js
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

export function createRouter() {
    return new Router({
        mode: 'history',
        routes: [
            {path: '/', component: () => import('./components/Article-list.vue')},
            {path: '/articles/:id', component: () => import('./components/Article-detail.vue')}
        ]
    })
}

根据context.url去做路由切换,然后返回切换之后的app

// entry-server.js
const {createApp} =require("./app");

module.exports =  context => {
    return new Promise((resolve, reject) => {
        const {app, router} = createApp()

        router.push(context.url) //根据context.url去做路由切换,然后返回切换之后的app

        router.onReady(() => {
            const matchedComponents = router.getMatchedComponents()

            if (!matchedComponents.length) {
                return reject({code: 404})
            }
            resolve(app)
        }, reject)
    })
}

拿到请求之后,将请求的url添加到context中,传给createApp()方法

// server.js
const path = require('path')
const express = require('express')

const createApp = require(path.resolve(__dirname, './dist/main.js'))

const template = require('fs').readFileSync(__dirname + '/index.template.html', 'utf-8');

const app = express()
const renderer = require('vue-server-renderer').createRenderer({
    template
})


app.get('*', (req, res) => {
    const context = {url: req.url} //将请求的url添加到context中,传给createApp

    createApp(context).then(app => {
        renderer.renderToString(app, (err, html) => {
            if (err) {
                res.status(500).end(JSON.stringify(err))
                return
            }
            res.end(html)
        })
    })
})

app.listen(8000)

使用webpack打包,生成能在Node环境运行的bundle

我们发现我们现在有.vue文件,有import语句,这些都不能在node环境中运行,于是万能的webpack登场了。我们需要webpack做以下事情:

  • 使用vue-loadervue转成js
  • import/export 转换成 require / module.exports
    这样我们才能在node服务器上直接调用vue的代码
// build/server-build.js
const path = require('path')
const nodeExternals = require('webpack-node-externals')
const VueLoaderPlugin = require('vue-loader/lib/plugin')


module.exports = {
    // 将 entry 指向应用程序的 server entry 文件
    entry: path.resolve(__dirname, '../entry-server.js'),

    // 这允许 webpack 以 Node 适用方式(Node-appropriate fashion)处理动态导入(dynamic import),
    // 并且还会在编译 Vue 组件时,
    // 告知 `vue-loader` 输送面向服务器代码(server-oriented code)。
    target: 'node',
    // 对 bundle renderer 提供 source map 支持
    devtool: 'source-map',

    // 此处告知 server bundle 使用 Node 风格导出模块(Node-style exports)
    output: {
        path: path.resolve(__dirname, '..', './dist'),
        libraryTarget: 'commonjs2'
    },

    // https://webpack.js.org/configuration/externals/#function
    // https://github.com/liady/webpack-node-externals
    // 外置化应用程序依赖模块。可以使服务器构建速度更快,
    // 并生成较小的 bundle 文件。
    externals: nodeExternals({
        whitelist: /\.css$/
    }),
    module: {
        rules: [
            {
                test: /\.vue$/,
                loader: 'vue-loader'
            }]
    },

// 这是将服务器的整个输出
    plugins: [
        new VueLoaderPlugin()
    ]
}

下面是我们的博客系统的结构图

系统结构图

我们为server端写一份打包配置文件,利用webpack打包成能在node
环境运行的代码。
当有请求到达node server时,node server使用server-bundle做服务器端渲染,生成html字符串作为response
线上代码: https://github.com/boomler/vue-ssr-demo/tree/master/ssr03

相关文章

网友评论

      本文标题:Vue SSR初探(1)生成HTML

      本文链接:https://www.haomeiwen.com/subject/hfmvmftx.html