vite vue-router history模式打包部署
本文前提是前端使用
vite
+vue-router@4
,部署是用nginx
内容较多,如果想直接看如何配置,点击直达
背景小故事
众所周知,使用vue
开发时逃不掉url
后面的#号,也就是vue-router
的hash
模式,比如http://example.com/#/home
, 不论什么原因想要去掉#时,我们就得考虑使用histtory
模式了。
使用vue-router@4
创建history
模式路由,大致代码如下👇
js
const router = createRouter({
history: createWebHistory(),
})
对于前端而言,无论是history
模式还是hash
模式其实和我画页面没有任何影响,但是当我们辛辛苦苦开发完,准备打包部署的时候问题就随之而来了
随之而来的问题
前端打包交给运维部署的时候,会发现和之前一样的部署方式却404
了,为什么呢?这里就涉及到hash
模式和history
模式的区别了。
hash
模式 简单来说,它是基于浏览器的锚点实现的,#号后的url
变化不会向服务器发送请求history
模式url
的变化都会向服务器发送一个请求,如果服务器没有做处理,对应的url
找不到对应的资源返回,那么就会页面404
大家都知道,前端打包文件中(一般情况下)只包含了一个index.html
文件和其他如js
、css
等资源文件,所以当history
模式的url
变化时,在服务器上找不到与之对应的资源,就会404
了。
说的有点抽象,给大家举个例子:
同一个页面的url
,hash
模式是http://example.com/#/home
,那么访问后到服务器时#号后面的内容是不会发送到服务器的,那么就是访问/
根目录,/
后面没有内容,就会默认访问index.html
页面,打的包中正好有index.html
文件,那么就一点问题也没有。但如果是history
模式,此时的url
为http://example.com/home
,那么这个url
代表的意思就是(服务端没做其他操作的前提下)访问服务器根目录下的home
目录下的index.html
文件,但我们知道正常打的包根本就没有这个home
目录,那就理所当然的404
了
解决办法来了
正如vue-router
官网上所说
要解决这个问题,你需要做的就是在你的服务器上添加一个简单的回退路由。如果 URL 不匹配任何静态资源,它应提供与你的应用程序中的
index.html
相同的页面
所以我们在nginx
做如下配置,相关链接
nginx
# nginx 相关配置
location / {
try_files $uri $uri/ /index.html;
}
这里用到了try_files
配置,简单说下它的作用:它首先匹配url
有没有对应的资源(即上面的$uri
),如果有则返回;如果没有,则继续查找url
目录下(即上面的$uri/
)有没有默认的index.html
文件,如果还没有,最后返回根目录下的index.html
(即上面的/index.html
)。其实在这里,我们想要的就是让它无脑返回根目录下的index.html
(因为我们只有这个可访问文件)
这个时候,我们把打的包扔到nginx
根目录 下,并且加上如上配置,访问如http://example.com/home
页面就一切正常了。
但到这里结束了吗?不,这才刚刚开始...
新的问题
经过上面几步的操作,我们确实能访问到使用history
模式的页面了,但这有个前提:把我们的页面部署在根目录 下。如果是部署在子目录下,那又不能正常显示了。
比如,我们把网页部署在/wj
目录下(下文出现的所有wj
指的都是这个二级目录名称),并把前端打的包扔在了服务器上的/wj
目录下,期望访问http://example.com/wj/home
能正常访问,那么按照经验,我们把nginx
的配置修改成如下:
nginx
# nginx 修改后配置
location /wj {
try_files $uri $uri/ /wj/index.html;
}
nginx -s reload
后你会发现一个新问题
html
文件其实正常返回了,但是资源文件都404
了,检查资源文件的请求路径,发现根本没有请求到真实的资源地址,因为我们的资源文件都是在/wj
目录下,图中请求的是根目录下的资源文件,那当然没有了。
新的解决办法
书接上回,资源路径请求错误,聪明的小伙伴应该知道要改哪里了,对的,就是vite
的配置中有个base
配置,可以配置开发或生产环境服务的公共基础路径,官网配置链接
所以我们将vite.config.js
的配置修改成如下
js
// vite.config.js
{
base: '/wj/'
// 其他代码省略
}
再重新打包部署,你会发现
所有请求都200
都正常,但页面却白屏,百思不得其解。
但我们仔细分析,我们此时访问的路径是http://localhost:8008/wj/survey/edit
,但是我们vue-router
定义的路由其实是http://localhost:8008/survey/edit
,差了一个/wj
,当你用/wj/survey/edit
匹配路由的时候是匹配不到的,因为你定义的路由是/survey/edit
。
这时候聪明的你肯定想到什么了,没错,vue-router
也有一个对应的base
设置,官网配置链接,所以我们修改对应配置如下
js
const router = createRouter({
history: createWebHistory('/wj/')
})
重新打包上传,你会发现一切正常,非常完美,大功告成!
不同环境的打包
不是大功告成,不是非常完美了吗?怎么还有?
别急,且看下文
确实到这一步已经能完美运行了,但不同环境的打包怎么办?手动改一次代码打包一次?不可能也不应该吧。
比如在我司测试环境是使用Jenkins
自动化部署的,它是直接部署到根目录 下的,如果按照上面的步骤,把vite
, vue-router
的base
改成固定值的话,自动部署后页面就不能正常访问了。所以我们要能根据不同环境切换不同的base
,这里采用的方式是使用vite环境变量和模式来切换,具体方式如下:
创建 3 个.env
文件,分别为.env.dev
(测试Jenkins环境), .env.development
(开发环境), .env.production
(生产环境)
xml
# .env.dev
VITE_BASE_URL=/
xml
# .env.development
VITE_BASE_URL=/wj/
xml
# .env.production
VITE_BASE_URL=/wj/
定义了不同环境时的环境变量,那么只要在vue-router
和vite config
对应位置使用环境变量替换原来的固定值即可
js
const router = createRouter({
// 这里由原来的 '/wj/' 固定值改为import.meta.env.VITE_BASE_URL替换
history: createWebHistory(import.meta.env.VITE_BASE_URL)
})
vite.config.js
中略有不同,大致代码如下:
js
import { defineConfig, loadEnv } from 'vite'
export default ({ mode }) => {
// 使用loadEnv获取环境变量
const env = loadEnv(mode, process.cwd())
const BASE_URL = env.VITE_BASE_URL
return defineConfig({
base: BASE_URL, // 使用loadEnv获取VITE_BASE_URL替换原来的固定值 '/wj/'
// ...省略其他代码
})
}
修改package.json
文件,加入不同环境的打包命令
json
"build-prod": "vue-tsc && vite build",
"build-dev": "vue-tsc && vite build --mode dev",
分别对应打包生产环境,打包测试环境的包
另外还有一点,测试环境Jenkins
自动化部署时记得修改nginx
配置,只要在其中加入如下配置即可:
nginx
location / {
try_files $uri $uri/ /index.html;
}
因为测试环境是部署在根目录下,所以nginx
直接使用这个配置就行,不需要其他修改。
至此,就大结局了。
总结
总结一下history
模式部署时需要改动的地方
如果是部署在根目录 下,只需要改nginx
配置如下即可,前端配置无需修改
nginx
location / {
try_files $uri $uri/ /index.html;
}
如果是部署在二级目录 下(如部署在/wj
目录),修改内容如下
nginx
配置
nginx
# nginx 修改后配置
location /wj {
try_files $uri $uri/ /wj/index.html;
}
vue-router
配置
js
const router = createRouter({
history: createWebHistory('/wj/')
})
vite.config
配置
js
// vite.config.js
{
base: '/wj/'
// 其他代码省略
}