vue-ssr | 具体实现篇 | 基础实现

2,064 阅读2分钟
请先阅读vue-ssr思路篇

1. Vue+koa实现简单服务端渲染

搭建koa服务

 yarn add koa koa-router koa-static
const Koa = require('koa');
const Router = require('koa-router');
const Static = require('koa-static');

const app = new Koa();
const router = new Router();



router.get('/',ctx =>{
    ctx.body = 'hello world';
})


app.use(router.routes());

app.listen(3000);

后端构建vue并返回

 yarn add vue vue-router vuex vue-server-renderer
const Koa = require('koa');
const Router = require('koa-router');
const Static = require('koa-static');

const app = new Koa();
const router = new Router();

const Vue = require('vue');
const VueServerRender = require('vue-server-renderer');

const vm = new Vue({
    data(){
        return {
            msg: 'Hello'
        }
    },
    template: `<div>{{msg}}</div>`
})

let render = VueServerRender.createRenderer();
router.get('/',async ctx =>{
    ctx.body = await render.renderToString(vm);
})
app.use(router.routes());
app.listen(3000);

新增模板渲染

  1. 新建template.html 其中需要挂载点
  2. 读取模板文件并传给render的配置选项template

template/html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>

    <!--vue-ssr-outlet-->
</body>
</html>

server.js

const Koa = require('koa');
const Router = require('koa-router');
const Static = require('koa-static');

const app = new Koa();
const router = new Router();

const Vue = require('vue');
const VueServerRender = require('vue-server-renderer');

const fs = require('fs');
const vm = new Vue({
    data(){
        return {
            msg: 'Hello11'
        }
    },
    template: `<div>{{msg}}</div>`
})

const template = fs.readFileSync('./template.html','utf8');

let render = VueServerRender.createRenderer({
    template
});
router.get('/',async ctx =>{
    ctx.body = await render.renderToString(vm);
})


app.use(router.routes());
app.listen(3000);

2. webpack实现编译Vue项目

配置webpack

loader(module-rules)

  • 支持高级语法降级:配置babel-loader(注意使用exclude: /node_modules/排除对第三方进行打包)
  • 支持css文件处理:css-loader vue-style-loader
  • 支持解析vue组件生成render方法:vue-loader

plugins

  • 将定义过的其它规则复制并应用到 .vue 文件里相应语言的块:VueLoaderPlugin
  • 将打包的js内联进预定义的模板html:HtmlWebpackPlugin

其他

  • 预定义文件后缀:resolve-extensions

完整配置

yarn add webpack webpack-cli webpack-dev-server babel-loader @babel/preset-env @babel/core vue-style-loader vue-loader html-webpack-plugin webpack-merge -D
const path = require('path');
// 它的职责是将你定义过的其它规则复制并应用到 .vue 文件里相应语言的块。例如,如果你有一条匹配 /\.js$/ 的规则,那么它会应用到 .vue 文件里的 <script> 块。
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const resolve = (dir)=>{
    return path.resolve(__dirname,dir);
}

module.exports = {
    entry: resolve('./src/main.js'),
    output: {
        filename: 'bundle.js',
        path: resolve('./dist')
    },
    resolve: {
        extensions:[
            '.js',
            '.vue'
        ]
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                },
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: [
                    'vue-style-loader',
                    'css-loader'
                ]
            },
            {
                test: /\.vue$/,
                use: 'vue-loader'
            }
        ]
    },
    plugins: [
        new VueLoaderPlugin(),
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: './plubic/index.html'
        })
    ]
}

构建项目结构

src
│  App.vue
│  list.txtmain.js
│  
└─components
        Bar.vue
        Foo.vue
        

test

npx webpack-dev-server

3. 返回渲染后的html且css、js功能实现(客户端激活)

根据客户端服务端不同需求定义不同的入口及webpack配置文件

编译命令 package.json
    "client:dev": "webpack-dev-server --config ./build/webpack.client.js --mode development",
    "client:build": "webpack --config ./build/webpack.client.js --mode production",
    "server:build": "webpack --config ./build/webpack.server.js --mode production"
配置文件
  • webpack.base.js :通用配置
  • webpack.client.js :客户端配置
  • webpack.server.js : 服务端配置
编译入口
  • main.js :返回app实例

    import Vue from 'Vue';
    import App from './App';
    
    export default () => {
        const app = new Vue({
            render : h=>h(App)
        })
        return {app};
    }
    
  • server-entry.js :

    const WebpackMerge = require('webpack-merge');
    const base = require('./webpack.base');
    const path = require('path');
    const resolve = dir => {
        return path.resolve(__dirname,dir);
    }
    
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    module.exports = WebpackMerge.merge(base,{
        entry: {
            server: resolve('../src/server-entry.js')
        },
        target: 'node',
        output: {
            libraryTarget: 'commonjs2'
        },
        plugins: [
            new HtmlWebpackPlugin({
                filename: 'index.ssr.html',
                template: './plubic/index.ssr.html',
                excludeChunks: ['server']
            })
        ]
    })
    
  • client-entry.js

    // 客户端
    
    import createApp from './main';
    
    let {app} = createApp();
    
    app.$mount('#app');
    
模板html
  • index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <div id="app"></div>
    </body>
    </html>
    
  • index.ssr.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <!--vue-ssr-outlet-->
    </body>
    </html>
    
客户端激活

其实很简单,在ssr中,我们需要在入口组件的最外层div上指定id为app,这样编译出来的js才能找到这个挂载点;为什么?假设不设置,我们以index.ssr.html为模板页的时候编译出来的html自然没有id为app的div,这样vue自然挂载不上去