JavaScript 打包工具:深入对比 👍👎 Webpack 仍然是 2021 年最好的打包工具吗?📦
首先,什么是bundler?🤔
场景🖥
1. Webpack
2. Rollup
3. Browserify
4. ESBuild
5. 包裹
比较📈
我的最终结论是什么?😼
现在轮到你了!🔥
免责声明👇
大家好!🚀
过去几天,我一直在研究目前市面上有哪些JavaScript 打包工具,试图得出自己的结论,并找出哪一个更适合我的项目。当然,也想知道这是否只是因为流行度,而我们开发者是否高估了其中一些,而低估了另一些?😇
由于过去几年我一直在使用的唯一捆绑器是Webpack,因此我决定查看npm 趋势,找出2021 年最流行的 JS 捆绑器并尝试一下。
这就是我得到的:
因此今天,我们将根据npm 趋势对5 个最流行的捆绑器进行比较:Webpack、Rollup、Browserify、ESbuild 和 Parcel。
在这个比较中,我们将使用当今最常用的几种资源/工具为它们各自创建一个非常基本的场景,我们将讨论它们的优缺点,并根据一些参数对它们进行比较。
首先,什么是bundler?🤔
捆绑器是一种工具,它将你所有的 JavaScript 代码及其依赖项放在一起,并生成一个包含所有内容的新的 JavaScript 输出文件,以供 Web 使用,通常称为捆绑文件。
除了 JavaScript 之外,这些打包工具还可以处理其他类型的文件,但它们需要一些帮助才能完成打包。我们将在下面的每个示例中更深入地讨论这一点。
它们都不需要配置文件,这对于最基本的 bundle 来说非常完美。这意味着你只需极少的设置就能将一个.js文件转换为另一个.js文件。但是,一旦你开始有越来越多的文件需要转译,并因此需要配置,就该添加配置 文件了,否则,你会发现自己陷入混乱之中😰
场景🖥
要试用这些打包工具,我们不需要为项目构建复杂的特定结构,因此我们先来设想一个非常基础的场景:一个 HTML 文件,包含一些样式(我们会使用像 SASS 这样的预处理器稍微复杂化一下),并且准备使用 ES6。这意味着,即使我们没有使用 React、Vue 或任何依赖它的库/框架,我们也会包含Babel。不过,无论如何,我们先来设置一下。
/dist
bundle.js
[styles.css]
/src
index.js
/styles
styles.scss
index.html
package.json
[*.config.js]
/dist是打包过程完成后创建的文件夹,其中包含所有打包文件。样式的打包文件是可选的,因为我们可以选择直接在 HTML 中注入样式,或者生成一个包含样式的新转译文件。
/src是包含捆绑器启动捆绑过程的入口点的文件夹。
/styles是包含捆绑包之前的原始样式文件的文件夹。
index.html是包含我们将在浏览器中看到的内容的文件。
package.json是存储所有依赖项、脚本和一些配置的文件。
*.config.js是定义捆绑器所有配置的文件。此文件对于此列表中的每个捆绑器都是可选的,但强烈建议使用。 * 将被捆绑器的名称替换。
说了这么多,让我们看看这 5 个捆绑器分别能为我们提供什么。
1. Webpack
有人爱有人恨,但也有人熟知。Webpack 仍然是2021 年最受欢迎的打包工具。截至撰写本文时,每周下载量超过 1500 万次,毫无疑问, Webpack 仍然是2021 年 最出色的打包工具。但是,它是否最容易使用、配置和理解其工作原理呢?
让我们看看应该如何配置它以使其准备好工作。
Webpack 使用的方法
- 构建脚本
- 配置文件
- 用于转换文件的加载器
- 更复杂内容的插件
package.json
...
"scripts": {
"build": "rm -rf dist && webpack --mode development"
}
...
真的非常简单。基本配置无需执行任何其他操作。实际上,如果您不想为配置文件使用其他名称,甚至无需在构建脚本中指定配置。如果您想使用其他名称,只需在命令中添加--config your_config_file.js即可。
请注意,我们将在每个打包器的每次构建中添加命令rm -rf dist 。这样做的目的是每次执行新的构建脚本时删除dist文件夹。
webpack.config.js
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve("dist")
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: "/node-modules/",
use: "babel-loader"
},
{
test: /\.html$/,
use: "html-loader"
},
{
test: /\.(scss|sass)$/,
use: ["style-loader", "css-loader", "sass-loader"]
}
]
}
}
与其他捆绑器相比,它一开始可能有点棘手且难以理解,但一旦你了解了一切如何协同工作,就会变得非常容易。
这个配置文件里发生了什么?🙃
首先,我们需要一个入口点,以便打包器开始合并所有内容。入口点在 entry属性中指定,该文件就是src文件夹中的index.js文件。
对于输出文件也是一样,我们会告诉 Webpack 我们的文件将被称为bundle.js并且它应该存储在文件夹dist中。
现在,只剩下处理非 JavaScript (ES5) 文件了。Webpack 使用loader来处理这些文件。为了转换这些文件,我们只需要指定文件格式以及哪个 loader 来处理它们。
这就是我们所需要的:一些加载器来处理我们的样式、HTML 和 JS(ES6 - 请记住,我们正在为.jsx等格式做准备):,style-loader
以及样式、HTML 文件和ES6。css-loader
sass-loader
html-loader
babel-loader
请注意,我们还转换了HTML 文件(如果我们想要添加一些直接在 HTML 文件中加载的资源,例如图片,这个加载器会很有用)。这个加载器在大型项目中非常有用,但在本例中并非必需(因为它的结构比较简单),对于其他打包器,我们将跳过此步骤。
就是这样。一旦我们运行构建命令,所有内容都会被捆绑。
关于捆绑包
由于我们使用style-loader
来捆绑样式,而不是使用插件来缩小 CSS 并生成新文件(MiniCSSExtractPlugin
),样式被注入到<script>
标签内的 HTML 文件中,因此唯一的输出文件是bundle.js
,需要将其添加到index.html
。
我对 Webpack 有什么看法?👇
不得不承认,第一次接触 Webpack 时,我感觉配置简直不可能。那是我第一次使用打包工具,我几乎无法理解它的整体概念。更别提所有的加载器以及更复杂的相关内容了,毕竟项目规模太大了。
但在我进行了一些从头开始的配置之后,我不得不说,如果我将它与了解其余部分的感觉进行比较,现在我发现它更加直观且更容易设置。
让我们看看其他的,你就会明白为什么!
2. Rollup
现在让我们把注意力转向Rollup。和其他加载器一样,这是我第一次尝试它,所以我也会分享一下我对它的第一印象 🤓
Rollup 使用的方法
- 构建命令。
- 可选的 配置文件。
- 用于转换文件的插件
package.json
没有配置文件的基本包:
...
"scripts": {
"build": "rm -rf dist && rollup src/index.js --file dist/bundle.js"
}
...
使用配置文件:
...
"scripts": {
"build": "rm -rf dist && rollup -c"
}
...
这也是一个非常简单的构建命令,因此这里没有什么可指出的。
现在让我们检查配置文件,这是可选的但建议这样做。
rollup.config.js
import babel from "@rollup/plugin-babel";
import scss from "rollup-plugin-scss";
export default {
input: "./src/index.js",
output: {
file: "./dist/bundle.js",
format: "cjs",
},
plugins: [
babel({ exclude: "node_modules/**" }),
scss({ output: "styles.css" }),
]
}
Webpack 中定义的loader,在 Rollup 中被称为插件。这次我们只需要几个:一个用于将 ES6 转译成 ES5 的插件(Babel),以及一个用于 SCSS 的插件:@rollup/plugin-babel
和rollup-plugin-scss
。
这些插件也有各自的可选配置。在本例中,对于 Babel,我们排除了node_modules文件夹;对于 SCSS,我们为输出文件赋予了不同的名称。否则,它将保留为output.css。
对于使用纯 CSS的配置,有一个名为的插件,rollup-plugin-css-only
其工作方式与我们用于 SCSS 的插件完全相同。
请注意,我们需要像之前使用 Webpack 一样指定入口和输出点。
就这样吧。
关于捆绑包
Rollup 包包含两个文件:bundle.js
和styles.css
。需要在入口点导入原始样式文件,index.js
以便 bundler 能够找到该文件(没有其他地方可以引用它)。
还需要将这两个包添加到 HTML 索引文件中。
我对 Rollup 的第一印象
说实话,我对这些其他更简单、更轻量的捆绑器并没有抱太大的期望,因为 Webpack 一直对我有用,而我不得不说 Rollup 以一种好的方式给了我惊喜。
我发现它与 Webpack 非常相似(配置文件具有几乎相同的结构,插件以与加载器相同的方式工作以转换无 js文件,简单的构建命令......),这意味着熟悉、使用回忆,因此易于使用。
目前我发现的唯一缺点是它依赖的依赖项太多,导致项目体积庞大(相当于使用 Webpack 打包后项目的 3 倍)。我们将在文章末尾更深入地探讨这个问题。
3. Browserify
现在我们来讨论一下Browserify。
Browserify 使用的方法
- 没有配置文件
- 用于转换文件的转换
- 您需要配置的一切 ->
package.json
Browserify 使用的方法与传统的构建命令和配置文件的方法完全不同。使用这个打包器,所有可能的配置都会被分配进去package.json
,如果我们概念不清晰,构建命令可能会变得有点繁琐。
它还需要插件(或转换,因为它们也被称为)将所有内容转换为浏览器可读的内容。
让我们看一下如何配置它:
package.json
...
"scripts": {
"build": "rm -rf dist && browserify -o dist/bundle.js src/index.js"
}
...
此打包器的一个非常基本的用法类似于上面的代码片段。我们只定义了输入和输出文件(没有样式或任何更复杂的配置)。
请注意,构建命令的长度仅声明了输入源和输出。
让我向您展示如果我们添加适合处理纯 CSS 的插件它会是什么样子。
...
"scripts": {
"build": "rm -rf dist && browserify -t [browserify-css --output dist/styles.css] -o dist/bundle.js src/index.js"
}
...
然后,如果我们想向插件添加一些配置,我们可以在同一个文件中执行以下操作:
...
"browserify": {
"browserify-css": {
"autoInject": true,
"minify": true,
"rootDir": "."
}
}
...
它开始变得不那么易于维护。
现在,让我们通过添加 SCSS 和 Babel 插件来使其更复杂一些。我们需要两个名为和 的插件。Babelify
scssify
需要考虑的事情
我一直在尝试用Node 最新发布的版本
node-sass
(v16.4.2) 来测试这个打包工具,在尝试安装任何依赖(更具体地说是scssify
和)的依赖项时,命令行会抛出多个错误sassify
。这是一个非常糟糕的点。
我们可以用两种不同的方式来实现这一点:
- 通过用更多内容填充构建脚本😅
- 通过添加transform属性
通过构建脚本
为了使用 Browserify 在构建脚本中指定多个转换,我们应该根据需要添加尽可能多的-t [转换选项],如下所示:
...
"scripts": {
"build": "rm -rf dist && browserify -t [ scssify --output dist/styles.css ] -t [ babelify --presets [ @babel/preset-env ] ] -o dist/bundle.js src/index.js"
}
...
如果你使用这种方法,请密切注意数组内的空格。它们很重要✌️
我觉得这种方法很繁琐,难以理解,最重要的是难以维护。而且我们只用了两个插件。总而言之。
通过 transform 属性
...
"browserify": {
"transform": [
[ "babelify", {
"presets": [
"@babel/preset-env"
]
}
],
[ "scssify", { "autoInject": true } ]
]
}
...
通过使用此方法,构建脚本将看起来就像原来一样,只是执行了输入 js 文件的简单捆绑:
...
"scripts": {
"build": "rm -rf dist && browserify -o dist/bundle.js src/index.js"
}
...
好多了😊
关于捆绑包
Browserify 包包含bundle.js
文件,并且只有当我们在负责样式的插件中设置了输出文件时,我们才会得到一个 style.css文件。否则<head>
,样式将被注入到HTML 文件中元素底部的元素内<script>
。
看一下这两个不同的配置示例browserify-css
:
...
[ "browserify-css", {
"autoInject": false,
"minify": true,
"rootDir": ".",
"output": "dist/styles.css"
}
]
...
上述配置将创建一个单独的.css文件。
...
[ "browserify-css", {
"autoInject": true,
"minify": true,
"rootDir": "."
}
]
...
而这个其他配置将把代码注入到<script>
头部的标签中index.html
。
我对 Browserify 的第一印象
到目前为止,我不太喜欢这个。我觉得它不像其他两个那样直观,而且它使用的方法与我们通常习惯的完全不同。此外,如果一开始你不知道如何以及在哪里处理所需的插件,我觉得配置起来会更繁琐。
此外,空格也很重要,如果你事先不知道这一点,你完全可以花 2 个小时来找出代码中的问题👎
4. ESBuild
是时候讨论ESBuild 了。
ESBuild 使用的方法
- 构建命令(鼓励使用终端)
- 可选的 配置文件
- 用于转换文件的插件
使用 ESBuild,您可以使用命令行或 配置 文件以及其他方式,甚至可以进行更复杂的配置。这完全取决于您,但出于可维护性、可扩展性、可读性和生产力的考虑,始终建议指定配置文件。
我们将创建一个名为的配置文件,并通过运行命令从构建esbuild.config.js
脚本中执行它。node
但首先,让我们看一下使用 ESBuild 捆绑文件的最简单方法(这次不需要配置文件):
package.json
...
"scripts": {
"build": "rm -rf dist && esbuild --bundle src/index.js --outfile=dist/bundle.js"
}
...
像往常一样,我们声明入口点和输出文件。就是这样。但是,当我们需要继续捆绑更多不同类型的文件时会发生什么?
让我们看一下下面的例子:
...
"scripts": {
"build": "rm -rf dist && esbuild --bundle src/index.js --outfile=dist/bundle.js && esbuild --bundle styles/styles.css --outfile=dist/bundle.css"
}
...
我们现在也打包了样式,并通过定义两个不同的打包器,在构建脚本中添加了更多信息(又乱了!)。我们可能(而且肯定会)有更多需要打包的文件类型,这可能会变得一团糟。
因此,让我们抛开这种方法并创建一个配置文件。
esbuild.config.js
import esbuild from 'esbuild';
import { sassPlugin } from "esbuild-sass-plugin";
import babel from 'esbuild-plugin-babel';
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/bundle.js',
plugins: [sassPlugin(), babel()],
}).catch(() => process.exit(1));
以下是我发现的(让我这么说)奇怪的事情以及我花了一些时间才弄明白的事情。
可能是因为我希望以与 Webpack 和 Rollup 相同的方式运行此配置文件(如果配置文件存在且具有默认名称,它们会默认运行),所以我在尝试告诉 ESBuild 将其作为配置的输入时遇到了一些麻烦。
最后,我意识到应该通过节点命令调用它来运行脚本😬
"scripts": {
"build": "rm -rf dist && node esbuild.config.js"
}
就这样了。
这里我想提一下,插件选择实在太少,而且大多数都过时了,这让我很郁闷。另外,如果允许我提点建议,尽量选择使用 CommonJS (通过require插入模块)或 ES 模块(使用import插入模块)的插件,因为如果混用的话……你只会得到错误和混乱的结果!😖
如果您使用 ES 模块(import)将插件加载到配置文件中,请确保更改type 属性:package.json
...
"type": "module"
...
几乎所有插件都是由社区创建的(即使不是全部)。在本例中,我选择了esbuild-sass-plugin
SASS/SCSS 和esbuild-plugin-babel
Babel 的插件。它们都可以使用import,所以不会出现其他问题。
值得一提的是:与其他工具相比,ESBuild 确实速度很快。至少在这种情况下是这样。
我对 ESBuild 的第一印象
百感交集。起初,我以为配置起来会非常容易(如果你只打算执行常规捆绑的话),但后来我开始对配置文件感到有些困惑,不是因为语法,而是因为终端上出现了多个与 Node 相关的错误。
5. 包裹
现在让我们来看看列表中的最后一个打包器:著名的Parcel。向广大的 Parcel 粉丝社区问好 👋
Parcel 使用的方法
Parcel 方法主要基于零 配置环境 😱 一开始我不太愿意相信(这也是我这么想尝试的主要原因),但是,是的,确实可以打包一个像我们在本文中测试的项目,只需编写最低限度的配置,只需几分钟,无需绞尽脑汁 🙌
零配置?你确定吗?😪
零意味着非常少,而且非常精确。让我向你展示一下我在这个基本项目中使用的配置:
package.json
...
"scripts": {
"build": "rm -rf dist && rm -rf && parcel build src/index.js --no-scope-hoist --no-source-maps"
}
...
步骤基本相同:我们需要指定应用程序的入口点位置。此外,我还添加了标志位--no-scope-hoist
,以避免require
在运行js脚本时出现异常行为,并--no-source-maps
避免创建 sourcemaps。否则,Parcel 会默认为每个 bundle 文件创建一个 sourcemaps 。
现在,如果我们想要更改输出包文件的位置和名称,我们需要更改中主要属性的值package.json
,如下所示:
...
"main": "dist/bundle.js"
...
否则,该包将在根级别生成,并使用存储在main中的名称进行调用,大多数情况下为index.js(如果我们在运行时没有更改它npm init
)。
现在,让我们(零)配置样式和 Babel
由于我们使用 SCSS,因此需要使用 SASS 作为预处理器。所以,当我看到SASS 已包含在 Parcel 安装包中时,我感到很惊讶。不仅如此,还有LESS、Stylus,以及…… Babel!😧
因此,这里唯一需要采取的步骤是为 SASS 和 Babel 创建几个配置文件。
我们的 SASS 配置文件将被命名.sassrc
并包含以下代码:
{
"includePaths": ["node_modules"]
}
当我们运行build命令时,Parcel 会自动将插件安装@parcel/transformer-sass
为依赖项,并在同一个指定目录中为 bundle 创建一个bundle.css
文件,这就是所有的配置。很酷吧?
现在不要忘记将此文件链接到您的 HTML 🤗 并且请记住,您的.scss文件应该先前已在您的入口点文件中导入,以便捆绑器知道它必须转换哪个文件。
在 Babel 方面,我们需要创建一个.babelrc
配置文件来指定所需的预设(假设我们希望为将来使用 React 做好准备):
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
Parcel 会自动拨打电话@parcel/transformer-babel
并为我们完成这项工作。
不要忘记先前安装@babel/preset-env
,@babel/preset-react
以及 React 所需的所有依赖项。
就这样……一切就绪。一切准备就绪,准备摇滚啦😁
我对 Parcel 的第一印象
我想指出的第一件事是,一开始 Parcel 对我来说并不容易,因为我真的(真的)费了好大劲才让它准备好工作,而且它似乎不会停止抛出有关操作系统的错误,并因为某些依赖项的过时版本而制造更多麻烦😥所以,说实话,Parcel 不会出现在这个列表中,因为如果我不能亲自尝试的话,我不想谈论它。
但是,神奇的是✨(由于我不懈的坚持😅),我终于可以做到,并把一切都准备好了🙌
之后,相比其他打包工具,它真的很容易。所以,让我们先忽略这些挫折,给它一个机会。
Parcel 也相当快,因为它使用缓存。
但是……我完全不喜欢的是安装后出现的几个安全漏洞(大约12个,其中一些风险很高)😖 Parcel,这说明你不太好。更不用说这个项目的规模有多大了。它是这几个项目中最重的。
比较📈
下表总结了本次比较的亮点:
我的最终结论是什么?😼
好吧,我认为除了 Webpack 之外的其他一些捆绑器对于小型项目或侧面项目来说也很酷,但实际上,我个人认为Webpack仍然是健壮项目的最佳选择(我们只需要看看与其他捆绑器相比每周的下载量有多大)。
另外,我发现它最容易管理,因为一旦你了解了它如何处理配置,就可以更轻松地继续向该配置中添加值。但这并非显而易见。你必须花些时间慢慢尝试,才能初步掌握一些基本概念。
此外,您所需的大部分资源(加载器、插件……)都来自其创建者,因此您可以确保使用的是一个真正的可靠来源。而且它们的更新频率非常高,因此您可以放心地使用较新版本的 Node 和其他软件包。
所以,是的,我将继续选择 Webpack作为我的首选。
我的第二选择肯定是Rollup ,我真的认为我肯定会在我的一些副业中使用它,因为我发现它配置起来很直观,而且它似乎在强大的项目上也能正常工作。
至于他们的插件,大多数也可以从创建者那里获得,因此,再次成为真正的真相来源和更多优势。
我也觉得Parcel是个很有意思的选择,想在更大的项目中试用一下,看看它是否真的不需要进一步配置。这绝对是个很棒的发现。
另一个很大的优点是,Babel、SASS、LESS等插件都是内置的,可以开箱即用。
那么Browserify和ESBuild怎么样?
这两个是我最头疼的,尤其是 Browserify。它不需要配置文件,所有东西都应该在package.json中声明,这迫使你不得不改变对传统打包器配置方式的理解。更何况,你最终会把一堆复杂的配置塞满整个文件,使其难以阅读和维护。
此外,说到插件,大多数插件都不是创建者开发和维护的(尤其是最常见的插件),而且确实已经过时了(其中许多插件在过去 4 年内都没有更新过),这一事实导致了较新的 Node/其他软件包版本和兼容性问题。
至于ESBuild,我也不是特别喜欢。第一印象还不错,但后来由于配置文件给我带来了一些麻烦,最终导致我对于如何在有/无此文件的情况下管理配置感到困惑。因此,我发现它相当模糊,花了一段时间才弄清楚如何以不同的方式设置这两种情况。
关于他们的插件,和 Browserify 一样,几乎所有插件都是由社区而非作者创建的,所以你必须自行承担使用风险。不过,它们的一个优点是经常更新和维护。
现在轮到你了!🔥
你觉得这个比较怎么样?你同意吗?你最喜欢的打包器是哪一个?你还知道其他不在列表中的打包器吗?你想在未来的比较文章中推荐不同的打包器吗?请在下方评论!
免责声明👇
请记住,这篇文章只是我初次尝试某件事物时的感受。我决定与大家分享整个过程以及我对体验的感受。本文表达的观点并不意味着某些打包工具比其他打包工具更好。我的建议是像我一样,尝试所有打包工具,并得出自己的结论。在此基础上,选择你最喜欢的、最符合你需求的打包工具。
🎉 不要忘记在Instagram和Twitter上关注@underscorecode,获取更多每日 webdev 内容 🖥🖤
最后但同样重要的一点是...在我们离开之前有一个快速的友情提醒😊
我们都知道,编程和开发有无数种方法可以完成任务,而我们在这里是为了提供帮助和学习。所以,如果您知道其他人分享的方法(不更好,也不更差,只是不同),请随意分享,但请始终保持友善和尊重,以尊重作者和社区其他成员。谢谢您,祝您编程愉快!
文章来源:https://dev.to/underscorecode/javascript-bundlers-an-in-depth-comparative-is-webpack-still-the-best-bundler-in-2021-59jk