javascript模块_JavaScript模块第2部分:模块捆绑

长孙昀
2023-12-01

javascript模块

by Preethi Kasireddy

通过Preethi Kasireddy

JavaScript模块第2部分:模块捆绑 (JavaScript Modules Part 2: Module Bundling)

In Part I of this post, I talked about what modules are, why developers use them, and the various ways to incorporate them into your programs.

在本文的第一部分中,我讨论了什么是模块,开发人员为何使用它们以及将它们合并到程序中的各种方法。

In this second part, I’ll tackle what exactly it means to “bundle” modules: why we bundle modules, the different ways to do so, and the future of modules in web development.

在第二部分中,我将解决“捆绑”模块的确切含义:我们为什么捆绑模块,捆绑方式不同以及网络开发中模块的未来。

什么是模块捆绑? (What is module bundling?)

On a high level, module bundling is simply the process of stitching together a group of modules (and their dependencies) into a single file (or group of files) in the correct order.

从高层次上讲,模块捆绑只是将一组模块(及其依赖项)按正确顺序拼接到一个文件(或一组文件)中的过程。

As with all aspects of web development, the devil is in the details. :)

与Web开发的所有方面一样,细节在于魔鬼。 :)

为什么要捆绑模块? (Why bundle modules at all?)

When you divide your program into modules, you typically organize those modules into different files and folders. Chances are, you’ll also have a group of modules for the libraries you’re using, like Underscore or React.

将程序分为模块时,通常将这些模块组织到不同的文件和文件夹中。 您还可能会为正在使用的库提供一组模块,例如Underscore或React。

As a result, each of those files has to be included in your main HTML file in a <script> tag, which is then loaded by the browser when a user visits your home page. Having separate &lt;script> tags for each file means that the browser has to load each file individually: one… by… one.

结果,这些文件中的每个文件都必须包含在主HTML文件中的<scri pt>标记中,然后当用户访问您的主页时,浏览器就会将其加载。 每个文件具有单独的&l t; script>标签意味着浏览器必须分别加载每个文件:一个…一个…一个。

…Which is bad news for page load time.

…这对页面加载时间是个坏消息。

To get around this problem, we bundle, or “concatenate” all our files into one big file (or a couple files as the case may be) in order to reduce the number of requests. When you hear developers talking about the “build step” or “build process,” this is what they’re talking about.

为了解决此问题,我们将所有文件捆绑或“连接”为一个大文件(或视情况而定为几个文件),以减少请求数量。 当您听到开发人员在谈论“构建步骤”或“构建过程”时,这就是他们在谈论的内容。

Another common approach to speed up the bundling operation is to “minify” the bundled code. Minification is the process of removing unnecessary characters from source code (e.g. whitespace, comments, new line characters, etc.), in order to reduce the overall size of the content without changing the functionality of the code.

加快捆绑操作的另一种常用方法是“最小化”捆绑的代码。 缩小是从源代码中删除不必要的字符(例如,空格,注释,换行符等)的过程,目的是在不更改代码功能的情况下减小内容的整体大小。

Less data means less browser processing time, which in turn reduces the time it takes to download files. If you’ve ever seen a file that had a “min” extension like “underscore-min.js”, you probably noticed that the minified version is pretty tiny (and unreadable) compared to the full version.

更少的数据意味着更少的浏览器处理时间,从而减少了下载文件的时间。 如果您曾经看过具有“ mins ”扩展名的文件,例如“ underscore-min.js ”,那么您可能会注意到,与完整版本相比,缩小版本很小(并且不可读)。

Task runners like Gulp and Grunt make concatenation and minification straightforward for developers, ensuring that human-readable code stays exposed for developers while machine-optimized code gets bundled for browsers.

诸如Gulp和Grunt之类的任务运行程序使开发人员可以轻松进行串联和缩小操作,从而确保在开发人员将机器优化的代码捆绑到浏览器中的同时,对开发人员而言仍可以公开人类可读的代码。

捆绑模块有哪些不同的方式? (What are the different ways to bundle modules?)

Concatenating and minifying your files works great when you’re using one of the standard module patterns (discussed in the previous post) to define your modules. All you’re really doing is mashing together a bunch of plain vanilla JavaScript code.

