原文链接:Webpack and Dynamic Imports: Doing it Right
webpack和异步加载
在webpackv1中,我们推荐使用AMD的require或者webpack的require.ensure来实现动态加载模块。但在这篇文章,我们将介绍webpackV2支持的ES2015 dynamic import方式来加载模块,需要用到babel插件和webpack一些特性。
基础用法
语法比较简单:
1 | import("module/foo").then(foo => console.log(foo.default)) |
上面的代码将在运行时加载foo模块,打印出模块的默认输出。import接收一个字符串参数。
动态加载模块的语法
假设你的应用在移动端和PC端有不用的展现方式,只有一种响应式的设计不能满足要求的,那么就需要在不同的设备上加载不同的页面去渲染。作为一个聪明的开发者,如果用户使用的是移动端那么你是不希望去加载PC端的代码,反之亦然。那么将会用以下方式处理:
1 | export default function pageLoader(platform) { |
“但是,这只是一个简单的例子,真正项目里不会只有一个页面要处理”
所以,尽管我们使用了组件的动态加载方式,但还是不够灵活,让我们重构一下代码:
1 | export default function pageLoader(platform, componentName) { |
“还是不太好,应用不只是支持这两个设备,以后可能会支持更多的设备”
1 | const load = (platform="desktop") => componentName => import(`components/${platform}/${componentName}`); |
在这个例子中,我们采用了更优雅的方法。可以适用任何我们想用的平台。
加载文件存在的问题
在webpack中通常我们处理图片的方式是使用file-loader,file-loader会将文件路径映射到模块内。
1 | import(`assets/images/${imageName}.jpg`).then( src => ... ) |
现在存在的问题是:如果你想要动态加载一个文件,像这个例子中的图片,webpack会生成一个额外的chunk:
1 | webpackJsonp([4],{ |
所以最大的问题是: 当你请求一个异步加载的图片时,会先发一个网络请求去请求该模块的chunk,再去请求图片。每个图片都需要请求两次才能加载成功,如果使用https的话那么就更糟糕了。
那么如何解决这个问题呢:
webpack“神奇的注释”
webpack为动态引入增加了一个非常好的特性:神奇的注释。在配置中添加一些注释,以告诉webpack如何创建并加载该chunk。
Webpack Mode
1 | import(/* webpackMode: "eager" */ `assets/images/${imageName}.jpg`) |
这将使webpack把该异步加载的chunk加入到其父chunk中,不再去单独创建一个chunk。这种方式下,所有的文件路径在父chunk加载的时候就加载进来了。
webpack有四种模式(lazy,lazy-once,eager,weak)来处理异步加载。具体看这里。
Webpack Chunk名称
“hey,我注意到webpack将异步加载的模块命名为数字,这样调试起来有点麻烦,因为我不知道某个特定的chunk是否加载!”
我们既然可以控制加载方式,那么也可以通过注释去改变chunk的名称,例如:
1 | import(/* webpackChunkName: "foo-image" */ "assets/images/foo.jpg"); |
Prefetch/Preload
注:这个特性在webpackV4.6之后才有
如果你使用的是http2,那么最好将大chunk分割成小的chunk。所以,最好的方式是将异步加载的chunk和父chunk分开并且在请求图片前加载好图片的chunk。
那就需要用webpackPrefetch: true来替代webpackMode: eager。
“那么Prefetch和Preload有什么区别吗?”
webpack的文档解释的更详细:
- A preloaded chunk starts loading in parallel to the parent chunk. A prefetched chunk starts after the parent chunk finish.
preload:异步chunk和其父chunk是并行加载的。prefetch:异步chunk是在父chunk加载完之后再加载的。- A preloaded chunk has medium priority and instantly downloaded. A prefetched chunk is downloaded in browser idle time.
preload:异步chunk在浏览器中有中等优先级,会立即下载。prefetch:异步chunk会在浏览器空闲的时候下载。- A preloaded chunk should be instantly requested by the parent chunk. A prefetched chunk can be used anytime in the future.
preload:异步chunk在父chunk加载完之后会立刻发起请求。prefetch:异步chunk会在父chunk加载完之后的任意时间发起。- Browser support is different.
浏览器支持是有差异的
因为prefetch chunk会在浏览器空闲的时候加载,所以可以加数字注释说明chunk加载的顺序。
1 | // 0 is same as true |
顺序将是:bar>foo>slowpoke。
深入研究代码分割
如果你想要深入研究在单页应用中是如何进行懒加载的,可以看我之前的两篇文章,虽然是用react作为例子的,你可以在任何基于SPA的框架/库中运用相同的思想:
Lazy Loading with React and Webpack 2
Lazy Loading with React + Redux and Webpack 2
结束语
代码分割是一个强大的功能,可以使应用变得更快,更智能的去加载依赖关系。但是正如uncle Ben曾经说的:”with great power comes great responsibility” 。了解工具是如何工作的才能最大限度发挥他的性能,希望这篇文章能对你有所帮助!