记一次Vue部署后无法访问的解决过程

Daotin 于 2024-12-11 发布 编辑

背景

同事打包若依框架的 vue2 spa 项目后,部署到 nginx 的子目录rwc_web下,导致无法访问http://10.x.x.100/rwc_web,经过一些修改,即便可以访问主页,但是仍然存在两个问题:

1、刷新后报 404

2、子路由无法访问,提示Cannot find module '@/views/xxx'

分析

1、首先检查,本地运行,是不报错,是否可以跳转到子路由。没问题 ✅

2、然后,既然是配置到 nginx 的子目录rwc_web下,那么需要修改几个地方:

如果需要使用 history 模式,并让应用正常运行和刷新,具体配置如下:


// vue.config.js
module.exports = {
  publicPath: '/rwc_web/' // 静态资源路径(绝对路径),当 publicPath 设置为 /rwc_web/ 时,Vue CLI 认为应用的静态资源需要从 http://<domain>/rwc_web/ 加载。
};

// router.js
const router = new Router({
  mode: 'history',
  base: '/subdir/', // Vue Router 假定所有路由都以 /rwc_web/ 为前缀
  routes: [...]
});

// ngnix配置
location /rwc_web/ {
  root /path/to/your/nginx/html;
  index index.html;
  try_files $uri $uri/ /rwc_web/index.html; // 请求重定向到 index.html
}

一切看起来很合理,但是现在的问题是:子路由无法访问,提示Cannot find module '@/views/rwc/orderInfo/index'

在网上搜索,很多答案说是若依框架的懒加载问题,需要将 permission.js 的 import 改成 require(因为 webpack4 不支持变量方式的动态 import)。但源码中特意更新此处以提升页面加载速度,感觉不对劲。

到这里就卡住了,没有找到解决方案。。。官方给的解决方案是这个:https://gitee.com/y_project/RuoYi-Vue/issues/I4PZJF#note_8309871_link ,有待尝试。

然后我就去看了金科的项目,发现他是这么配置的:

vue spa 打包后的文件放在 nginx 的 html/rwc_web 目录下,之前 publicPath 和 router 的 base 设置为/rwc_web/,并且 router 的 mode 为 history 的时候,若依框架,跳转子路由总是提示 Cannot find module '@/views/xxx',但是金科的项目是 publicPath 设置为./,并且去掉 router 的 base,并且 mode 设置为 hash 的时候,跳转子路由就可以了,这是什么原因?

看了 vue-cli 配置,可以设置为相对路径,这样就避免资源找不到的问题,可以放在 nginx 的任何目录。

然后 history 改成 hash,避免 nginx 配置不足,导致的刷新问题。

当然,目前金科的问题是,没有懒加载。就是在点击其他菜单的时候,会发现没有调用 chunk-xxx.js 文件,也就是没有完全的懒加载(具体可以看提问部分):

export const loadView = (view) => {
  // 路由懒加载
  return (resolve) => require([`@/views/${view}`], resolve);
};

解决

配置hash+相对路径的完整的配置如下:

// vue.config.js
module.exports = {
  publicPath: './' // 静态资源路径(绝对路径),当 publicPath 设置为 /rwc_web/ 时,Vue CLI 认为应用的静态资源需要从 http://<domain>/rwc_web/ 加载。
};

// router.js
const router = new Router({
  mode: 'hash',
  routes: [...]
});

// ngnix配置
server {
  listen 80;
  server_name example.com;

  # 前端静态资源
  location /rwc_web/ {
    root /path/to/your/nginx/html;
    index index.html;
    try_files $uri $uri/ /rwc_web/index.html;
  }

  # 后端 API 代理
  location /rwc-api/ {
    proxy_pass http://backend-ip:8080/api/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}

结论:hash 是没问题的,history 如果通过配置 try_files 后,应该也是没问题的。

附录

不同版本的 Vue CLI 对应不同版本的 Webpack。以下是常见版本的对应关系:

Vue CLI 版本 对应的 Webpack 版本 Vue Loader 版本
Vue CLI 2.x Webpack 3.x Vue Loader 13.x
Vue CLI 3.x Webpack 4.x Vue Loader 14.x
Vue CLI 4.x Webpack 4.x Vue Loader 15.x
Vue CLI 5.x Webpack 5.x Vue Loader 16.x

require 的几种写法

写法类型 是否异步 支持代码分割 环境支持 典型用途 示例代码
标准同步写法 Node.js 同步加载核心模块、工具模块。 const fs = require('fs');
动态异步加载 Webpack 前端按需加载模块。 require(['./moduleA'], function(moduleA) { console.log(moduleA); });
require.ensure Webpack 旧版 Webpack 异步加载,已被弃用。 require.ensure(['./moduleB'], function(require) { const moduleB = require('./moduleB'); console.log(moduleB); }, 'chunk-name');
动态路径拼接 否(Node.js)/是(Webpack) 否(Node.js)/是(Webpack) Node.js, Webpack 动态加载模块。 const module = require(`./modules/${name}`);
动态导入 (import()) 浏览器, Webpack 推荐的现代异步加载方式,前端常用。 import(`./module/${name}`).then(module => console.log(module));
CommonJS 动态加载 Node.js 动态加载模块,路径在运行时决定。 const module = require(modulePath);
条件加载 Node.js, Webpack 根据运行环境加载不同模块。 let module;if (process.env.NODE_ENV === 'development') { module = require('./devModule'); } else { module = require('./prodModule'); }

说明:以上写法 在 Vue + Webpack 项目中都可以使用

提问

1、return () => import(@/views/${view});改成return (resolve) => require([@/views/${view}], resolve); 打包的体积会变大吗?require 不也是懒加载吗?两者的效果应该一样吧?

Webpack 在处理 require([path], resolve) 时,通常会将多个动态模块合并到一个较大的文件中,而不是分割成独立的文件。

比如:return (resolve) => require([@/views/${view}], resolve);,如果 view 是动态的,Webpack 可能会将所有可能的模块打包到一起,而不是分割。这样,多个模块可能会被打包到同一个文件中,即使你只加载其中一个,文件大小也可能过大。

2、为什么 Webpack 可能无法动态分析路径的依赖?

Webpack 可以通过静态分析依赖路径信息来正确打包模块。但是动态路径(如基于变量的路径拼接)可能导致 Webpack 无法预判所有可能的模块。例如:

const view = 'Dashboard';
require([`@/views/${view}`], resolve);

在这种情况下:

Webpack 无法确定 view 的值(例如,它可能是 “Dashboard”、”Settings” 等),所以它不知道哪些模块应该被包含。

默认情况下,Webpack 会采取保守策略,把 @/views 目录下的所有文件都打包到主文件或同一个 Chunk 中,而不是分割成单独的文件。

解决办法:是使用 import() 替代 require()

return () => import(`@/views/${view}`);