当您使用一种标准模块模式(在一篇文章中讨论 )来定义模块时,串联和缩小文件非常有用。 您真正要做的就是将一堆普通JavaScript代码混在一起。

However, if you’re adhering to non-native module systems that browsers can’t interpret like CommonJS or AMD (or even native ES6 module formats), you’ll need to use a specialized tool to convert your modules into properly-ordered browser-friendly code. That’s where Browserify, RequireJS, Webpack, and other “module bundlers” or “module loaders” come into play.

但是,如果您坚持使用浏览器无法解释的非本机模块系统,例如CommonJS或AMD(甚至是本机 ES6模块格式),则需要使用专用工具将模块转换为顺序正确的浏览器友好的代码。 那就是Browserify,RequireJS,Webpack和其他“模块捆绑器”或“模块加载器”发挥作用的地方。

In addition to bundling and/or loading your modules, module bundlers offer a ton of additional features like auto-recompiling code when you make a change or producing source maps for debugging.

除了捆绑和/或加载模块外,模块捆绑器还提供大量其他功能,例如在您进行更改或生成用于调试的源映射时自动重新编译代码。

Let’s walk through some common module bundling methods:

让我们来看一些常见的模块捆绑方法:

捆绑CommonJS (Bundling CommonJS)

As you know from Part 1, CommonJS loads modules synchronously, which would be fine except that it’s not practical for browsers. I mentioned that there was a workaround to this — one of them is a module bundler called Browserify. Browserify is a tool that compiles CommonJS modules for the browser.

第1部分中可以知道,CommonJS同步加载模块,这很好,除了它对浏览器不实用之外。 我提到了一种解决方法-其中之一是称为Browserify的模块捆绑器。 Browserify是为浏览器编译CommonJS模块的工具。

For example, let’s say you have this main.js file that imports a module to calculate the average of an array of numbers:

例如,假设您有一个main.js文件,该文件导入一个模块来计算数字数组的平均值:

So in this case, we have one dependency (myDependency). Using the command below, Browserify recursively bundles up all the required module(s) starting at main.js into a single file called bundle.js:

因此,在这种情况下,我们只有一个依赖项(myDependency)。 使用下面的命令,Browserify将所有必需的模块从main.js开始递归地捆绑到一个名为bundle.js的文件中:

Browserify does this by jumping in to parse the AST for each require call in order to traverse the entire dependency graph of your project. Once it’s figured out how your dependencies are structured, it bundles them all in the right order into a single file. At that point, all you have to do is insert a single <script> tag with your “bundle.js” file into your html to ensure that all of your source code is downloaded in one HTTP request. Bam! Bundled to go.

Browserify通过跳跃来解析这是否AST的每个要求 ,以遍历您的项目的整个依赖图通话。 一旦弄清楚了依赖项的结构,便将它们按照正确的顺序捆绑到一个文件中。 在这一点上,所有你需要做的就是插入你的“外滩 le.js”文件到您HTML一个<SCRI PT>标签,以确保所有的源代码在一个HTTP请求被下载。 am! 捆绑走。

Similarly, if you have multiple files with multiple dependencies, you simply tell Browserify what your entry file is and sit back while it does its magic.

同样,如果您有多个具有多个依赖项的文件,则只需告诉Browserify您的条目文件是什么,然后静静地处理它。

The final product: bundled files prepped and ready for tools like Minify-JS to minify the bundled code.

最终产品:准备捆绑文件,并准备使用Minify-JS之类的工具来缩减捆绑代码。

捆绑AMD (Bundling AMD)

If you’re using AMD, you’ll want to use an AMD loader like RequireJS or Curl. A module loader (vs. a bundler) dynamically loads modules that your program needs to run.

如果使用的是AMD,则需要使用AMD 加载器,例如RequireJS或Curl。 模块加载器(相对于捆绑器)动态加载程序需要运行的模块。

As a reminder, one of the main differences of AMD over CommonJS is that it loads modules asynchronously. In this sense, with AMD, you technically don’t actually need a build step where you bundle your modules into one file since you’re loading your modules asynchronously — meaning you’re progressively downloading only those files which are strictly necessary to execute the program instead of downloading all the files at once when the user first visits the page.

