webpack 优化从两个方面入手。
一、优化开发体检。包括优化构建速度和优化使用体验。参考链接优化开发体验
二、优化输出质量,包括减少用户能感知到的加载时间(首屏加载时间)和提升流畅度(提升代码性能)。
参考webpack官网、深入浅出webpack
减少用户能感知到的加载时间
压缩代码
压缩后,代码文件会变小,且混淆。文件变小会使在网络中传输更优化一点。使用UglifyJS进行压缩。-
代码分离
代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以 按需加载或并行加载 这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。
代码分离是为了防止文件过大,多页面的时候有重复代码导入等原因。
常用的代码分离方法有三种:- 入口起点:使用
entry
配置手动地分离代码。(也就是多页面配置,entry中有多个入口的情况下,打包出来的就是按照入口分离的。但是这种没有去除重复的。)
// 1.入口起点 缺点:重复模块都会引入。比如lodash entry: { index: './src/index.js', another: './src/another-module.js', },
- 防止重复:使用
SplitChunksPlugin
去重和分离 chunk。(替代了webpack3.0版本中的CommonsChunkPlugin)
分离的方式是:
多入口的时候:将公共的代码分离到一个默认为vendors的chunk里面。然后每个入口是一个chunk.比如我有两个入口 a.js 和 b.js 。那么打包出来的就是a.bundle.js 、 b.bundle.js、 vendors~a~b.bundle.js。
单入口的时候:会按照设置规则打包。比如默认的设置这种。会将入口文件打包成一个文件比如index.bundle.js,将里面引用的比较大的文件,打包成单独的文件为vendors~index.bundle.js。
// 2.防止重复 如果是多页面应用可以去重并分离。如果是单页面应用可以做到分离。 optimization: { splitChunks: { chunks: 'all' }, },
- 动态导入:通过模块中的内联函数调用来分离代码。动态引入的文件也会单独打包出来。
包括两种方式。 第一种,推荐的,使用符合 ECMAScript提案的 import() 语法 来实现动态导入。第二种,则是 webpack 的遗留功能,使用 webpack 特定的 require.ensure。他们的原理就是利用JSONP。参考Webpack的异步加载原理及分包策略。动态导入完整代码参考官网介绍。
- 入口起点:使用
异步加载原理
webpack ensure 有人称它为异步加载,也有人称为代码切割,他其实就是将 js 模块给独立导出一个.js 文件,然后使用这个模块的时候,再创建一个 script 对象,加入到 document.head 对象中,浏览器会自动帮我们发起请求,去请求这个 js 文件,然后写个回调函数,让请求到的 js 文件做一些业务操作。
异步加载 就是动态导入。通过异步加载实现了按需加载。懒加载= 按需加载。依靠的方式是动态导入的方式
- 按需加载。就说用的时候才加载。框架里面的路由配置。多数都配置成路由按需加载。会自动生成对应的chunk。
- CDN 加速
针对 HTML 文件:不开启缓存,把 HTML 放到自己的服务器上,而不是 CDN 服务上,同时关闭自己服务器上的缓存。自己的服务器只提供 HTML 文件和数据接口。
针对静态的 JavaScript、CSS、图片等文件:开启 CDN 和缓存,上传到 CDN 服务上去,同时给每个文件名带上由文件内容算出的 Hash 值, 例如app_a6976b6d.css 文件。 带上 Hash 值的原因是文件名会随着文件内容而变化,只要文件发生变化其对应的 URL 就会变化,它就会被重新下载,无论缓存时间有多长。
用 Webpack 实现 CDN 的接入。配置如下:
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const {WebPlugin} = require('web-webpack-plugin');
module.exports = {
// 省略 entry 配置...
output: {
// 给输出的 JavaScript 文件名称加上 Hash 值
filename: '[name]_[chunkhash:8].js',
path: path.resolve(__dirname, './dist'),
// 指定存放 JavaScript 文件的 CDN 目录 URL
publicPath: '//js.cdn.com/id/',
},
module: {
rules: [
{
// 增加对 CSS 文件的支持
test: /\.css$/,
// 提取出 Chunk 中的 CSS 代码到单独的文件中
use: ExtractTextPlugin.extract({
// 压缩 CSS 代码
use: ['css-loader?minimize'],
// 指定存放 CSS 中导入的资源(例如图片)的 CDN 目录 URL
publicPath: '//img.cdn.com/id/'
}),
},
{
// 增加对 PNG 文件的支持
test: /\.png$/,
// 给输出的 PNG 文件名称加上 Hash 值
use: ['file-loader?name=[name]_[hash:8].[ext]'],
},
// 省略其它 Loader 配置...
]
},
plugins: [
// 使用 WebPlugin 自动生成 HTML
new WebPlugin({
// HTML 模版文件所在的文件路径
template: './template.html',
// 输出的 HTML 的文件名称
filename: 'index.html',
// 指定存放 CSS 文件的 CDN 目录 URL
stylePublicPath: '//css.cdn.com/id/',
}),
new ExtractTextPlugin({
// 给输出的 CSS 文件名称加上 Hash 值
filename: `[name]_[contenthash:8].css`,
}),
// 省略代码压缩插件配置...
],
};
以上代码中最核心的部分是通过 publicPath 参数设置存放静态资源的 CDN 目录 URL, 为了让不同类型的资源输出到不同的 CDN,需要分别在:
- output.publicPath 中设置 JavaScript 的地址。
- css-loader.publicPath 中设置被 CSS 导入的资源的的地址。
- WebPlugin.stylePublicPath 中设置 CSS 文件的地址。
提升流畅度
1.使用 Prepack
实际上 Prepack 就是一个部分求值器,编译代码时提前将计算结果放到编译后的代码中,而不是在代码运行时才去求值。
Prepack 的工作原理和流程大致如下:
通过 Babel 把 JavaScript 源码解析成抽象语法树(AST),以方便更细粒度地分析源码;
Prepack 实现了一个 JavaScript 解释器,用于执行源码。借助这个解释器 Prepack 才能掌握源码具体是如何执行的,并把执行过程中的结果返回到输出中。
从表面上看去这似乎非常美好,但实际上 Prepack 还不够成熟与完善。Prepack 目前还处于初期的开发阶段,局限性也很大。可以看官网或者看书。不再多做介绍。
总结
优化输出质量。就是更快的让用户能看到页面。一方面页面足够小(加载会快点,利用压缩),一次不要加载太多(解决首屏慢)。一方面则是利用网络可以一次请求4-7个请求。利用此特点,则不能全部放到一个文件中,合理的使用代码分离。进而可以做到按需加载。按需加载又解决了首屏慢的问题。另外通过cdn可以让用户访问最近的代码。加快请求。还可以做一些预加载之类的。
在页面请求渲染的整个过程中,每一项都可以有优化的点。但是物极必反。合理的利用才是最好的选择。