摘要:或者的,都会对其进行分析。舒适的开发体验,有助于提高我们的开发效率,优化开发体验也至关重要组件热刷新热刷新自从推出热刷新后,前端开发者在开环境下体验大幅提高。实现热调试后,调试流程大幅缩短,和普通非直出模式调试体验保持一致。
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(, 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 (* 加入html-loader识别html文件) } } export default Assets这即将按需加载
{ 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/], }),加入预加载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等内容的文章
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/114801.html
摘要:或者的,都会对其进行分析。舒适的开发体验,有助于提高我们的开发效率,优化开发体验也至关重要组件热刷新热刷新自从推出热刷新后,前端开发者在开环境下体验大幅提高。实现热调试后,调试流程大幅缩短,和普通非直出模式调试体验保持一致。 showImg(https://segmentfault.com/img/bVbtOR3?w=1177&h=635); webpack,打包所有的资源 不知道不...
摘要:或者的,都会对其进行分析。舒适的开发体验,有助于提高我们的开发效率,优化开发体验也至关重要组件热刷新热刷新自从推出热刷新后,前端开发者在开环境下体验大幅提高。实现热调试后,调试流程大幅缩短,和普通非直出模式调试体验保持一致。 showImg(https://segmentfault.com/img/bVbtOR3?w=1177&h=635); webpack,打包所有的资源 不知道不...
摘要:根据依赖关系,按照配置文件把模块函数分组打包成若干个。会随着自身的的修改,而发生变化。只需要在命令行运行时带上参数就搞定一些插件的废除和替换废弃了顶替者用属性变化压缩优化代码分割,下面详解还有一些新的插件,。 1. 前端工程化项目打包历史 前端工程化之前的时代略过 1. 半自动执行脚本来压缩合并文件 自从xmlhttprequest被挖掘出来,网页能够和服务端通讯,js能做的事越来越多...
摘要:根据依赖关系,按照配置文件把模块函数分组打包成若干个。会随着自身的的修改,而发生变化。只需要在命令行运行时带上参数就搞定一些插件的废除和替换废弃了顶替者用属性变化压缩优化代码分割,下面详解还有一些新的插件,。 1. 前端工程化项目打包历史 前端工程化之前的时代略过 1. 半自动执行脚本来压缩合并文件 自从xmlhttprequest被挖掘出来,网页能够和服务端通讯,js能做的事越来越多...
阅读 2962·2021-10-15 09:41
阅读 1619·2021-09-22 15:56
阅读 2103·2021-08-10 09:43
阅读 3271·2019-08-30 13:56
阅读 1776·2019-08-30 12:47
阅读 646·2019-08-30 11:17
阅读 2769·2019-08-30 11:09
阅读 2190·2019-08-29 16:19