提醒一下,AMD与CommonJS的主要区别之一是它异步加载模块。 从这个意义上讲,对于AMD,从技术上讲,您实际上不需要构建步骤,因为您是异步加载模块的,因此您可以将模块捆绑到一个文件中-这意味着您将仅逐步下载执行该命令所必需的那些文件。程序,而不是在用户首次访问该页面时立即下载所有文件。

In reality, however, the overhead of high-volume requests over time for every user action doesn’t make much sense in production. Most web developers still use build tools to bundle and minify their AMD modules for optimal performance, using tools like RequireJS optimizer, r.js, for example.

但是,实际上,随着时间的推移,每个用户操作的高容量请求的开销在生产中没有多大意义。 大多数Web开发人员仍然使用构建工具来捆绑和缩小其AMD模块,以实现最佳性能,例如使用诸如RequireJS优化器r.js之类的工具。

Overall, the difference between AMD and CommonJS when it comes to bundling is this: during development, AMD apps can get away without a build step. At least, until you push the code live, at which point optimizers like r.js can step in to handle it.

总体而言,AMD和CommonJS在捆绑方面的区别在于:在开发过程中,AMD应用程序无需构建就可以摆脱困境。 至少,直到您实时发布代码为止,这时诸如r.js之类的优化器才能介入其中。

For an interesting discussion on CommonJS vs. AMD, check out this post at Tom Dale’s blog :)

有关CommonJS与AMD的有趣讨论,请查看Tom Dale博客中的这篇文章 :)

Webpack (Webpack)

So far as bundlers go, Webpack is the new kid on the block. It was designed to be agnostic to the module system you use, allowing developers to use CommonJS, AMD, or ES6 as appropriate.

就打包器而言,Webpack是新手。 它的设计与您使用的模块系统无关,允许开发人员酌情使用CommonJS,AMD或ES6。

You might be wondering why we need Webpack when we already have other bundlers like Browserify and RequireJS that get the job done and do a pretty darn good job at it. Well, for one, Webpack provides some useful features like “code splitting” — a way to split your codebase into “chunks” which are loaded on demand.

您可能想知道为什么当我们已经有其他捆绑器(如Browserify和RequireJS)完成工作并做得很好时,为什么我们需要Webpack。 好吧,其中一个是,Webpack提供了一些有用的功能,例如“代码拆分”-一种将您的代码库拆分为“块”的方法,可以按需加载。

For example, if you have a web app with blocks of code that are only required under certain circumstances, it might not be efficient to put the whole codebase into a single massive bundled file. In this case, you could use code splitting to extract code into bundled chunks that can be loaded on demand, avoiding trouble with big up-front payloads when most users only need the core of your application.

例如,如果您的Web应用程序仅在某些情况下需要使用代码块,则将整个代码库放入单个大型捆绑文件中可能不会很有效。 在这种情况下,您可以使用代码拆分将代码提取到可以按需加载的捆绑块中,从而避免了在大多数用户只需要应用程序核心的情况下使用较大的前期有效负载的麻烦。

Code splitting is just one of many compelling features Webpack offers, and the Internet is full of strong opinion pieces on whether Webpack or Browserify is better. Here are just a few of the more level-headed discussions that I found useful for wrapping my head around the issue:

代码拆分只是Webpack提供的众多引人注目的功能之一,并且Internet上对于Webpack或Browserify是否更好,有很多意见。 以下是一些我认为有助于解决问题的更高级的讨论:

ES6模块 (ES6 modules)

Back already? Good! Because next up I want to talk about ES6 modules, which in some ways could reduce the need for bundlers in the future. (you’ll see what I mean momentarily.) First, let’s understand how ES6 modules are loaded.

回来了吗 好! 因为接下来我想谈谈ES6模块,它在某些方面可以减少将来对捆绑器的需求。 (您很快就会明白我的意思。)首先,让我们了解如何加载ES6模块。

The most important difference between the current JS Module formats (CommonJS, AMD) and ES6 modules is that ES6 modules are designed with static analysis in mind. What this means is that when you import modules, the import is resolved at compile time — that is, before the script starts executing. This allows us to remove exports that are not used by other modules before we run the program. Removing unused exports can lead to significant space savings, reducing stress on the browser.

