webpack或esbuild,为什么不能同时进行呢?

7,851 阅读6分钟

使用esbuild等工具可以使构建速度更快。然而,如果你已经投资了webpack,但仍然想利用更快的构建,有一个方法。

在本教程中,我们将向你展示如何使用esbuild和webpack以及esbuild-loader。

webpack or esbuild: Why not both?

网络开发的世界在不断发展

对那些患有JavaScript疲劳症的人表示歉意,Web开发的世界又在演变了。长期以来,通过某种基于Node.js的构建工具(如webpack或rollup.js)来运行你的JavaScript和TypeScript是一种普遍做法。这些工具都是用相同的语言编写的,即JavaScript或TypeScript。

博客上的新孩子是esbuildViteswc等工具。这些工具与它们的前辈之间的显著区别是,新派工具是用Go和Rust等语言编写的。Go和Rust的性能远比JavaScript好。这就意味着构建速度大大加快

这些新工具是变革性的,可能代表了网络构建工具的未来。从长远来看,像esbuild、Vite和朋友们很可能会取代目前的标准构建工具--webpacks、rollups等等。

然而,这只是长期的。有很多项目已经在他们目前的构建工具上投入了大量的资金--主要是webpack。迁移到一个新的构建工具不是一个小任务。新项目可能会从Vite开始,但现有的项目不太可能被移植。webpack如此受欢迎是有原因的;它确实把很多事情做得非常好。它在大型项目中经受住了考验,它很成熟,而且它能处理广泛的使用情况。

那么,如果你的团队想拥有更快的构建,但又没有时间进行大规模的迁移,你能做什么吗?是的,有一个可以探索的中间地带。

有一个相对较新的项目叫esbuild-loader。由hiroki osame开发,esbuild-loader是一个建立在esbuild之上的webpack加载器。它允许用户用自己来替换ts-loaderbabel-loader ,这极大地提高了构建速度。

为了充分披露,我是ts-loader的主要维护者,这是一个流行的TypeScript加载器,通常与webpack一起使用。然而,我强烈地感觉到,这里最重要的是开发人员的生产力。作为基于Node.js的项目,ts-loaderbabel-loader 将永远无法与esbuild-loader 进行同样的竞争。作为一门语言,Go真的,呃,很好!

虽然esbuild不一定适用于所有的用例,但它可以适用于大多数的任务。因此,esbuild-loader 代表了一个中间地带--也是在不告别webpack的情况下获得esbuild所提供的更高构建速度的早期方法。本攻略将探讨在你的webpack设置中使用esbuild-loader

将一个现有的项目迁移到esbuild

将一个使用babel-loaderts-loader 的项目迁移到esbuild-loader 是非常简单的。首先,安装该依赖项。

npm i -D esbuild-loader

如果你目前使用的是babel-loader ,请对你的webpack.config.js 做如下修改。

  module.exports = {
    module: {
      rules: [
-       {
-         test: /\.js$/,
-         use: 'babel-loader',
-       },
+       {
+         test: /\.js$/,
+         loader: 'esbuild-loader',
+         options: {
+           loader: 'jsx',  // Remove this if you're not using JSX
+           target: 'es2015'  // Syntax to compile to (see options below for possible values)
+         }
+       },

        ...
      ],
    },
  }

或者,如果你正在使用ts-loader ,请对你的webpack.config.js 做如下修改。

  module.exports = {
    module: {
      rules: [
-       {
-         test: /\.tsx?$/,
-         use: 'ts-loader'
-       },
+       {
+         test: /\.tsx?$/,
+         loader: 'esbuild-loader',
+         options: {
+           loader: 'tsx',  // Or 'ts' if you don't need tsx
+           target: 'es2015'
+         }
+       },

        ...
      ]
    },
  }

创建一个基线应用程序

让我们看看esbuild-loader 在实践中是如何工作的。我们将使用Create React App来创建一个新的React应用程序

npx create-react-app my-app --template typescript

这将在my-app 目录中使用TypeScript搭建出一个新的React应用程序。值得一提的是,Create React App在幕后使用了babel-loader

CRA还使用了Fork TS Checker Webpack Plugin来提供TypeScript类型检查。这非常有用,因为esbuild只是进行转译,并不是为了提供类型检查支持而设计的。所以很幸运的是,我们仍然有这个插件在那里。否则,我们将失去类型检查。

现在你明白了转移到esbuild的好处,我们首先需要一个基线来了解使用babel-loader 的性能是怎样的。我们将运行time npm run build 来执行我们简单应用的构建。

Completed Build for Create React App

