vue-cli 构建打包优化(JeecgBoot-Vue2 配置优化篇)

发布于:2025-05-22 ⋅ 阅读:(20) ⋅ 点赞:(0)

项目:jeecgboot-Vue2

在项目二次开发后,在本人电脑打包时间为3分35秒左右

webpack5默认优化:

  1. Tree Shaking(摇树优化):删除未使用的代码
  2. base64 内联: 小于 8KB 的资源(图片等)进行 Base64 内联,减少 HTTP 请求,会增加33%的大小

原配置: vue.config.js

  1. 生产环境取消 console,productionSourceMap
  2. 生产环境js、css Gzip压缩
  3. less转译
const path = require('path')
const CompressionPlugin = require("compression-webpack-plugin")

function resolve(dir) {
  return path.join(__dirname, dir)
}

module.exports = {
  productionSourceMap: false,
  publicPath:'/',
  configureWebpack: config => {
    // 生产环境取消 console.log
    if (process.env.NODE_ENV === 'production') {
      config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true
    }
  },
  chainWebpack: (config) => {
    config.resolve.alias
      .set('@$', resolve('src'))
      .set('@api', resolve('src/api'))
      .set('@assets', resolve('src/assets'))
      .set('@comp', resolve('src/components'))
      .set('@views', resolve('src/views'))

    // 生产环境,开启js\css压缩
    if (process.env.NODE_ENV === 'production') {
        config.plugin('compressionPlugin').use(new CompressionPlugin({
          test: /\.(js|css|less)$/, // 匹配文件名
          threshold: 10240, // 对超过10k的数据压缩
          deleteOriginalAssets: false // 不删除源文件
        }))
    }

    // 配置 webpack 识别 markdown 为普通的文件
    config.module
      .rule('markdown')
      .test(/\.md$/)
      .use()
      .loader('file-loader')
      .end()

    // 编译vxe-table包里的es6代码,解决IE11兼容问题
    config.module
      .rule('vxe')
      .test(/\.js$/)
      .include
        .add(resolve('node_modules/vxe-table'))
        .add(resolve('node_modules/vxe-table-plugin-antd'))
        .end()
      .use()
      .loader('babel-loader')
      .end()

  },

  css: {
    loaderOptions: {
      less: {
        modifyVars: {
          /* less 变量覆盖,用于自定义 ant design 主题 */
          'primary-color': '#1890FF',
          'link-color': '#1890FF',
          'border-radius-base': '4px',
        },
        javascriptEnabled: true,
      }
    }
  },

  devServer: {
    port: 3000,
    proxy: {
      '/jeecg-boot': {
        target: 'http://localhost:8080',
        ws: false,
        changeOrigin: true
      },
    }
  },

  lintOnSave: undefined
}

优化后: 首次打包2分30秒,二次打包25秒

  1. CDN 外部依赖 (externals),HTML 自动注入CDN
  2. 持久化缓存 (cache),极大加速二次构建,修改配置后失效
  3. 移除 prefetch,避免预加载未使用的资源
  4. Moment.js 语言包裁剪,只保留中文语言包
  5. 图片压缩(image-webpack-loader)
  6. CSS压缩(CssMinimizerPlugin)
  7. JS压缩(terser)
  8. 多线程构建 (thread-loader),适用于中大型项目
const path = require('path')
const webpack = require('webpack')
const CompressionPlugin = require("compression-webpack-plugin")
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin')

const assetsCDN = {
  externals: {
    'viser-vue': 'ViserVue'
  },
  js: ['//unpkg.com/viser-vue/umd/viser-vue.min.js'],
  css: []
}

function resolve(dir) {
  return path.join(__dirname, dir)
}

