马上加入IBC程序猿 各种源码随意下,各种教程随便看! 注册 每日签到 加入编程讨论群

C#教程 ASP.NET教程 C#视频教程程序源码享受不尽 C#技术求助 ASP.NET技术求助

【源码下载】 社群合作 申请版主 程序开发 【远程协助】 每天乐一乐 每日签到 【承接外包项目】 面试-葵花宝典下载

官方一群:

官方二群:

实现ssr服务端渲染demo

[复制链接]
查看2338 | 回复0 | 2019-8-13 18:15:53 | 显示全部楼层 |阅读模式
最近在研究SSR服务器端渲染,自己写了的小demo。
项目布局
  1. ├── build                                       // 配置文件│ │──webpack.base                            // 公共配置│ │──webpack.client                          // 生成Client Bundle的配置│ │── webpack.server                          // 生成Server Bundle的配置├── dist                                        // 项目打包路径├── public                                      // 模板文件│   │──index.html                              // Client模板html文件│ │──index.ssr.html                          // Server模板html文件├── src                                         // 源码目录│ ├── assets                                  // 图片目录│ ├── components                              // 组件│ │ ├── Bar.vue                             // Bar测试组件│ │ ├── Foo.vue                             // Foo测试组件│ │── App.vue                                 // Vue应用的根组件│ │── main.js                                 //  入口基础文件│ ├── client-entry.js                         // 浏览器环境入口│ ├── server-entry.js                         // 服务器环境入口│   │ ├── router.js                           // 路由配置│   │ ├── store.js                            // vuex的状态管理├── favicon.ico                                 // 图标
复制代码
注:以防版本不对应产生的问题。package.json我也把放出来了,不过在文章的最后面
182009fpbbsbz0pz8shg01.png


上图是Vue官方的SSR原理先容图片。从这张图片,我们可以知道:我们需要通过Webpack打包生成两份bundle文件:

  • Client Bundle,给浏览器用。和纯Vue前端项目Bundle类似
  • Server Bundle,供服务端SSR使用,一个json文件
技能栈

vue + vuex + vue-router + webpack +ES6/7 + less + koa
拆分 Webpack 打包配置

构建文件目录
182010mgqssa4tssaslvz5.png


webpack.base.js 是公共配置,配置如下:
  1. // 基础的webpack配置// webpack专用配置const path = require('path')const VueLoader = require('vue-loader/lib/plugin')const resolve = dir => {    return path.resolve(__dirname, dir)}module.exports = {    output: {        filename: '[name].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'            },            {                test: /\.less$/,                loader: 'vue-style-loader!css-loader!less-loader'            },            {                test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,                use: {                    loader: 'url-loader',                    options: {                        limit: 300000,                        name: '[name].[ext]?[hash]'                    }                }            }        ]    },    plugins: [        new VueLoader()    ]}
复制代码
webpack.client.js 是生成Client Bundle的配置,配置如下:
  1. const merge = require('webpack-merge')const base = require('./webpack.base')const path = require('path')const resolve = dir => {    return path.resolve(__dirname, dir)}const ClientRenderPlugin = require('vue-server-renderer/client-plugin')const HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = merge(base, {    entry: {        client: resolve('../src/client-entry.js')    },    plugins: [        new ClientRenderPlugin(),        new HtmlWebpackPlugin({            filename: 'index.html',            template: resolve('../public/index.html')        })    ]})
复制代码
webpack.server.js是生成Server Bundle的配置,配置如下:
  1. const merge = require('webpack-merge')const base = require('./webpack.base')const path = require('path')const resolve = dir => {    return path.resolve(__dirname, dir)}const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')const HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = merge(base, {    entry: {        server: resolve('../src/server-entry.js')    },    target: 'node', // 用给node来使用    // devtool: 'source-map',    output: {        libraryTarget: 'commonjs2'    },    plugins: [        new VueSSRServerPlugin(),        new HtmlWebpackPlugin({            filename: 'index.ssr.html',            template: resolve('../public/index.ssr.html'),            excludeChunks: ['server'] // 排查某个模块        }),    ]})
复制代码
下图是我的项目文件目录
182010a2ze1ztkqskij488.png


components 目录下是组件
App.vue Vue应用的根组件
client-entry.js 浏览器环境入口
server-entry.js 服务器环境入口
main.js 入口基础文件
router.js 路由配置文件
store.js vuex状态管理文件
前端渲染 Demo

前端渲染demo部分比较简单,就包含两个组件:Foo 和 Ba
Foo.vue
  1.             Foo--{{num}}-点击测试js是否正常
  2.         {{this.$store.state.name}}
  3.              -----图片分割线----
  4.         [align=center][img]http://ibcibc.com/logo[/img][/align]
  5.         [align=center][img]http://ibcibc.com/../assets/images/kfbg.png[/img][/align]
  6.    
复制代码
Bar.vue
  1.            bar      
  2. Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。然而,也可以将同一个组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。   
