内容简介:在此对
webpack
,打包所有的资源
不知道不觉, webpack
已经偷偷更新到 4.34
版本了,本人决定,这是今年最后一篇写 webpack
的文章,除非它更新到版本5,本人今年剩下的时间都会放在 Golang
和二进制数据操作以及后端的生态上
在看本文前,假设你对 webpack
有一定了解,如果不了解,可以看看我之前的手写 React
和 Vue
脚手架的文章
- 手写优化版React脚手架
- 手写Vue的脚手架
- 前端性能优化不完全手册
- 跨平台webpack配置
- 都是百星
star
的优质文章
在此对 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
等 - 实现React懒加载,按需加载 , 代码分割 并且支持服务端渲染
- 支持
less sass stylus
等预处理 -
code spliting
优化首屏加载时间 不让一个文件体积过大 - 加入
dns-prefetch
和preload
预请求必要的资源,加快首屏渲染(京东策略) - 加入
prerender
,极大加快首屏渲染速度 - 提取公共代码,打包成一个
chunk
- 每个
chunk
有对应的chunkhash
,每个文件有对应的contenthash
,方便浏览器区别缓存 - 图片压缩
-
CSS
压缩 - 增加
CSS
前缀 兼容各种浏览器 - 对于各种不同文件打包输出指定文件夹下
- 缓存
babel
的编译结果,加快编译速度 - 每个入口文件,对应一个
chunk
,打包出来后对应一个文件 也是code spliting
- 删除
HTML
文件的注释等无用内容 - 每次编译删除旧的打包代码
- 将
CSS
文件单独抽取出来 - 让babel不仅缓存编译结果,还在第一次编译后开启多线程编译,极大加快构建速度
- 等等....
本质上, webpack
是一个现代 JavaScript
应用程序的静态模块打包器( module bundler
)。当 webpack
处理应用程序时,它会递归地构建一个依赖关系图( dependency graph
),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle
webpack
打包原理
- 识别入口文件
- 通过逐层识别模块依赖。(
Commonjs、amd
或者es6的import,webpack
都会对其进行分析。来获取代码的依赖) -
webpack
做的就是分析代码。转换代码,编译代码,输出代码 - 最终形成打包后的代码
- 这些都是
webpack
的一些基础知识,对于理解webpack
的工作机制很有帮助。
舒适的开发体验,有助于提高我们的开发效率,优化开发体验也至关重要
- 组件热刷新、CSS热刷新
- 自从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, }, }, } }
简要解释上面这段配置
- 将node_modules共用部分打入
vendor.js bundle
中; - 将react全家桶打入
react.js bundle
中; - 如果项目依赖了
antd
,那么将antd
打入单独的bundle
中;(其实不用这样,可以看我下面的babel
配置,性能更高) - 最后剩下的业务模块超过3次引用的公共模块,将自动提取公共块
注意 上面的配置只是为了给大家看,其实这样配置代码分割,性能更高
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
连接自动更新页面。 - 实现热调试后,调试流程大幅缩短,和普通非直出模式调试体验保持一致。下面是SSR热调试的流程图:
加入 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/], }),
加入预渲染 prerener
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'] }),
我这套 webpack
配置,无论多复杂的环境,都是可以搞定的
-
webpack
真的非常非常重要,如果用不好,就永远是个初级前端 - 只要
webpack
不更新到5,以后就不出webpack
的文章了 -
webpack4
大结局,谢谢 - 以后会出一些偏向跨平台技术,原生
javascript
,TS
,Golang
等内容的文章
以上所述就是小编给大家介绍的《webpack4大结局:加入腾讯IM配置策略,实现前端工程化环境极致优化》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。