加入收藏 | 设为首页 | 会员中心 | 我要投稿 云计算网_泰州站长网 (http://www.0523zz.com/)- 视觉智能、AI应用、CDN、行业物联网、智能数字人!
当前位置: 首页 > 综合聚焦 > 编程要点 > 语言 > 正文

为何用ssr服务端渲染?详解vue的ssr服务端渲染运用

发布时间:2022-01-01 13:51:35 所属栏目:语言 来源:互联网
导读:这篇文章给大家分享的是ssr服务端渲染的相关内容。下文介绍了为什么使用服务器端渲染以及vue的ssr服务端渲染使用,文中示例代码介绍的非常详细,感兴趣的朋友接下来一起跟随小编看看吧。 为什么使用服务器端渲染 (SSR) 更好的 SEO,由于搜索引擎爬虫抓取工
 这篇文章给大家分享的是ssr服务端渲染的相关内容。下文介绍了为什么使用服务器端渲染以及vue的ssr服务端渲染使用,文中示例代码介绍的非常详细,感兴趣的朋友接下来一起跟随小编看看吧。
 
    为什么使用服务器端渲染 (SSR)
 
更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。
请注意,截至目前,Google 和 Bing 可以很好对同步 JavaScript 应用程序进行索引。在这里,同步是关键。如果你的应用程序初始展示 loading 菊花图,然后通过 Ajax 获取内容,抓取工具并不会等待异步完成后再行抓取页面内容。也就是说,如果 SEO 对你的站点至关重要,而你的页面又是异步获取内容,则你可能需要服务器端渲染(SSR)解决此问题。
更快的内容到达时间 (time-to-content),特别是对于缓慢的网络情况或运行缓慢的设备。无需等待所有的 JavaScript 都完成下载并执行,才显示服务器渲染的标记,所以你的用户将会更快速地看到完整渲染的页面。通常可以产生更好的用户体验,并且对于那些「内容到达时间(time-to-content) 与转化率直接相关」的应用程序而言,服务器端渲染 (SSR) 至关重要。
    使用服务器端渲染 (SSR) 时还需要有一些权衡之处:
 
开发条件所限。浏览器特定的代码,只能在某些生命周期钩子函数 (lifecycle hook) 中使用;一些外部扩展库 (external library) 可能需要特殊处理,才能在服务器渲染应用程序中运行。
涉及构建设置和部署的更多要求。与可以部署在任何静态文件服务器上的完全静态单页面应用程序 (SPA) 不同,服务器渲染应用程序,需要处于 Node.js server 运行环境。
更多的服务器端负载。在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用 CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 (high traffic) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。
    目录结构
 
 
 
    1、定义打包命令 和 开发命令
    开发命令是用于客户端开发
 
    打包命令用于部署服务端开发
 
    -watch 便于修改文件再自动打包
 
"client:build": "webpack --config scripts/webpack.client.js --watch",
"server:build": "webpack --config scripts/webpack.server.js --watch",
"run:all": "concurrently "npm  run client:build" "npm run server:build""
    为了同时跑client:build 和 server:build
 
    1.1 package.json
 
{
  "name": "11.vue-ssr",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "client:dev": "webpack serve --config scripts/webpack.client.js",
    "client:build": "webpack --config scripts/webpack.client.js --watch",
    "server:build": "webpack --config scripts/webpack.server.js --watch",
    "run:all": "concurrently "npm  run client:build" "npm run server:build""
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "concurrently": "^5.3.0",
    "koa": "^2.13.1",
    "koa-router": "^10.0.0",
    "koa-static": "^5.0.0",
    "vue": "^2.6.12",
    "vue-router": "^3.4.9",
    "vue-server-renderer": "^2.6.12",
    "vuex": "^3.6.0",
    "webpack-merge": "^5.7.3"
  },
  "devDependencies": {
    "@babel/core": "^7.12.10",
    "@babel/preset-env": "^7.12.11",
    "babel-loader": "^8.2.2",
    "css-loader": "^5.0.1",
    "html-webpack-plugin": "^4.5.1",
    "vue-loader": "^15.9.6",
    "vue-style-loader": "^4.1.2",
    "vue-template-compiler": "^2.6.12",
    "webpack": "^5.13.0",
    "webpack-cli": "^4.3.1",
    "webpack-dev-server": "^3.11.2"
  }
}
 
    1.2 webpack.base.js 基础配置
 
// webpack打包的入口文件 , 需要导出配置
 
// webpack webpack-cli
// @babel/core babel的核心模块
// babel-loader  webpack和babel的一个桥梁
// @babel/preset-env  把es6+ 转换成低级语法
 
