背景
同事打包若依框架的 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
下,那么需要修改几个地方:
- vue 配置的
publicPath
- router 配置的
base
- nginx 的
try_files
如果需要使用 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 项目中都可以使用
- 标准同步写法 和 动态路径拼接:适用于 Node.js,模块会立即加载。
- 动态异步加载 和
require.ensure
:Webpack 环境下支持的异步加载写法,但建议用现代import()
替代。 - 动态导入 (
import()
):推荐在前端和现代项目中使用,支持异步加载和代码分割。 - 条件加载:适用于需要根据环境加载不同模块的场景。
提问
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}`);