写在最后面
最近工作中webpack
用的比拟多,想零碎的学习一下,找到了极客工夫的教程,跟着视频上讲的学了一遍,播种挺大的。尽管视频教程是2019年出的,有点老,有些常识曾经过期了,但并不障碍学习。那些过期的常识全当理解,能够搜寻寻找代替计划。学习资源:玩转webpack,上面是我的学习笔记。
构建工具
前端技术一直倒退,比方:React
的jsx
,Vue
和Angular
指令,CSS
预处理器,最新的ES
语法等,浏览器都不能辨认。
构建工具的作用就是将这些浏览器不能辨认的语法(高级语法)转换成了浏览器能辨认的语法(低级语法)。
还有一个作用是将代码压缩混同,在压缩代码体积的同时也让代码不易浏览。
webpack
是当初前端风行的构建工具。
初识webpack
配置script
脚本
装置好webpack
后,在命令行中应用webpack
时,有两种形式:
- 指定门路
./node_modules/.bin/webpack
- 应用
npx
工具,npx webpack
这两种办法应用挺麻烦的。有种简略的形式应用package.json
中的scripts
,它可能读到node_modules/.bin
目录下的命令。
在命令行中执行npm run build
即可。
"scripts": { "build": "webpack"}
配置文件
webpack
的默认配置文件webpack.config.js
,能够通过webpack --config
来指定配置文件。
比方,生产环境的配置文件webpack.config.prod.js
,开发环境的配置文件是webpack.config.dev.js
。
"scripts": { "build": "webpack --config webpack.config.prod.js", "build:dev": "webpack --config webpack.config.dev.js"}
外围概念
webpack
有 5 个外围概念:entry
、output
、mode
、loaders
、plugins
。
entry
打包时的入口文件,默认是./src/index.js
entry
是有两种形式:单入口、多入口。
单入口用于单页面利用,entry
是个字符串
module.exports = { entry: "./src/index.js",};
多入口用于多页面利用,entry
是对象模式
module.exports = { entry: { index: "./src/index.js", app: "./src/app.js", },};
output
打包后的输入文件,默认是./dist/main.js
。
entry
是单入口,output
可通过批改参数path
和filename
。
const path = require("path");module.exports = { entry: "./src/index.js", output: { path: path.resolve(__dirname, "dist"), filename: "index.js", },};
entry
是多入口,output
的filename
须要用[name]
占位符,用来指定打包后的名称,对应的是entry
中的key
const path = require("path");module.exports = { entry: { index: "./src/index.js", app: "./src/app.js", }, output: { path: path.resolve(__dirname, "dist"), filename: "[name].js", },};
mode
可设置为development
、production
、none
,默认是production
。
development
:开发环境,设置process.env.NODE_ENV
的值为development
。开启NamedChunksPlugin
和NamedModulesPlugin
production
:生产环境,设置process.env.NODE_ENV
的值为production
。开启FlagDependencyUsagePlugin
,FlagIncludedChunksPlugin
,ModuleConcatenationPlugin
,NoEmitonErrorsPlugin
,OccurrenceOrderPlugin
,SideEffectsFlagPlugin
和TerserPlugin
none
:不开启任何优化选项
ps:development
模式下开启的两个插件在代码热更新阶段,会在控制台打印出哪个模块产生了热更新,这个模块的门路是啥。production
模式下开启的插件会在压缩代码。
loaders
webpack
只反对js
和json
两种文件类型,loader
的作用就是用来解决其余的文件,并将它们增加到依赖图中。
loader
是个函数,接管源文件作为参数,返回转换后的后果。
个别loader
命名的形式都是以-loader
为后缀,比方css-loader
、babel-loader
。
test
是指定匹配的规定,use
是指定应用loader
的名称
module.exports = { module: { rules: [ { test: /.less$/, use: ["style-loader", "css-loader", "less-loader"], }, ], },};
一个loader
个别只做一件事,在解析less
时,须要用less-loader
将less
转成css
,因为webpack
不能辨认css
,又须要用css-loader
将css
转换成commonjs
对象放到js
中,最初须要用style-loader
将css
插入到页面的style
中。
ps:loader
的组合通常由两种形式,一种是从左到右(相似unix
的pipe
),另一种是从右到左(compose
)。webpack
抉择的是compose
,从右到左一次执行loader
plugins
任何loader
没法做的事件,都能够用plugin
解决,它次要用于文件优化、资源管理、环境变量注入,作用于整个构建过程。
个别plugin
命名的形式是以-webpack-plugin
为后缀结尾,也有是以-plugin
为后缀结尾的。
const CleanWebpackPlugin = require("clean-webpack-plugin");module.exports = { plugins: [new CleanWebpackPlugin()],};
webpack
资源解析
解析es6
须要装置@babel/core
,@babel/preset-env
,babel-loader
在根目录上面新建.babelrc
文件,将@babel/preset-env
增加到presets
中
{ "presets": ["@babel/preset-env"]}
webpack
中配置babel-loader
module.exports = { module: { rules: [ { test: /.js$/, use: "babel-loader", }, ], },};
还有一种配置的形式,不应用.babelrc
文件,将它配置在use
的参数options
中
module.exports = { module: { rules: [ { test: /.js$/, use: { loader: "babel-loader", options: { presets: ["@babel/preset-env"], }, }, }, ], },};
解析css
须要装置css-loader
和style-loader
。
module.exports = { moudle: { rules: [ { test: /\.css$/, use: ["style-loader", "css-loader"], }, ], },};
解析less
须要装置less-loader
,css-loader
,style-loader
module.exports = { module: { rules: [ { test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"], }, ], },};
解析图片和字体
须要装置file-loader
module.exports = { module: { rules: [ { test: /.(png|jpeg|jpg|gif)$/, use: ["file-loader"], }, { test: /.(woff|woff2|eot|ttf|otf)$/, use: ["file-loader"], }, ], },};
url-loader
url-loader
能够将较小的图片转换成base64
。
module.exports = { module: { rules: [ { test: /.(png|jpeg|jpg|gif)$/, use: { loader: "url-loader", options: { limit: 10240, // 小于 10k 图片,webpack 在打包时主动转成 base64 }, }, }, ], },};
配置vue
配置vue
开发环境,须要装置vue
,vue-loader
,vue-template-compiler
const { VueLoaderPlugin } = require("vue-loader");module.export = { plugins: [new VueLoaderPlugin()], module: { rules: [ { test: /\.vue$/, use: "vue-loader", }, ], },};
ps: 这里应用的vue-loader
是15.x
版本,我装置最新的版本16.x
有问题,始终没有解决。
配置react
配置react
开发环境须要装置react
,react-dom
,@babel/preset-react
module.exports = { module: { rules: [ { test: /\.js$/, use: { loader: "babel-loader", options: { presets: ["@babel/preset-env", "@babel/preset-react"], }, }, }, ], },};
webpack
文件监听及热更新
文件监听
每次批改代码后,都须要手动构建,影响开发效率。webpack
提供了文件监听的作用。开启监听时,webpack
会调用node
中fs
模块来判断文件是否发生变化,如果产生了变动,主动从新构建输入新的文件。
webpack
开启监听模式,有两种形式:(须要手动刷新浏览器)
- 启动
webpack
命令时,带上--watch
参数
"scripts": { "watch": "webpack --watch"}
webpack.config.js
中设置watch: true
module.exports = { watch: true,};
文件监听剖析
webpack
文件监听判断根据是看文件的最初编辑工夫是否发生变化。
它会将批改的工夫保存起来,当文件批改时,它会和上一次的批改工夫进行比对。
如果发现不统一,它并不会马上通知监听者,而是先把文件的批改缓存起来,期待一段时间,如果这段时间内还有其余文件发生变化,它就会把这段时间内的文件列表一起构建。这个期待的工夫就是aggregateTimeout
。
module.exports = { watch: true, watchOptions: { ignored: /node_modules/, aggregateTimeout: 300, poll: 1000, }}
watch
:默认为false
watchOptions
:只有watch
为true
时,才失效ignored
:须要疏忽监听的文件或者文件夹,默认为空,疏忽node——modules
性能会有所晋升。aggregateTimeout
:监听到文件变动后,期待的工夫,默认300ms
poll
:轮询工夫,1s
一次
热更新
热更新须要webpack-dev-server
和HotModuleReplacementPlugin
两个插件背地应用。
与watch
相比,它不输入文件,间接形式内存中,所以它的构建熟读更快。
热更新在开发模式中才会应用。
const webpack = require("webpack");const path = require("path");module.exports = { mode: "development", plugins: [new webpack.HotModuleReplacementPlugin()], devServer: { contentBase: path.join(__dirname, "dist"), hot: ture, },};
在package.json
中配置命令
webpack4.x
"scripts": { "dev": "webpack-dev-server --open"}
webpack5.x
"scripts": { "dev": "webpack server"}
ps:webpack5.x
和webpack-dev-server
有抵触,不能应用--open
关上浏览器。
HotModuleReplacementPlugin
热更新的外围是HMR Server
和HMR Runtime
。
HMR Server
:是服务端,用来将变动的js
模块通过websocket
的音讯告诉给浏览器端HMR Runtime
:是浏览器端,用于接管HMR Server
传递过去的模块数据,浏览器端能够看到.hot-update.json
文件
hot-module-replacement-plugin
的作用:webpack
自身构建进去的bundle.js
自身是不具备热更新的,HotModuleReplacementPlugin
的作用就是HMR Runtime
注入到bundle.js
,使得bundle.js
能够和HMR Server
建设websocket
的通信能力。一旦磁盘里的文件产生批改,那么HMR Server
将有批改的js
模块通过websocket
发送给HMR Runtime
,而后HMR Runtime
去部分更新页面的代码,这种办法不会刷新浏览器。
webpack-dev-server
的作用:提供bundle server
的能力,就是生成的bundle.js
能够通过localhost://xxx
的形式去拜访,另外也提供livereload
(浏览器主动刷新)。
文件指纹
什么是文件指纹
文件指纹是指打包后输入的文件名的后缀。比方:index_56d51795.js
。
通常用于用于版本治理
文件指纹类型
hash
: 和我的项目的构建相干,只有我的项目文件扭转,构建我的项目的hash
值就会扭转。采纳hash
计算的话,每一次构建后的哈希值都不一样,如果说文件内容没有发生变化,那这样子是没法实现缓存的。chunkhash
:和webpack
打包的chunk
无关,不同的entry
会生成不同的chunkhash
。生产环境会将一些公共库和源码离开来,独自用chunkhash
构建,只有不扭转公共库的代码,它的哈希值就不会变,就能够实现缓存。contenthash
:依据文件内容来定义hash
,文件内容不变,则contenthash
不变。个别用于css
资源,如果css
资源应用chunkhash
,那么批改了js
,css
资源就会变动,缓存就会生效,所以css
应用contenthash
。
ps:
- 应用
hash
的场景要联合mode
来思考,如果mode
是development
,在应用HMR
状况下,应用chunkhash
是不适宜的,应该应用hash
。而mode
是production
时,应该用chunkhash
。js
应用chunkhash
是便于寻找资源,因为js
的资源关联度更高。css
应用contenthash
是因为css
个别是依据不同的页面书写的,css
资源之间的关联度不高,也就不必在其余资源批改时,从新更新css
。
js
文件指纹
设置output
的filename
,应用[chunkhash]
,
const path = require("path");module.exports = { output: { path: path.join(__dirname, "dist"), filename: "[name]_[chunkhash:8].js", // 取 hash 的前 8位 },};
css
文件指纹
须要装置mini-css-extract-plugin
style-loader
是将css
插入到页面的head
中,mini-css-extract-plugin
是提取为独自的文件,它们之间是互斥的。
const MiniCssExtractPlugin = require("mini-css-extract-plugin");module.exports = { module: { rules: [ { test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"], }, ], }, plugins: [ new MiniCssExtractPlugin({ filename: "[name]_[contenthash:8].css", }), ],};
图片文件指纹
须要装置file-loader
占位符名称 | 含意 |
---|---|
[ext] | 资源后缀名 |
[name] | 文件名称 |
[path] | 文件的相对路径 |
[folder] | 文件所在的文件夹 |
[contenthash] | 文件内容的hash ,默认是md5 |
[hash] | 文件内容的hash ,默认是md5 |
[emoji] | 一个随机的指代文件内容的emoji |
图片的hash
和css/js
的hash
概念不一样,图片的hash
是有图片的内容决定的。
module.exports = { module: { rules: [ { test: /.(png|jpeg|jpg|gif)$/, use: { loader: "file-loader", options: { filename: "[folder]/[name]_[hash:8].[ext]", }, }, }, { test: /.(woff|woff2|eot|ttf|otf)$/, use: [ { loader: "file-loader", options: { filename: "[name]_[hash:8].[ext]", }, }, ], }, ], },};
代码压缩
代码压缩次要分为js
压缩,css
压缩,html
压缩。
js
压缩
webpack
内置了uglifyjs-webpack-plugin
插件,默认打包后的文件都是压缩过的,这里无需额定配置。
能够手动装置这个插件,能够额定设置一下配置,比方开启并行压缩,须要将parallel
设置为true
const UglifyjsWebpackPlugin = require("uglifyjs-webpack-plugin");module.exports = { optimization: { minimizer: [ new UglifyjsWebpackPlugin({ parallel: true, }), ], },};
css
压缩
装置optimize-css-webpack-plugin
,同时装置css
预处理器cssnano
。
const OptimizeCssWebpackPlugin = require("optimize-css-webpack-plugin")module.exports = { plugins: [ new OptimizeCssWebpackPlugin({ assetNameRegExp: /\.css$/g, cssProcessor: require("cssnano) }) ]}
html
压缩
须要装置html-webpack-plugin
,通过设置压缩参数。
ps: 多页面利用须要写多个new HtmlWebpackPlugin({...})
const HtmlWebpackPlugin = require("html-webpack-plugin");const path = require("path");module.exports = { plugins: [ new HtmlWebpackPlugin({ template: path.join(__dirname, "src/search.html"), //html 模版所在的地位,模版外面能够应用 ejs 的语法 filename: "search.html", // 打包进去后的 html 名称 chunks: ["search"], // 打包进去后的 html 要应用那些 chunk inject: true, // 设置为 true,打包后的 js css 会主动注入到 HTML 中 minify: { html5: true, collapseWhitespace: true, preserveLineBreaks: false, minifyCSS: true, minifyJS: true, removeComments: false }), new HtmlWebpackPlugin({...}) ],};
HtmlWebpackPlugin
参数minify
外面的minifyJS
和minifyCSS
的作用是用于压缩一开始就内联在html
里的js
(不能应用ES6
语法)和css
。
chunks
对应的是entry
中的key
。你心愿哪个chunk
主动注入,就将哪个chunk
写到chunks
。
chunk
、bundle
、module
区别:
chunk
:每个chunk
是又多个module
组成,能够通过代码宰割成多个chunk
bundle
:打包生成的最终文件module
:webpack
中的模块(js
、css
、图片)
主动清理构建目录
每次在构建的时候,会产生新的文件,造成输入目录output
文件越来越多。
最常见的清理办法用命令rm
去删除,在打包之前先执行rm -rf
命令将dist
目录删除,在进行打包。
"scripts": { "build": "rm -rf ./dist && webpack"}
另一种是应用rimraf
进行删除。
装置rimraf
,应用时也是先在打包前先将dist
目录进行删除,而后在打包。
"scripts": { "build": "rimraf ./dist && webpack"}
这两种计划尽管都能将dist
目录清空,但不太优雅。
webpack
提供clean-webpack-plugin
,它会主动清理output.path
下的文件。
const { CleanWebpackPlugin } = require("clean-webpack-plugin");module.exports = { plugins: [new CleanWebpackPlugin()],};
主动补齐款式前缀
不同的浏览器厂商在实现css
个性的规范没有齐全对立,比方display: flex
,在webkit
内核中要写成display: -webkit-box
。
在开发的时候一个个加上内核,将是一个微小的工程。
在webpack
中能够用loader
去解决主动补齐css
前缀的问题。
装置postcss-loader
,以及它的插件autoprefixer
。
autoprefixer
是依据can i use
这个网站提供的css
兼容性进行不全前缀的。
autoprefixer
是后置处理器,它和预处理器不同,预处理器是在打包的时候解决,而autoprefixer
是在代码解决好之后,款式曾经生成了再进行解决。
在webpack4.x
中装置postcss-loader@3.0.0
,autoprefixer@9.5.1
。
形式一: 间接在webpack.config.js
中配置
webpack.config.js
:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");module.exports = { module: { rules: [ { test: /\.less$/, use: [ MiniCssExtractPlugin.loader, "css-loader", "less-loader", { loader: "postcss-loader", options: { plugins: () => [ require("autoprefixer")({ overrideBrowserslist: ["last 2 version", ">1%", "ios 7"], //最新的两个版本,用户大于1%,且兼容到 ios7 }), ], }, }, ], }, ], },};
形式二: 利用postcss
配置文件postcss.config.js
,webpack.config.js
间接写postcss-loader
即可。
webpack.config.js
:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");module.exports = { module: { rules: [ { test: /\.less$/, use: [ MiniCssExtractPlugin.loader, "css-loader", "less-loader", "postcss-loader", ], }, ], },};
postcss.config.js
:
module.exports = { plugins: [ require("autoprefixer")({ overrideBrowserslist: ["last 2 version", ">1%", "ios 7"], }), ],};
办法三: 浏览器的兼容性能够写package.json
中,postcss.config.js
中只需加载autofixer
即可。
webpack.config.js
:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");module.exports = { module: { rules: [ { test: /\.less$/, use: [ MiniCssExtractPlugin.loader, "css-loader", "less-loader", "postcss-loader", ], }, ], },};
postcss.config.js
:
module.exports = { plugins: [require("autofixer")],};
package.json
:
"browserslist": { "last 2 version", "> 1%", "ios 7"}
资源内联
在一些场景中须要应用到资源内联,常见的有:
- 页面框架的初始化脚本
- 缩小
http
网络申请,将一些小的图片变成base64
内容到代码中,较少申请 css
内联减少页面体验- 尽早执行的
js
,比方REM
计划
html
内联
在多页面我的项目中,head
中有很多专用的标签,比方meta
,想要晋升维护性,会将它提取成一个模版,而后援用进来。
装置raw-loader@0.5.1
<head> ${require("raw-loader!./meta.html")}</head>
js
内联
在REM
计划中,须要尽早的html
标签的font-size
,那么这段js
就要尽早的加载、执行。
<head> <script> ${require('raw-loader!babel-loader!./calc-root-fontsize.js')} </script></head>
css
内联
为了更好的体验,防止页面闪动,须要将打包好的css
内联到head
中。
装置html-inline-css-webpack-plugin
,这个插件须要放在html-webpack-plugin
前面。
const MiniCssExtractPlugin = require("mini-css-extract-plugin");const HtmlWebpackPlugin = require("html-webpack-plugin");const HtmlInlineCssWebpackPlugin = require("html-inline-css-webpack-plugin");module.exports = { module: { rules: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"], }, plugins: [ new MiniCssExtractPlugin({ filename: "[name]_[contenthash:8].css", }), new HtmlWebpackPlugin(), new HtmlInlineCssWebpackPlugin(), ],};
须要先将css
提取成独自的文件才行。
ps:
style-laoder
和html-inline-css-webpack-plugin
的区别是:
style-loader
:插入款式是一个动静的过程,打包后的源码不会有style
标签,在执行的时候通过js
动静插入style
标签html-inline-css-webpack-plugin
:是在构建的时候将css
插入到页面的style
标签中
多页面利用
每个页面对应一个entry
,同时对应plugins
中一个html-webpack-plugin
。
这种形式有个毛病,每个减少一个entry
就要减少一个html-webpack-plguin
。
const HtmlWebpackPlugin = require("html-webpack-plugin");module.exports = { entry: { index: "./src/index.js", search: "./src/search.js", }, plugins: [ new HtmlWebpackPlugin({ template: "./src/index.html" }), new HtmlWebpackPlugin({ template: "./src/search.html" }), ],};
借助glob
能够实现通用化的配置形式。
装置glob
,同时所有的页面都要放在src
上面,并且入口文件都要叫做index.js
。
const path = require("path");const glob = require("glob");const HtmlWebpackPlugin = require("html-webpack-plugin");const setMPA = () => { const entry = {}; const htmlWebpackPlugins = []; const entryFiles = glob.sync(path.join(__dirname, "./src/*/index.js")); entryFiles.forEach((entryFile) => { const match = entryFile.match(/src\/(.*)\/index\.js/); const pageName = match && match[1]; entry[pageName] = entryFile; htmlWebpackPlugins.push( new HtmlWebpackPlugin({ template: path.join(__dirname, `src/${pageName}/index.html`), filename: `${pageName}.html`, chunks: [pageName], inject: true, minify: { html5: true, collapseWhitespace: true, preserveLineBreaks: false, minifyCSS: true, minifyJS: true, removeComments: false, }, }) ); }); return { entry, htmlWebpackPlugins, };};const { entry, htmlWebpackPlugins } = setMPA();module.exports = { entry, plugins: [].concat(htmlWebpackPlugins),};
sourceMap
应用办法
公布到生成环境中的代码,要将代码进行压缩混同,然而在压缩混同之后,看代码就像在看天书一样。
sourceMap
提供了压缩后的代码和源代码之间的映射关系。
sourceMap
在开发环境开启,线上环境中敞开,在线上排查问题时将source map
上传到谬误监控零碎。
source map
关键字
eval
:应用eval
包裹模块代码source map
:产生map
文件cheap
:不蕴含列信息inline
:将.map
作为DataURI
嵌入,不独自生成.map
文件module
:蕴含loader
的sourcemap
source map
类型
devtool | 首次构建 | 二次构建 | 是否适宜生产环境 | 能够定位的代码 |
---|---|---|---|---|
(none) | +++ | +++ | yes | 最终输入的代码 |
eval | +++ | +++ | no | webpack 生成的代码(一个个的模块) |
cheap-eval-source-map | + | ++ | no | 通过 loader 转换后的代码(只能看到行) |
cheap-module-eval-source-map | o | ++ | no | 源代码(只能看到行) |
eval-source-map | -- | + | no | 源代码 |
cheap-source-map | + | o | yes | 通过loader 转换后的代码只能看到行) |
cheap-module-source-map | o | - | yes | 源代码(只能看到行) |
inline-cheap-source-map | + | - | no | 通过loader 转换后的代码(只能看到行) |
inline-cheap-module-source-map | o | - | no | 源代码(只能看到行) |
source-map | -- | -- | yes | 源代码 |
Inline-source-map | -- | -- | no | 源代码 |
hidden-source- map | -- | -- | yes | 源代码 |
提取页面公共资源
在我的项目中,多个页面中都会应用到一些根底库,比方react
、react-dom
,还有一写公共的代码,当在打包时这些都会被打包进最终的代码中,这是比拟节约的,而且打包后的体积也比拟大。
webpack4
内置了SplitChunksPlugin
插件。
chunks
参数阐明:
async
异步引入的库进行拆散(默认)initial
同步引入的库进行拆散all
所有引入的库进行拆散(举荐)
抽离出根底库的名称cacheGroups
里中的filename
放到html-webpack-plugin
中chunks
中,会主动导入:
module.exports = { plugins: [ new HtmlWebpackPlugin({ chunks: ["vendors", "commons", "index"], //打包进去后的 html 要应用那些 chunk }) ); ], optimization: { splitChunks: { chunks: "async", minSize: 30000, // 抽离的公共包最小的大小,单位字节 maxSize: 0, // 最大的大小 minChunks: 1, // 资源应用的次数(在多个页面应用到), 大于1, 最小应用次数 maxAsyncRequests: 5, // 并发申请的数量 maxInitialRequests: 3, // 入口文件做代码宰割最多能分成3个js文件 automaticNameDelimiter: "~", // 文件生成时的连接符 automaticNameMaxLength: 30, // 主动主动命名最大长度 name: true, //让cacheGroups里设置的名字无效 cacheGroups: { //当打包同步代码时,下面的参数失效 vendors: { test: /[\\/]node_modules[\\/]/, //检测引入的库是否在node_modlues目录下的 priority: -10, //值越大,优先级越高.模块先打包到优先级高的组里 filename: "vendors.js", //把所有的库都打包到一个叫vendors.js的文件里 }, default: { minChunks: 2, // 下面有 priority: -20, // 下面有 reuseExistingChunk: true, //如果一个模块曾经被打包过了,那么再打包时就疏忽这个上模块 }, }, }, },};
tree shaking
(摇树优化)
在一个模块中有多个办法,用到的办法会被打包进bundle
中,没有用到的办法不会打包进去。
tree shaking
就是只把用到的办法打包到bundle
,其余没有用到的会在uglify
阶段擦除。
webpack
默认反对,在.babelrc
里设置modules: false
即可。production
阶段默认开启。
必须是ES6
语法,CJS
的形式不反对。
ps: 如果把ES6
转成ES5
,同时又要开启tree shaking
,须要在.babelrc
里设置module: false
,不然babel
默认会把ES6
转成CJS
标准的写法,这样就不能进行tree shaking
。
DCE
(deal code elimination)
DEC
全称 deal code elimination,中文意思是死代码删除,次要是上面三种:
- 代码不会被执行
if (false) { console.log("代码不会被执行");}
- 代码执行的后果不会被用到
function a() { return "this is a";}let A = a();
- 代码只写不读
let a = 1;a = 2;
tree shaking
原理
对代码进行动态剖析,在编译阶段,代码有没有用到是要确定下来的,不能通过在代码运行时在剖析哪些代码有没有用到,tree shaking
会把没用到的代码用正文标记进去,在uglify
阶段将这些无用代码擦除。
利用ES6
模块的特点:
- 只能作为模块顶层的语句呈现
import
的模块名只能是字符串常量import binding
是immutable
删除无用的css
purgecss-webpack-plugin
:遍历代码,辨认曾经用到的css class
- 和
mini-css-extract-plugin
配合应用
- 和
uncss
:html
须要通过jsdom
加载,所有的款式通过postCSS
解析,通过document.querySelector
来辨认在html
文件外面不存在的选择器
const PurgecssPlugin = require("purgecss-webpack-plugin");const PATHS = { src: path.join(__dirname, "src"),};module.exports = { plugins: [ new PurgecssPlugin({ paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }), }), ],};
scope hoisting
应用和原理剖析
构建够的代码会产生大量的闭包,如下图所示:
当引入内部模块时,会用一个函数包裹起来。当引入的模块越多时,就会产生大量的函数包裹代码,导致体积变大,在运行的时候,因为创立的函数作用域越多,内存开销也会变大。
- 被
webpack
转换后的模块会被包裹一层 import
会被转换成__webpack_require
剖析
- 打包进去的是一个
IIFE
(匿名闭包) modules
是一个数组,每一项是一个模块初始化函数__webpack_require
用来加载模块,返回module.exports
- 通过
WEBPACK_REQUIRE_METHOD(0)
启动程序
scope hoisting
原理
将所有模块的代码依照援用程序放在一个函数作用域里,而后适当的重名一些变量以避免变量名抵触。
模块调用有先后顺序,a
模块调用b
模块,因为有函数包裹,a
模块和b
模块的程序无所谓,如果打消包裹代码的话,须要依据模块的援用程序来排放模块,就要将b
模块放到a
模块之前,b
模块能力被a
模块调用。
通过scope hoisting
能够缩小函数申明和内存开销。production
阶段默认开启。
必须是ES6
语法,CJS
不反对。
ps:scope hoisting
把多个作用域变成一个作用域。当模块被援用的次数大于1
次时,是不产生成果的。如果一个模块援用次数大于1
次,那么这个模块的代码会被内联屡次,从而减少打包后文件的体积。
应用ESLint
是ESLint
可能对立团队的代码格调,可能帮忙发现带谬误。
两种应用办法:
- 与
CI/CD
集成 - 与
webpack
集成
webpack
与CI/CD
集成
须要装置husky
。
减少scripts
,通过lint-staged
查看批改文件。
"scripts": { "precommit": "lint-staged"},"lint-staged": { "linters": { "*.{js,less}": ["eslint --fix", "git add"] }}
webpack
与ESLint
集成
应用eslint-loader
,构建是查看js
标准
装置插件babel-eslint
、eslint
、eslint-config-airbnb
、eslint-config-airbnb-base
、eslint-loader
、eslint-plugin-import
、eslint-plugin-jsx-ally
、eslint-plugin-react
。
新建.eslintrc.js
文件
module.exports = { parser: "babel-eslint", extends: "airbnb", env: { browser: true, node: true, }, rules: { indent: ["error", 2], },};
webpack.config.js
文件配置eslint-loader
module.exports = { module: { rules: [ { test: /\.js$/, use: ["babel-loader", "eslint-loader"], }, ], },};
代码宰割和动静import
对于大的web
利用来说,将所有的代码都放在一个文件中显然是不够无效的,特地是当你的某些代码块是在某些非凡状况下才会被用到。
webpack
有一个性能是将你的代码宰割成chunks
(语块),当代码运行到须要它们的时候在进行加载。
应用场景:
- 抽离雷同代码到一个共享块
- 脚本懒加载,使得初始下载的代码更小
懒加载js
脚本的形式
CJS
:require.ensure
ES6
:import
(目前还没有原生反对,须要bable
转换)
装置@bable/plugin-syntax-dynamic-import
插件
.babelrc
文件:
{ plugins: ["@bable/plugin-syntax-dynamic-import"];}
例子:
class Index extends React.Component { constructor() { super(...arguments); this.state = { Text: null, }; } loadComponent = () => { import("./text.js").then((Text) => { this.setState({ Text: Text.default }); }); }; render() { const { Text } = this.state; return ( <div className="search"> react1 {Text && <Text />} <div onClick={this.loadComponent}>点我</div> <img src={img} alt="" /> </div> ); }}
这里要留神一点,当配置了cacheGroups
时,minChunks
设置了1
,下面设置的懒加载脚本就不失效了,因为import
在加载时是动态剖析的。
cacheGroups: { commons: { name: "commons", chunks: "all", priority: -20, //值越大,优先级越高.模块先打包到优先级高的组里 minChunks: 1, }}
多过程/多实例:并行压缩
办法一:应用webpack-parallel-uglify-plugin
插件
const WebpackParalleUglifyPlugin = require("webpack-parallel-uglify-plugin");module.exports = { plugins: [ new WebpackParalleUglifyPlugin({ uglifyJs: { output: { beautify: false, comments: false, }, compress: { warnings: false, drop_console: true, collapse_vars: true, reduce_vars: true, }, }, }), ],};
办法二:应用uglifyjs-webapck-plugin
开启parallel
参数
const UglifyJsPlugin = require("uglifyjs-webpack-plugin")modules.exports = { plugins: [ UglifyJsPlugin: { warnings: false, parse: {}, compress: {}, mangle: true, output: null, toplevel: false, nameCache: null, ie8: false, keep_fnames: false, }, parallel: true ]}
办法三:terser-webpack-plugin
开启parallel
参数(webpack4
举荐)
const TerserPlugin = require("terser-webpack-plugin");module.exports = { optimization: { minimizer: [ new TerserPlugin({ parallel: 4, }), ], },};
晋升构建速度
思路:将react
、react-dom
、redux
、react-redux
根底包和业务根底包打包成一个文件
办法:应用DLLPlugin
进行分包,DLLReferencePlugin
对manifest.json
援用
应用DLLPlugin
进行分包
const path = require("path");const webpack = require("webpack");module.exports = { context: process.cwd(), resolve: { extensions: [".js", ".jsx", ".json", ".less", ".css"], modules: [__dirname, "nodu_modules"], }, entry: { library: ["react", "react-dom", "redux", "react-redux"], }, output: { filename: "[name].dll.js", path: path.resolve(__dirname, "./build/library"), library: "[name]", }, plugins: [ new webpack.DllPlugin({ name: "[name]", path: "./build/library/[name].json", }), ],};
在webpack.config.js
引入
module.exports = { plugins: [ new webapck.DllReferencePlugin({ manifest: require("./build/library/manifest.json"), }), ],};
在我的项目应用了webpack4
,对dll
的依赖没那么大了,应用dll
相对来说晋升也不是特地显著,而hard-source-webpack-plugin
能够极大的晋升二次构建。
不过从理论的前端工厂中来说,dll
还是很有必要的。对于一个团队而言,根本是应用雷同的技术栈,要么react
,要么vue
。这时候,通常的做法都是把公共框架打成一个common bundle
文件供所有我的项目应用。dll
就能够很好的满足这种场景:将多个npm
包打成一个公共包。因而团队外面的分包计划应用dll
还是很有价值。
splitChunks
也能够做DllPlugin
的事件,然而举荐应用splitChunks
去提取页面间的公共js
文件,因为应用splitChunks
每次去提取根底包还是须要消耗构建工夫的,如果是DllPlugin
只须要预编译一次,前面的根底包工夫都是能够省略掉的。
晋升二次构建速度
办法一:应用terser-webpack-plugin
开启缓存
module.exports = { optimization: { minimizer: [ new TerserWebpackPlugin({ parallel: true, cache: true, }), ], },};
办法二:应用cache-loader
或者hard-source-webpack-plugin
module.exports = { plugins: [new HardSourceWebpackPlugin()],};
放大构建指标
比方babel-loader
不解析node_modules
module.exports = { rules: { test: /\.js$/, loader: "happypack/loader", // exclude: "node_modules" /-- 或者 --/ // include: path.resolve("src"), }}
缩小文件搜寻范畴
- 优化
resolve.modules
配置(缩小模块搜寻层级) 优化
resolve.mainFields
配置- 先查找
package.json
中main
字段指定的文件 -> 查找根目录的index.js
-> 查找lib/index.js
- 先查找
优化
resolve.extensions
配置- 模块门路的查找,
import xx from "index"
会先找.js
后缀的
- 模块门路的查找,
- 正当应用
alias
module.exports = { resolve: { alias: { react: path.resolve(__dirname, "./node_modules/react/dist/react.min.js"), }, modules: [path.resolve(__dirname, "node_modules")], // 查找依赖 extensions: [".js"], // 查找模块门路 mainFields: ["main"], // 查找入口文件 },};