前言:关于前端优化始终是无法跟构建工具相分离,下面由我从日常的前端的视角从文件的提取、合并、拆分等几个方面来说一下 webpack 是如何进行打包配置的。
写在前面:
前端是怎么做到性能优化的? 带着这个问题 我们从几个方面出发:
- 减少 HTTP 请求
- 静态资源使用 CDN
- 将 CSS 放在文件头部,JavaScript 文件放在底部
- 使用字体图标 iconfont 代替图片图标
- 善用缓存,不重复加载相同的资源
- 使用服务端渲染
- ...
详情可以看:前端性能优化 24 条建议;大佬总结的很细致,但是如果从 webpack 出发的话 我们对以上的建议可以做到几条?
答:都可以
项目准备
以 react 为例
webpack: 4.31.0
webpack-cli : 3.3.2
解释一下 为什么用的是 webpack4x,目前组内大部分都是用的 webpack4 进行构建,后面也会单独出一份关于 webpack5 的教程。
目录结构
如上图所示,当前的项目放置一些静态的文件即可。
package.json
项目开始前的准备插件
{
"name": "webpack-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server --open --config webpack.dev.js", // 本地运动
"build": "webpack --config webpack.prod.js" // 打包优化
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.13.10",
"@babel/preset-env": "^7.13.10",
"@babel/preset-react": "^7.13.13",
"babel-loader": "^8.2.2",
"html-webpack-plugin": "^3.2.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"webpack": "^4.31.0",
"webpack-cli": "^3.3.2"
},
"dependencies": {
"webpack-dev-server": "^3.11.2"
}
}
.babelrc
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
减少 http 请求
合并图片资源
npm i url-loader -D
webpack.prod.js
module: {
rules: [
...
{
test: /.(png|jpg|gif|jpeg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 102400, // 100k
},
},
],
},
],
},
场景:对于项目中有几个的小图标可以使用 url-loader 进行优化处理,它默认会把文件转为 DataURL,如果文件小于 limit,那么 url-loader 会调用file-loader进行处理。当前,图标过多推荐使用iconfont进行加载。
参考: url-loader
减少文件的搜索范围
webpack.prod.js
module: {
rules: [
...
{
test: /.js$/,
use: [
{
loader: 'babel-loader',
exclude: /node_modules/, // 不需要被解析的模块
// include: path.resolve('src') // 不需要被解析的模块
},
],
},
],
},
静态资源使用 CDN
配置publicPath
webpack.prod.js
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js', // 多个入口的情况下 不知道对应的名称、可以用占位符来指定[name]
publicPath: 'https://cdn.example.com/assets/', // 配置CDN地址
},
mode: 'production', // 生产环境
将 CSS 放在文件头部,JavaScript 文件放在底部
配置H5的rem适配方案为例:
npm i px2rem-loader [email protected] lib-flexible -D
新建 src/meta.html
添加公共的 mate 标签
在 src/meta.html
配置:
${require('raw-loader!./meta.html')}
Document
场景:使用raw-loader
可以动态的配置模板的占位符,达到渲染的位置。需要注意的是[email protected]
比较稳定,其他版本有些许问题。
参考: raw-loader
抽离资源文件
npm i html-webpack-externals-plugin -D
webpack.prod.js
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');
...
plugins:[
new HtmlWebpackExternalsPlugin({
// 提取公共资源
externals: [
{
module: 'react',
entry: 'https://unpkg.com/react@16/umd/react.production.min.js',
global: 'React', // 全局注入
},
{
module: 'react-dom',
entry: 'https://unpkg.com/react-dom@16/umd/react-dom.production.min.js',
global: 'ReactDOM', // 全局注入
},
{
module: 'google-roboto',
entry: {
path: '//at.alicdn.com/t/font_460072_qm96unh8hja.css',
type: 'css',
},
},
],
files: ['index.html']
})
]
优化结果:
场景:对于项目中存在很多的插件或者 UI 组件库等、可以在 HTML 插入外链的 CDN 方式进行打包优化,如果有加载顺序渲染的限制也可以使用raw-loader
进行打包设置。
参考: html-webpack-externals-plugin
分离公共方法
使用 webpack 内置的SplitChunksPlugin
,它的强大之处是可以在项目中分离公共方法的引入次数。
例子: 在 src/ 创建common/index.js
export function common() {
return '我是公共的JavaScript';
}
分别在:src/index/index.js
和src/search/index.js
import { common } from '../common';
...
let result = common();
console.log(result);
webpack.prod.js
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src/index.html'),
chunks: ['commons', 'index'], // 引入当前的名称 commons
}),
],
...
optimization: {
splitChunks: {
minSize: 0, // 引入的模块的大小,设置为0 有引入就会打包成模块
cacheGroups: {
commons: {
minChunks: 1, // 最少引入的次数
name: 'commons', // 命名chunks_name
chunks: 'all',
},
},
},
},
场景: 可以根据项目引入的次数进行公共方法 chunk 的抽离,不用在每次文件构建中反复构建。
善用缓存构建
yarn add hard-source-webpack-plugin -D
// or
npm i hard-source-webpack-plugin -D
webpack.prod.js
...
plugins: [
new HardSourceWebpackPlugin(),
],
第一次构建:
场景:当项目在本地构建的时候需要的依赖较多,可以增加为模块提供中间缓存的方式进行构建,构建的速度可以达到80%
左右。 详细的配置也可以参考文档
参考: hard-source-webpack-plugin
文件指纹
yarn add mini-css-extract-plugin -D
// or
npm i mini-css-extract-plugin -D
Hash :和整个项目的构建相关,只要项目文件有修改,整个项目构建的`hash`值就会随之更改
Chunkhash:和`webpack`打包的`chunk`有关,不同的`entry`会申城不同的`chunkhash`值
Contenthash : 根据文件内容来定义`hash`,文件内容不变,则`contenthash`不变
webpack.prod.js
"use strict";
const path = require("path");
const MiniCssExtractplugin = require("mini-css-extract-plugin"); // 提取css单独一个文件
module.exports = {
entry: {
// 入口文件可以用对象的形式来写
index: "./src/index.js",
search: "./src/search.js",
},
output: {
path: path.join(__dirname, "dist"),
filename: "[name]_[chunkhash:8].js", // chunkhash 8位的长度
},
mode: "production",
module: {
rules: [
{
test: /.js$/,
use: "babel-loader",
exclude: /node_modules/,
},
{
test: /.css$/, // 配置css的后缀名
exclude: /node_modules/,
use: [MiniCssExtractplugin.loader, "css-loader"], //tips:执行的顺序是右到左的
},
{
test: /.less$/, // 配置less的后缀名
exclude: /node_modules/,
use: [MiniCssExtractplugin.loader, "css-loader", "less-loader"], //tips:执行的顺序是右到左的
},
{
test: /.(png|jpg|gif|jpeg)$/,
exclude: /node_modules/,
use: [
{
loader: "file-loader",
options: {
name: "[name]_[hash:8].[ext]",
},
},
],
},
{
test: /.(woff|woff2|eot|ttf|otf)$/,
exclude: /node_modules/,
use: [
{
loader: "file-loader",
options: {
name: "[name]_[hash:8].[ext]",
},
},
],
},
],
},
plugins: [
new MiniCssExtractplugin({
filename: "[name]_[contenthash:8].css",
}),
],
};
场景:
用作版本管理时,如果一个项目需要发布,只需要发布修改过的文件指纹;对于没有修改过的文件,用户在访问的时候,依旧可以使用浏览器缓存好的,无需二次加载,加速页面访问。
参考:
代码压缩
uglifyjs-webpack-plugin
// js 压缩 [内置]optimize-css-assets-webpack-plugin
// css 压缩html-webpack-plugin
// html 压缩
yarn add html-webpack-plugin optimize-css-assets-webpack-plugin cssnano -D
// or
npm i html-webpack-plugin optimize-css-assets-webpack-plugin cssnano -D
webpack.prod.js
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
plugins:[
...
new OptimizeCssAssetsWebpackPlugin({
assetNameRegExp: /.css$/g, // 匹配的正则的名称后缀、跟loader配置一致
cssProcessor: require('cssnano'), // 用于最小化的css处理器,默认是cssnano
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src/search.html'),
filename: 'search.html',
chunks: ['search'],
/**
* inject : true || 'head' || 'body' || false
* body : 所有javascript资源将被放置在body元素的底部。
* head : 把脚本放置在head元素中.
* true : script标签位于html文件的 body 底部 [默认]
* false: 不插入生成的 js 文件,只是单纯的生成一个 html 文件
* */
inject: true,
minify: {
collapseWhitespace: true, // 清理html中的空格、换行符。 默认值:false
minifyCSS: true, // 压缩html内的样式。默认值:false
minifyJS: true, // 压缩html内的js。 默认值:false
removeComments: false, // 清理html中的注释。 默认值:false
},
}),
]
参考:
optimize-css-assets-webpack-plugin
cssnano
html-webpack-plugin
Tips:_关于详细的html-webpack-plugin
的minify可以详细的参考_
写在最后
在目前的前端的性能优化中,构建工具必不可少,怎么做才能使当前的项目更快、性能更好是前端业界的一个老生畅谈的问题、只有熟练的掌握构建工具的配置才能在性能渲染独领风骚。
参考文献
https://segmentfault.com/a/11...
https://segmentfault.com/a/11...
https://segmentfault.com/a/11...
https://juejin.cn/post/684490...
其他
插播一条招聘信息,LeapFE 招聘前端工程师
如果你对 用户体验、交互操作流程及用户需求 "有一些" 追求如果你对 web 、小程序 、Electron 技术 "有一些" 认识如果你 很擅长前端新技术的学习和分享 欢迎加入好未来,欢迎加入 LeapFE 一起做一些有意思的事情