webpack5.0+SSR尝鲜【排坑记录】

1,124 阅读5分钟

webpack5.0+Vue+SSR+vue-router+vuex

一些包

webpack相关

  • webpack
  • webpack-cli 命令行解析工具 4.0之前是一起的 4.0之后拆开了 需要安装
  • webpack-dev-server
  • html-webpack-plugin
  • webpack-merge

es6转es5

  • babel-loader es6=>es5 webpack中接入babel
  • @babel/core babel-loader依赖
  • @babel/preset-env 加入新的语法特性 比如2015年加入的新特性 Env包括所有的新特性
  • @babel/plugin-transform-runtime 减少冗余代码 默认的polifill属性已经被废除掉了
  • @babel/runtime @babel/plugin-transform-runtime依赖

解析css的包

  • vue-style-loader 支持服务端渲染 和style-loader功能一样
  • css-loader

vue相关

  • vue-loader 处理.vue文件
  • vue-template-loader 处理模板编译

npx webpack === node_modules/bin/webpack 打包

webpack5.0 bug记录

问题一

webpack5.0与webpack-dev-server不兼容

npm script 需把 "run":"webpack-dev-server --open"改成 "run":"webpack serve"

问题2

vue-style-loader与css-loader 在服务端打包时style样式不生效问题解决方案

{
test: /\.css$/,
use: [
    "vue-style-loader",
    {
    loader: "css-loader",
        options: {
            esModule: false  //默认为true 需要设置为false
        }
    }
]
}

问题三

build目录中 webpack.server.js const ServerRenderPlugin = require("vue-server-renderer/server-plugin") 报错

解决方案 修改vue-server-renderer包中的代码

github.com/vuejs/vue/i…

问题四

使用

const ClientRenderPlugin = require("vue-server-renderer/client-plugin");
const ServerRenderPlugin = require("vue-server-renderer/server-plugin");

自动引入js文件时服务端会报错 暂时没有解决办法 需要手动引入js文件

npm run client:build -- --watch 文件变动执行打包编译

为什么服务端vue vuex vueRouter 都需要调用一个函数,从函数中获取实例?

因为Node.js 服务器是一个长期运行的进程 当代码进入进程时,它将进行一次取值并留存在内存中。也就是会把vue实例保存到内存中 假如说不用函数返回新的实例,那么每个人访问同一个页面时都会共用同一个实例,那么就会造成数据的污染。

ssr.vuejs.org/zh/guide/st…

路由集成

首屏只渲染一个路由,但是其他路由的逻辑混淆再js文件中。如果需要分开 可以使用路由懒加载。 每个路由需要返回一个函数 每个服务器都要返回一个路由实例

此时的问题 此时在服务器中执行构建, 访问localhose:3000/ 应该渲染出对应的组件Foo 但是没有渲染出来 并且控制台会报错 Failed to execute 'appendChild' on 'Node': This node type does not support this method.at Object.appendChild

解造成此问题的原因: 服务器访问根目录时 router.get("/") 渲染出来的时字符串 它并不知道哪个页面对应哪个路由 所以只会渲染app.vue

解决方案:

server.js中 render.renderToString({url:'/' })

server.enter.js中

export default context => {
    // 服务端需要调用当前这个文件 去产生一个vue实例
    const { app, router } = createApp();
    // context.url  服务端要把对应的路由和此url相匹配
    router.push(context.url); // 渲染当前页面对应的路由
    return app; //拿这个实例去服务端查找渲染结果
};

切换到其他路由localhose:3000/bar然后刷新浏览器会报404错误 Not Fount

重点:如果此时是点击切换 那就只是客户端切换 并不是服务端切换。并不会造成服务端重新渲染。 也可以理解为服务端不认识router-link,解析不出对应的组件, localhose:3000/bar 只有刷新页面时才会走服务端渲染。 但是此时会报404 因为器服务器根本没有此路径。 解决方法: 所以在服务器中还得再配其他路径,使用中间件,使得每个路径渲染对应的页面

中间件 当找不到路由时会走此逻辑 如果匹配不到路由就会走此逻辑(当路由不是跟路径时,要跳转到对应的路径,渲染对应的页面) 如果服务器没有此路径,会渲染当前的app.vue(首页)文件,在渲染时又会重新指向/bar路径对应的页面 然后 server.entry.js 中router.push(context.url)找对应的组件

   app.use(async ctx => {
   ctx.body = await new Promise((resolve, reject) => {
       // 必须写成回调函数的形式否则css样式不生效
       render.renderToString(
       {
           title: "服务",
           url: ctx.url  //比如当前请求的/bar 那就把/bar传到server.entry.js中的content
       },
       (err, data) => {
           if (err) reject(err);
           resolve(data);
       }
       );
   });
   });

也就是先渲染app.vue---->找其他路由对应的组件

这也是history模式需要后端支持的原理

Vuex集成

Vuex中为什么需要此判断?

if (typeof window !== "undefined" && window.__INITIAL_STATE__) {
    store.replaceState(window.__INITIAL_STATE__);
}

因为客户端和服务端各自生成一个vuex实例 而他们两个需要共用一个状态,因此需要服务端状态改变之后传给客户端

服务端与客户端各自的用处?

服务端用于渲染html 有利于seo 客户端用于处理js逻辑比如 点击事件 client.bundle.js

服务端调用在组件中调用asyncDate() vuex必须返回promise才能生效,并且只有等resolve()执行完成之后才会返回结果

如果是服务端调用必须返回 promise 否则不生效  因为在service.entry.js的逻辑是Promise.all()
自己有里面的所有数据都获取完之后才会渲染app.vue 所以会等待三秒才渲染数据
只写setTimeout 不生效 因为setTimeout是异步的,在vuex实例渲染完成后才被调用,此时页面已经被当成字符串渲染到浏览器上了。

哪些请求放在ajax哪些放在服务端请求?

被爬虫爬取,比如新闻列表的数据 由服务端返回

使用

  • 在SSR-3目录中 npm install 安装依赖

  • 打包服务端 npm run server:build

  • 打包客户端 npm run client:build

  • 在dist目录index.ssr.html中引入客户端代码<script src="./client.bundle.js"></script>

  • 执行服务端脚本 node server.js

项目地址

github.com/wensiyuanse…