复制代码
App.vue
  1.                         foo             Bar        
  2.            
  3.     .nav{        text-align: center;        display: flex;        align-items: center;      a{          flex: 2;          background: #f5f5f5;           text-decoration:none;            color: #333;           &.currentClass{               background:#f43553;               color: #fff;           }      }    }
复制代码
router.js
  1. import Vue from 'vue'import Foo from './components/Foo.vue'import VueRouter from 'vue-router'Vue.use(VueRouter)export default () => {    const router = new VueRouter({        mode: 'history',        routes: [            {                path: '/',                component: Foo            },            {                path: '/bar',                component: () => import('./components/Bar.vue')            },        ]    })    return router}
复制代码
store.js
  1. import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default () => {    const store = new Vuex.Store({        state: {            name: ''        },        mutations: {            changeName(state) {                state.name = 'yxf'            }        },        actions: {            changeName({ commit }) {                return new Promise((resolve, reject) => {                    setTimeout(() => {                        commit('changeName')                        resolve()                    })                })            }        }    })    if(typeof window !== 'undefined' && window.__INITIAL_STATE__) {        store.replaceState(window.__INITIAL_STATE__)    }    return store}
复制代码
拆分 JS 入口

在前端渲染的时候,只需要一个入口 main.js。现在要做后端渲染,就得有两个 JS 文件:client-entry.js 和 server-entry.js 分别作为浏览器和服务器的入口。
main.js基础文件
  1. //入口文件import Vue from 'vue'import createRouter from './router'import App from './App.vue'import createStore from './store'import { sync } from 'vuex-router-sync' // 把当前VueRouter状态同步到Vuex中export default () => {    const router = createRouter()    const store = createStore()    sync(store, router)    const app = new Vue({        router,        store,        render: h => h(App)    })    return { app, router, store }}
复制代码
client-entry.js 浏览器入口
  1. import createApp from './main'const { app,  router } = createApp()router.onReady(() => {    app.$mount('#app')})
复制代码
server-entry.js 服务器入口
  1. import createApp from './main'// 服务器需要调用当前这个文件产生一个vue实例export default context => {    // 涉及到异步组件的问题    return new Promise((resolve, reject) => {        const { app, router, store } = createApp()        // 设置路由        router.push(context.url)        // 返回的实例应跳转到 /     如/bar        router.onReady(() => {            const matchs = router.getMatchedComponents()            console.log(matchs.length)            if(matchs.length === 0) {                reject({ code: 404 })            }            // matchs匹配到所有的组件,整个都在服务端执行的            Promise.all(                matchs.map(component => {                    if(component.asyncData) {                        // asyncData 是在服务端调用的                        return component.asyncData(store)                    }                })            ).then(() => {                // 以上all中的方法,会改变store中的state                context.state = store.state;// 把vuex的状态挂载到上下文中,会将状态挂到window上                resolve(app)            }).catch(reject)        },reject)    })}// 服务器端配置好后,需要导出给node使用
复制代码
模板文件
182011rsdaipuar9srzigg.png


index.html client模板html文件
  1.                 Document   
  2.    
复制代码
index.ssr.html server模板html文件
  1.                 Document   
复制代码
编写服务端渲染主体逻辑

Vue SSR 依赖于包 vue-server-render,它的调用支持两种入口格式:createRenderer 和 createBundleRenderer,前者以 Vue 组件为入口,后者以打包后的 JS 文件为入口,本文接纳后者。
server.js
  1. const Koa = require('koa')const Router = require('koa-router')const server = new Koa()const router = new Router()const path = require('path')const static = require('koa-static')const fs = require('fs')const { createBundleRenderer } = require('vue-server-renderer')const serverBundle = require('./dist/vue-ssr-server-bundle.json')//渲染打包后的结果const template = fs.readFileSync(path.resolve(__dirname, './dist/index.ssr.html'), 'utf8')//客户端manifest.jsonconst clientManifest = require('./dist/vue-ssr-client-manifest.json')const render = createBundleRenderer(serverBundle, {    template, // 模板里必须要有 vue-ssr-outlet    clientManifest})router.get('/',async ctx => {    ctx.body = await new Promise((resolve, reject) => {        render.renderToString({url: '/'}, (err, data) => {            if(err) reject(err);            resolve(data);        })      })    })server.use(router.routes())// koa 静态服务中间件server.use(static(path.resolve(__dirname,'./dist')))server.use( async ctx => {    try{        ctx.body = await new Promise((resolve, reject) => {            render.renderToString({ url: ctx.url }, (err, data) => {                if(err) reject(err)                resolve(data)            })        })    }catch (e) {        ctx.body = '404'    }}) server.listen(3002, () => {    console.log('服务器已启动!')  })
复制代码
项目地点:https://github.com/xiaonizi66/vue-ssr-demo

package.json
  1. {  "name": "ssr",  "version": "1.0.0",  "description": "",  "main": "index.js",  "scripts": {    "test": "echo "Error: no test specified" && exit 1",    "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"  },  "author": "",  "license": "ISC",  "dependencies": {    "koa": "^2.7.0",    "koa-router": "^7.4.0",    "koa-static": "^5.0.0",    "vue": "^2.6.10",    "vue-loader": "^15.7.1",    "vue-router": "^3.1.2",    "vue-server-renderer": "^2.6.10",    "vuex": "^3.1.1",    "vuex-router-sync": "^5.0.0"  },  "devDependencies": {    "@babel/core": "^7.5.5",    "@babel/preset-env": "^7.5.5",    "babel-loader": "^8.0.6",    "css-loader": "^3.2.0",    "html-webpack-plugin": "^3.2.0",    "less": "^3.9.0",    "less-loader": "^5.0.0",    "url-loader": "^2.1.0",    "vue-style-loader": "^4.1.2",    "vue-template-compiler": "^2.6.10",    "webpack": "^4.39.1",    "webpack-cli": "^3.3.6",    "webpack-dev-server": "^3.8.0",    "webpack-merge": "^4.2.1"  }}
复制代码
最终渲染效果:

182011qzf7ifq3157iddwd.png


182012nviho50lozi5i155.png


项目运行

git clone https://github.com/xiaonizi66/vue-ssr-demo
npm install
npm run server:build
npm run cilent:build
nodemon server.js
也可在build后面加上 -- --watch 如:npm run server:build -- --watch 用来监听

来源:https://www.cnblogs.com/yanxiafei/archive/2019/08/13/11346829.html
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
*滑块验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则