前端二次打包的那些坑我替你们踩过了
二次打包这个坑,我真的踩得有点惨
前两天遇到一个需求,需要把现有的一个H5项目重新打包,换一套配置参数发给客户测试。听起来很简单吧,结果这一搞就是三天,各种奇怪的问题层出不穷。
最开始我想着直接修改package.json里面的配置,然后重新npm run build就完事了。结果第一次打包出来,静态资源路径全错,页面一片空白。后来试了下发现,这个项目用的是vue-cli构建的,但是里面的路径配置特别复杂,而且还有好几个环境变量文件混在一起。
折腾了半天才发现问题所在
这里我踩了个坑,原本以为只要改个baseUrl和环境变量就行了,结果发现这个项目在打包的时候还做了很多自定义配置。比如它有个特殊的cdn配置,还有移动端的rem适配配置,这些都影响到了二次打包的结果。
排查过程简直是一场灾难。首先是静态资源找不到,查了半天发现是publicPath设置有问题。然后是CSS样式错乱,因为rem转换的比例被我改乱了。最后连路由模式都有问题,history模式在某些环境下压根跑不起来。
折腾了一天,我把所有相关的配置文件都翻了一遍,包括vue.config.js、.env文件、webpack配置等等。每改一处都要重新build一下,那个等待时间真的让人崩溃。
核心配置修改就这几行
后来我整理了一下,其实二次打包主要需要关注这几个地方:
首先vue.config.js里面的关键配置:
module.exports = {
publicPath: process.env.VUE_APP_PUBLIC_PATH || './',
outputDir: process.env.VUE_APP_OUTPUT_DIR || 'dist',
assetsDir: 'static',
productionSourceMap: false,
// 这里是重点,移动端适配配置
css: {
extract: process.env.NODE_ENV === 'production' ? {
ignoreOrder: true
} : false
},
configureWebpack: {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
},
chainWebpack: config => {
// 静态资源处理
config.module
.rule('images')
.use('url-loader')
.loader('url-loader')
.tap(options => Object.assign(options, { limit: 1024 }))
}
}
然后是环境变量配置,这里需要注意不同环境的区分:
# .env.production
NODE_ENV=production
VUE_APP_API_BASE_URL=https://api.example.com
VUE_APP_PUBLIC_PATH=./
VUE_APP_OUTPUT_DIR=dist
VUE_APP_CDN_URL=https://cdn.example.com
# .env.customer1
NODE_ENV=production
VUE_APP_API_BASE_URL=https://customer1-api.com
VUE_APP_PUBLIC_PATH=./
VUE_APP_OUTPUT_DIR=dist_customer1
VUE_APP_CDN_URL=https://customer1-cdn.com
最关键的package.json脚本配置:
{
"scripts": {
"build": "vue-cli-service build",
"build:customer1": "vue-cli-service build --mode customer1",
"build:customer2": "vue-cli-service build --mode customer2",
"build:test": "vue-cli-service build --mode test"
}
}
静态资源路径处理是个大坑
这里注意我踩过好几次坑的地方,就是静态资源的路径处理。如果publicPath设置不对,打包出来的文件访问就会出问题。特别是移动项目,很多都是直接放在服务器根目录下,或者嵌套在某个子目录里。
对于不同的部署环境,publicPath需要动态调整。我用了一个比较稳妥的方式:
// vue.config.js
const getPublicPath = () => {
if (process.env.NODE_ENV === 'development') {
return '/'
}
const mode = process.env.VUE_APP_MODE
switch(mode) {
case 'customer1':
return './'
case 'customer2':
return '/app/customer2/'
default:
return './'
}
}
module.exports = {
publicPath: getPublicPath(),
// ... 其他配置
}
还有一个问题就是CDN资源的处理,有些客户要求走自己的CDN,这就需要动态替换静态资源的域名。我用了webpack的externals配置来处理第三方库:
configureWebpack: config => {
if (process.env.NODE_ENV === 'production') {
config.externals = {
'vue': 'Vue',
'vue-router': 'VueRouter',
'vuex': 'Vuex',
'axios': 'axios'
}
}
}
移动端特殊处理不能忘
移动端的二次打包还要特别注意几个点。首先是viewport的适配,不同客户的屏幕可能不一样,所以我在html模板里加了动态处理:
<!-- public/index.html -->
<script>
// 动态设置viewport
var dpr = window.devicePixelRatio || 1;
var scale = 1 / dpr;
var metaEl = document.querySelector('meta[name="viewport"]');
if (!metaEl) {
metaEl = document.createElement('meta');
metaEl.setAttribute('name', 'viewport');
document.head.appendChild(metaEl);
}
metaEl.setAttribute('content',
'width=device-width,initial-scale=' + scale +
',maximum-scale=' + scale +
',minimum-scale=' + scale +
',user-scalable=no'
);
</script>
然后是字体大小的处理,移动端经常需要根据dpr调整字体大小:
// main.js
function setRootFontSize() {
const deviceWidth = document.documentElement.clientWidth;
const rootFontSize = deviceWidth / 10; // 设计稿宽度/10
document.documentElement.style.fontSize = rootFontSize + 'px';
}
window.addEventListener('resize', setRootFontSize);
setRootFontSize();
构建速度优化也得考虑
二次打包意味着要频繁构建,如果不优化的话每次都要等很久。我加了一些构建优化配置:
// vue.config.js
module.exports = {
configureWebpack: config => {
if (process.env.NODE_ENV === 'production') {
// 压缩配置
config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true;
// 分包优化
config.optimization.splitChunks = {
chunks: 'all',
cacheGroups: {
vendor: {
name: 'chunk-vendors',
test: /[\/]node_modules[\/]/,
priority: 10,
chunks: 'initial'
}
}
}
}
}
}
另外我还配置了构建进度显示和错误提示,这样打包过程中出了问题能及时发现:
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
chainWebpack: config => {
if (process.env.NODE_ENV === 'production') {
config.plugin('progress').use(ProgressBarPlugin);
}
}
踩坑提醒:这几点一定要注意
最后总结几个特别需要注意的地方:
- 环境变量一定要隔离清楚,不同客户用不同的env文件
- 静态资源路径最容易出问题,publicPath和outputDir的组合要测试好
- 移动端适配配置不要漏掉,特别是字体和viewport
- 构建产物的命名要有区分,避免覆盖之前的内容
整个流程跑通之后,现在我可以根据不同客户需求快速生成对应的打包版本了。虽然过程很折腾,但至少以后再遇到类似需求就不会手忙脚乱了。
以上是我踩坑后的总结,如果你有更好的二次打包方案,欢迎评论区交流。

暂无评论