这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战css
上一篇文章中咱们讲到了 webpack
的一些基础特性,可是呢,单单会基础特性仍是远远不够的。所以,在今天的文章中,将给你们带来 webpack
的高级特性,包括但不限于 dev
环境和 prod
环境的区分打包,以及使用 webpack
对项目进行代码分割等等技巧。html
废话很少说,下面开始进入今天的学习吧~🎳vue
假设如今咱们有一个需求,写一段程序来对两个数作加法和减法。如今,咱们来实现这个功能。具体代码以下:node
export const add = (a, b) => {
console.log(a + b);
}
export const minus = (a, b) => {
console.log(a - b);
}
复制代码
接下来,咱们在入口文件引入它。具体代码以下:jquery
import { add } from './math.js';
add(1, 2);
复制代码
在这种状态下,咱们用 npx webpack
命令来对项目进行打包。查看打包后的文件代码:webpack
/***/ "./src/math.js":
/*!*********************!*\ !*** ./src/math.js ***! \*********************/
/*! exports provided: add, minus */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "add", function() { return add; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "minus", function() { return minus; });
const add = (a, b) => {
console.log(a + b);
};
const minus = (a, b) => {
console.log(a - b);
};
/***/ })
复制代码
咱们能够发现,在入口文件咱们只引入了加法的操做,由于咱们当下只想用到加法功能,而暂时还不须要用减法。可是呢,打包后的文件,把减法部分的内容,也一块儿,给打包进去了。这无形之中,多多少少给咱们添加了很多麻烦。git
所以呢,咱们就须要引入 webpack
中的 Tree Shaking
,来解决这个问题。github
首先咱们须要在 webpack.common.js
里面进行配置。若是是在 Development
的模式下,那么默认是不拥有 Tree Shaking
的。所以须要进行如下配置,具体代码以下:web
//node的核心模块
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');
module.exports = {
mode:'development',
optimization: {
usedExports: true
}
}
复制代码
在开发环境下,咱们须要加上 optimization
模块来对项目开启 Tree Shaking
。npm
接下来,咱们继续对 package.json
进行配置。在这个文件下,咱们须要添加如下代码:
{
"sideEffects": false
}
复制代码
sideEffects:false
指的是什么意思呢?当设置为 false
时,代表对全部的 ES Module
开启 Tree Shaking
操做。
值得注意的是, Tree Shakig
只支持 ES Module
这种类型的引入,并不支持 commonJS
等类型的引入。这是由于, ES Module
的底层实现是静态的,而 commonJS
的实现则是动态的。
还有另一种状况就是,若是你想要使得某些模块不开启 Tree Shaking
,那么能够将 sideEffects
进行如下配置。具体代码以下:
{
"sideEffects": [
"*.css",
"@babel/poly-fill"
]
}
复制代码
以上代码的意思为,对全部的 css
文件以及 @babel/poly-fill
不开启 Tree-shaking
功能。
接下来咱们来看一下配置完成以后,在开发环境下的效果。具体代码以下:
/***/ "./src/math.js":
/*!*********************!*\ !*** ./src/math.js ***! \*********************/
/*! exports provided: add, minus */
/*! exports used: add */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return add; });
/* unused harmony export minus */
const add = (a, b) => {
console.log(a + b);
};
const minus = (a, b) => {
console.log(a - b);
};
/***/ })
/******/ });
//# sourceMappingURL=main.js.map
复制代码
你们能够看到,若是是在开发模式下,减法功能依然是存在的,只是多个一句 /*! exports used: add */
来代表只使用 add
这个功能。
这是为何呢?由于在开发模式下, webpack
怕这个地方若是引入了其余模块,那么删除就很容易致使报错,因此它没有进行删除。
但若是 mode
是在生产环境下时, Tree Shaking
的做用就比较明显了。咱们修改完 mode
后来看下打包后的结果。具体代码以下:
function (e, n, r) { "use strict"; r.r(n); var t, o; t = 1, o = 2, console.log(t + o) }
复制代码
生产环境下打包后只有一行,小编摘取了最终要的部分出来。
你们能够看到,当处于生产环境下时,打包后的结果只显示了加法功能。而咱们没有使用到减法,因此这个时候就不会连带着打包出来了。
顺带着这个话题,接下来咱们就来说一下, webpack
在 development
和 Production
模式下的区分打包。
一般状况下,咱们的项目会有三个 webpack
的配置文件。一个是 webpack.common.js
,一个是 webpack.dev.js
,另一个是webpack.prod.js
。第一个文件用来放开发环境和生产环境下共同的配置,第二个文件用来放开发环境下的配置,第三个文件用来放生产环境下的配置。
接下来咱们来了解下,这三个配置文件的代码。
若是咱们不编写 common
文件的话,那么 dev
和 prod
这两个文件的代码重合度就会比较高,因此咱们把相同的部分抽离出来。具体代码以下:
//node核心模块
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
// 放置入口文件,明确怎么打包
entry:{
main: './src/index.js'
},
module:{
rules:[{
test: /\.m?js$/,
//exclude,顾名思义即排除在外。若是js文件在node_modules文件夹下面,那么咱们就排除在外
// 由于node_module通常都是来自于第三方库,它已经自动的处理好此部分工做,因此咱们不必再去作重复操做
exclude: /node_modules/,
use: {
loader: "babel-loader",
}
},{
test:/\.(jpg|png|gif)$/,
use:{
loader:'file-loader',
options: {
//placeholder 占位符
name: '[name]_[hash].[ext]',
outputPath: 'images/',
limit: 10240
}
}
},{
test:/\.scss$/,
use:[
'style-loader',
{
loader: 'css-loader',
options: {
//代表前面要先走sass-loader和postcss-loader
importLoaders: 2,
modules: true
}
},
'sass-loader',
'postcss-loader'
]
},{
test:/\.css$/,
use:[
'style-loader',
'css-loader',
'postcss-loader'
]
},{
test: /\.(eot|ttf|svg)$/,
use: {
loader: 'file-loader',
}
}]
},
plugins: [new HtmlWebpackPlugin({
//代表要引用哪个模板
template: 'src/index.html'
}),new CleanWebpackPlugin(['dist'])
],
// 输出,代表webpack应该怎么输出
output: {
publicPath: '/',
//用[]能够生成多个文件
filename: '[name].js',
// 指打包后的文件要放在哪一个文件下
path: path.resolve(__dirname, 'dist')
}
}
复制代码
抽离完 common
代码以后,如今咱们来编写 webpack.dev.js
文件。具体代码以下:
const webpack = require('webpack');
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const devConfig = {
mode:'production',
devtool: 'cheap-module-eval-source-map',
devServer: {
contentBase: './dist',
// 当运行完npm run start时,会自动的帮咱们打开浏览器
open: true,
port: 8080,
// 让咱们的webpackDevServer开启hotModuleReplacement这样子的功能
hot: true,
// 即使HMR没有生效,也不让浏览器自动刷新
hotOnly: true
},
plugins: [
//热模块更新
new webpack.HotModuleReplacementPlugin()
],
optimization: {
usedExports: true
}
}
module.exports = merge(commonConfig, devConfig)
复制代码
继续,咱们来编写抽离后的 prod
代码。具体代码以下:
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const prodConfig = {
mode:'development',
devtool: 'cheap-module-source-map'
}
module.exports = merge(commonConfig, prodConfig)
复制代码
到这里,你们对比上面三个文件的代码能够发现,这样的代码抽离使得咱们的项目结构变得更为清晰了。
上面配置完成以后,咱们如今要来想一下,想要运行开发环境和生产环境不用的配置,是否是应该把运行命令也给区分开来。所以,咱们对 package.json
文件作出如下配置:
{
"scripts": {
"dev": "webpack-dev-server --config webpack.dev.js",
"build": "webpack --config webpack.prod.js"
}
}
复制代码
经过以上配置,那么咱们就能够经过命令 npm run dev
和 npm run build
来运行项目,以此来区分项目是开发环境仍是生产环境。
同时,若是咱们想要在控制台更直观的看到报错信息,那么在开发环境的状况下,咱们能够不用 webpack-dev-server
来运行,能够直接用用 webpack
来运行。具体代码以下:
{
"scripts": {
"dev-server": "webpack --config webpac.dev.js",
"dev": "webpack-dev-server --config webpack.dev.js",
"build": "webpack --config webpack.prod.js"
}
}
复制代码
这样,咱们就能够经过 npm run dev-build
,来使用 webpack
来运行项目。
有时候,咱们可能会遇到一个业务逻辑里面有上万行代码,那么这上万行代码打包后,所有就丢到 main.js
文件里面了,这样大的文件,会使得整个项目的加载速度会变得非常缓慢。所以,咱们就须要用到代码分割Code Splitting来解决这件事情。
咱们在 webpack.common.js
中进行配置。具体代码以下:
module.exports = {
optimization: {
splitChunks: {
chunks: 'all'
}
}
}
复制代码
经过以上代码咱们能够得知,经过使用 optimization
中的 splitChunks
,达到了代码分割的效果。
那使用这个配置以后, webpack
想要作的事情是什么呢?
事实上,使用 splitChunks
以后,那么当 webpack
遇到公用的类库时,会帮咱们自动地打包生成一个新的文件,以后再把其他的业务逻辑拆分到另一个文件中去。
值得注意的是,公用类库无论是以同步的方式仍是以异步的方式进行加载, webpack
都可以帮咱们进行代码分割。
上面咱们讲到了 webpack
的代码分割,那么实际上, webpack
的代码分割,其底层实现原理所使用的是 splitChunksPlugin
这个插件。接下来咱们来说一下这个插件。
在没有用 SplitChunksPlugin
这个插件以前,若是咱们异步引入一个库,那么 webpack
给其打包后的文件名将会命名为 0.js
、 1.js
和 ......
。
咱们如今但愿在作代码分割时, webpack
能给咱们的第三方库进行自定义命名,这又该怎么处理呢?
首先咱们对引入的库前面,添加 webpackChunkName
配置。具体代码以下:
function getComponent() {
return import(/*webpackChunkName:"lodash"*/'lodash').then(({ default: _ }) => {
var element = document.createElement('div');
element.innerHTML = _.join(['Monday', 'Tuesday'], '_');
return element;
})
}
getComponent().then(element => {
document.body.appendChild(element);
})
复制代码
/*webpackChunkName:"lodash"*/
这句话想要代表的意思为:当咱们异步的引入 lodash
这个库而且想要作代码分割时,即当咱们给 webpack
进行打包时,给其起名为 lodash
。
上面这个配置知识第一步,接下来咱们要来安装并使用一个动态引入的插件。具体代码以下:
安装插件:
npm install --save-dev @babel/plugin-syntax-dynamic-import
复制代码
在 .babelrc
下引入:
{
// plugins: ["dynamic-import-webpack"] 非官方支持插件
plugins: ["@babel/plugin-syntax-dynamic-import"]
}
复制代码
配置 webpack.common.js
:
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: false,
default: false
}
}
},
}
复制代码
接下来咱们再来看关于 SplitChunksPlugin
的一些经常使用配置。具体代码以下:
module.exports = {
optimization: {
splitChunks: {
/*使用async时,只对异步代码进行代码分割; 使用all时,对同步和异步代码同时进行代码分割; 使用initial时,表示对同步代码进行代码分割*/
chunks: 'all',
//大于30kb时,作代码分割
minSize: 30000,
//代表代码分割后文件最大的大小,若是超过了则会继续进行拆分;有一些文件若是拆分不了则此配置想基本就没啥用了
maxSize: 0,
minRemainingSize: 0,
//至少有两个块被使用了才会被提取
minChunks: 2,
//表示同时加载的模块数,最可能是5个
/*好比咱们引入了10个类库,那么咱们会作10次代码分割。 而这个时候咱们将此参数填为5,那么webpack在打包时会将前10个库给咱们生成5个js文件, 以后的就不会再作代码分割了,所有丢到一个文件里面去*/
maxAsyncRequests: 5,
//入口文件作代码分割最多只能分割成3个js文件,超过3个就不会再作代码分割了
maxInitialRequests: 3,
//文件生成时的中间符号
automaticNameDelimiter: '~',
//让defaultVendors和default中的文件名有效
name: true,
enforceSizeThreshold: 50000,
//当打包同步代码时,cacheGroups
cacheGroups: {
defaultVendors: {
//检测你引入的库,查询是不是在node_module下的
test: /[\\/]node_modules[\\/]/,
priority: -10,
//肯定是在node_modules下后,将其进行打包,并命名为vendors.js
filename: 'vendors.js'
},
//对非第三方库的代码作代码分割
default: {
priority: -20,
reuseExistingChunk: true,
filename: 'common.js'
}
},
}
}
}
复制代码
打包分析指的是,当咱们使用 webpack
来进行代码打包以后,咱们能够借助打包分析的一些工具,来对咱们打包生成的文件进行必定的分析,以后来看其打包的是否合理。那么如何进行打包分析呢?
咱们要用到一个 github
的第三方仓库,戳此连接进入~
了解完文档该库的内容后,接下来咱们要对 package.json
进行配置。具体代码以下:
{
"scripts": {
"dev-build": "webpack --profile --json > stats.json --config ./build/webpack.dev.js",
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js"
}
}
复制代码
经过上面的代码,咱们能够分析:在 --config
前面加上配置 --profile --json > stats.json
,意思为 webpack
打包后会生成打包分析文件,这个文件叫作 stats.json
,同时, --json
表示的意思为 stats.json
文件的格式是一个 json
格式的。
在生成完 stats.json
文件之后,咱们就能够把它放到打包工具里进行分析。你们能够定位到官方文档中的 bundle-analysis ,里面提供了 webpack-chart
、 webpack-visualizer
等可视化工具供咱们使用,这个能够依据我的需求,选择对应的工具,对 stats.json
文件进行分析。
事实上,当咱们配置 splitChunks
时,里面的 chunks
默认值是 async
。也就是说,若是咱们不对它进行配置,那 webpack
默认只对异步代码进行代码分割。 webpack
为何要这么作呢?
webpack认为把同步代码打包在一个文件就行了,同时呢,它但愿咱们要多写点异步加载代码,这样才能让咱们的网站性能获得真正的提高。
如今,咱们来讲一个生活中一个很是常见的场景。假设如今咱们在登陆知乎网站,那么刚进去的时候咱们是尚未登陆的。咱们如今但愿的是点击登陆这个按钮,登陆对应的模态框就能显示出来,而不是点击完还要等到它加载,这无形这种就可使得页面加载速度变快了许多。
那这该怎么处理呢?这就要谈到 webpack
中的 preloading
和 prefetching
了。
假设咱们如今要引入一个 click.js
文件,那么咱们能够这么处理。具体代码以下:
document.addEventListener('click', () => {
// 这句话的意思为:当主要的js文件都加载完成以后,以后就是网络带宽有空闲的时候,它就会偷偷的把 ./click.js 文件给加载好
import(/* webpackPrefetch: true */ './click.js').then(({default: func}) => {
func();
})
});
复制代码
经过以上代码咱们能够知道,在所引入的文件前面,即 ./click.js
前面,加入 /* webpackPrefetch: true */
便可达到咱们想要的效果。这句话的意思为,当主要的 js
文件都加载完成以后,这时就是网络带宽有空闲的时候,那么 webpack
机会偷偷的在这个时间段,把 ./click.js
文件给加载好。
这里说到的是 prefetch
,但其实咱们也能够把 webpackPrefetch
改为 webpackPreload
, preload
与 prefetch
的区别在于: preload
是跟主文件同时进行加载,而不是在主文件加载完才加载的。通常来讲,咱们都用 prefetch
, 只有等主文件把活干完了,再来加载剩余的咱们想要的文件,这样的逻辑和对页面的优化才是比较完美的。
综上,原先咱们打包像 jQuery
、 lodash
之类的库时,只须要在第一次访问的时候加载,而等到第二次访问的时候,咱们就能够借助缓存,提升访问的速度。但这种方式也只是提升第二次访问的速度,而咱们想要实现的是,当第一次访问的时候, webpack
就可使得页面的加载速度是最快的。
因此最终咱们使用了 preload
和 prefetch
这两种实现方式来解决这个问题。
上面咱们讲到了 js
文件如何进行代码分割,如今,咱们来说 css
文件,如何进行代码分割。对于 css
文件的代码分割来讲,咱们要引用到官方文档中提到的一个插件: MiniCssExtractPlugin
。接下来咱们来看一下这个插件如何使用。
第一步: 安装插件。具体命令以下:
npm install --save-dev mini-css-extract-plugin
复制代码
第二步: 在开发环境和线上环境中使用。咱们打开 webpack.common.js
文件,引入该插件并使用。具体代码以下:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry:{
main: './src/index.js'
},
module:{
rules:[{
test:/\.scss$/,
use:[
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
//代表前面要先走sass-loader和postcss-loader
importLoaders: 2,
modules: true
}
},
'sass-loader',
'postcss-loader'
]
},{
test:/\.css$/,
use:[
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
}]
},
plugins: [
new MiniCssExtractPlugin({
// 若是文件被直接引用,走filename
filename: '[name].css',
// 若是文件被间接的引用,那么走chunkFilename
chunkFilename: '[name].chunk.js'
})
],
optimization: {
//使用treeShaking
usedExports: true,
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: false,
default: false
}
}
}
}
复制代码
第三步: 配置 package.json
文件。具体代码以下:
{
"sideEffects": [
//表示不对css文件开启treeShaking
"*.css"
]
}
复制代码
对于打包后的css文件,其大小仍是比较大的,因此咱们须要对文件大小进行压缩。这该怎么处理呢?
**第一步:**安装插件。具体命令以下:
npm install optimize-css-assets-webpack-plugin -D
复制代码
第二步: 在开发环境和线上环境中使用。咱们打开 webpack.common.js
文件,引入该插件并使用。具体代码以下:
const CssMinimizerPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = {
optimization: {
minimizer: [new OptimizeCSSAssetsPlugin({})]
}
}
复制代码
有时候,咱们可能会有不少个入口文件,每一个入口文件又都有其对应的若干个 css
文件。那么这个时候咱们想把全部的 css
文件,都打包到同一个文件下,该怎么处理呢?
咱们须要在 webpack.common.js
进行配置,具体代码以下:
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
styles: {
name: "styles",
test: /\.css$/,
chunks: "all",
enforce: true,
},
},
},
}
}
复制代码
经过以上代码,咱们能够了解到,咱们须要在 splitChunks
中额外配置一个 styles
的 cacheGroups
,将全部的 css
文件打包到一个 命名为 styles
的文件夹下。
当咱们刚访问网站时,第一次加载老是须要从零开始加载各类文件的,而假设咱们的代码没有更新时,咱们但愿再次加载时能够从浏览器中直接拉取缓存,而不是从新进行加载。而等到咱们的代码发生更新时,再从新加载网页。那这该怎么处理呢?
第一步: 配置开发环境 webpack.dev.js
文件。具体代码以下:
const devConfig = {
output: {
filename: '[name].js',
chunkFilename: '[name].chunk.js',
}
}
复制代码
第二步: 配置生产环境 webpack.prod.js
文件。具体代码以下:
const prodConfig = {
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js',
}
}
复制代码
以上的代码旨在解决的问题是,当在线上环境时,对输出的文件加一个哈希值。这个时候,若是咱们的代码有发生改变时,webpack就会生成一个新的哈希值,网页就会进行更新。若是咱们的代码没有发生改变,那么这个哈希值仍是同样的不会改变,网页就会从浏览器中拉取内存信息,进行加载。
以上内容呢,若是发生在一些比较低的 webpack
版本,是不能生效的。因此咱们须要进行一个配置,来兼容低版本问题。咱们在 webpack.common.js
下进行配置,具体代码以下:
module.exports = {
optimazation: {
runtimeChunk: {
name: 'runtime'
}
}
}
复制代码
继续,如今咱们来了解一下在 webpack
中, shimming
,即垫片的概念。
在 webpack
的打包过程当中呢,咱们每每要作一些代码上或者打包过程上的兼容。
好比,对于两个 js
文件来讲,模块和模块之间是相互独立的,他们之间是没有耦合度的。假设咱们如今有两个文件,具体代码以下:
jquery.ui.js:
export function ui(){
$('body').css('background', 'red');
}
复制代码
index.js:
import _ from 'lodash';
import $ from 'jquery';
import { ui } from './jquery.ui';
ui();
const dom = $('<div>');
dom.html(_.join(['Mondady', 'Tuesday']), '~');
$('body').append(dom);
复制代码
你们能够看到,如今咱们想要在 index.ui.js
文件中引入 $
,可是在这个文件中,它并无引入 jquery
这个库。因此,若是就这样子直接运行,是确定的会报错的。
所以,若是咱们想要解决这个问题,该怎么进行配置呢?
下面咱们将对 webpack.common.js
进行配置。具体代码以下:
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
//'_': 'lodash',
//_join: ['lodash', 'join']
})
]
}
复制代码
因此,这间接的能够理解为,拿点东西把 $
垫上,也就有了咱们标题说的 shimming
,垫片的概念。
对于项目中的文件来讲,其 this
的指向都是指向模块自己,而不是指向全局。那咱们若是想让项目中的全部 js
文件都指向全局,该怎么处理呢?
第一步: 安装 loader
。具体命令以下:
npm install imports-loader --save-dev
复制代码
第二步: 配置 webpack.common.js
。具体代码以下:
module.exports = {
module: {
rules: [{
test: /\.m?js$/,
exclude: /node_modules/,
use: [{
loader: "babel-loader",
},{
loader: 'imports-loader?this=>window'
}]
}]
}
}
复制代码
经过配置 imports-loader
之后, webpack
代表,它将会把 js
文件中的 this
指向所有指向 window
全局中。
在今天的文章中,咱们学习了使用 Tree Shaking
来优化代码的打包大小,同时,学习了 Dev
和 Prod
环境下的区分打包,须要明确在这两个模式下各自相同的配置以及不一样的配置,分清楚他们之间的关系。
除此以外呢,咱们还学会了使用 webpack
来对 js
文件和 css
文件进行代码分割。以及使用 webpack
来对代码进行打包分析和提早加载 preloading
。
最后,咱们还学会了关于 webpack
如何开启浏览器缓存,以及垫片 Shimming
的做用。
关于 webpack
的基础特性和高级特性讲到这里就结束啦!但愿对你们有帮助~
若有疑问或文章有误欢迎小伙伴们评论区留言呀~💬
本系列文章代码已上传至公众号,后台回复关键词 webpack
便可获取~
🙃 关注公众号星期一研究室,第一时间关注优质文章,更多精选专栏待你解锁~
🙃 若是这篇文章对你有用,记得留个脚印jio再走哦~
🙃 以上就是本文的所有内容!咱们下期见!👋👋👋