按需加载,或称“懒加载”,是优化您的网站或应用程序的一种绝佳方式。这种做法本质上涉及在逻辑断点处拆分代码,然后在用户执行了需要或将需要新代码块的操作时才加载它。这加快了应用程序的初始加载速度,并减轻了其整体大小,因为某些代码块可能永远不会被加载。
让我们以《代码分割》中的示例为基础,稍微调整一下,以更深入地演示这个概念。那里的代码确实会生成一个单独的 chunk,lodash.bundle.js
,并且在脚本运行后会技术性地“懒加载”它。问题在于加载这个 bundle 不需要任何用户交互——这意味着每次页面加载时,请求都会被触发。这并没有给我们带来太大帮助,反而会对性能产生负面影响。
让我们尝试一些不同的做法。我们将添加一个交互,当用户点击按钮时,向控制台输出一些文本。但是,我们将等到第一次交互发生时才加载该代码(print.js
)。为此,我们将回到《代码分割》中最终的*动态导入*示例进行修改,并将 lodash
保留在主 chunk 中。
项目
webpack-demo
|- package.json
|- package-lock.json
|- webpack.config.js
|- /dist
|- /src
|- index.js
+ |- print.js
|- /node_modules
src/print.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
+ 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());
现在让我们运行 webpack 并查看我们新的按需加载功能
...
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]
...
在某些情况下,将模块的所有使用都转换为异步可能会很麻烦或困难,因为它强制对所有函数进行不必要的异步化,而无法仅延迟同步求值工作。
TC39 提案《模块求值延迟》旨在解决这个问题。
该提案旨在引入一种新的语法导入形式,它将始终只返回一个命名空间异质对象。使用时,模块及其依赖项不会被执行,但在模块图被认为是加载完成之前,它们会完全加载到执行就绪的状态。
只有在访问该模块的属性时,才会执行执行操作(如果需要)。
通过启用 experiments.deferImport 可以使用此功能。
项目
webpack-demo
|- package.json
|- package-lock.json
|- webpack.config.js
|- /dist
|- /src
|- index.js
+ |- print.js
|- /node_modules
src/print.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
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
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());
许多框架和库都有关于如何在各自方法中实现这一目标的建议。以下是一些示例: