美文网首页
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