当前的JS模块格式(CommonJS,AMD)和ES6模块之间最重要的区别是ES6模块在设计时考虑了静态分析。 这意味着在导入模块时,将在编译时(即在脚本开始执行之前)解决导入问题。 这使我们可以在运行程序之前删除其他模块未使用的导出。 删除未使用的导出可以节省大量空间,从而减轻浏览器的压力。

One common question that comes up is: how is this any different from the dead code elimination that happens when you use something like UglifyJS to minify your code? The answer is, as always, “it depends.”

一个常见的问题是:这与使用UglifyJS之类的代码来缩小代码时所产生的死代码消除有何不同? 答案始终是“取决于情况”。

(NOTE: Dead code elimination is an optimization step which removes unused code and variables — think of it as removing the excess baggage that your bundled program doesn’t need to run, *after* it’s been bundled).

(注意:消除死代码是一个优化步骤,它删除了未使用的代码和变量-将其视为消除捆绑程序不需要运行的多余包,在捆绑之后*)。

Sometimes, dead code elimination could work exactly the same between UglifyJS and ES6 modules, and other times not. There’s a cool example at Rollup’s wiki) if you want to check it out.

有时,清除死代码可能在UglifyJS和ES6模块之间完全相同,而有时则不行。 如果您想检查一下,请在Rollup的wiki上有一个很棒的示例。

What makes ES6 modules different is the different approach to dead code elimination, called “tree shaking”. Tree shaking is essentially dead code elimination reversed. It only includes code that your bundle needs to run, rather than excluding code your bundle doesn’t need. Let’s look at an example of tree shaking:

使ES6模块与众不同的是消除死代码的不同方法,称为“树抖动”。 树抖动本质上是消除死代码的反向操作。 它仅包含捆绑软件需要运行的代码,而不是排除捆绑软件不需要的代码。 让我们看一个摇树的例子:

Let’s say we have a utils.js file with the functions below, each of which we export using ES6 syntax:

假设我们有一个utils.js文件,其中包含以下函数,我们使用ES6语法导出每个函数:

Next, let’s say we don’t know what utils functions we want to use in our program, so we go ahead and import all of the modules in main.js like so:

接下来,假设我们不知道我们要在程序中使用哪些utils函数,因此我们继续将所有模块导入main.js中,如下所示:

And then we later end up only using the each function:

然后,我们最终只使用每个函数:

The “tree shaken” version of our main.js file would look like this once the modules have been loaded:

加载模块后,main.js文件的“摇树”版本将如下所示:

Notice how the only exports included are the ones we use: each.

请注意,仅包含的出口是我们使用的出口: each

Meanwhile, if we decide to use the filter function instead of the each function, we wind up looking at something like this:

同时,如果我们决定使用过滤器函数而不是每个函数,那么我们最终将看到以下内容:

The tree shaken version looks like:

树动版本看起来像:

Notice how this time both each and filter are included. This is because filter is defined to use each, so we need both exports for the module to work.

注意,这一次每个都和过滤怎么也包括在内。 这是因为filter被定义为使用each ,所以我们需要两个导出模块才能正常工作。

Pretty slick, huh?

很漂亮吧?

I challenge you to play around and explore tree shaking in Rollup.js’s live demo and editor.

我挑战您在Rollup.js的实时演示和编辑器中玩耍并探索摇树。

构建ES6模块 (Building ES6 modules)

Ok, so we know that ES6 modules are loaded differently than other module formats, but we still haven’t talked about the build step for when you’re using ES6 modules.

好的,所以我们知道ES6模块的加载方式与其他模块格式不同,但是对于您使用ES6模块时的构建步骤,我们仍然没有谈到。

Unfortunately, ES6 modules still require some extra work, since there isn’t a native implementation for how browsers load ES6 modules just yet.

不幸的是,ES6模块仍然需要做一些额外的工作,因为还没有一种本地化的实现方式来实现浏览器如何加载ES6模块。

Here are a couple of the options for building/converting ES6 modules to work in the browser, with #1 being the most common approach today:

以下是用于构建/转换ES6模块以在浏览器中工作的几个选项,其中#1是当今最常见的方法:

  1. Use a transpiler (e.g. Babel or Traceur) to transpile your ES6 code to ES5 code in either CommonJS, AMD, or UMD format. Then pipe the transpiled code through a module bundler like Browserify or Webpack to create one or more bundled files.

    使用编译器(例如Babel或Traceur)将您的ES6代码转换为CommonJS,AMD或UMD格式的ES5代码。 然后,通过诸如Browserify或Webpack之类的模块捆绑器通过管道传输已编译的代码,以创建一个或多个捆绑文件。
  2. Use Rollup.js, which is very similar to option #1 except that Rollup piggybacks on the power of ES6 modules to statically analyze your ES6 code and dependencies before bundling. It uses “tree shaking” to include the bare minimum in your bundle. Overall, the main benefit of Rollup.js over Browserify or Webpack when you’re using ES6 modules is that tree shaking could make your bundles smaller. The caveat is that Rollup provide several formats to bundle your code to, including ES6, CommonJS, AMD, UMD, or IIFE. The IIFE and UMD bundles would work in your browser as they are, but if you choose to bundle to AMD, CommonJS, or ES6, you need to find other methods to convert that code into a format the browser understands (e.g. by using Browserify, Webpack, RequireJS, etc.).

    使用Rollup.js ,它与选项#1非常相似,不同的是Rollup.js依靠 ES6模块的强大功能在捆绑之前静态分析ES6代码和依赖项。 它使用“摇树”来将捆绑包中的最低限度包括在内。 总体而言,使用ES6模块时,Rollup.js相对于Browserify或Webpack的主要好处是,摇晃树可以使捆绑包更小。 需要注意的是,汇总提供了多种格式来捆绑您的代码,包括ES6,CommonJS,AMD,UMD或IIFE。 IIFE和UMD捆绑软件可以在您的浏览器中正常使用,但是,如果您选择捆绑到AMD,CommonJS或ES6,则需要找到其他方法将该代码转换为浏览器可以理解的格式(例如,使用Browserify, Webpack,RequireJS等)。

跳铁圈 (Jumping through hoops)

As web developers, we have to jump through a lot of hoops. It’s not always easy to convert our beautiful ES6 modules into something browsers can interpret.

作为Web开发人员,我们必须克服很多困难。 将我们漂亮的ES6模块转换成浏览器可以解释的内容并不总是那么容易。

The question is, when will ES6 modules run in the browser without all this overhead?

问题是,什么时候ES6模块将在浏览器中运行而没有所有这些开销?

The answer, thankfully, “sooner than later.”

幸运的是,答案是“早于晚”。

ECMAScript currently has a specification for a solution called the ECMAScript 6 module loader API. In short, this is a programmatic, Promise-based API that is supposed to dynamically load your modules and cache them so that subsequent imports do not reload a new version of the module.

ECMAScript当前具有解决方案的规范,称为ECMAScript 6模块加载器API 。 简而言之,这是一个基于Promise的程序化API,应该动态加载您的模块并对其进行缓存,以便后续导入时不会重新加载该模块的新版本。

It’ll look something like this:

它看起来像这样:

myModule.js

myModule.js

main.js

main.js

Alternately, you could also define modules by specifying “type=module” directly in the script tag, like so:

或者,您也可以通过直接在script标记中指定“ type = module”来定义模块,如下所示:

If you haven’t checked out the repo for the module loader API polyfill yet, I strongly encourage you to at least take a peek.

如果您尚未签出模块加载程序API polyfill的存储库,强烈建议您至少看一眼

Moreover, if you want to test-drive this approach, check out SystemJS, which is built on top of the ES6 Module Loader polyfill . SystemJS dynamically loads any module format (ES6 modules, AMD, CommonJS and/or global scripts) in the browser and in Node. It keeps track of all loaded modules in a “module registry” to avoid re-loading modules that were previously loaded. Not to mention that it also automatically transpiles ES6 modules (if you simply set an option) and has the ability to load any module type from any other type! Pretty neat.

此外,如果您想试用这种方法,请查看SystemJS ,它是在ES6 Module Loader polyfill之上构建的。 SystemJS在浏览器和Node中动态加载任何模块格式(ES6模块,AMD,CommonJS和/或全局脚本)。 它在“模块注册表”中跟踪所有已加载的模块,以避免重新加载以前加载的模块。 更不用说它还能自动转译ES6模块(如果您只是设置一个选项),并且能够从任何其他类型加载任何模块类型! 漂亮整齐。

