前端二次打包的那些坑我替你们踩过了

迷人的艺童 移动 阅读 1,641
赞 14 收藏
二维码
手机扫码查看
反馈

二次打包这个坑,我真的踩得有点惨

前两天遇到一个需求,需要把现有的一个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
  • 构建产物的命名要有区分,避免覆盖之前的内容

整个流程跑通之后,现在我可以根据不同客户需求快速生成对应的打包版本了。虽然过程很折腾,但至少以后再遇到类似需求就不会手忙脚乱了。

以上是我踩坑后的总结,如果你有更好的二次打包方案,欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论