module.exports = {
  publicPath: '/',
  assetsDir: 'static',
  outputDir: 'dist',
  lintOnSave: undefined,
  filenameHashing: true,  		// 文件名哈希
  productionSourceMap: false,   // 生产环境的 source map

  configureWebpack: (config) => {
    if (process.env.NODE_ENV === 'production') {
      // terser 配置,压缩JavaScript 
      config.optimization.minimizer = [
        new TerserPlugin({
          terserOptions: {
            compress: {
              drop_console: true // 移除 console
            }
          }
        }),
          
        // css压缩
        new CssMinimizerPlugin(),
      ]
      
      // 配置外部依赖
      config.externals = assetsCDN.externals;
    }
    
    // 缓存配置
    config.cache = {
      type: 'filesystem',
      buildDependencies: {
        config: [__filename],           // vue.config.js 变更时失效
        // dependencies: ['package.json']  // 依赖变更时失效(存在兼容性问题)
      },
      // 指定缓存位置,默认位置:node_modules/.cache/webpack
      cacheDirectory: path.resolve(__dirname, '.webpack_cache')   
    }
  },

  chainWebpack: (config) => {
    // 添加多线程构建
    config.module
      .rule('js')
      .use('thread-loader')
      .loader('thread-loader')
      .options({
        workers: require('os').cpus().length - 1, // CPU核心数减1
      })
      .end()

    // 移除 prefetch,避免加载多余的资源
    config.plugins.delete('prefetch')

    // 只保留中文语言包
    config.plugin('ContextReplacementPlugin')
      .use(webpack.ContextReplacementPlugin, [/moment[/\\]locale$/, /zh-cn/])

    // 配置别名
    config.resolve.alias
      .set('@$', resolve('src'))
      .set('@api', resolve('src/api'))
      .set('@assets', resolve('src/assets'))
      .set('@comp', resolve('src/components'))
      .set('@views', resolve('src/views'))

    if (process.env.NODE_ENV === 'production') {
      // 添加 CDN 配置到 HTML
      config.plugin('html').tap(args => {
        args[0].cdn = assetsCDN
        return args
      })

      // 开启js\css Gzip压缩
      config.plugin('compressionPlugin').use(new CompressionPlugin({
        test: /\.(js|css|less)$/,   // 匹配文件名
        threshold: 10240,           // 对超过10k的数据压缩
        deleteOriginalAssets: false // 不删除源文件
        minRatio: 0.8,				// 压缩比小于0.8不予压缩
      }))
      
      // 图片压缩
	  config.plugin('image-optimizer')
        .use(ImageMinimizerPlugin, [{
          minimizer: {
            implementation: ImageMinimizerPlugin.squooshMinify,
            options: {
              encodeOptions: {
                mozjpeg: { quality: 80 },
                webp: { lossless: true },
              },
            },
          },
        }]);

      // 分包策略
      config.optimization.splitChunks({
        chunks: 'all',
        minSize: 20000,
        maxSize: 244000,
        maxAsyncRequests: 30,   // 按需加载时的最大并行请求数
        maxInitialRequests: 20, // 入口点上的最大并行请求数
        cacheGroups: {
          vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10
          },
          common: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true
          }
        }
      })
    }

    // 配置 webpack 识别 markdown 为普通的文件
    config.module
      .rule('markdown')
      .test(/\.md$/)
      .use()
      .loader('file-loader')
      .end()

    // 编译vxe-table包里的es6代码,解决IE11兼容问题
    config.module
      .rule('vxe')
      .test(/\.js$/)
      .include.add(resolve('node_modules/vxe-table'))
      .add(resolve('node_modules/vxe-table-plugin-antd'))
      .end()
      .use()
      .loader('babel-loader')
      .end()
  },

  css: {
    loaderOptions: {
      less: {
        modifyVars: {
          /* less 变量覆盖,用于自定义 ant design 主题 */
          'primary-color': '#1890FF',
          'link-color': '#1890FF',
          'border-radius-base': '4px'
        },
        javascriptEnabled: true
      }
    }
  },

  devServer: {
    port: 3000,
    client: {
      overlay: {
        runtimeErrors: false
      }
    },
    proxy: {
        target: 'http://localhost:8080',
        ws: false,
        changeOrigin: true
      }
    }
  }
}

继续优化: 首次打包1分50秒,二次打包25秒

  1. ESBuild :替代 terser + babel + thread-loader,ESBuild 本身启用多线程打包,用ESBuild加速JS转译,极大提升构建速度,需要考虑兼容性(现代浏览器)
  2. Brotli 压缩:Gzip 压缩率高 15%~25%,但压缩速度稍慢。需要服务器支持和浏览器支持,前端构建时预生成 .br 和 .gz,可通过浏览器请求头判断支持,服务器动态响应