现在有了本机ES6模块,是否仍需要捆绑器? (Will we still need bundlers now that we have native ES6 modules?)

The rising popularity of ES6 modules has some interesting consequences:

ES6模块的日益普及具有一些有趣的后果:

HTTP / 2会使模块捆绑器过时吗? (Will HTTP/2 make module bundlers obsolete?)

With HTTP/1, we’re only allowed one request per TCP connection. That’s why in loading multiple resources requires multiple requests. With HTTP/2, everything changes. HTTP/2 is fully multiplexed, meaning multiple requests and responses can happen in parallel. As a result, we can serve multiple requests simultaneously with a single connection.

使用HTTP / 1,每个TCP连接只允许一个请求。 这就是为什么在加载多个资源时需要多个请求。 使用HTTP / 2,一切都会改变。 HTTP / 2是完全复用的,这意味着可以并行发生多个请求和响应。 结果,我们可以通过单个连接同时处理多个请求。

Since the cost per HTTP request is significantly lower than HTTP/1, loading a bunch of modules isn’t going to be a huge performance issue in the long run. Some argue that this means module bundling isn’t going to be necessary anymore. It’s certainly possible, but it really depends on the situation.

由于每个HTTP请求的成本大大低于HTTP / 1,因此从长远来看,加载一堆模块不会成为一个巨大的性能问题。 有人认为这意味着不再需要模块捆绑。 当然有可能,但这确实取决于情况。

For one, module bundling offers benefits that HTTP/2 doesn’t account for, like removing unused exports to save space. If you’re building a website where every tiny bit of performance matters, bundling may give you incremental advantages in the long run. That said, if your performance needs aren’t so extreme, you could potentially save time at minimal cost by skipping the build step altogether.

首先,模块捆绑提供了HTTP / 2无法解决的优势,例如删除未使用的导出以节省空间。 如果您要建立一个每一个细节都至关重要的网站,那么从长远来看,捆绑可以为您带来更多的优势。 也就是说,如果您对性能的要求不是很高,则可以完全跳过构建步骤,以最低的成本节省时间。

Overall, we’re still pretty far away from having a majority of websites serving their code over HTTP/2. I’m inclined to predict that the build process is here to stay at least for the near term.

总体而言,我们距离大多数网站都通过HTTP / 2提供代码的地方还很遥远。 我倾向于预测构建过程至少会在短期内保持下去。

PS: There are other differences with HTTP/2 as well, and if you’re curious, here’s a great resource.

PS:HTTP / 2也有其他差异,如果您很好奇,这是一个很好的资源

CommonJS,AMD和UMD会过时吗? (Will CommonJS , AMD, and UMD become obsolete?)

Once ES6 becomes the module standard, do we really need other non-native module formats?

一旦ES6 成为模块标准,我们真的需要其他非本机模块格式?

I doubt it.

我对此表示怀疑。

Web development stands to benefit greatly from following a single standardized method to import and export modules in JavaScript, free of intermediary steps. How long will it take to reach the point where ES6 is the module standard?

通过遵循一种标准化的方法来导入和导出JavaScript中的模块而无需中间步骤,Web开发将从中受益匪浅。 达到ES6作为模块标准需要多长时间?

Chances are, quite a while ;)

很可能有一段时间;)

Plus, there are many people who like having “flavors” to choose from, so the “one truthful approach” may not ever become a reality.

另外,有很多人喜欢从“口味”中选择,因此“一种真实的方法”可能永远不会成为现实。

结论 (Conclusion)

I hope this two-part post helped clear up some of the jargon developers use when talking about modules and module bundling. Go ahead and check out part I if you found any of the terms above confusing.

我希望这篇由两部分组成的文章有助于弄清开发人员在谈论模块和模块捆绑时使用的一些行话。 如果您发现上面的任何条款令人困惑,请继续进行第一部分

As always, talk to me in the comments and feel free to ask questions!

与往常一样,在评论中与我交谈,并随时提出问题!

Happy bundling :)

快乐捆绑:)

翻译自: https://www.freecodecamp.org/news/javascript-modules-part-2-module-bundling-5020383cf306/

javascript模块

 类似资料: