• 基于vue-cli项目添加服务端渲染


    两个示例的git地址:

    1. 我的环境

    2. 方式一:使用prerender-spa-plugin插件获得SSR的效果。

    2.1 说明

    prerender大致工作流程

    2.2 初始化

    1
    vue init webpack vue-prerender-demo //此文章都是在webpack基础上配置的
    1
    2
    3
    cd vue-prerender-demo
    npm install
    npm run dev

    2.3 配置

    2.4 开始

    1. 安装 prerender-spa-plugin, 因为依赖phantom js,phantom 的安装比较蛋疼,太耗时了~
    1
    npm install prerender-spa-plugin -D
    2. 开始 prerender 相关的配置:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //引用
    var PrerenderSpaPlugin = require('prerender-spa-plugin')

    //...

    plugins: {
    //....
    //配置 prerender-spa-plugin
    new PrerenderSpaPlugin(
    // 生成文件的路径,此处与webpack打包地址一致
    path.join(config.build.assetsRoot), //config.build.assetsRoot为vue cli生成的配置,打包后的文件地址
    // 配置要做预渲染的路由,只支持h5 history方式
    [ '/', '/test']
    )

    //....
    }
    3. 编译
    1
    npm run build

    dist目录结构

    4. 验证
    1
    2
    cd dist //进入到对应目录
    python -m SimpleHTTPServer 8888 //将dist作为根目录,启动8888端口,

    test 页面
    test 查看源代码页面

    2.5 优缺点

    3. 方式二:使用官方提供的轮子在node端做SSR

    3.1 说明

    3.2 约束

    3.3 准备工作

    1
    vue init webpack vue-ssr-demo
    1
    2
    3
    cd vue-ssr-demo
    npm install
    npm run dev

    3.4 开始折腾

    1. 首先安装 ssr 支持
    1
    npm i -D vue-server-renderer
    2. 增加路由test与页面
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <template>
    <div>
    Just a test page.
    <div>
    <router-link to="/">Home</router-link>
    </div>
    <div><h2>{{mode}}</h2></div>
    <div><span>{{count}}</span></div>
    <div><button @click="count++">+1</button></div>
    </div>
    </template>
    <script>
    export default {
    data () {
    return {
    mode: process.env.VUE_ENV === 'server' ? 'server' : 'client',
    count: 2
    }
    }
    }
    </script>
    3. 在src目录下创建两个js:
    1
    2
    3
    src
    ├── entry-client.js # 仅运行于浏览器
    └── entry-server.js # 仅运行于服务器
    4. 修改router配置。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import Vue from 'vue'
    import Router from 'vue-router'
    import HelloWorld from '@/components/HelloWorld'

    Vue.use(Router)

    export function createRouter () {
    return new Router({
    mode: 'history', // 注意这里也是为history模式
    routes: [
    {
    path: '/',
    name: 'Hello',
    component: HelloWorld
    }, {
    path: '/test',
    name: 'Test',
    component: () => import('@/components/Test') // 异步组件
    }
    ]
    })
    }
    5. 改造main.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import Vue from 'vue'
    import App from './App'
    import { createRouter } from './router'

    export function createApp () {
    // 创建 router 实例
    const router = new createRouter()
    const app = new Vue({
    // 注入 router 到根 Vue 实例
    router,
    render: h => h(App)
    })
    // 返回 app 和 router
    return { app, router }
    }
    6. entry-client.js加入以下内容:
    1
    2
    3
    4
    5
    6
    import { createApp } from './main'
    const { app, router } = createApp()
    // 因为可能存在异步组件,所以等待router将所有异步组件加载完毕,服务器端配置也需要此操作
    router.onReady(() => {
    app.$mount('#app')
    })
    7. entry-server.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // entry-server.js
    import { createApp } from './main'
    export default context => {
    // 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise,
    // 以便服务器能够等待所有的内容在渲染前,
    // 就已经准备就绪。
    return new Promise((resolve, reject) => {
    const { app, router } = createApp()
    // 设置服务器端 router 的位置
    router.push(context.url)
    // 等到 router 将可能的异步组件和钩子函数解析完
    router.onReady(() => {
    const matchedComponents = router.getMatchedComponents()
    // 匹配不到的路由,执行 reject 函数,并返回 404
    if (!matchedComponents.length) {
    // eslint-disable-next-line
    return reject({ code: 404 })
    }
    // Promise 应该 resolve 应用程序实例,以便它可以渲染
    resolve(app)
    }, reject)
    })
    }
    8. webpack配置
    1
    2
    3
    4
    build
    ├── webpack.base.conf.js # 基础通用配置
    ├── webpack.client.conf.js # 客户端打包配置
    └── webpack.server.conf.js # 服务器端打包配置
    9. webpack 客户端的配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
    // ...
    // ...
    plugins: [
    new webpack.DefinePlugin({
    'process.env': env,
    'process.env.VUE_ENV': '"client"' // 增加process.env.VUE_ENV
    }),
    //...
    // 另外需要将 prod 的HtmlWebpackPlugin 去除,因为我们有了vue-ssr-client-manifest.json之后,服务器端会帮我们做好这个工作。
    // new HtmlWebpackPlugin({
    // filename: config.build.index,
    // template: 'index.html',
    // inject: true,
    // minify: {
    // removeComments: true,
    // collapseWhitespace: true,
    // removeAttributeQuotes: true
    // // more options:
    // // https://github.com/kangax/html-minifier#options-quick-reference
    // },
    // // necessary to consistently work with multiple chunks via CommonsChunkPlugin
    // chunksSortMode: 'dependency'
    // }),

    // 此插件在输出目录中
    // 生成 `vue-ssr-client-manifest.json`。
    new VueSSRClientPlugin()
    ]
    // ...
    10. webpack 服务器端的配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    const webpack = require('webpack')
    const merge = require('webpack-merge')
    const nodeExternals = require('webpack-node-externals')
    const baseConfig = require('./webpack.base.conf.js')
    const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
    // 去除打包css的配置
    baseConfig.module.rules[1].options = ''

    module.exports = merge(baseConfig, {
    // 将 entry 指向应用程序的 server entry 文件
    entry: './src/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: {
    libraryTarget: 'commonjs2'
    },
    // https://webpack.js.org/configuration/externals/#function
    // https://github.com/liady/webpack-node-externals
    // 外置化应用程序依赖模块。可以使服务器构建速度更快,
    // 并生成较小的 bundle 文件。
    externals: nodeExternals({
    // 不要外置化 webpack 需要处理的依赖模块。
    // 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
    // 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单
    whitelist: /.css$/
    }),
    plugins: [
    new webpack.DefinePlugin({
    'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
    'process.env.VUE_ENV': '"server"'
    }),
    // 这是将服务器的整个输出
    // 构建为单个 JSON 文件的插件。
    // 默认文件名为 `vue-ssr-server-bundle.json`
    new VueSSRServerPlugin()
    ]
    })
    1
    baseConfig.module.rules[1].options = '' // 去除分离css打包的插件
    11. 配置package.json增加打包服务器端构建命令并修改原打包命令
    1
    2
    3
    4
    5
    6
    "scripts": {
    //...
    "build:client": "node build/build.js",
    "build:server": "cross-env NODE_ENV=production webpack --config build/webpack.server.conf.js --progress --hide-modules",
    "build": "rimraf dist && npm run build:client && npm run build:server"
    }
    12. 修改index.html
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>vue-ssr-demo</title>
    </head>
    <body>
    <!--vue-ssr-outlet-->
    </body>
    </html>
    13. 运行构建命令
    1
    npm run build
    14. 构建服务器端(官方例子使用的express,所以此 demo 将采用koa2来作为服务器端,当然,无论是 koa 与 express 都不重要…)
    1
    npm i -S koa
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const Koa = require('koa')
    const app = new Koa()

    // response
    app.use(ctx => {
    ctx.body = 'Hello Koa'
    })

    app.listen(3001)
    15. 编写服务端代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    const Koa = require('koa')
    const app = new Koa()
    const fs = require('fs')
    const path = require('path')
    const { createBundleRenderer } = require('vue-server-renderer')

    const resolve = file => path.resolve(__dirname, file)

    // 生成服务端渲染函数
    const renderer = createBundleRenderer(require('./dist/vue-ssr-server-bundle.json'), {
    // 推荐
    runInNewContext: false,
    // 模板html文件
    template: fs.readFileSync(resolve('./index.html'), 'utf-8'),
    // client manifest
    clientManifest: require('./dist/vue-ssr-client-manifest.json')
    })

    function renderToString (context) {
    return new Promise((resolve, reject) => {
    renderer.renderToString(context, (err, html) => {
    err ? reject(err) : resolve(html)
    })
    })
    }
    app.use(require('koa-static')(resolve('./dist')))
    // response
    app.use(async (ctx, next) => {
    try {
    const context = {
    title: '服务端渲染测试', // {{title}}
    url: ctx.url
    }
    // 将服务器端渲染好的html返回给客户端
    ctx.body = await renderToString(context)

    // 设置请求头
    ctx.set('Content-Type', 'text/html')
    ctx.set('Server', 'Koa2 server side render')
    } catch (e) {
    // 如果没找到,放过请求,继续运行后面的中间件
    next()
    }
    })

    app.listen(3001)
    1
    node server.js
    16. 大功告成

    test 页面

    test 页面

    3.4 优缺点

  • 相关阅读:
    【Cocos2d-X游戏实战开发】捕鱼达人之游戏场景的创建(六)
    WPF-24:绘制正多边形
    长假引起的系统审批流的变更的思考
    Linux shell编程02 shell程序的执行 及文件权限
    poj2787 算24
    REVERSE关键字之REVERSE索引
    设计模式读书笔记-----备忘录模式
    乔布斯的基本原则 (斯卡利访谈录 )
    MediaInfo源代码分析 1:整体结构
    Python 入门教程 9 ---- A Day at the Supermarket
  • 原文地址:https://www.cnblogs.com/axl234/p/9272118.html
Copyright © 2020-2023  润新知