const path = require('path')
const webpack = require('webpack')
const CompressionPlugin = require("compression-webpack-plugin")
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
const { EsbuildPlugin } = require('esbuild-loader')
const BrotliPlugin = require('brotli-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

// 注意版本
const assetsCDN = {
  externals: {
    'viser-vue': 'ViserVue',
    'ant-design-vue': 'antd',
    'vue': 'Vue',
    'vue-router': 'VueRouter',
    'vuex': 'Vuex'
  },
  js: [
    '//unpkg.com/viser-vue/umd/viser-vue.min.js',
    '//unpkg.com/ant-design-vue@3.2.6/dist/antd.min.js',
    '//unpkg.com/vue@3.2.47/dist/vue.global.prod.js',
    '//unpkg.com/vue-router@4.2.2/dist/vue-router.global.prod.js',
    '//unpkg.com/vuex@4.1.0/dist/vuex.global.prod.js'
  ],
  css: [
    '//unpkg.com/ant-design-vue@3.2.6/dist/antd.min.css'
  ]
}

function resolve(dir) {
  return path.join(__dirname, dir)
}

module.exports = {
  publicPath: '/',
  assetsDir: 'static',
  outputDir: 'dist',
  lintOnSave: undefined,
  filenameHashing: true,  		// 文件名哈希
  productionSourceMap: false,   // 生产环境的 source map

  configureWebpack: (config) => {
    if (process.env.NODE_ENV === 'production') {
      config.optimization.minimizer = [
        new EsbuildPlugin ({
          target: 'es2015',  // 压缩为 ES6
          drop: ['console'], // 移除 console
          css: false
        }),
        new CssMinimizerPlugin()
      ]
      
      // 配置外部依赖
      config.externals = assetsCDN.externals;
    }
    
    // 缓存配置
    config.cache = {
      type: 'filesystem',
      buildDependencies: {
        config: [__filename],           // vue.config.js 变更时失效
        // dependencies: ['package.json']  // 依赖变更时失效(存在兼容性问题)
      },
      // 指定缓存位置,默认位置:node_modules/.cache/webpack
      cacheDirectory: path.resolve(__dirname, '.webpack_cache')   
    }
  },

  chainWebpack: (config) => {
    // 移除 prefetch,避免加载多余的资源
    config.plugins.delete('prefetch')

    // 只保留中文语言包
    config.plugin('ContextReplacementPlugin')
      .use(webpack.ContextReplacementPlugin, [/moment[/\\]locale$/, /zh-cn/])

    // 配置别名
    config.resolve.alias
      .set('@$', resolve('src'))
      .set('@api', resolve('src/api'))
      .set('@assets', resolve('src/assets'))
      .set('@comp', resolve('src/components'))
      .set('@views', resolve('src/views'))

    if (process.env.NODE_ENV === 'production') {
      // 添加 CDN 配置到 HTML
      config.plugin('html').tap(args => {
        args[0].cdn = assetsCDN
        return args
      })

       // 开启 Gzip 和 Brotli 压缩
      config.plugin('compressionPlugin').use(new CompressionPlugin({
        test: /\.(js|css|less|scss|html|svg)$/,
        threshold: 10240,
        minRatio: 0.8,
        deleteOriginalAssets: false
      }))
      config.plugin('brotliPlugin').use(new BrotliPlugin({
        test: /\.(js|css|less|scss|html|svg)$/,
        threshold: 10240,
        minRatio: 0.8,
        deleteOriginalAssets: false
      }))
      
      // 图片压缩
	  config.plugin('image-optimizer')
        .use(ImageMinimizerPlugin, [{
          minimizer: {
            implementation: ImageMinimizerPlugin.squooshMinify,
            options: {
              encodeOptions: {
                mozjpeg: { quality: 80 },
                webp: { lossless: true },
              },
            },
          },
        }]);

      // 分包策略
      config.optimization.splitChunks({
        chunks: 'all',
        minSize: 20000,
        maxSize: 244000,
        maxAsyncRequests: 30,   // 按需加载时的最大并行请求数
        maxInitialRequests: 20, // 入口点上的最大并行请求数
        cacheGroups: {
          vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10
          },
          common: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true
          }
        }
      })
    }

    // 配置 webpack 识别 markdown 为普通的文件
    config.module
      .rule('markdown')
      .test(/\.md$/)
      .use()
      .loader('file-loader')
      .end()

    // ESBuild 转译 JS(替代 Babel)
    config.module
      .rule('js')
      .test(/\.js$/)
      .exclude.add(/node_modules/)
      .end()
      .use('esbuild-loader')
      .loader('esbuild-loader')
      .options({ loader: 'jsx', target: 'es2015' });
  },

  css: {
    loaderOptions: {
      less: {
        modifyVars: {
          /* less 变量覆盖,用于自定义 ant design 主题 */
          'primary-color': '#1890FF',
          'link-color': '#1890FF',
          'border-radius-base': '4px'
        },
        javascriptEnabled: true
      }
    }
  },

  devServer: {
    port: 3000,
    client: {
      overlay: {
        runtimeErrors: false
      }
    },
    proxy: {
        target: 'http://localhost:8080',
        ws: false,
        changeOrigin: true
      }
    }
  }
}

网站公告

今日签到

点亮在社区的每一天
去签到