vue项目打包优化之happypack

3,888 阅读3分钟
HappyPack通过并行转换文件使得初始webpack构建更快。这句话可不是我说的,是npm官网中happypack的readme的开场白。

我在前几天的《vue项目打包优化之webpack-parallel-uglify-plugin》一文中提到过happypack,我说它有些鸡肋,为什么这么说呢,如果项目较小,可能打包时间优化的并不是太好,甚至可能还会延长打包时间。不过在公司项目中我再使用webpack-parallel-uglify-plugin之后项目打包时间大概在3分钟左右,在此基础上使用happypack优化之后,时间缩短了将近一分钟。

现在我将happypack配置做个记录和说明,以自己的demo为例。项目是用vuecli脚手架搭建。

首先我们先了解一下happypack的一些参数

  1. id: String 用唯一的标识符 id。加载程序使用它来知道它应该与哪个插件进行通.
  2. loaders: Array 用法和 webpack Loader 配置中一样.
  3. threads: Number 代表开启几个子进程去处理这一类型的文件。默认为:3
  4. verbose: Boolean 启用此选项可将状态消息从HappyPack记录到STDOUT,默认是 true。
  5. threadPool: HappyThreadPool 代表共享进程池,即多个 HappyPack 实例都使用同一个共享进程池中的子进程去处理任务,以防止资源占用过多。默认为:null
  6. verboseWhenProfiling: Boolean 如果您希望HappyPack即使在webpack --profile运行时仍能生成其输出,请启用此选项。默认为:false
  7. debug: Boolean 启用此选项可将诊断消息从HappyPack记录到STDOUT。用于故障排除。默认 false

接下来是demo中的配置

第一步,安装happypack

npm i happypack -D

//"happypack": "^5.0.1",demo中happypack版本
//"webpack": "^3.6.0",demo中webpack版本

第二步,配置happypack

1、修改webpack.base.conf.js文件

const HappyPack = require('happypack');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });

提示:由于HappyPack 对file-loader、url-loader 支持的不友好,所以没修改相应loader使用。
module.exports = {
  context: path.resolve(__dirname, '../'),
  entry: {
    app: './src/main.js'
  },
  output: {
    path: config.build.assetsRoot,
    filename: '[name].js',
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
    }
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'happypack/loader?id=vueLoader',
      },
      {
        test: /\.js$/,
        loader: 'happypack/loader?id=babelLoader',
        include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('media/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      }
    ]
  },
  node: {
    // prevent webpack from injecting useless setImmediate polyfill because Vue
    // source contains it (although only uses it if it's native).
    setImmediate: false,
    // prevent webpack from injecting mocks to Node native modules
    // that does not make sense for the client
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty'
  },
  plugins:[
    new HappyPack({
      //用id来标识 happypack处理那里类文件
      id: 'vueLoader',
      //如何处理  用法和loader 的配置一样
      loaders: [
        {
          loader:'vue-loader',
          options: vueLoaderConfig
        }
      ],
      //共享进程池
      threadPool: happyThreadPool,
      //允许 HappyPack 输出日志
      verbose: true,
    }),
    new HappyPack({
      //用id来标识 happypack处理那里类文件
      id: 'babelLoader',
      //如何处理  用法和loader 的配置一样
      loaders: [
        {
          loader:'babel-loader',
        }
      ],
      //共享进程池
      threadPool: happyThreadPool, 
     //允许 HappyPack 输出日志
      verbose: true,
    })
  ]}

2、修改utils.js文件

const HappyPack = require('happypack');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });

提示:由于HappyPack 对less-loader、sass-loader、stylus-loader支持的不友好,所以没修改相应loader使用。

exports.styleLoaders = function (options) {
  const output = []
  const plugins = []
  const loaders = exports.cssLoaders(options)
  for (const extension in loaders) {
    const loader = loaders[extension]
    if(extension!='less'&&extension!='sass'&&extension!='scss'&&extension!='stylus'&&extension!='styl'){
      output.push({
        test: new RegExp('\\.' + extension + '$'),
        use: [`happypack/loader?id=${extension}Loader`]
      })
      plugins.push(
        new HappyPack({
          //用id来标识 happypack处理那里类文件
          id: `${extension}Loader`,
          //如何处理  用法和loader 的配置一样
          loaders: loader,
          //共享进程池
          threadPool: happyThreadPool,
          //允许 HappyPack 输出日志
          verbose: true,
        })
      )
    }else{
      output.push({
        test: new RegExp('\\.' + extension + '$'),
        use: loader
      })
    }
   }
  return {output,plugins}
}

由于更改了css的规则,所以需要修改相关引用处的配置

3、修改webpack.dev.conf.js文件

const styleLoaders= utils.styleLoaders({ sourceMap: config.dev.cssSourceMap,
 usePostCSS: true
})

const devWebpackConfig = merge(baseWebpackConfig, {plugins:styleLoaders.plugins},{
  module: {
    rules: styleLoaders.output
  },
  // cheap-module-eval-source-map is faster for development
  devtool: config.dev.devtool,
  ...
})

4、修改webpack.prod.conf.js文件

const styleLoaders= utils.styleLoaders({
  sourceMap: config.build.productionSourceMap,
  extract: true,
  usePostCSS: true
})

const webpackConfig = merge(baseWebpackConfig, {plugins:styleLoaders.plugins},{
  module: {
    rules: styleLoaders.output
  },
  devtool: config.build.productionSourceMap ? config.build.devtool : false,
 ...
})

TIME!