webpack性能优化(1):分隔/分包/异步加载+组件与路由懒加载
Author:zhoulujun Date:
webpack ensure相信大家都听过。有人称它为异步加载,也有人说做代码切割。
异步加载,或称为代码分割,是一种优化技术。它允许将代码分割成不同的块,并在实际需要时异步加载它们。
那这个家伙到底是用来干嘛的?
其实说白了,它就是把js模块给独立导出一个.js文件的,然后使用这个模块的时候,webpack会构造script dom元素,由浏览器发起异步请求这个js文件。这样解决整个项目打包成同一个非常大js、css,首屏加载慢等问题——可以减少应用程序的初始加载时间,因为用户只需在开始时下载核心代码,additional features 或内容可以根据用户的操作按需加载。
其实和我们加载百度统计代码类似, 把一些js模块给独立出一个个js文件,然后需要用到的时候,在创建一个script对象,加入到document.head对象中即可,浏览器会自动帮我们发起请求,去请求这个js文件,在写个回调,去定义得到这个js文件后,需要做什么业务逻辑操作。
什么是懒加载
懒加载也叫延迟加载,即在需要的时候进行加载,随用随载。
当页面中一个文件过大并且还不一定用到的时候,我们希望在使用到的时才开始加载,这就是按需加载。要实现按需加载,我们一般想到的方法:动态创建script标签,并将src属性指向对应的文件路径。
为什么需要懒加载
在单页应用中,如果没有应用懒加载,运用webpack打包后的文件将会异常的大,造成进入首页时,需要加载的内容过多,延时过长,不利于用户体验,而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载时间。
实现过程中存在的问题:
怎么保证相同的文件只加载一次?
怎么判断文件加载完成?
文件加载完成后,怎么通知所有引入文件的地方?
webpcak 的按需加载已经完美解决了上述问题,但如何与webpack配合实现组件懒加载?
如何与webpack配合实现组件懒加载
webpack chunk 流
webpack配置文件中的output路径配置chunkFilename属性
output: { path: resolve(__dirname, 'dist'), filename: '[name].js?[chunkhash]', chunkFilename: '[name].js?[hash:5]', publicPath: '/assets/' },
chunkFilename路径将会作为组件懒加载的路径
webpack支持的异步加载方法回顾
webpack1
在 Webpack 1 中,实现代码分割的方法是使用 require.ensure 函数。该函数允许定义一个分割点,Webpack 会将其内部 require 的模块打包到一个单独的 chunk 中。
require.ensure
v1和v2均可使用, 但V2向原生支持 ES2015 (ES6) 模块语法迈,虽然此方法仍然可用,所以此方法不推荐!
require.ensure([], function() { var module = require('../../jsLib/module'); //do something })
require方式可以将多个模块js组合分割打包,
require下面方法ensure第一个参数是依赖,如果不需要请写[](空数组)
而import只能将每个模块独立打包成一个js文件;
也就是说,如果现在有三个导航A、B、C,你现在用require可以将A单独分割出来做懒加载,进入a模块只请求A,B和C你可以组合在一起进行分割,进入B和C将加载共同一个文件;
System.import();
Webpack 2.1.0-beta.28 版本后,System.import() 被标记为已弃用,并被建议用新的 import() 语法替代。
System.import('./module') .then(module => { // 使用加载的模块 }) .catch(err => { // 处理加载错误 });
webpack2
Webpack 2 引入了对 ES2015 (ES6) 模块语法的原生支持,包括 import()
() => import(URL)
webpack2官网开始推荐,官方文档webpack中使用import()
import('./module').then(module => { var foo = module.default; // 使用模块 }).catch(err => { console.log('Chunk loading failed'); });
require是由webpack社区提供方案,import为es官方提供;
import 语法是 ES6(ES2015)引入的 JavaScript 官方模块系统,但import() 作为动态导入的语法,是 ECMAScript (ES) 的一个提案,这个提案允许开发者在运行时动态地加载 ES 模块。
这个提案的标识是 proposal-dynamic-import,它提供了一种异步加载模块的语法,可以在代码执行过程中按需加载模块。使用 import() 返回的是一个 Promise 对象,这使得它可以很自然地融入到基于 Promise 的异步编程模式中。
至 2023 年初,动态导入已被大多数现代 JavaScript 环境和浏览器支持,但是还为纳入ES标准!
更多的,推荐阅读 《webpack 的打包原理》
Webpack 4
Webpack 4 进一步简化了代码分割,通过配置 optimization.splitChunks 可以更容易地自定义和优化代码分割的行为。import() 继续作为动态导入的主要方式。
在 Webpack 4 中,还可以使用魔法注释来为异步加载的 chunk 指定名称。
import(/* webpackChunkName: "my-chunk-name" */ './module').then(module => { var foo = module.default; // 使用模块 });
Webpack 5
Webpack 5 在此基础上进一步改进了许多现有特性,并提供了新的优化策略和配置项。比如,它引入了名为 Module Federation 的高级特性,使得不同的 webpack 构建项目可以共享模块或chunk,即使它们部署在不同的地方。
这个部分参看:《webpack5 Module Federation学习杂记》
webppack异步加载具体应用
如果遇到使用import 报错,需要安装babelrc, 需要配合babel的syntax-dynamic-import插件使用, 具体使用方法如下
npm install --save-dev babel-core babel-loader babel-plugin-syntax-dynamic-import babel-preset-es2015
webpack babel-loader 需要配置
use: [{ loader: 'babel-loader', options: { presets: [['es2015', {modules: false}]], plugins: ['syntax-dynamic-import'] } }]
使用如下
//导入整个模块 import('./component').then(Component => /* ... */); //使用await async function determineDate() { const moment = await import('moment'); return moment().format('LLLL'); } determineDate().then(str => console.log(str));
vue-router配置路由:vue官方文档:路由懒加载(使用import())
{ path: '/', component: () => import('../pages/home.vue'), meta: { title: 'home' }}
component: resolve => require(['../pages/home.vue'], resolve)
vue-router配置路由,使用webpack的require.ensure技术,也可以实现按需加载。
{ // 进行路由配置,规定'/'引入到home组件 path: '/', component: resolve => require(['../pages/home.vue'], resolve), meta: { title: 'home' }}
这是异步加载组件,当你访问 / ,才会加载 home.vue。
对于vue的路由配置文件(routers.js)
用import引入的话,当项目打包时路由里的所有component都会打包在一个js中,造成进入首页时,需要加载的内容过多,时间相对比较长。
当用require这种方式引入的时候,会将你的component分别打包成不同的js,加载的时候也是按需加载,只用访问这个路由网址时才会加载这个js。
你可以打包的时候看看目录结构就明白了。
require: 运行时调用,理论上可以运用在代码的任何地方,
import:编译时调用,必须放在文件开头
router中实现懒加载
vue的单页面(SPA)项目,必然涉及路由按需的问题
路由中配置异步组件
export default new Router({ routes: [ { mode: 'history', path: '/my', name: 'my', component: resolve =>require(['../page/my/my.vue'], resolve),//懒加载 }, ] })
实例中配置异步组件
components: { historyTab: resolve => {require(['../../component/historyTab/historyTab.vue'], resolve)},//懒加载 //historyTab: () => import('../../component/historyTab/historyTab.vue') },
全局注册异步组件
Vue.component('mideaHeader', () => { System.import('./component/header/header.vue') })
关于webpack异步加载的问题
多次进出同一个异步加载页面是否会造成多次加载组件?在多个地方使用同一个异步组件时是否造成多次加载组件?
否,首次需要用到组件时浏览器会发送请求加载组件,加载完将会缓存起来,以供之后再次用到该组件时调用
如果在两个异步加载的页面中分别同步与异步加载同一个组件时是否会造成资源重用?
会, 将会造成资源重用, 根据打包后输出的结果来看, a页面中会嵌入historyTab组件的代码, b页面中的historyTab组件还是采用异步加载的方式, 另外打包chunk;在协同开发的时候全部人都使用异步加载组件
在异步加载页面中载嵌入异步加载的组件时对页面是否会有渲染延时影响?
会, 异步加载的组件将会比页面中其他元素滞后出现, 页面会有瞬间闪跳影响;因为在首次加载组件的时候会有加载时间, 出现页面滞后, 所以需要合理的进行页面结构设计, 避免首次出现跳闪现象;
只要文章:
VUE2组件懒加载浅析 https://www.cnblogs.com/zhanyishu/p/6587571.html
解析 Webpack中import、require、按需加载的执行过程 https://segmentfault.com/a/1190000013630936
揭秘webpack按需加载原理 https://zhuanlan.zhihu.com/p/159216534
vue项目实现按需加载的3种方式:vue异步组件、es提案的import()、webpack的require.ensure() https://segmentfault.com/a/1190000011519350
https://webpack.js.org/guides/code-splitting/
转载本站文章《webpack性能优化(1):分隔/分包/异步加载+组件与路由懒加载》,
请注明出处:https://www.zhoulujun.cn/html/tools/Bundler/webpackTheory/8384.html
延伸阅读:
- process.argv妙用:webpack打包,自定义命令参数
- webpack4与UglifyJs打包如何清除(删除)console,配置uglifyOptions参数
- webpack5新功能展望
- webpack loader与plugin有何区别,分别如何实现?
- webpack外网ip访问
- webpack多页面配置
- webpack4从零开始搭建react与vue工程过程实录
- webpack性能优化(2):splitChunks用法详解
- webpack原理(1):Webpack热更新实现原理代码分析
- webpack原理(2):ES6 module在Webpack/vite中如何Tree-shaking构建
- webpack原理(3):Tapable源码分析及钩子函数作用分析