webpack代码拆分
by Kalalau Cantrell
通过Kalalau Cantrell
通过示例学习Webpack:在Vanilla JavaScript应用程序中进行简单的代码拆分 (Learn Webpack by example: simple code-splitting in a vanilla JavaScript app)
使用webpack 4和动态导入 (Using webpack 4 and dynamic imports)
This article is part of an episodic guide for learning Webpack through various examples. If you need a refresher on what loaders and plugins are as far as Webpack goes, or what a basic webpack.config.js
file looks like, check out this article I wrote that focuses on those basics.
本文是通过各种示例学习Webpack的临时指南的一部分。 如果您需要关于Webpack的加载器和插件的最新知识,或者基本的webpack.config.js
文件的外观,请阅读我写的这篇文章 ,重点介绍这些基础知识。
If you are like I was, you have heard the term code-splitting before and have read some about it. But perhaps you kept running into posts about how to do it with this or that framework, rather than explanations of what is it for, and a basic example showing that purpose.
如果您像我一样,那么您以前曾经听说过“代码拆分”一词,并已阅读过有关它的内容。 但是,也许您一直不停地发布有关如何使用此框架或该框架进行操作的文章,而不是对其用途的解释,以及表明该目的的基本示例。
Although I highly value frameworks and the speed and structure they bring to coding, especially within teams, I also value understanding programming concepts as deeply as is practically possible.
尽管我非常重视框架以及它们为编码带来的速度和结构,尤其是在团队内部,但我也非常重视在实践中尽可能深入地理解编程概念。
This often means that if I’m trying to learn a new concept, I will try to decompose that concept into smaller sub-concepts and then study each in isolation before integrating them all together.
这通常意味着,如果我想学习一个新概念,我将尝试将该概念分解为较小的子概念,然后在将它们整合在一起之前对其进行单独研究。
To use a non-programming example, if wanted to learn how to longboard, I’d want to focus heavily on just keeping my balance while standing on the longboard before I worry about going fast, doing tricks, or customizing my longboard setup.
要使用一个非编程示例,如果想学习如何进行退潮,我想集中精力只是在站着长板时保持平衡,然后再担心快,技巧或自定义长板设置。
So, to learn about code-splitting, I decided that I wanted to make as small and easy of an app as possible, which for me meant no frameworks and nothing fancy.
因此,为了学习代码拆分,我决定要使应用程序尽可能的小巧易用,这对我来说意味着没有框架,也没有花哨的东西。
应用概述 (App overview)
I want to share the little app that I made to help me explore code-splitting with Webpack. My hope is that it may help you better understand the topic as well. All we’re going to do is make the single page app depicted in the below gif.
我想分享为帮助我探索Webpack进行代码拆分而制作的小应用程序。 我希望它也可以帮助您更好地理解该主题。 我们要做的就是制作下面gif中描述的单页应用程序。
If you want to follow along in your code editor, check out the code in the code-split
branch of this repo. Once you install the packages, npm start
will run a development server for you if you want to see the code-splitting in action.
如果要继续使用代码编辑器,请在此repo的code-split
分支中检出代码。 安装软件包后,如果您想查看实际的代码拆分, npm start
将为您运行开发服务器。
Our app has two routes — a home route, which the user starts off on, and a tasty route. The view for the home route is very basic — just a header and a link to the tasty route.
我们的应用程序有两条路线-用户开始使用的本地路线和一条美味路线。 本地路线的视图非常基本-仅是标题和指向该美味路线的链接。
The view for the tasty route, however, has much more going on. It features a delightful donut animation made with SVG and all the markup and CSS that goes along with that kind of thing. That is a lot of code compared to our home route. P.S. thanks Ben Visser for creating the animation.
然而,关于美味路线的看法还有很多。 它具有由SVG以及与之相关的所有标记和CSS制作的令人愉悦的甜甜圈动画。 与我们的本地路线相比,这是很多代码。 PS感谢Ben Visser制作动画。
Does it make sense to have the user download all the code for this app right away, including the code for the tasty route and its animation? Only if you were interested in causing slow initial load times and frustration, not to mention fear of missing out on what could have happened if the user stuck around for your app to load ;). So, let’s figure out how to code-split this thing.
让用户立即下载此应用程序的所有代码(包括美味路线及其动画的代码)是否有意义? 仅当您有兴趣让缓慢的初始加载时间和沮丧的事情发生时,更不用说担心会遗漏如果用户停留在要加载您的应用程序时可能发生的事情;)。 因此,让我们弄清楚如何对这件事进行代码拆分。
First, however, is a high-level overview of the code behind the app. The app is written in vanilla JS. I only used one external library, navigo
, to handle our client-side routing. Let’s look at the index.js
file:
但是,首先是应用程序背后的代码的高级概述。 该应用程序是使用Vanilla JS编写的。 我只使用了一个外部库navigo
来处理我们的客户端路由。 让我们看一下index.js
文件:
And here’s what the App
module does:
这是App
模块的作用:
And here’s an example of a UI component, our Home
component:
这是一个UI组件(我们的Home
组件)的示例:
没有代码拆分 (No code-splitting)
Without code-splitting, you would be sending your user one big bundle of code when they initially load your app. Let’s establish a baseline by looking at the size of our bundle here with no code-splitting.
如果没有代码拆分,您将在用户最初加载应用程序时向他们发送一大堆代码。 让我们通过查看没有打包代码的包的大小来建立基线。
You can see in the image below that the size of our bundle is 22.8K. Although that’s not very big compared to real apps in the world, let’s pretend it is for the sake of learning.
您可以在下面的图像中看到,我们的捆绑包的大小为22.8K 。 尽管与世界上的真实应用相比,这并不算太大,但我们还是假装它是为了学习。
动态导入的代码拆分 (Code-splitting with dynamic imports)
Now let’s bring in the code-splitting! Remember, what we want to do is keep the user from having to download the code needed to render the tasty route until it is needed.
现在让我们进行代码拆分! 记住,我们要做的是使用户不必下载渲染美味路线所需的代码,直到需要它为止。
To accomplish this, we our going to use a feature that is coming to JavaScript called dynamic imports. Even though the feature hasn’t landed in the ECMAScript spec yet, Webpack and Babel allow us to use it now.
为了实现这一点,我们将使用JavaScript中的一种称为动态导入的功能 。 即使该功能尚未出现在ECMAScript规范中,Webpack和Babel仍允许我们现在使用它。
A dynamic import allows us to asynchronously fetch a module. It returns a promise. Within the promise callback, we can specify what to do with the module once it’s loaded. The syntax for a dynamic import looks like this:
动态导入使我们能够异步获取模块。 它返回一个承诺。 在promise回调中,我们可以指定模块加载后的处理方式。 动态导入的语法如下所示:
import('./path/to/module')
When Webpack sees a dynamic import like this, it does not bundle up the imported module into the current bundle. Instead, it splits the bundle into two smaller chunks.
当Webpack看到这样的动态导入时,它不会将导入的模块捆绑到当前捆绑中。 而是将捆绑包分成两个较小的块。
The current chunk may be synchronously loaded (like our initial page load), but the module that is imported by our dynamic import is asynchronously loaded. In our case, the module for the tasty route is loaded when the user vists that route.
当前块可以同步加载(例如我们的初始页面加载),但是动态导入的模块是异步加载的。 在我们的例子中,对于美味的路由模块被加载时,该路由的用户vists。
In order to access the dynamic import feature, we’ll need to npm install
a few Babel packages into our build process: babel-core
, babel-loader
and babel-plugin-syntax-dynamic-import
are definitely needed.
为了访问动态导入功能,我们需要npm install
在构建过程中npm install
一些Babel软件包:绝对需要babel-core
, babel-loader
和babel-plugin-syntax-dynamic-import
。
Depending on the browser you’re using, you may not need babel-preset-env
(i.e. the current version of Chrome supports all the other JavaScript syntax we’re using) but let’s get it anyway just for good measure.
根据您使用的浏览器,您可能不需要babel-preset-env
(即,当前版本的Chrome支持我们使用的所有其他JavaScript语法),但是无论如何,还是让我们获得它,只是为了很好。
Then, we configure Webpack for Babel:
然后,我们为Babel配置Webpack:
So finally, we can write our dynamic import:
最后,我们可以编写动态导入:
Here’s what this code says to do: when the tasty route is triggered, first fetch the Tasty
component. Then, once it finishes loading, render it to the page.
这是这段代码的说明:触发美味路线时,首先获取Tasty
组件。 然后,完成加载后,将其呈现到页面上。
Let’s see what this does for us. In the image below, you can see that the initial page load now downloads a bundle that’s 10.8K instead of 22.8K — much better! Then, when the user clicks to go to the tasty route, another bundle chunk of 13.6K is downloaded.
让我们看看这对我们有什么作用。 在下图中,您可以看到初始页面加载现在下载了10.8K而不是22.8K的捆绑软件 -更好! 然后,当用户单击以转到该美味路线时,将下载另一个13.6K捆绑包。
Webpack automatically names these chunks — if you want control over that, check out this section of the webpack docs.
Webpack会自动命名这些块-如果您想对其进行控制,请查看webpack文档的这一部分 。
UX改进 (UX improvements)
It’s great that we’ve saved the user from having to wait extra time for the page to initially load. But, can you guess what would happen if the user was on a super slow connection and tried to load the tasty route?
非常棒的是,我们使用户不必再花时间等待页面首次加载,从而节省了很多时间。 但是,你可以猜测,如果用户是在一个超慢的连接,并试图加载美味的路线会发生什么?
With the way we currently have things setup, the page would just hang there until the Tasty
module fully loaded. These few moments of hanging might leave the user wondering if our app was even working.
使用我们当前进行事物设置的方式,页面将一直挂在那里,直到Tasty
模块完全加载为止。 挂起的这些片刻可能会让用户怀疑我们的应用程序是否还在运行。
Let’s improve this experience by giving the user some signal that our app is doing something while they wait:
让我们通过在用户等待时向用户发出一些信号来改善我们的体验:
Now, our app will show a loading spinner while the Tasty
component loads. While this may increase the size of our initial bundle some, it gives the user an indication that something is going on while they wait.
现在,当Tasty
组件加载时,我们的应用程序将显示一个加载微调器。 尽管这可能会增加一些初始捆绑包的大小,但它可以向用户指示在等待时发生了某些事情。
This trade off exchanges some performance for a better user experience — finding that balance is what it’s all about!
这种权衡会交换一些性能以获得更好的用户体验-找到平衡点就是要解决的问题!
结论和进一步阅读 (Conclusion and further reading)
I hope this example served as a simple portrayal of the benefit of code-splitting as well as how to use a tool like Webpack to help you do it.
我希望这个示例可以简单地描述代码拆分的好处,以及如何使用Webpack之类的工具来帮助您实现这一目标。
I also hope it showed that code-splitting is not a technique that’s useful just for certain frameworks. In fact, vanilla JS apps can make use of code-splitting, and even apps that are mostly server-rendered but have interactive widgets embedded here and there can make use of the technique.
我还希望它表明代码拆分不是仅对某些框架有用的技术。 实际上,香草JS应用程序可以利用代码拆分功能,甚至大多数服务器渲染的应用程序都可以利用该技术,而这些应用程序在此处和那里嵌入了交互式小部件。
If you want to dive deeper into some granular code-splitting that webpack lets you do, look into the optimization.splitChunks
plugin that comes with webpack 4.
如果您想更深入地研究webpack可以进行的一些细粒度的代码拆分,请查看webpack 4随附的optimization.splitChunks
插件。
Please clap if this helped you learn something, comment below with any questions, and feel free to say hi to me on Twitter.
如果这有助于您学习,请鼓掌,在下面对任何问题进行评论,并随时在Twitter上对我问好。
webpack代码拆分