// vue-loader vue-template-compiler  解析.vue文件 并且编译模板
// vue-style-loader css-loader 解析css样式并且插入到style标签中, vue-style-loader支持服务端渲染
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
    mode: 'development',
    output: {
        filename: '[name].bundle.js' ,// 默认就是main, 默认是dist目录
        path:path.resolve(__dirname,'../dist')
    },
    module: {
        rules: [{
            test: /.vue$/,
            use: 'vue-loader'
        }, {
            test: /.js$/,
            use: {
                loader: 'babel-loader', // @babel/core -> preset-env
                options: {
                    presets: ['@babel/preset-env'], // 插件的集合
                }
            },
            exclude: /node_modules/ // 表示node_modules的下的文件不需要查找
        }, {
            test: /.css$/,
            use: ['vue-style-loader', {
                loader: 'css-loader',
                options: {
                    esModule: false, // 注意为了配套使用vue-style-loader
                }
            }] // 从右向左执行
        }]
    },
    plugins: [
        new VueLoaderPlugin() // 固定的
    ]
}
    1.3 webpack.client.js 配置是客户端开发配置 就是正常的vue spa开发模式的配置
 
const {merge} = require('webpack-merge');
const base =require('./webpack.base');
const path = require('path')
const  HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = merge(base,{
    entry: {
        client:path.resolve(__dirname, '../src/client-entry.js')
    },
    plugins:[
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, '../public/index.html'),
            filename:'client.html'
            // 默认的名字叫index.html
        }),
    ]
})
    1.4 webpack.server.js配置是打包后 用于服务端部署时引入的使用
 
const base =require('./webpack.base')
const {merge} = require('webpack-merge');
const  HtmlWebpackPlugin = require('html-webpack-plugin')
const path = require('path')
module.exports = merge(base,{
    target:'node',
    entry: {
        server:path.resolve(__dirname, '../src/server-entry.js')
    },
    output:{
        libraryTarget:"commonjs2" // module.exports 导出
    },
    plugins:[
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, '../public/index.ssr.html'),
            filename:'server.html',
            excludeChunks:['server'],
            minify:false,
            client:'/client.bundle.js'
            // 默认的名字叫index.html
        }),
    ]
})
    excludeChunks:[‘server'] 不引入 server.bundle.js包
 
    client 是变量
    minify 是不压缩
 
    filename是打包后的生成的html文件名字
 
    template: 模板文件
 
    2、编写html文件
    两份:
 
    2.1 public/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>
    2.2 public/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-->
 
    <!-- ejs模板 -->
    <script src="<%=htmlWebpackPlugin.options.client%>"></script>
</body>
</html>
<!--vue-ssr-outlet-->  是服务端渲染dom用到的插槽位置  固定写法
<%=htmlWebpackPlugin.options.client%>  填充htmlwebpackplugin的变量
    3、按照正常的vue开发, 编写对应文件
    定义一个app.js文件
 
    src/app.js
 
    入口改装成了函数 目的是服务端渲染时 每次访问的适合都可以通过这个工厂函数返回一个全新的实例,保证每个人访问都可以拿到一个自己的实例
 
import Vue from 'vue';
import App from './App.vue'
import createRouter from './router.js'
import createStore from './store.js'
// 入口改装成了函数 目的是服务端渲染时 每次访问的适合都可以通过这个工厂函数返回一个全新的实例,
//保证每个人访问都可以拿到一个自己的实例
export default () => {
    const router = createRouter();
    const store = createStore()
    const app = new Vue({
        router,
        store,
        render: h => h(App)
    });
    return { app, router,store }
}
    src/app.vue
 
<template>
  <div id="app">
    <router-link to="/">foo</router-link>
    <router-link to="/bar">bar</router-link>
    <router-view></router-view>
  </div>
</template>
<script>
export default {};
</script>
    src/component/Bar.vue
 
<template>
  <div>
    {{ $store.state.name }}  
   
  </div>
</template>
 
<style scoped="true">
div {
  background: red;
}
</style>
 
<script>
export default {
    asyncData(store){ // 在服务端执行的方法  ,只是这个方法在后端执行
      console.log('server call')
       // axios.get('/服务端路径')
        return Promise.resolve('success')
    },
    mounted(){ // 浏览器执行 ,后端忽略
      
    }
}
</script>
    src/component/Foo.vue
 
<template>
    <div @click="show">foo</div>
</template>
<script>
export default {
    methods:{
        show(){
            alert(1)
        }
    }
}
</script>
    src/router.js
 
import Vue from 'vue';
import VueRouter from 'vue-router';
import Foo from './components/Foo.vue'
import Bar from './components/Bar.vue'
Vue.use(VueRouter);// 内部会提供两个全局组件 Vue.component()
 
 
// 每个人访问服务器都需要产生一个路由系统
 
export default ()=>{
    let router = new VueRouter({
        mode:'history',
        routes:[
            {path:'/',component:Foo},
            {path:'/bar',component:Bar}, // 懒加载,根据路径动态加载对应的组件
            {path:'*',component:{
                render:(h)=>h('div',{},'404')
            }}
        ]
    });
    return router;
}
 
 
 
 
//前端的路由的两种方式 hash  history
 
// hash #
 
// 路由就是根据路径的不同渲染不同的组件 hash值特点是hash值变化不会导致页面重新渲染,我们可以监控hash值的变化 显示对应组件
//(可以产生历史记录)  hashApi 特点就是丑  (服务端获取不到hash值,)
 
// historyApi H5的api  漂亮。问题是刷新时会产生404。
    src/store.js
 
import Vue from 'vue';
import Vuex from 'vuex';
 
Vue.use(Vuex);
// 服务端中使用vuex ,将数据保存到全局变量中 window,浏览器用服务端渲染好的数据,进行替换
export default ()=>{
    let store = new Vuex.Store({
        state:{
            name:'zhufeng'
        },
        mutations:{
            changeName(state,payload){
                state.name = payload
            }
        },
        actions:{
            changeName({commit}){// store.dispatch('changeName')
                return new Promise((resolve,reject)=>{
                    setTimeout(() => {
                        commit('changeName','jiangwen');
                        resolve();
                    }, 5000);
                })
            }
        }
 
    });
 
    if(typeof window!='undefined' && window.__INITIAL_STATE__){
        // 浏览器开始渲染了
 
        // 将后端渲染好的结果 同步给前端  vuex中核心方法
        store.replaceState(window.__INITIAL_STATE__); // 用服务端加载好的数据替换掉
    }
    return store;
}
    4、 定义入口文件
    客户端包的打包入口文件:
 
    src/client-entry.js 用于客户端的js入口文件
 
import createApp from './app.js';
let {app} = createApp();
app.$mount('#app'); // 客户端渲染可以直接使用client-entry.js
    src/server-entry.js 服务端的入口文件
 
    是一个函数 在服务端请求时 再各自去执行, 给sever.js去执行用的
 
// 服务端入口
 
 
import createApp from './app.js';
 
 
// 服务端渲染可以返回一个函数
 
export default (context) => { // 服务端调用方法时会传入url属性
    // 此方法是在服务端调用的
    // 路由是异步组件 所以这里我需要等待路由加载完毕
    const { url } = context;
    return new Promise((resolve, reject) => { // renderToString()
        let { app, router, store } = createApp(); // vue-router
        router.push(url); // 表示永远跳转/路径
        router.onReady(() => { // 等待路由跳转完毕 组件已经准备号了触发
            const matchComponents = router.getMatchedComponents(); // /abc
 
 
            if (matchComponents.length == 0) { //没有匹配到前端路由
                return reject({ code: 404 });
            } else {
                // matchComponents 指的是路由匹配到的所有组件 (页面级别的组件)
                Promise.all(matchComponents.map(component => {
                    if (component.asyncData) {
// 服务端在渲染的时候 默认会找到页面级组件中的asyncData,并且在服务端也会创建一个vuex ,传递给asyncData
                        return component.asyncData(store)
                    }
                })).then(()=>{ // 会默认在window下生成一个变量 内部默认就这样做的
                    // "window.__INITIAL_STATE__={"name":"jiangwen"}"
                    context.state = store.state; //  服务器执行完毕后,最新的状态保存在store.state上
                    resolve(app); // app是已经获取到数据的实例
                })
            }
        })
    })
 
 
 
    // app 对应的就是newVue 并没有被路由所管理,我希望等到路由跳转完毕后 在进行服务端渲染
 
    // 当用户访问了一个不存在的页面,如何匹配到前端的路由
 
    // 每次都能产生一个新的应用
}
 
// 当用户访问bar的时候:我在服务端直接进行了服务端渲染,渲染后的结果返回给了浏览器。 浏览器加载js脚本,根据路径加载js脚本,
//用重新渲染了bar
component.asyncData 是一个异步请求 等待请求结束后再 设置context.state = store.state; 此时 “window.INITIAL_STATE={“name”:“jiangwen”}”
客户端的store就能拿到window.INITIAL_STATE 重新赋值。
 
    5、定义服务端文件 server.js , 用node部署的一个服务器,请求对应的模板文件
    用了koa、koa-router做请求处理
 
    vue-server-renderer是服务端渲染必备包
 
    koa-static 是处理静态资源的请求 比如js等文件
 
    serverBundle 是打包后的js
 
    template 是服务端入口打包后的html server:build
 
const Koa = require('koa');
const app = new Koa();
const Router = require('koa-router');
const router = new Router();
const VueServerRenderer = require('vue-server-renderer')
const static = require('koa-static')
 
const fs = require('fs');
const path = require('path')
const serverBundle = fs.readFileSync(path.resolve(__dirname, 'dist/server.bundle.js'), 'utf8')
const template = fs.readFileSync(path.resolve(__dirname, 'dist/server.html'), 'utf8');

(编辑:云计算网_泰州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读