webpack 或 esbuild:为什么不是两者兼而有之?
【翻译于LogRocket网站上John Reilly所写的webpack or esbuild: Why not both?】 使用像esbuild的工具可以更快地进行构建。但是,如果您正投资于webpack,但是仍想利用更快的构建,那么有一种方法。
在本教程中,我们将向您展示如何将 esbuild 与 webpack 与esbuild-loader一起使用。
Web 开发的世界正在发展
向那些遭受 JavaScript 疲劳的人道歉,Web 开发的世界再次发展。长期以来,通过某种基于 Node.js 的构建工具(如 webpack 或 rollup.js)运行 JavaScript 和 TypeScript 一直是常见做法。这些工具是用它们编译成的相同语言编写的——即 JavaScript 或 TypeScript。
博客上的新成员是esbuild、Vite和swc等工具。它们与它们的前辈之间的显着区别在于,新式工具是用 Go 和 Rust 等语言编写的。Go 和 Rust 的性能比 JavaScript 好得多。这转化为显着更快的构建。
这些新工具具有变革性,可能代表了 Web 构建工具的未来。从长远来看,esbuild、Vite和朋友之类的工具很可能会取代当前的标准构建工具——webpacks、rollups 等。
然而,这是长期的。有很多项目已经在他们当前的构建工具上投入了大量资金——主要是 webpack。迁移到新的构建工具并非易事。新项目可能会从 Vite 开始,但现有项目不太可能被移植。webpack 如此受欢迎是有原因的;它确实可以很好地完成很多事情。它经过大型项目的实战测试,非常成熟,并且可以处理广泛的用例。
因此,如果您的团队想要更快的构建但没有时间进行大规模迁移,您有什么可以做的吗?是的,有一个中间地带需要探索。
有一个相对较新的项目,名为esbuild-loader。esbuild-loader由hiroki osame开发,是一个建立在 esbuild 之上的 webpack 加载器。它允许用户通过它交换ts-loader
或 babel-loader
,这大大提高了构建速度。
在这里声明一个兴趣以进行全面披露,我是ts-loader的主要维护者,这是一个流行的 TypeScript 加载器,通常与 webpack 一起使用。但是,我强烈认为这里重要的是开发人员的生产力。作为Node.js为基础的项目,ts-loader
和babel-loader
将永远无法与esbuild-loader
在相同方式上竞争。作为一种语言,Go 真的,呃,可行!
虽然 esbuild 可能不适用于所有用例,但它适用于大多数任务。因此,esbuild-loader
代表了一个中间立场——也是一种在不告别 webpack 的情况下获得 esbuild 提供的更高构建速度的早期方法。本演练将探索在您的 webpack 设置中使用esbuild-loader
。
将现有项目迁移到 esbuild
使用无论babel-loader
或是ts-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)
+ }
+ },
创建基线应用程序
让我们在练习中看看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 插件来提供 TypeScript 类型检查。这非常有用,因为 esbuild 只是进行转译而不是设计来提供类型检查支持。所以幸运的是我们仍然有那个插件。否则,我们将失去类型检查。
现在您理解了迁移esbuild的优势,我们先需要一个基线来理解用babel-loader
会有怎样的表现。我们运行time npm run build
去执行一个我们简单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
接着我们将交换我们各种scripts
在我们的package.json
以便使用CRACO
"start": "craco start",
"build": "craco build",
"test": "craco test",
我们的应用使用CRACO,但是我们还未配置他。所以我们正添加一个craco.config.js
的文件到我们项目的根目录上。这是我们为esbuild-loader
而换出babel-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
删除单个加载程序,因此,我们将删除两者并重新添加非应用程序 JavaScript babel-loader
。我们还将添加esbuild-loader
用{ loader: 'tsx', target: 'es2015' }
选项设置,以确保我们能处理JSX / TSX。
最后,我们也将使用Terser替换为esbuild的 JavaScript 缩小。
巨大的性能提升
我们的迁移已完成。下次我们构建时,我们将在没有弹出的情况下用esbuild-loader
运行 Create React App 。再一次,我们将运行time npm run build
以执行我们的简单应用程序的构建并确定需要多长时间:
我们的完整构建、TypeScript 类型检查、转译、缩小等全部耗时 13.85 秒。通过迁移到esbuild-loader
,我们将整体编译时间减少了大约三分之一。这是一个巨大的进步!
随着代码库的扩展和应用程序的增长,编译时间可能会激增。使用esbuild-loader
,您应该从构建时间中获得持续的好处。
转载自:https://juejin.cn/post/7023240232522743844