前端工程webpack打包优化方法

为提高前端工程webpack打包速度,对工程进行改造

第一部分:vue-cli2工程

可以采用js分包+压缩混淆升级+CDN加速+gzip压缩等手段进行加速

(1)分包,在webpack的配置中添加

module.exports = {
  externals: {
    vue: \'Vue\',
    moment: \'moment\',
    rxjs: \'Rx\',
    ramda: \'R\',
    \'vue-router\': \'VueRouter\',
    jquery: \'jQuery\',
    \'element-ui\': \'ELEMENT\',
    axios: \'axios\',
    qs: \'Qs\',
    vuex: \'Vuex\',
    \'echarts\': \'echarts\',
    lodash: {
      //如果我们的库运行在Node.js环境中,import _ from \'lodash\'等价于const _ = require(\'lodash\')
      commonjs: "lodash", 
      commonjs2: "lodash", //同上
      amd: "lodash", //如果我们的库使用require.js等加载,等价于 define(["lodash"], factory);
      root: "_", //如果我们的库在浏览器中使用,需要提供一个全局的变量‘_’,等价于 var _ = (window._) or (_);
    }
  },


}

(2)分包后,这些被排除的包将不会被打包进入vendor中去,那么我们就必须使用CDN来提供这些包的功能

在入口文件index.html中添加对应的js文件

使用CDN的优点:

1.可以充分利用客户端缓存,大大减少每次webpack打包的vendor的体积,提高打包速度

2.每次工程迭代时,这些包将不需要更新,也不需要客户端重新加载3

3.用户在第一次加载之后,后面的版本迭代大部分文件将直接读缓存,不需要再次加载外部依赖文件

缺点:工程部署必须能够连接外网,内网部署的工程将不能够使用CDN,可以通过在内网维护一个自己的简单的CDN库来解决

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <link crossorigin="anonymous"
        href="https://lib.baomitu.com/element-ui/2.3.9/theme-chalk/index.css" rel="stylesheet">
</head>
<body>
<div ></div>
<!-- built files will be auto injected -->
<script src="https://lib.baomitu.com/jquery/3.2.1/jquery.min.js"></script>
<script src="https://lib.baomitu.com/vue/2.5.2/vue.js"></script>
<script src="https://lib.baomitu.com/vue-router/2.7.0/vue-router.min.js"></script>
<script src="https://lib.baomitu.com/moment.js/2.18.1/moment.min.js"></script>
<script src="https://lib.baomitu.com/moment.js/2.18.1/locale/zh-cn.js"></script>
<script src="https://lib.baomitu.com/rxjs/5.4.3/Rx.min.js"></script>
<script src="https://lib.baomitu.com/ramda/0.24.1/ramda.min.js"></script>
<script crossorigin="anonymous"
        src="https://lib.baomitu.com/lodash.js/4.17.0/lodash.min.js"></script>
<script crossorigin="anonymous"
        src="https://lib.baomitu.com/element-ui/2.3.9/index.js"></script>
<script crossorigin="anonymous"
        src="https://lib.baomitu.com/axios/0.16.2/axios.min.js"></script>
<script crossorigin="anonymous"
        src="https://lib.baomitu.com/vuex/3.0.1/vuex.min.js"></script>
<script crossorigin="anonymous"
        src="https://lib.baomitu.com/qs/6.5.1/qs.min.js"></script>
<script crossorigin="anonymous"
        src="https://lib.baomitu.com/xlsx/0.14.2/xlsx.min.js"></script>
</body>
</html>

(3)使用uglifyjs-webpack-plugin,新版的uglifyjs-webpack-plugin能够支持es6语法

在webpack.prod.config.js中添加

const UglifyJsPlugin = require(\'uglifyjs-webpack-plugin\')

module.exports= {
  // 其他配置...,
  plugins:[
    new UglifyJsPlugin({
      cache: true,
      uglifyOptions: {
        compress: {
          drop_debugger: true,
          drop_console: true,
        },
        ecma: 6,
        output: {
          comments: false,
          beautify: false,
        },
        warnings: false,
      },
      sourceMap: false,
      parallel: true,
    }),
  ]
}

(4)使用开发环境资源缓存,提高第二次及以后的启动速度

在webpack.dev.conf.js中,添加配置

const HardSourceWebpackPlugin = require(\'hard-source-webpack-plugin\')

module.exports = {
  // ...其他配置
  plugins: [
     // 缓存加速二次构建速度
    new HardSourceWebpackPlugin({
      // Either an absolute path or relative to webpack\'s options.context.
      // 设置缓存在磁盘中存放的路径
      cacheDirectory: \'./../disk/.cache/hard-source/[confighash]\',
      // Either a string of object hash function given a webpack config.
      recordsPath: \'./../disk/.cache/hard-source/[confighash]/records.json\',
      configHash: function (webpackConfig) {
        // node-object-hash on npm can be used to build this.
        return require(\'node-object-hash\')({ sort: false }).hash(webpackConfig);
      },
      // An object.
      info: {
        // \'none\' or \'test\'.
        mode: \'none\',
        // \'debug\', \'log\', \'info\', \'warn\', or \'error\'.
        level: \'debug\',
      },
      // Clean up large, old caches automatically.
      cachePrune: {
        // Caches younger than `maxAge` are not considered for deletion. They must
        // be at least this (default: 2 days) old in milliseconds.
        maxAge: 2 * 24 * 60 * 60 * 1000,
        // All caches together must be larger than `sizeThreshold` before any
        // caches will be deleted. Together they must be at least this
        // (default: 50 MB) big in bytes.
        sizeThreshold: 100 * 1024 * 1024
      },
    }),


  ]
}

第二部分:vue/cli3+版本

vue/cli3+版本里面的webpack配置作者进行了内置

我们需要在vue.config.js中进行配置

(1)

const path = require(\'path\')
const webpack = require(\'webpack\')
const AddAssetHtmlPlugin = require(\'add-asset-html-webpack-plugin\')
const HardSourceWebpackPlugin = require(\'hard-source-webpack-plugin\')

// 拼接路径
function resolve (dir) {
  return path.join(__dirname, dir)
}

const baseUrlObject = {
  development: \'\',
  beta: \'//fezz.wormpex.com/ripei-fe-web\',
  prod: \'//fezz.blibee.com/ripei-fe-web\',
  production: \'//fezz.blibee.com/ripei-fe-web\'
}

const env = process.env.NODE_ENV || \'production\'

// 基础路径 注意发布到生产环境之前要先修改这里
const baseUrl = baseUrlObject[env] || process.env.VUE_APP_BASE_URL
const IS_DEV = process.env.VUE_APP_NODE_ENV === \'development\'

console.log(process.env.NODE_ENV)
console.log(process.env.VUE_APP_BASE_URL)
console.log(baseUrl)

// 用于开发环境下,缓存第一次编译的文件,提高二次构建速度
const devPlugins = [
  // 缓存加速二次构建速度
  new HardSourceWebpackPlugin({
    // Either an absolute path or relative to webpack\'s options.context.
    // 设置缓存在磁盘中存放的路径
    cacheDirectory: \'./../disk/.cache/hard-source/[confighash]\',
    // Either a string of object hash function given a webpack config.
    recordsPath: \'./../disk/.cache/hard-source/[confighash]/records.json\',
    configHash: function (webpackConfig) {
      // node-object-hash on npm can be used to build this.
      return require(\'node-object-hash\')({ sort: false }).hash(webpackConfig);
    },
    // An object.
    info: {
      // \'none\' or \'test\'.
      mode: \'none\',
      // \'debug\', \'log\', \'info\', \'warn\', or \'error\'.
      level: \'debug\',
    },
    // Clean up large, old caches automatically.
    cachePrune: {
      // Caches younger than `maxAge` are not considered for deletion. They must
      // be at least this (default: 2 days) old in milliseconds.
      maxAge: 4 * 24 * 60 * 60 * 1000,
      // All caches together must be larger than `sizeThreshold` before any
      // caches will be deleted. Together they must be at least this
      // (default: 50 MB) big in bytes.
      sizeThreshold: 100 * 1024 * 1024
    },
  }),
  new HardSourceWebpackPlugin.ExcludeModulePlugin([
    {
      test: /mini-css-extract-plugin[\\/]dist[\\/]loader/
    }
  ])
]

const configPlugins = [
  new webpack.ProvidePlugin({
    $: \'jquery\',
    jQuery: \'jquery\',
    \'windows.jQuery\': \'jquery\'
  }),
  new webpack.DllReferencePlugin({
    context: process.cwd(),
    manifest: require(\'./public/vendor/vendor-manifest.json\')
  }),
  // 将 dll 注入到 生成的 html 模板中
  new AddAssetHtmlPlugin({
    // dll文件位置
    filepath: path.resolve(__dirname, \'./public/vendor/*.js\'),
    // dll 引用路径
    publicPath: `${baseUrl}/vendor`,
    // dll最终输出的目录
    outputPath: \'./vendor\'
  })
]

module.exports = {
  publicPath: baseUrl, // 根据你的实际情况更改这里
  lintOnSave: true,
  configureWebpack: { // 引入jquery
    // externals: { // 引入百度地圖
    //   \'BMap\': \'BMap\'
    // },
    plugins: IS_DEV ? configPlugins.concat(devPlugins) : configPlugins
  },
  devServer: {
    https: false,
    publicPath: baseUrl, // 和 baseUrl 保持一致
    // 代理设置
    proxy: {
     
    }
  },
  // webpack 设置
  chainWebpack: config => {
    // 修复HMR
    config.resolve.symlinks(true)
    // svg
    const svgRule = config.module.rule(\'svg\')
    svgRule.uses.clear()
    svgRule
      .include
      .add(resolve(\'src/assets/svg-icons/icons\'))
      .end()
      .use(\'svg-sprite-loader\')
      .loader(\'svg-sprite-loader\')
      .options({
        symbolId: \'d2-[name]\'
      })
      .end()
    // image exclude
    const imagesRule = config.module.rule(\'images\')
    imagesRule
      .test(/\.(png|jpe?g|gif|webp|svg)(\?.*)?$/)
      .exclude
      .add(resolve(\'src/assets/svg-icons/icons\'))
      .end()
    // 重新设置 alias
    config.resolve.alias
      .set(\'@\', resolve(\'src\'))
    // babel-polyfill 加入 entry
    const entry = config.entry(\'app\')
    entry
      .add(\'babel-polyfill\')
      .end()
  }
}

(2)使用DllPlugin进行包的拆分

在根目录下创建webpack.dll.conf.js

/**
 * 配置vue-cli4工程的DllPlugins,用以减少热更新和打包时的文件数量,提高热更新和打包的速度
 * 参考文献:https://blog.csdn.net/DongFuPanda/article/details/104866788
 * 步骤
 */

const path = require(\'path\')
const webpack = require(\'webpack\')
const { CleanWebpackPlugin } = require(\'clean-webpack-plugin\')

// dll文件存放的目录
const dllPath = \'public/vendor\'

module.exports = {
  entry: {
    // 需要提取的库文件
    vendor: [\'vue\', \'vue-router\', \'vuex\', \'axios\', \'jquery\', \'moment\', \'ramda\', \'element-ui\', \'core-js\', \'lodash\']
  },
  output: {
    path: path.join(__dirname, dllPath),
    filename: \'[name].[hash].dll.js\',
    // vendor.dll.js中暴露出的全局变量名
    // 保持与 webpack.DllPlugin 中名称一致
    library: \'[name]_[hash]\'
  },
  plugins: [
    // 清除之前的dll文件
    new CleanWebpackPlugin(),
    // 设置环境变量
    new webpack.DefinePlugin({
      \'process.env\': {
        NODE_ENV: JSON.stringify(\'production\')
      }
    }),
    // manifest.json 描述动态链接库包含了哪些内容
    new webpack.DllPlugin({
      path: path.join(__dirname, dllPath, \'[name]-manifest.json\'),
      // 保持与 output.library 中名称一致
      name: \'[name]_[hash]\',
      context: process.cwd()
    })
  ]
}

在package.json中添加

 "scripts": {
    "start": "vue-cli-service serve --open --mode development",
    "serve": "vue-cli-service serve --open --mode development",
    "build": "vue-cli-service build",
    "beta": "vue-cli-service build --mode beta",
    "lint": "vue-cli-service lint",
    "dll": "webpack -p --progress --config ./webpack.dll.conf.js"
  },

使用webpack-bundle-analyzer 进行代码打包体积分析

const BundleAnalyzerPlugin = require(\'webpack-bundle-analyzer\').BundleAnalyzerPlugin


module.exports = {
  // ...其他配置
  plugins:[
       // new BundleAnalyzerPlugin()
  ]
}