浅谈esbuild-loader的原理

6,239 阅读1分钟

esbuild-loader是什么

esbuild-loader是EGOIST出的基于esbuild 的webpack loader和plugin, 不得不说速度真快,具体用法用法也很简单,下面是我直接从readme拷过来的

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

module.exports = {
  module: {
    rules: [
      {
        test: /\.[jt]sx?$/,
        loader: 'esbuild-loader',
        options: {
          // All options are optional
          target: 'es2015', // default, or 'es20XX', 'esnext'
          jsxFactory: 'React.createElement',
          jsxFragment: 'React.Fragment',
          sourceMap: false // Enable sourcemap
        },
      },
    ],
  },
  plugins: [new ESBuildPlugin()],
}

OK,下面看看怎么实现的

原理

其实loader和plugin主要是让esbuild能在webpack跑起来,首先只支持const exts = ['.js', '.jsx', '.ts', '.tsx']这四个后缀,先看看loader里面的实现,service就是esbuild起的服务,这个service是在plugin实现起的,服务没有起来,就没办法在loader编译,直接抛出错误,然后在判断资源的后缀是不是我们支持的后缀,不是也抛出错误,最后直接 service.transform 转化代码,传入done

module.exports = async function (source) {
  const done = this.async()
  const options = getOptions(this)
  if (!service) {
    return done(
      new Error(
        `[esbuild-loader] You need to add ESBuildPlugin to your webpack config first`
      )
    )
  }

  try {
    const ext = path.extname(this.resourcePath)
    if (!exts.includes(ext)) {
      return done(
        new Error(`[esbuild-loader] Unsupported file extension: ${ext}`)
      )
    }

    const result = await service.transform(source, {
      target: options.target || 'es2015',
      loader: ext.slice(1),
      jsxFactory: options.jsxFactory,
      jsxFragment: options.jsxFragment,
      sourcemap: options.sourceMap
    })
    done(null, result.js, result.jsSourceMap)
  } catch (err) {
    done(err)
  }
}

loader主要就是让esbuild转化代码,plugin就是为了起服务,下面看看plugin,代码如下,如果是正常的build,在run hooks起esbuild的服务,如果是watch模式,在watchrun起服务,最后编译结束的时候,如果我们起了服务,而且不是watch下,那么久关闭service,清空service变量,打完收工,就这两个行数,换上了目前暂时最快的js编译器。

module.exports.ESBuildPlugin = class ESBuildPlugin {
  /**
   * @param {import('webpack').Compiler} compiler
   */
  apply(compiler) {
    let watching = false

    const startService = async () => {
      if (!service) {
        service = await esbuild.startService()
      }
    }

    compiler.hooks.run.tapPromise('esbuild', async () => {
      await startService()
    })
    compiler.hooks.watchRun.tapPromise('esbuild', async () => {
      watching = true
      await startService()
    })

    compiler.hooks.done.tap('esbuild', () => {
      if (!watching && service) {
        service.stop()
        service = undefined
      }
    })
  }
}