懒加载
懒加载
T> 本指南是代码分离的补充说明。如果你尚未阅读该指南,请先阅读。
懒加载,或者称为“按需加载”,是一种优化站点或应用程序的绝佳方式。这种做法本质上涉及在逻辑断点处拆分代码,然后在用户执行了需要或将需要新代码块的操作时加载它。这加快了应用程序的初始加载速度,并减轻了其整体重量,因为某些代码块可能永远不会被加载。
动态导入示例
让我们以代码分离中的示例为基础,稍作调整以更深入地展示这一概念。那里的代码确实生成了一个单独的代码块 lodash.bundle.js,并且在脚本运行时就在技术上“懒加载”了它。问题在于,加载该代码块不需要任何用户交互——这意味着每次加载页面时,都会发起请求。这对我们帮助不大,而且会对性能产生负面影响。
让我们尝试一些不同的方法。我们将添加一个交互,当用户点击按钮时向控制台记录一些文本。但是,我们将等到交互首次发生时,才加载那段代码(print.js)。为此,我们将回顾并重新编写《代码分离》中的最终《动态导入》示例,并将 lodash 保留在主代码块中。
项目
diff
webpack-demo
├── package.json
├── package-lock.json
├── webpack.config.js
├── /dist
├── /src
│ ├── index.js
+│ └── print.js
└── /node_modules
src/print.js
js
console.log(
"The print.js module has loaded! See the network tab in dev tools...",
);
export default () => {
console.log('Button Clicked: Here\'s "some text"!');
};
src/index.js
diff
+ import _ from 'lodash';
+
- async function getComponent() {
+ function component() {
const element = document.createElement('div');
- const _ = await import(/* webpackChunkName: "lodash" */ 'lodash');
+ const button = document.createElement('button');
+ const br = document.createElement('br');
+ button.innerHTML = 'Click me and look at the console!';
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+ element.appendChild(br);
+ element.appendChild(button);
+
+ // Note that because a network request is involved, some indication
+ // of loading would need to be shown in a production-level site/app.
+ button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => {
+ const print = module.default;
+
+ print();
+ });
return element;
}
- getComponent().then(component => {
- document.body.appendChild(component);
- });
+ document.body.appendChild(component());
W> 请注意,在 ES6 模块中使用 import() 时,你必须引用 .default 属性,因为当 promise 被 resolve 时,返回的是实际的 module 对象。
现在让我们运行 webpack,并查看我们新的懒加载功能:
bash
...
Asset Size Chunks Chunk Names
print.bundle.js 417 bytes 0 [emitted] print
index.bundle.js 548 kB 1 [emitted] [big] index
index.html 189 bytes [emitted]
...
延迟导入示例
W> 此功能不是懒惰地“加载”模块,而是懒惰地“评估”模块。这意味着模块仍然会被下载和解析,但其评估是懒惰的。
在某些情况下,将模块的所有用法转换为异步可能会很烦人或很困难,因为这强制对所有函数进行不必要的异步化,而无法仅延迟同步评估工作。
TC39 提案延迟模块评估旨在解决此问题。
该提案是引入一种新的语法导入形式,它只会返回一个命名空间奇异对象。使用时,模块及其依赖项不会被执行,但会在模块图被视为已加载之前,完全加载到准备执行的状态。
只有当访问此模块的属性时,才会执行(如果需要)执行操作。
此功能可通过启用 experiments.deferImport 来使用。
W> 此功能仍处于实验阶段,在 webpack 的未来版本中可能会发生变化。
项目
diff
webpack-demo
├── package.json
├── package-lock.json
├── webpack.config.js
├── /dist
├── /src
│ ├── index.js
+ │ └── print.js
└── /node_modules
src/print.js
js
console.log(
"The print.js module has loaded! See the network tab in dev tools...",
);
export default () => {
console.log('Button Clicked: Here\'s "some text"!');
};
src/index.js
diff
import _ from 'lodash';
+ import defer * as print from './print';
function component() {
const element = document.createElement('div');
const button = document.createElement('button');
const br = document.createElement('br');
button.innerHTML = 'Click me and look at the console!';
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
element.appendChild(br);
element.appendChild(button);
- // Note that because a network request is involved, some indication
- // of loading would need to be shown in a production-level site/app.
+ // In this example, the print module is downloaded but not evaluated,
+ // so there is no network request involved after the button is clicked.
- button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => {
+ button.onclick = e => {
const print = module.default;
+ // ^ The module is evaluated here.
print();
- });
+ };
return element;
}
getComponent().then(component => {
document.body.appendChild(component);
});
document.body.appendChild(component());
这类似于 CommonJS 风格的懒加载:
src/index.js
diff
import _ from 'lodash';
- import defer * as print from './print';
function component() {
const element = document.createElement('div');
const button = document.createElement('button');
const br = document.createElement('br');
button.innerHTML = 'Click me and look at the console!';
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
element.appendChild(br);
element.appendChild(button);
// In this example, the print module is downloaded but not evaluated,
// so there is no network request involved after the button is clicked.
button.onclick = e => {
+ const print = require('./print');
+ // ^ The module is evaluated here.
const print = module.default;
- // ^ The module is evaluated here.
print();
};
return element;
}
getComponent().then(component => {
document.body.appendChild(component);
});
document.body.appendChild(component());
将 import.defer() 与上下文模块一起使用
import.defer() 也适用于上下文模块 - 导入路径可以是动态表达式。webpack 将所有匹配的模块包含在模块图中,但所选模块的评估会延迟到首次访问命名空间对象上的属性时。
以下示例演示了使用动态上下文路径对语言环境模块进行延迟评估:
src/locales/en.js
js
export const greeting = "Hello";
src/locales/fr.js
js
export const greeting = "Bonjour";
src/index.js
text
const language = navigator.language.split("-")[0]; // "en", "fr", etc.
const locale = import.defer("./locales/" + language + ".js");
document.getElementById("btn").addEventListener("click", () => {
// The locale module is evaluated here, on first property access.
document.getElementById("output").textContent = locale.greeting;
});
webpack 会准备好所有匹配的语言环境模块,以便它们可以随时执行,但只有在首次访问 locale.greeting 时,才会评估所选模块。这允许你加载多个语言环境文件而无需立即执行它们。
框架
许多框架和库对于如何在其方法论中实现这一点有自己的建议。以下是一些示例:
- React: 代码分离和懒加载
- Vue: Vue.js 中的动态导入以获得更好的性能
- Angular: 懒加载路由配置 和 AngularJS + webpack = lazyLoad
帮助我们改进文档
发现翻译问题或内容错误?请告诉我们。