我们的完整构建,TypeScript类型检查,转译,最小化等等,都需要22.08秒。现在的问题是,如果我们将esbuild放入其中,会发生什么?

介绍esbuild-loader

定制Create React App构建的一种方法是运行npm run eject ,然后定制CRA所输出的代码。这样做是可以的,但这意味着你无法跟踪CRA的发展。另一种方法是使用Create React App Configuration Override(CRACO)这样的工具,它允许你在当地调整配置。CRACO将自己描述为 "一个简单易懂的配置层,用于create-react-app"。

让我们把esbuild-loader 和CRACO作为依赖项加入。

npm install @craco/craco esbuild-loader --save-dev

然后,我们将把我们的package.json 中的各种scripts 换成使用CRACO

"start": "craco start",
"build": "craco build",
"test": "craco test",

我们的应用程序现在使用CRACO,但我们还没有配置它。因此,我们将在我们项目的根部添加一个craco.config.js 文件。这就是我们把babel-loader 换成esbuild-loader 的地方。

const { addAfterLoader, removeLoaders, loaderByName, getLoaders, throwUnexpectedConfigError } = require('@craco/craco');
const { ESBuildMinifyPlugin } = require('esbuild-loader');

const throwError = (message) =>
    throwUnexpectedConfigError({
        packageName: 'craco',
        githubRepo: 'gsoft-inc/craco',
        message,
        githubIssueQuery: 'webpack',
    });

module.exports = {
    webpack: {
        configure: (webpackConfig, { paths }) => {
            const { hasFoundAny, matches } = getLoaders(webpackConfig, loaderByName('babel-loader'));
            if (!hasFoundAny) throwError('failed to find babel-loader');

            console.log('removing babel-loader');
            const { hasRemovedAny, removedCount } = removeLoaders(webpackConfig, loaderByName('babel-loader'));
            if (!hasRemovedAny) throwError('no babel-loader to remove');
            if (removedCount !== 2) throwError('had expected to remove 2 babel loader instances');

            console.log('adding esbuild-loader');

            const tsLoader = {
                test: /\.(js|mjs|jsx|ts|tsx)$/,
                include: paths.appSrc,
                loader: require.resolve('esbuild-loader'),
                options: { 
                  loader: 'tsx',
                  target: 'es2015'
                },
            };

            const { isAdded: tsLoaderIsAdded } = addAfterLoader(webpackConfig, loaderByName('url-loader'), tsLoader);
            if (!tsLoaderIsAdded) throwError('failed to add esbuild-loader');
            console.log('added esbuild-loader');

            console.log('adding non-application JS babel-loader back');
            const { isAdded: babelLoaderIsAdded } = addAfterLoader(
                webpackConfig,
                loaderByName('esbuild-loader'),
                matches[1].loader // babel-loader
            );
            if (!babelLoaderIsAdded) throwError('failed to add back babel-loader for non-application JS');
            console.log('added non-application JS babel-loader back');

            console.log('replacing TerserPlugin with ESBuildMinifyPlugin');
            webpackConfig.optimization.minimizer = [
                new ESBuildMinifyPlugin({
                    target: 'es2015' 
                })
            ];

            return webpackConfig;
        },
    },
};

那么,这里发生了什么?脚本会在默认的Create React App配置中寻找babel-loader 的使用。会有两个:一个是TypeScript/JavaScript应用代码(我们要替换这个),一个是非应用的JavaScript代码。现在还不太清楚有哪些非应用的JavaScript代码,所以我们会把它留在原地;它可能很重要。我们真正关心的代码是应用程序的代码。

你不能使用CRACO 删除单一的加载器,因此,我们将删除这两个加载器,并重新添加非应用的JavaScriptbabel-loader 。我们还将添加esbuild-loader ,并设置{ loader: 'tsx', target: 'es2015' } 选项,以确保我们能够处理JJSX/TSX。

最后,我们也将换成使用Terseresbuild的JavaScript进行粉碎处理。

巨大的性能改进

我们的迁移已经完成。下次构建时,我们将使用esbuild-loader ,在没有弹出的情况下运行Create React App。再一次,我们将运行time npm run build ,执行我们的简单应用的构建,并确定它需要多长时间。

Completed Build for Create React App With esbuild

我们的完整构建,TypeScript类型检查,转译,最小化等等,都需要13.85秒。通过迁移到esbuild-loader ,我们的整体编译时间减少了大约三分之一。这是一个巨大的进步!

随着你的代码库的扩展和你的应用程序的增长,编译时间会急剧上升。有了esbuild-loader ,你应该在构建时间上获得持续的好处。

The postwebpack or esbuild:首先出现在LogRocket博客