• vue-cli3.0搭建服务端渲染SSR


    vue
    专栏收录该内容
    14 篇文章0 订阅
    订阅专栏
    文章目录
    关于SSR
    什么是SSR
    为什么要用SSR
    SSR原理
    通用代码约束:
    构建SSR应用程序
    创建vue项目
    修改router/index.js
    修改store/index.js
    修改main.js
    服务端数据预取
    客户端数据预取
    构建配置
    webpack进行打包操作
    创建服务
    关于SSR
    什么是SSR
    可以将同一个组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序
    简单点说:就是将页面在服务端渲染完成后在客户端直接显示。无需等待所有的 JavaScript 都完成下载并执行,才显示服务器渲染的标记,所以你的用户将会更快速地看到完整渲染的页面。
    为什么要用SSR
    更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。
    更快的内容到达时间 (time-to-content),特别是对于缓慢的网络情况或运行缓慢的设备。
    SSR原理


    1)所有的文件都有一个公共的入口文件app.js
    2)进入ServerEntry(服务端入口)与clientEntry(客户端入口)
    3)经过webpack打包生成ServerBundle(供服务端SSR使用,一个json文件)与ClientBundle(给浏览器用,和纯Vue前端项目Bundle类似)
    4)当请求页面的时候,node中ServerBundle会生成html界面,通过ClientBundle混合到html页面中
    通用代码约束:
    实际的渲染过程需要确定性,所以我们也将在服务器上“预取”数据 ("pre-fetching" data) - 这意味着在我们开始渲染时,我们的应用程序就已经解析完成其状态。
    避免在 beforeCreate 和 created 生命周期时产生全局副作用的代码,例如在其中使用 setInterval 设置 timer
    通用代码不可接受特定平台的 API,因此如果你的代码中,直接使用了像 window 或 document,这种仅浏览器可用的全局变量,则会在 Node.js 中执行时抛出错误,反之也是如此
    构建SSR应用程序
    创建vue项目
    vue create vue-ssr-demo
    1
    根据提示启动项目

    修改router/index.js
    import Vue from 'vue'
    import VueRouter from 'vue-router'

    Vue.use(VueRouter)

    export function createRouter(){
    return new VueRouter({
    mode: 'history',
    routes: [
    {
    path: '/',
    name: 'Home',
    component: () => import(/* webpackChunkName: "about" */ '../views/Home.vue')
    },
    {
    path: '/about',
    name: 'About',
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
    }
    ]
    })
    }
    1
    2
    3

    修改store/index.js
    import Vue from 'vue'
    import Vuex from 'vuex'

    Vue.use(Vuex)

    export function createStore(){
    return new Vuex.Store({
    state: {
    },
    mutations: {
    },
    actions: {
    },
    modules: {
    }
    })
    }
    1

    修改main.js
    import Vue from 'vue'
    import App from './App.vue'
    import { createRouter } from './router'
    import { createStore } from './store'

    Vue.config.productionTip = false

    export function createApp(){
    const store = createStore();
    const router = createRouter();

    const app = new Vue({
    store,
    router,
    render: h => h(App)
    })

    return {app, store, router}
    }
    1
    2
    3
    服务端数据预取
    在src目录下创建entry-server.js
    // entry-server.js
    import { createApp } from './main'

    export default context => {
    return new Promise((resolve, reject) => {
    const { app, router, store } = createApp()

    router.push(context.url)

    router.onReady(() => {
    const matchedComponents = router.getMatchedComponents()
    if (!matchedComponents.length) {
    return reject({ code: 404 })
    }

    // 对所有匹配的路由组件调用 `asyncData()`
    Promise.all(matchedComponents.map(Component => {
    if (Component.asyncData) {
    return Component.asyncData({
    store,
    route: router.currentRoute
    })
    }
    })).then(() => {
    // 在所有预取钩子(preFetch hook) resolve 后,
    // 我们的 store 现在已经填充入渲染应用程序所需的状态。
    // 当我们将状态附加到上下文,
    // 并且 `template` 选项用于 renderer 时,
    // 状态将自动序列化为 `window.__INITIAL_STATE__`,并注入 HTML。
    context.state = store.state

    resolve(app)
    }).catch(reject)
    }, reject)
    })
    }
    1
    2

    客户端数据预取
    在src创建entry-client.js
    import { createApp } from './main';

    const {app, store, router} = createApp();

    router.onReady(() => {
    // 添加路由钩子函数,用于处理 asyncData.
    // 在初始路由 resolve 后执行,
    // 以便我们不会二次预取(double-fetch)已有的数据。
    // 使用 `router.beforeResolve()`,以便确保所有异步组件都 resolve。
    router.beforeResolve((to, from, next) => {
    const matched = router.getMatchedComponents(to)
    const prevMatched = router.getMatchedComponents(from)

    // 我们只关心非预渲染的组件
    // 所以我们对比它们,找出两个匹配列表的差异组件
    let diffed = false
    const activated = matched.filter((c, i) => {
    return diffed || (diffed = (prevMatched[i] !== c))
    })

    if (!activated.length) {
    return next()
    }

    // 这里如果有加载指示器 (loading indicator),就触发

    Promise.all(activated.map(c => {
    if (c.asyncData) {
    return c.asyncData({ store, route: to })
    }
    })).then(() => {

    // 停止加载指示器(loading indicator)

    next()
    }).catch(next)
    })

    app.$mount('#app')
    })
    1
    2
    3

    构建配置
    在根目录下创建vue.config.js
    const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
    const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
    const nodeExternals = require('webpack-node-externals')
    const merge = require('lodash.merge')
    const TARGET_NODE = process.env.WEBPACK_TARGET === 'node'
    const target = TARGET_NODE ? 'server' : 'client'
    module.exports = {
    css: {
    extract: false
    },
    configureWebpack: () => ({
    // 将 entry 指向应用程序的 server / client 文件
    entry: `./src/entry-${target}.js`,
    // 对 bundle renderer 提供 source map 支持
    devtool: 'source-map',
    target: TARGET_NODE ? 'node' : 'web',
    node: TARGET_NODE ? undefined : false,
    output: {
    libraryTarget: TARGET_NODE ? 'commonjs2' : undefined
    },
    // https://webpack.js.org/configuration/externals/#function
    // https://github.com/liady/webpack-node-externals
    // 外置化应用程序依赖模块。可以使服务器构建速度更快,
    // 并生成较小的 bundle 文件。
    externals: TARGET_NODE
    ? nodeExternals({
    // 不要外置化 webpack 需要处理的依赖模块。
    // 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
    // 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单
    allowlist: [/.css$/]
    })
    : undefined,
    optimization: {
    splitChunks: TARGET_NODE ? false : undefined
    },
    plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
    }),
    chainWebpack: config => {
    config.module
    .rule('vue')
    .use('vue-loader')
    .tap(options => {
    return merge(options, {
    optimizeSSR: false
    })
    })
    }
    }
    1
    2
    webpack进行打包操作
    修改package.json
    "build:client": "vue-cli-service build",
    "build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build",
    "build:win": "npm run build:server && move dist\vue-ssr-server-bundle.json bundle && npm run build:client && move bundle dist\vue-ssr-server-bundle.json"
    1
    2
    3
    执行build:win, 生成的dist目录如下:

    创建服务
    在根目录下创建server.js
    const express = require('express');
    const fs = require('fs');
    const path = require('path');
    const { createBundleRenderer } = require('vue-server-renderer');

    const app = express();

    const serverBundle = require('./dist/vue-ssr-server-bundle.json');
    const clientManifest = require('./dist/vue-ssr-client-manifest.json');
    const template = fs.readFileSync(path.resolve('./src/index.template.html'), 'utf-8');

    const render = createBundleRenderer(serverBundle, {
    runInNewContext: false, // 推荐
    template, // (可选)页面模板
    clientManifest
    });

    app.use(express.static('./dist',{index:false}))

    app.get('*', (req, res) => {
    const context = { url: req.url }
    // 这里无需传入一个应用程序,因为在执行 bundle 时已经自动创建过。
    // 现在我们的服务器与应用程序已经解耦!
    render.renderToString(context, (err, html) => {
    console.log(html)
    // 处理异常……
    res.end(html)
    })
    })

    const port = 3003;
    app.listen(port, function() {
    console.log(`server started at localhost:${port}`);
    });
    1
    2

    index.template.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title></title>
    </head>
    <body>
    <!--vue-ssr-outlet-->
    </body>
    </html>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    运行node server.js


    原文链接:https://blog.csdn.net/qq_26443535/article/details/107714957

  • 相关阅读:
    Python ES操作
    SVN总结
    MongoDB问题总结
    MySQL
    PyQt小工具
    Python logging模块
    shell脚本
    cmd命令
    eclipse java 项目打包
    Robot Framework:failed: Data source does not exist.错误
  • 原文地址:https://www.cnblogs.com/onesea/p/15048883.html
Copyright © 2020-2023  润新知