主要从下面几个地方优化:
- 在HTML内实现Loading状态或者骨架屏
- 去掉外联 css
- 缓存基础框架
- 使用动态 polyfill
- 使用 SplitChunksPlugin 拆分公共代码
- 正确地使用 Webpack 4.0 的 Tree Shaking
- 使用动态 import,切分页面代码,减小首屏 JS 体积
- 编译到 ES2015+,提高代码运行效率,减小体积
- 使用 lazyload 和 placeholder 提升加载体验
------------------------------在HTML内实现Loading状态或者骨架屏------------------------
1. 普通的处理方式,自定义Loading界面 (可以加载SVG文件)
2. 使用html-webpack-plugin自动插入loading
var HtmlWebpackPlugin = require('html-webpack-plugin');
var path = require('path');
// 读取写好的 loading 态的 html 和 css
var loading = {
html: fs.readFileSync(path.join(__dirname, './loading.html')),
css: '<style>' + fs.readFileSync(path.join(__dirname, './loading.css')) + '</style>'
}
var webpackConfig = {
entry: 'index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'index_bundle.js'
},
plugins: [
new HtmlWebpackPlugin({
filename: 'xxxx.html',
template: 'template.html',
loading: loading
})
]
};
然后在模板中引用即可:
<!DOCTYPE html>
<html lang="en">
<head>
<%= htmlWebpackPlugin.options.loading.css %>
</head>
<body>
<div id="root">
<%= htmlWebpackPlugin.options.loading.html %>
</div>
</body>
</html>
3.使用prerender-spa-plugin渲染首屏(参考: https://zhuanlan.zhihu.com/p/31954853)
在一些比较大型的项目中,Loading 可能本身就是一个 React/Vue 组件,在不做服务器端渲染的情况下,想把一个已经组件化的 Loading 直接写入 html 文件中会很复杂,不过依然有解决办法。
prerender-spa-plugin 是一个可以帮你在构建时就生成页面首屏 html 的一个 webpack 插件,原理大致如下:
1).指定 dist 目录和要渲染的路径
2).插件在 dist 目录中开启一个静态服务器,并且使用无头浏览器(puppeteer)访问对应的路径,执行JS,抓取对应路径的 html。
3).把抓到的内容写入 html,这样即使没有做服务器端渲染,也能达到跟服务器端渲染几乎相同的作用(不考虑动态数据的话)
plugins: [
new PrerenderSpaPlugin(
path.join(__dirname, 'dist'),
[ '/', '/products/1', '/products/2', '/products/3']
)
]
------------------------------去掉外联CSS------------------------
实际上,webpack 默认就是没有外链 css 的,你什么都不需要做就可以了。当然如果你的项目之前配置了 extract-text-webpack-plugin 或者 mini-css-extract-plugin 来生成独立的 css 文件,直接去掉即可。
------------------------------缓存基础框架--------------------------------------------------
HTTP为我们提供了很好几种缓存的解决方案,不妨总结一下:
1. expires
expires: Thu, 16 May 2019 03:05:59 GMT
在 http 头中设置一个过期时间,在这个过期时间之前,浏览器的请求都不会发出,而是自动从缓存中读取文件,除非缓存被清空,或者强制刷新。缺陷在于,服务器时间和用户端时间可能存在不一致,所以 HTTP/1.1 加入了 cache-control 头来改进这个问题。
2. cache-control
cache-control: max-age=31536000
设置过期的时间长度(秒),在这个时间范围内,浏览器请求都会直接读缓存。当 expires 和 cache-control 都存在时,expires 的优先级更高。
3. last-modified / if-modified-since
这是一组请求/相应头
响应头:
last-modified: Wed, 16 May 2018 02:57:16 GMT
请求头:
if-modified-since: Wed, 16 May 2018 05:55:38 GMT
服务器端返回资源时,如果头部带上了 last-modified,那么资源下次请求时就会把值加入到请求头 if-modified-since中,服务器可以对比这个值,确定资源是否发生变化,如果没有发生变化,则返回 304。
4. etag / if-none-match
这也是一组请求/相应头
响应头:
etag: "D5FC8B85A045FF720547BC36FC872550"
请求头:
if-none-match: "D5FC8B85A045FF720547BC36FC872550"
原理类似,服务器端返回资源时,如果头部带上了 etag,那么资源下次请求时就会把值加入到请求头 if-none-match 中,服务器可以对比这个值,确定资源是否发生变化,如果没有发生变化,则返回 304。
上面四种缓存的优先级:cache-control > expires > etag > last-modified
------------------------------使用动态Polyfill----------------------------------------------
------------------------------使用SplitChunksPlugin拆分公共代码------------------------
Webpack 4 抛弃了原有的 CommonChunksPlugin,换成了更为先进的 SplitChunksPlugin,用于提取公用代码。
它们的区别就在于,CommonChunksPlugin 会找到多数模块中都共有的东西,并且把它提取出来(common.js),也就意味着如果你加载了 common.js,那么里面可能会存在一些当前模块不需要的东西。
而 SplitChunksPlugin 采用了完全不同的 heuristics 方法,它会根据模块之间的依赖关系,自动打包出很多很多(而不是单个)通用模块,可以保证加载进来的代码一定是会被依赖到的。
下面的配置对象代表了splitChunksPlugin的默认行为:
splitChunks: {
chunks: "async",
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
参考:https://segmentfault.com/a/1190000015938570
------------------------------正确地使用Webpack 4.0的Tree Shaking-------------------------
去掉用不到的CSS, 使用purifycss-webpack
const path = require('path');
const glob = require('glob');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const PurifyCSSPlugin = require('purifycss-webpack');
module.exports = {
entry: {...},
output: {...},
module: {
rules: [
{
test: /\.css$/,
loader: ExtractTextPlugin.extract({
fallbackLoader: 'style-loader',
loader: 'css-loader'
})
}
]
},
plugins: [
new ExtractTextPlugin('[name].[contenthash].css'),
// Make sure this is after ExtractTextPlugin!
new PurifyCSSPlugin({
// Give paths to parse for rules. These should be absolute!
paths: glob.sync(path.join(__dirname, 'app/*.html')),
})
]
};
参考: https://www.npmjs.com/package/purifycss-webpack
----------------------------使用动态import切分页面代码,减小首屏JS体积------------------------
import: 遵循组件化思想, 减少当前JS代码的体积,好处就是代码易读,减少渲染,按需加载
--------------------编译到ES2015+,提高代码运行效率,减小体积---------------------------------
遵循ES6语法
------------------------------使用Lazyload和placeholder提升加载体验------------------------
1. placeholder: 简单的处理方式, 给标签设置默认值, 或者说是占位符
2. Lazyload:懒加载
import React from 'react';
import ReactDOM from 'react-dom';
import LazyLoad from 'react-lazyload';
import MyComponent from './MyComponent';
const App = () => {
return (
<div className="list">
<LazyLoad height={200}>
<img src="tiger.jpg" /> /*
Lazy loading images is supported out of box,
no extra config needed, set `height` for better
experience
*/
</LazyLoad>
<LazyLoad height={200} once >
/* Once this component is loaded, LazyLoad will
not care about it anymore, set this to `true`
if you're concerned about improving performance */
<MyComponent />
</LazyLoad>
<LazyLoad height={200} offset={100}>
/* This component will be loaded when it's top
edge is 100px from viewport. It's useful to
make user ignorant about lazy load effect. */
<MyComponent />
</LazyLoad>
<LazyLoad>
<MyComponent />
</LazyLoad>
</div>
);
};
ReactDOM.render(<App />, document.body);
参考: https://www.npmjs.com/package/react-lazyload
欢迎纠错