美文网首页
Vue SSR 服务端渲染

Vue SSR 服务端渲染

作者: Veycn | 来源:发表于2019-03-11 20:52 被阅读0次

    服务端渲染的基本模型

    const http = require('http')
    let str = "hello, SSR"
    http.createServer(function(req, res){
        req.write('<h1>' + str + '</h1>');
        req.end();
    }).listen(8090)
    

    所谓服务端渲染, 其实一直都有, java, python, php 都有渲染模板来做服务端渲染, 简单来讲就是前发起请求, 服务器去数据库请求数据, 服务器拿到数据以后, 并不是直接返回, 而是根据相应的设计, 将数据拼接成一个渲染模板, 返回给前端, 前端直接渲染或经过简单处理之后渲染.

    Vue 服务端渲染基本模板

    <!--template.html-->
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
        <title>Title</title>
    </head>
    <body>
        <p>title</p>
        <!--vue-ssr-outlet-->
    </body>
    </html>
    

    里面的那一行注释就是告诉 serverRenderer 将模板插入到这里

    // server.js
    const http = require("http")
    const Vue = require('vue')
    const serverRender = require('vue-server-renderer')
    const app = new Vue({
        template: `<div>{{ title }}</div>`,
        data: {
            title: 'server side render'
        }
    })
    const render = serverRender.createRenderer({
        template: require('fs').readFileSync('./template.html', 'utf8')
    })
    let server = http.createServer(function (req, res) {
        render.renderToString(app, {
            // src 可以使用三括号插值语法将其放入渲染模板中 {{{ src }}}
            // 如果使用双括号, 将会被作为文本直接放在 document 中
            src: '<script>console.log("src")</script>',
            init: ''
            
        },(err, html) => {
            if(!err) {
                res.end(html)
            }
        })
    
    })
    
    server.listen(8090, function () {
        console.log('server is runing at 8090');
    })
    

    更为具体的实例肯定不会如此简陋,因为这也做不了什么事.
    下面会有一个 express + vue + webpack 按照 vue 文档上的 ssr 写的实例.

    先放个大招:


    ssr.png

    按照这个流程
    我们看看目录结构:


    目录.png

    首先肯定需要使用到webpack, 编写webpack配置文件如下:

    const path = require('path') // 方便使用路径
    const root = path.resolve(__dirname, '..'); // root 就是根路径 ssr 了
    const VueLoaderPlugin = require('vue-loader/lib/plugin')
    
    module.exports = {
        mode: 'development',
        entry: path.join(root, 'entry/entry.server.js'), // ssr/entry/entry-server.js
        output: {
            libraryTarget: "commonjs2", // 将导出的文件放到 module.exports 上
            path: path.join(root, 'dist'),   // 输出到 ssr/dist
            filename: 'server.bundle.js'     // 定义输出文件名
        },
        module: {
            rules: [
                {
                    test: /\.vue$/,
                    // use: ['vue-loader']
                    loader: 'vue-loader'
                },
                {
                    test: /\.js$/,
                    loader: 'babel-loader',
                    exclude: /node_modules/ // 忽略 node_modules 中的 js
                }
            ]
        },
        plugins: [new VueLoaderPlugin()]
    }
    

    配置好webpack, 就开始写代码了
    肯定是需要一个开发路径src的, 在里面写vue的组件等等, 另外,main.js也在这里定义.

    // main.js
    import Vue from 'vue'
    import App from './App.vue'
    
    export default function () {
        return new Vue({
            template:  `<App />`,
            components: {
                App
            }
        })
    }
    

    它的主要作用就是生成一个最外层的vue实例, 作为服务端渲染vue的入口.
    然后看看 App.vue

    // App.vue
    <template>
        <div id="app">
            <h1>Server Side Render</h1>
            <test />
        </div>
    </template>
    
    <script>
        import test from './components/test.vue'
        export default {
            name: "App",
            components: {
                test
            }
        }
    </script>
    
    <style scoped>
    
    </style>
    

    App.vue里, 引入了一个组件test.这意味着和在客户端使用vue没什么区别.
    但是其实是有区别的. 不如它会失去vue应有的特性. 这个后面会说.

    // test.vue  注意, 这里在浏览器渲染之后, 理想中的双向数据绑定不会生效
    <template>
        <div class="test">
            <input type="text" v-model="val">
            <p>{{ val }}</p>
        </div>
    </template>
    
    <script>
        export default {
            name: "test",
            data () {
                return {
                    val: 'hello, ssr'
                }
            }
        }
    </script>
    <style scoped>
    </style>
    

    后面就应该是服务端的入口登场了, entry.server.js 是服务端的入口

    // entry.server.js
    import createApp from '../src/main.js'
    
    export default function () {
        return createApp()
    }
    

    最后就是在server.js里面, 使用它, 为服务端渲染创建一个vue实例, 再使用服务端的渲染插件vue-server-renderer, 将我们创建的vue实例生成字符串, 返回给浏览器, 由浏览器去将其渲染出来.

    // server.js
    const express = require('express')
    const serverRender = require('vue-server-renderer')
    const createApp = require('./dist/server.bundle.js')['default']  // default 才能拿到生成实例的函数
    console.log(createApp);  // [Function]
    
    const render = serverRender.createRenderer({
        template: require('fs').readFileSync('./template.html', 'utf8')  // fs 读取 html 模板
    })
    
    const server = express()
    
    server.get('*', (req, res) => {
        let app = createApp()
        render.renderToString(app, (err, html) => {
           res.end(html)   // 渲染成字符串返回给浏览器
        })
    })
    
    server.listen(8090, function () {
        console.log('server is runing at 8090');
    })
    

    使用 webpack 打包, 使用node执行server.js, 在浏览器打开localhost:8090, 看到如下画面:

    test.png

    到目前为止, 大招图的上半部分已经通了:

    ssr_half.png
    此时, vue的特性依然没有. 只是一个死的不能再死的页面.
    接下来要做的事情, 就是要让vue活过来.那么这就是客户端的事情了
    entry目录中, 添加一个entry.client.js
    import createApp from '../src/main.js'
    let app = createApp()
    // 页面构建完成, 将 app 挂载
    window.onload = function () {
        app.$mount('#app')
    }
    

    当然, 也要写一个webpack客户端的配置文件, 基本跟服务端的一样, 只是名字不同.

    // webpack.client.js
    const path = require('path') // 方便使用路径
    const root = path.resolve(__dirname, '..'); // root 就是根路径 ssr 了
    const VueLoaderPlugin = require('vue-loader/lib/plugin')
    entry = path.join(root, 'dist');
    console.log(entry);
    
    
    module.exports = {
        mode: 'development',
        entry: path.join(root, 'entry/entry.client.js'), // ssr/entry/entry-server.js
        output: {
            path: path.join(root, 'dist'),   // 输出到 ssr/dist
            filename: 'client.bundle.js'     // 定义输出文件名
        },
        module: {
            rules: [
                {
                    test: /\.vue$/,
                    // use: ['vue-loader']
                    loader: 'vue-loader'
                },
                {
                    test: /\.js$/,
                    loader: 'babel-loader',
                    exclude: /node_modules/ // 忽略 node_modules 中的 js
                }
            ]
        },
        plugins: [new VueLoaderPlugin()]
    }
    

    到了这里, 我们来想一下, 为什么浏览器页面的vue是死的? 以为没有vue.js文件, 2333....
    这个时候, 三括号插值语法就派上用场了.
    先在template.html里面, 添加一行: {{{ load }}}

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
        <title>Title</title>
    </head>
    <body>
        <p>title</p>
        <!--vue-ssr-outlet-->
        {{{ load }}}
    </body>
    </html>
    

    意思是去加载一个js文件, 注意, 双花括号不会去加载, 直接被当成字符串了.
    然后 server.js里面

    const express = require('express')
    const path = require('path')
    const serverRender = require('vue-server-renderer')
    const createApp = require('./dist/server.bundle.js')['default']
    console.log(createApp);
    
    const render = serverRender.createRenderer({
        template: require('fs').readFileSync('./template.html', 'utf8')
    })
    
    const server = express()
    
    console.log(path.resolve('/client.bundle.js'));
    // 客户端请求这个文件, 将这个文件返回, 路径不一样, 可以偷偷的给它改了嘛
    // 请求文件写绝对路径, 返回文件随意
    server.get('/client.bundle.js', (req, res) => {
        res.sendFile(path.resolve('./dist/client.bundle.js'))
    })
    server.get('*', (req, res) => {
        let app = createApp()
        render.renderToString(app, {
            //  绝对路径
            load: '<script src="/client.bundle.js"></script>'
        }, (err, html) => {
           res.end(html)
        })
    })
    
    server.listen(8090, function () {
        console.log('server is runing at 8090');
    })
    

    然后打包运行, 就会看到:


    err.png

    为啥?
    这是因为, 在导入的时候 import Vue from 'vue' 使用的其实是vue/dist/vue.common.js, 所以会少一些东西, 将其改为vue/dist/vue.js就好了.这怎么改呢.
    main.js中, 修改.
    然后:

    r64.png
    可算是活过来了.
    但是这么修改, 意味着在每一个地方都需要这么修改.
    webpack里面修改一下, 两个文件中分别添加一个对象.
    resolve: {
            alias: {
                'vue': 'vue/dist/vue.js'
            }
        }
    

    这样基本的服务端渲染就算是完成了.

    使用 vue-router

    像正常使用vue-router一样, 写好 router 配置文件, 注入App组件的配置对象. 然后router-link, router-view 一气呵成.

    image.png

    使用 vuex

    也和往常使用一样, 使用store.

    image.png

    相关文章

      网友评论

          本文标题:Vue SSR 服务端渲染

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