在此对
webpack
的性能优化进行几点声明:
package.json
文件,即实行三次打包18K
的文件没必要单独打包成一个chunk
,http
请求次数过多反而影响性能prerender
和PWA
互斥,这个问题暂时没有解决babel
缓存编译缓存的是索引,即hash
值,非常吃内存,每次开发完记得清理内存babel-polyfill
按需加载在某些非常复杂的场景下比较适合prefetch,preload
对首屏优化提升是明显PWA
+预渲染+preload
是首屏优化的巅峰,但是pwa
无法缓存预渲染的html
文件webpack
主要针对React
技术栈,实现功能如下:JSX
文件class
组件alias
别名,简化import
的长字段SSR
的热调试(基于Node
做中间件)javaScript
的tree shaking
摇树优化 删除掉无用代码CSS
的tree shaking
async / await
和 箭头函数react-hot-loader
记录react
页面留存状态state
PWA
功能,热刷新,安装后立即接管浏览器 离线后仍让可以访问网站 还可以在手机上添加网站到桌面使用preload
预加载资源 prefetch
按需请求资源CSS
模块化,不怕命名冲突base64
处理jsx js json
等less sass stylus
等预处理code spliting
优化首屏加载时间 不让一个文件体积过大dns-prefetch
和preload
预请求必要的资源,加快首屏渲染(京东策略)prerender
,极大加快首屏渲染速度chunk
chunk
有对应的chunkhash
,每个文件有对应的contenthash
,方便浏览器区别缓存CSS
压缩CSS
前缀 兼容各种浏览器babel
的编译结果,加快编译速度chunk
,打包出来后对应一个文件 也是code spliting
HTML
文件的注释等无用内容CSS
文件单独抽取出来webpack
是一个现代 JavaScript
应用程序的静态模块打包器(module bundler
)。当 webpack
处理应用程序时,它会递归地构建一个依赖关系图(dependency graph
),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle
webpack
打包原理Commonjs、amd
或者es6的import,webpack
都会对其进行分析。来获取代码的依赖)webpack
做的就是分析代码。转换代码,编译代码,输出代码webpack
的一些基础知识,对于理解webpack
的工作机制很有帮助。React
技术栈,如何在构建中接入热刷新dev
模式下加上 webpack.HotModuleReplacementPlugin
插件 devServer: {
contentBase: '../build',
open: true,
port: 5000,
hot: true
},
注:也可以使用react-hot-loader来实现,具体参考官方文档
optimization: {
runtimeChunk: true,
splitChunks: {
chunks: 'all',
minSize: 10000, // 提高缓存利用率,这需要在http2/spdy
maxSize: 0,//没有限制
minChunks: 3,// 共享最少的chunk数,使用次数超过这个值才会被提取
maxAsyncRequests: 5,//最多的异步chunk数
maxInitialRequests: 5,// 最多的同步chunks数
automaticNameDelimiter: '~',// 多页面共用chunk命名分隔符
name: true,
cacheGroups: {// 声明的公共chunk
vendor: {
// 过滤需要打入的模块
test: module => {
if (module.resource) {
const include = [/[\\/]node_modules[\\/]/].every(reg => {
return reg.test(module.resource);
});
const exclude = [/[\\/]node_modules[\\/](react|redux|antd)/].some(reg => {
return reg.test(module.resource);
});
return include && !exclude;
}
return false;
},
name: 'vendor',
priority: 50,// 确定模块打入的优先级
reuseExistingChunk: true,// 使用复用已经存在的模块
},
react: {
test({ resource }) {
return /[\\/]node_modules[\\/](react|redux)/.test(resource);
},
name: 'react',
priority: 20,
reuseExistingChunk: true,
},
antd: {
test: /[\\/]node_modules[\\/]antd/,
name: 'antd',
priority: 15,
reuseExistingChunk: true,
},
},
}
}
vendor.js bundle
中;react.js bundle
中;antd
,那么将antd
打入单独的bundle
中;(其实不用这样,可以看我下面的babel
配置,性能更高)注意 上面的配置只是为了给大家看,其实这样配置代码分割,性能更高
optimization: {
runtimeChunk: true,
splitChunks: {
chunks: 'all',
}
}
react-hot-loader
记录react
页面留存状态state
yarn add react-hot-loader
// 在入口文件里这样写
import React from "react";
import ReactDOM from "react-dom";
import { AppContainer } from "react-hot-loader";-------------------1、首先引入AppContainre
import { BrowserRouter } from "react-router-dom";
import Router from "./router";
/*初始化*/
renderWithHotReload(Router);-------------------2、初始化
/*热更新*/
if (module.hot) {-------------------3、热更新操作
module.hot.accept("./router/index.js", () => {
const Router = require("./router/index.js").default;
renderWithHotReload(Router);
});
}
function renderWithHotReload(Router) {-------------------4、定义渲染函数
ReactDOM.render(
<AppContainer>
<BrowserRouter>
<Router />
</BrowserRouter>
</AppContainer>,
document.getElementById("app")
);
}
然后你再刷新试试
React
的按需加载,附带代码分割功能 ,每个按需加载的组件打包后都会被单独分割成一个文件 import React from 'react'
import loadable from 'react-loadable'
import Loading from '../loading'
const LoadableComponent = loadable({
loader: () => import('../Test/index.jsx'),
loading: Loading,
});
class Assets extends React.Component {
render() {
return (
<div>
<div>这即将按需加载</div>
<LoadableComponent />
</div>
)
}
}
export default Assets
html-loader
识别html
文件 {
test: /\.(html)$/,
loader: 'html-loader'
}
resolve: {
modules: [
path.resolve(__dirname, 'src'),
path.resolve(__dirname,'node_modules'),
],
alias: {
components: path.resolve(__dirname, '/src/components'),
},
}
eslint-loader
{
enforce:'pre',
test:/\.js$/,
exclude:/node_modules/,
include:resolve(__dirname,'/src/js'),
loader:'eslint-loader'
}
resolve
解析配置,为了为了给所有文件后缀省掉 js jsx json
,加入配置resolve: {
extensions: [".js", ".json", ".jsx"]
}
HTML
文件压缩,自动将入门的js
文件注入html
中,优化HTML
文件 new HtmlWebpackPlugin({
template: './public/index.html',
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
}
}),
SSR
同构直出热调试webpack watch+nodemon
结合的模式实现对SSR
热调试的支持。node
服务需要的html/js
通过webpack
插件动态输出,当nodemon
检测到变化后将自动重启,html
文件中的静态资源全部替换为dev
模式下的资源,并保持socket
连接自动更新页面。babel-loader
还有 解析JSX ES6
语法的 babel preset
@babel/preset-react
解析 jsx语法
@babel/preset-env
解析es6
语法@babel/plugin-syntax-dynamic-import
解析react-loadable
的import
按需加载,附带code spliting
功能 ["import", { libraryName: "antd-mobile", style: true }],
Antd-mobile的按需加载{
loader: 'babel-loader',
options: { //jsx语法
presets: ["@babel/preset-react",
//tree shaking 按需加载babel-polifill
["@babel/preset-env", { "modules": false, "useBuiltIns": "false", "corejs": 2 }]],
plugins: [
//支持import 懒加载
"@babel/plugin-syntax-dynamic-import",
//andt-mobile按需加载 true是less,如果不用less style的值可以写'css'
["import", { libraryName: "antd-mobile", style: true }],
//识别class组件
["@babel/plugin-proposal-class-properties", { "loose": true }],
],
cacheDirectory: true
},
}
特别提示,如果电脑性能不高,不建议开启
babel
缓存索引,非常吃内存,记得每次开发完了清理内存
thread-loader
,在babel
首次编译后开启多线程 const os = require('os')
{
loader: 'thread-loader',
options: {
workers: os.cpus().length
}
}
CSS
文件的loader
和插件const MiniCssExtractPlugin = require('mini-css-extract-plugin')
{
test: /\.(less)$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader', options: {
modules: true,
localIdentName: '[local]--[hash:base64:5]'
}
},
{loader:'postcss-loader'},
{ loader: 'less-loader' }
]
}
new MiniCssExtractPlugin({
filename:'[name].[contenthash:8].css'
}),
CSS
的tree shaking
const PurifyCSS = require('purifycss-webpack')
const glob = require('glob-all')
plugins:[
// 清除无用 css
new PurifyCSS({
paths: glob.sync([
// 要做 CSS Tree Shaking 的路径文件
path.resolve(__dirname, './src/*.html'), // 请注意,我们同样需要对 html 文件进行 tree shaking
path.resolve(__dirname, './src/*.js')
])
})
]
base64
处理,减少http
请求数量,并对输出的文件统一打包处理{
test: /\.(jpg|jpeg|bmp|svg|png|webp|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[name].[hash:8].[ext]',
}
}, {
exclude: /\.(js|json|less|css|jsx)$/,
loader: 'file-loader',
options: {
outputPath: 'media/',
name: '[name].[hash].[ext]'
}
}
]
}]
},
CSS
文件的loader
和插件const MiniCssExtractPlugin = require('mini-css-extract-plugin')
{
test: /\.(less)$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader', options: {
modules: true,
localIdentName: '[local]--[hash:base64:5]'
}
},
{loader:'postcss-loader'},
{ loader: 'less-loader' }
]
}
new MiniCssExtractPlugin({
filename:'[name].[contenthash:8].css'
}),
css
的插件 const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
new OptimizeCssAssetsWebpackPlugin({
cssProcessPluginOptions:{
preset:['default',{discardComments: {removeAll:true} }]
}
}),
const CleanWebpackPlugin = require('clean-webpack-plugin')
new CleanWebpackPlugin()
{
test: /\.(jpg|jpeg|bmp|svg|png|webp|gif)$/,
use:[
{loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[name].[hash:8].[ext]',
outputPath:'/img'
}},
{
loader: 'img-loader',
options: {
plugins: [
require('imagemin-gifsicle')({
interlaced: false
}),
require('imagemin-mozjpeg')({
progressive: true,
arithmetic: false
}),
require('imagemin-pngquant')({
floyd: 0.5,
speed: 2
}),
require('imagemin-svgo')({
plugins: [
{ removeTitle: true },
{ convertPathData: false }
]
})
]
}
}
]
}
var JavaScriptObfuscator = require('webpack-obfuscator');
// ...
// webpack plugins array
plugins: [
new JavaScriptObfuscator ({
rotateUnicodeArray: true
}, ['excluded_bundle_name.js'])
],
PWA
的插件 , WorkboxPlugin
pwa
这个技术其实要想真正用好,还是需要下点功夫,它有它的生命周期,以及它在浏览器中热更新带来的副作用等,需要认真研究。可以参考百度的lavas
框架发展历史~const WorkboxPlugin = require('workbox-webpack-plugin')
new WorkboxPlugin.GenerateSW({
clientsClaim: true, //让浏览器立即servece worker被接管
skipWaiting: true, // 更新sw文件后,立即插队到最前面
importWorkboxFrom: 'local',
include: [/\.js$/, /\.css$/, /\.html$/,/\.jpg/,/\.jpeg/,/\.svg/,/\.webp/,/\.png/],
}),
preload
new PreloadWebpackPlugin({
rel: 'preload',
as(entry) {
if (/\.css$/.test(entry)) return 'style';
if (/\.woff$/.test(entry)) return 'font';
if (/\.png$/.test(entry)) return 'image';
return 'script';
},
include: 'allChunks'
//include: ['app']
}),
const PrerenderSPAPlugin = require('prerender-spa-plugin')
new PrerenderSPAPlugin({
routes: ['/','/home','/shop'],
staticDir: resolve(__dirname, '../dist'),
}),
我这套
webpack
配置,无论多复杂的环境,都是可以搞定的
webpack
真的非常非常重要,如果用不好,就永远是个初级前端webpack
不更新到5,以后就不出webpack
的文章了webpack4
大结局,谢谢javascript
,TS
,Golang
等内容的文章