Webpack 使用esbuild-loader提升构建速度

1,327 阅读2分钟

背景

公司有个老项目,打包以及热更新都需要很长的时间,于是调研了下esbuild,决定使用esbuild-loader来提升构建速度

通过修改vue-cli源码输出打包花费的时间

为了方量化统计,我们需要改下Vue-cli的代码,显示打包或者server构建所需要的时间

build : {项目地址}\node_modules@vue\cli-service\lib\commands\build\index.js

serve: {项目地址}\node_modules@vue\cli-service\lib\commands\serve.js

build代码修改:

 let startTime = ''
async function build (args, api, options) {
  // ... other code
  startTime = Date.parse(new Date())
 	// ... other code
  return new Promise((resolve, reject) => {
    webpack(webpackConfig, (err, stats) => {
      stopSpinner(false)
      
      if (!args.silent) {
        const targetDirShort = path.relative(
          api.service.context,
          targetDir
        )
        log(formatStats(stats, targetDirShort, api))
        if (args.target === 'app' && !isLegacyBuild) {
          if (!args.watch) {
            // 输出这次打包所花费的时间
            console.log('打包花费时间:'+( Date.parse(new Date()) - startTime)/1000 + '秒')
            done(`Build complete. The ${chalk.cyan(targetDirShort)} directory is ready to be deployed.`)
            info(`Check out deployment instructions at ${chalk.cyan(`https://cli.vuejs.org/guide/deployment.html`)}\n`)
          } else {
            done(`Build complete. Watching for changes...`)
          }
        }
      }

      // test-only signal
      if (process.env.VUE_CLI_TEST) {
        console.log('Build complete.')
      }

      resolve()
    })
  })
}

build源代码位置:

serve代码修改:

let startTime = ''
//...other code
 return new Promise((resolve, reject) => {
  	compiler.hooks.done.tap('vue-cli-service serve', stats => {
      //...other code
       const networkUrl = publicUrl
          ? publicUrl.replace(/([^/])$/, '$1/')
          : urls.lanUrlForTerminal
        // 输出花费的时间
        console.log('打包花费时间:'+( Date.parse(new Date()) - startTime)/1000 + '秒')
        console.log()
        console.log(`  App running at:`)
        console.log(`  - Local:   ${chalk.cyan(urls.localUrlForTerminal)} ${copied}`)
      //...other code
    })
   
 })

serve源代码位置

\

使用esBuild 替换 babel 对js进行处理

安装esbuild-loader

yarn add esbuild-loader -D

修改 vue.config.js文件,这里使用了 ESBuildMinifyPlugin 这个插件,来进行代码压缩,所以项目中其他同功能的plugin就可以移除了,比如uglifyjs-webpack-pluginterser等,ESBuildMinifyPlugin的具体的配置可以看esbuild的官网 esbuild.github.io/,这里只添加了代码压缩以及删除生产环境的console debugger

const { ESBuildMinifyPlugin } = require('esbuild-loader')

module.exports = {
  chainWebpack: (config) => {
  	const rule = config.module.rule('js');
    // 清理自带的babel-loader
    rule.uses.clear();
    // 添加esbuild-loader
    rule.use('esbuild-loader').loader('esbuild-loader');
  	config.optimization.minimizers.delete('terser');
    // 生产模式下对代码进行压缩
    if (isProduction) {
      config.optimization
        .minimizer('esbuild')
        .use(ESBuildMinifyPlugin, [{ minify: true, css: true, drop:['console', 'debugger']}]);
    }
  }
}

针对jsx提供支持

如果项目中使用了jsx语法,则需要安装@lancercomet/vue2-jsx-runtime这个包来处理添加配置如下

yarn add @lancercomet/vue2-jsx-runtime -D

const jsRule = config.module.rule('js').test(/.jsx?$/)
    jsRule // 使用sbuild-loader
      .use(['thread-loader', 'esbuild-loader'])
      .loader('esbuild-loader')
      .options({
        target: 'es2015',
        loader: 'jsx',
        jsx: 'automatic',
        jsxImportSource: '@lancercomet/vue2-jsx-runtime'
      })
      .end()

做完这一步,可能还会出现以下情况,webpack无法识别 mjs文件的导出

Can't import the named export 'xxxx' from non EcmaScript module (only default export is available)

还需要添加以下配置才能解决这个问题

config.module 
      .rule('mjs$')
      .test(/.mjs$/)
      .include
      .add(/node_modules/)
      .end()
      .type('javascript/auto');

效果

可以看出来,对于开发模式的构建,提升其实不是特别大,但是对于生产环境构建来说,提升还是很明显的,达到了43%

命令babel-loader花费时间esbuild-loader花费时间提升
npm run serve64s42s18%
npm run build78s44s43%

下面是更加具体的显示

优化前:

npm run serve

  • with cache

image.png

  • without cache

image.png

npm run build

  • with cache

  • without cache

image.png

优化后:

npm run serve

  • without cache

image.png

  • with cache

image.png

npm run build

  • without cache

  • with cache