webpack学习笔记--按需加载
随着互联网的发展,一个网页需要承载的功能越来越多。 对于采用单页应用作为前端架构的网站来说,会面临着一个网页需要加载的代码量很大的问题,因为许多功能都集中的做到了一个 HTML 里。 这会导致网页加载缓慢、交互卡顿,用户体验将非常糟糕。
导致这个问题的根本原因在于一次性的加载所有功能对应的代码,但其实用户每一阶段只可能使用其中一部分功能。 所以解决以上问题的方法就是用户当前需要用什么功能就只加载这个功能对应的代码,也就是所谓的按需加载。
如何使用按需加载
在给单页应用做按需加载优化时,一般采用以下原则:
- 把整个网站划分成一个个小功能,再按照每个功能的相关程度把它们分成几类。
- 把每一类合并为一个 Chunk,按需加载对应的 Chunk。
- 对于用户首次打开你的网站时需要看到的画面所对应的功能,不要对它们做按需加载,而是放到执行入口所在的 Chunk 中,以降低用户能感知的网页加载时间。
- 对于个别依赖大量代码的功能点,例如依赖 Chart.js 去画图表、依赖 flv.js 去播放视频的功能点,可再对其进行按需加载。
被分割出去的代码的加载需要一定的时机去触发,也就是当用户操作到了或者即将操作到对应的功能时再去加载对应的代码。 被分割出去的代码的加载时机需要开发者自己去根据网页的需求去衡量和确定。
由于被分割出去进行按需加载的代码在加载的过程中也需要耗时,你可以预言用户接下来可能会进行的操作,并提前加载好对应的代码,从而让用户感知不到网络加载时间。
用 Webpack 实现按需加载
Webpack 内置了强大的分割代码的功能去实现按需加载,实现起来非常简单。
举个例子,现在需要做这样一个进行了按需加载优化的网页:
- 网页首次加载时只加载 main.js 文件,网页会展示一个按钮, main.js 文件中只包含监听按钮事件和加载按需加载的代码。
- 当按钮被点击时才去加载被分割出去的 show.js 文件,加载成功后再执行 show.js 里的函数。
其中 main.js 文件内容如下:
window.document.getElementById('btn').addEventListener('click', function () {
// 当按钮被点击后才去加载 show.js 文件,文件加载成功后执行文件导出的函数
import(/* webpackChunkName: "show" */ './show').then((show) => {
show('Webpack');
})
});
show.js 文件内容如下:
module.exports = function (content) {
window.alert('Hello ' + content);
};
代码中最关键的一句是 import(/* webpackChunkName: "show" */ './show') ,Webpack 内置了对 import(*) 语句的支持,当 Webpack 遇到了类似的语句时会这样处理:
- 以 ./show.js 为入口新生成一个 Chunk;
- 当代码执行到 import 所在语句时才会去加载由 Chunk 对应生成的文件。
import返回一个 Promise,当文件加载成功时可以在 Promise 的 then 方法中获取到 show.js 导出的内容。- 在使用
import()分割代码后,你的浏览器并且要支持 Promise API 才能让代码正常运行, 因为import()返回一个 Promise,它依赖 Promise。对于不原生支持 Promise 的浏览器,你可以注入 Promise polyfill。
/* webpackChunkName: "show" */ 的含义是为动态生成的 Chunk 赋予一个名称,以方便我们追踪和调试代码。 如果不指定动态生成的 Chunk 的名称,默认名称将会是 [id].js。 /* webpackChunkName: "show" */ 是在 Webpack3 中引入的新特性,在 Webpack3 之前是无法为动态生成的 Chunk 赋予名称的。
为了正确的输出在 /* webpackChunkName: "show" */ 中配置的 ChunkName,还需要配置下 Webpack,配置如下:
module.exports = {
// JS 执行入口文件
entry: {
main: './main.js',
},
output: {
// 为从 entry 中配置生成的 Chunk 配置输出文件的名称
filename: '[name].js',
// 为动态加载的 Chunk 配置输出文件的名称
chunkFilename: '[name].js',
}
};
其中最关键的一行是 chunkFilename: '[name].js ',,它专门指定动态生成的 Chunk 在输出时的文件名称。 如果没有这行,分割出的代码的文件名称将会是 [id].js 。 chunkFilename 具体含义见2-2 配置-Output。本实例提供项目完整代码
按需加载与 ReactRouter
在实战中,不可能会有上面那么简单的场景,接下来举一个实战中的例子:对采用了 ReactRouter 的应用进行按需加载优化。 这个例子由一个单页应用构成,这个单页应用由两个子页面构成,通过 ReactRouter 在两个子页面之间切换和管理路由。
这个单页应用的入口文件 main.js 如下:
import React, {PureComponent, createElement} from 'react';
import {render} from 'react-dom';
import {HashRouter, Route, Link} from 'react-router-dom';
import PageHome from './pages/home';
/**
* 异步加载组件
* @param load 组件加载函数,load 函数会返回一个 Promise,在文件加载完成时 resolve
* @returns {AsyncComponent} 返回一个高阶组件用于封装需要异步加载的组件
*/
function getAsyncComponent(load) {
return class AsyncComponent extends PureComponent {
componentDidMount() {
// 在高阶组件 DidMount 时才去执行网络加载步骤
load().then(({default: component}) => {
// 代码加载成功,获取到了代码导出的值,调用 setState 通知高阶组件重新渲染子组件
this.setState({
component,
})
});
}
render() {
const {component} = this.state || {};
// component 是 React.Component 类型,需要通过 React.createElement 生产一个组件实例
return component ? createElement(component) : null;
}
}
}
// 根组件
function App() {
return (
<HashRouter>
<div>
<nav>
<Link to='/'>Home</Link> | <Link to='/about'>About</Link> | <Link to='/login'>Login</Link>
</nav>
<hr/>
<Route exact path='/' component={PageHome}/>
<Route path='/about' component={getAsyncComponent(
// 异步加载函数,异步地加载 PageAbout 组件
() => import(/* webpackChunkName: 'page-about' */'./pages/about')
)}
/>
<Route path='/login' component={getAsyncComponent(
// 异步加载函数,异步地加载 PageAbout 组件
() => import(/* webpackChunkName: 'page-login' */'./pages/login')
)}
/>
</div>
</HashRouter>
)
}
// 渲染根组件
render(<App/>, window.document.getElementById('app'));
以上代码中最关键的部分是 getAsyncComponent 函数,它的作用是配合 ReactRouter 去按需加载组件,具体含义请看代码中的注释。
由于以上源码需要通过 Babel 去转换后才能在浏览器中正常运行,需要在 Webpack 中配置好对应的 babel-loader,源码先交给 babel-loader 处理后再交给 Webpack 去处理其中的 import(*) 语句。 但这样做后你很快会发现一个问题:Babel 报出错误说不认识 import(*) 语法。 导致这个问题的原因是 import(*) 语法还没有被加入到在 3-1使用ES6语言中提到的 ECMAScript 标准中去, 为此我们需要安装一个 Babel 插件 babel-plugin-syntax-dynamic-import ,并且将其加入到 . babelrc 中去:
{
"presets": [
"env",
"react"
],
"plugins": [
"syntax-dynamic-import"
]
}
执行 Webpack 构建后,你会发现输出了三个文件:
main.js:执行入口所在的代码块,同时还包括 PageHome 所需的代码,因为用户首次打开网页时就需要看到 PageHome 的内容,所以不对其进行按需加载,以降低用户能感知到的加载时间;
page-about.js:当用户访问 /about 时才会加载的代码块;
page-login.js:当用户访问 /login 时才会加载的代码块。
同时你还会发现 page-about.js 和 page-login.js 这两个文件在首页是不会加载的,而是会当你切换到了对应的子页面后文件才会开始加载。本实例提供项目完整代码
webpack学习笔记--按需加载的更多相关文章
- thinkphp学习笔记9—自动加载
原文:thinkphp学习笔记9-自动加载 1.命名空间自动加载 在3.2版本中不需要手动加载类库文件,可以很方便的完成自动加载. 系统可以根据类的命名空间自动定位到类库文件,例如定义了一个类Org\ ...
- 深入浅出的webpack4构建工具---webpack+vue+router 按需加载页面(十五)
1. 为什么需要按需加载? 对于vue单页应用来讲,我们常见的做法把页面上所有的代码都打包到一个bundle.js文件内,但是随着项目越来越大,文件越来越多的情况下,那么bundle.js文件也会越来 ...
- webpack中实现按需加载
webpack中的require.ensure()可以实现按需加载资源包括js,css等,它会给里面require的文件单独打包,不和主文件打包在一起,webpack会自动配置名字,如0.js,1.j ...
- React Router 4.0 + webpack 实现组件按需加载
网上关于React Router 4.0的按需加载文章有很多,大致的思路都一样,但是其实具体实现起来却要根据自己的实际情况来定,这里主要介绍一下我的实现方式. 主要方式是通过Route组件的rende ...
- 【EF学习笔记08】----------加载关联表的数据 显式加载
显式加载 讲解之前,先来看一下我们的数据库结构:班级表 学生表 加载从表集合类型 //显示加载 Console.WriteLine("=========查询集合===========&quo ...
- node 学习笔记 - Modules 模块加载系统 (1)
本文同步自我的个人博客:http://www.52cik.com/2015/12/11/learn-node-modules-path.html 用了这么久的 require,但却没有系统的学习过 n ...
- Openstack本学习笔记——Neutron-server服务加载和启动源代码分析(三)
本文是在学习Openstack过程中整理和总结.因为时间和个人能力有限.错误之处在所难免,欢迎指正! 在Neutron-server服务载入与启动源代码分析(二)中搞定模块功能的扩展和载入.我们就回到 ...
- Android学习笔记_37_ListView批量加载数据和页脚设置
1.在activity_main.xml布局文件中加入ListView控件: <RelativeLayout xmlns:android="http://schemas.android ...
- Spring学习笔记(1)——资源加载
<!-- 占坑,迟点补充底层原理 --> Spring支持4种资源的地址前缀 (1)从类路径中加载资源——classpath: classpath:和classpath:/是等价的,都是相 ...
随机推荐
- JavaScript使用方法和技巧大全
有些时候你精通一门语言,但是会发现你其实整天在和其它语言打交道,也许你以为这些微不足道,不至于影响你的开发进度,但恰恰是这些你不重视的东西会浪费你很多时间,我一直以为我早在几年前就已经精通Ja ...
- hibernate框架学习之主键生成策略generator
1)手工控制 assigned(不限制类型) 2)数据库自动生成 uuid(字符串类型) increment(整型数值类型) identity (整型数值类型) sequence (整型数值类型) n ...
- office xml 方式
office2007以上版本(2003需要增加2007插件)可以采用xml方式操作生成excel,效率高,无并发问题,比传统com组件方式更方便
- 微信小程序-聊天列表-角标
<div class="list-body" bindtap='openChat' data-Obj='{{oitem}}'> <!-- 头像 --> &l ...
- UDP/TCP拾遗
1.UDP的特点 (1)UDP 是无连接的,即发送数据之前不需要建立连接. (2)UDP 使用尽最大努力交付,即不保证可靠交付,同时也不使用拥塞控制. (3)UDP 是面向报文的.UDP 没有拥塞控制 ...
- VC++ 错误
1.error LNK2019: unresolved external symbol _WinMain@16 referenced in function ___tmainCRTStartup解决方 ...
- python学习第42、43天 HTML\CSS
前端是什么? 帮助不了解后端程序的客户轻松使用程序的工具,可以提升工作效率,提供各种各样的体验. 通用的前端大致会使用三种语言,用在三个不同的方面对前端进行架构和优化,这里也只介绍这三种 web前端常 ...
- [转]PhpStorm中如何使用Xdebug工具,入门级操作方法(亲测有效)
0 前言 网上试过很多方案,有的根本无效,有的是有效一段时间后失效,然而这个方法是一直有效果,所以留底记录一下 1 简介 PhpStorm是一个轻量级且便捷的PHP IDE,其提供的智能代码补全,快速 ...
- emoji错误:ER_TRUNCATED_WRONG_VALUE_FOR_FIELD: Incorrect string value:
1 前言 由于mysql数据库要存储微信昵称,但是当微信昵称带有emoj表情会出现标题的错误. 然后发现是emoj编码是4个字节保存的,于mysql数据库编码格式utf8默认保存的是1到3个字节. 2 ...
- ActiveMQ-为什么需要消息中间件?
消息中间件的优势 UNIX的进程间通信就开始运用消息队列技术,一个进程将数据写入某个特定的队列中,其它进程可以读取队列中的数据,从而实现异步通信.对于如今的分布式系统,消息队列已经演变为独立的消息中间 ...