一、webpack4--基本配置
1.初始化配置
mkdir webpack4
cd webpack4
mkdir demo1
cd demo1
npm init -y 或 npm init
目錄結(jié)構(gòu)
webpack4
├── webpack4/demo1
│ └── webpack4/demo1/package.json
└── webpack4/README.md
安裝webpack
npm install webpack --save-dev安裝指定版本webapck
npm install --save-dev webpack@<version>webpack 4+ 版本,還需要安裝webpack-cli
npm install webpack-cli --save-devnpx webpack -v:查看webpack版本npx webpack-cli -v:查看webpack-cli版本
推薦本地安裝webpack和webpack-cli
寫這篇博客的時(shí)候webpack最新版本為:4.30.0,也是這篇學(xué)習(xí)webpack4使用的版本
在demo1目錄下新建src目錄,在src目錄下新建index.js
mkdir src
cd src
touch index.js
demo1目錄結(jié)構(gòu)
demo1
├── demo1/package.json
├── demo1/package-lock.json
└── demo1/src
└── demo1/src/index.js
在index.js中加寫代碼,例如:
//index.js
let demo='webpack4'
console.log(demo)
webpack4可以零配置打包,webpack4會(huì)默認(rèn)對src目錄下的index.js文件打包。
現(xiàn)在運(yùn)行npx webapck,可以在demo1目錄下看到dist目錄,dist目錄下有一個(gè)main.js文件,這就是打包后的文件,打開查找可以看到 console.log(demo),說明index.js被打包到main.js中。
2.webpack4的簡單配置
在demo1目錄下新建webpack配置文件webpack.config.js
配置webpack--webpack.config.js
const path = require('path')
module.exports={
//mode development: 開發(fā)環(huán)境 production:生產(chǎn)環(huán)境
mode: 'development',
//entry 入口文件配置
entry: {
index: './src/index.js'
},
//打包完成后文件輸出位置配置
output: {
//filename 設(shè)置打包后文件的名字
//如果不設(shè)置filename,則文件的名字跟入口文件路徑的屬性名一樣
filename: 'bundle.js',
//path 設(shè)置打包完成后文件輸出路徑
path: path.resolve(__dirname,'dist')
}
}
運(yùn)行npx webpack命令
npx webpack等價(jià)于npx webpack --config webpack.config.js
當(dāng)webapck配置文件命名為webpack.config.js時(shí)可以省略--config *.js,直接執(zhí)行npx webpack即可,否則執(zhí)行npx webpack --config 配置文件名。
看到dist目錄下有bundle.js,說明webpack配置正確。
在package.json中配置'script'
"scripts": {
"build": "webpack"
}
添加"build": "webpack",運(yùn)行npm run build效果等價(jià)于執(zhí)行npx webpack命令。
配置webpack.config.js的modoule對象
loader的用法
file-loader的使用
安裝
file-loader
npm i file-loader --save-dev
webpack.config.js
const path = require('path')
module.exports={
//mode development: 開發(fā)環(huán)境 production:生產(chǎn)環(huán)境
mode: 'development',
//entry 入口文件配置
entry: {
index: './src/index.js'
},
//打包完成后文件輸出位置配置
output: {
//filename 設(shè)置打包后文件的名字
//如果不設(shè)置filename,則文件的名字跟入口文件路徑的屬性名一樣
filename: 'bundle.js',
//path 設(shè)置打包完成后文件輸出路徑
path: path.resolve(__dirname,'dist')
},
module: {
rules:[
{
test: /\.(png|jpg|gif)$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]', //對打包后的圖片命名
outputPath: 'images/' //打包后圖片輸出的位置 dist\images
}
}
}
]
}
}
在src目錄下新建images文件夾,存放圖片
修改index.js
//index.js
//import導(dǎo)入圖片
import image from './images/11.png'
let img=new Image()
img.src=image
document.body.append(img)
運(yùn)行npm run build后的目錄結(jié)構(gòu)如下
demo1
├── demo1/dist
│ ├── demo1/dist/bundle.js
│ ├── demo1/dist/images
│ │ └── demo1/dist/images/11.png
│ └── demo1/dist/index.html
├── demo1/package.json
├── demo1/package-lock.json
├── demo1/src
│ ├── demo1/src/images
│ │ └── demo1/src/images/11.png
│ └── demo1/src/index.js
└── demo1/webpack.config.js
在dist目錄下出現(xiàn)了images目錄和圖片,創(chuàng)建index.html,引入js文件,在瀏覽器中打開就可以看到圖片。
url-loader的使用
url-loader安裝
npm i url-loader -D
url-loader的作用跟'file-loader'的作用很類似
webpack.config.js
module: {
rules:[
{
test: /\.(png|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]', //對打包后的圖片命名
outputPath: 'images/', //打包后圖片放的位置 dist\images
limit: 20480
//1024 == 1kb
//小于20kb時(shí)打包成base64編碼的圖片否則單獨(dú)打包成圖片
}
}
}
]
}
}
limit屬性:當(dāng)圖片大小大于屬性值時(shí)打包成圖片輸出到images目錄下,否則打包成base64編碼的圖片注入bundle.js中
因?yàn)閎ase64編碼的圖片導(dǎo)致打包文件變大,所以圖片比較小時(shí)打包成base64編碼的圖片,圖片比較大時(shí)單獨(dú)打包成一張圖片。
對css和scss的打包
安裝相應(yīng)的loader
npm i css-loader style-loader -D
npm i node-sass sass-loader -D
npm i postcss-loader -D
npm i autoprefixer -D
postcss-loader和autoprefixer配合使用可以在打包過程中自動(dòng)添加前綴
在demo1根目錄下新建postcss.config.js,配置如下
//postcss.config.js
module.exports={
plugins: [
require('autoprefixer')
]
}
在webpack.config.js文件的`module.rules'數(shù)組中添加配置
module:{
rules:[
{
test: /\.css$/,
use:[
'style-loader',
'css-loader',
'postcss-loader'
//加前綴 npm i autoprefixer -D
//在項(xiàng)目根目錄下配置postcss.config.js文件
]
},
{
test: /\.scss$/,
use:[
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
//importLoaders
//用于配置css-loader作用于@import的資源之前有多少個(gè)loader先作用于@import的資源
}
},
'postcss-loader',
'sass-loader'
]
}
]
}
在demo1的src下新建css文件夾,在css文件夾下新建style.css和index.scss文件。
index.scss
body{
border: 1px solid red;
width: 300px;
height: 300px;
img{
width: 100px;
height: 100px;
border-radius: 10%;
transform: translate(100px,100px);
}
}
style.css
body{
border-radius: 10%;
}
index.js
//index.js
import image from './images/11.png'
import './style.css'
import './index.scss'
let img=new Image()
img.src=image
document.body.append(img)
運(yùn)行npm run build,在dist目錄下新建index.html,引入js文件,在瀏覽器中打開就可以看到效果,說明打包成功。
css模塊化
css模塊化,避免頁面樣式之間相互影響
在webpack.config.js中的css-loader添加modules: true
//webpack.config.js
module:{
rules: [
{
test: /\.css$/,
use:[
'style-loader',
{
loader: 'css-loader',
options: {
modules: true
}
},
'postcss-loader'
//加前綴 npm i autoprefixer -D
//在項(xiàng)目根目錄下配置postcss.config.js文件
]
},
{
test: /\.scss$/,
use:[
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
//importLoaders
//用于配置css-loader作用于@import的資源之前有多少個(gè)loader先作用于@import的資源
modules: true //加載css模塊化打包,避免樣式文件之間相互影響
}
},
'postcss-loader',
'sass-loader'
]
}
]
}
修改index.js
.img是類名需要在樣式文件中提前寫好樣式
//index.js
import image from './images/11.png'
import style from './css/style.css'
// import style from './css/index.scss'
let img=new Image()
img.src=image
//style.img .img是scss文件中寫好的類名
img.classList.add(style.img)
document.body.append(img)
index.scss
body{
border: 1px solid red;
width: 300px;
height: 300px;
img{
width: 100px;
height: 100px;
border-radius: 10%;
transform: translate(100px,100px);
}
.img{
border: 10px solid royalblue;
}
}
style.css
body{
border-radius: 10%;
}
body .img{
border: 10px solid yellow;
}
結(jié)果
可以看到添加了一個(gè)class,類名是一串比較復(fù)雜的字符串,從而避免這個(gè)樣式對別的元素產(chǎn)生影響。
二、進(jìn)一步配置webpack4,使自己在學(xué)習(xí)webpack4的時(shí)候更方便
這一部分主要是學(xué)會(huì)使用html-webpack-plugin和clean-webpack-plugin插件,主要是學(xué)會(huì)配置devServer以及使用webpack的熱模塊替換功能。
首先,在webpack4目錄下新建demo2文件夾將demo1目錄下的所有東西復(fù)制到demo2中
在上一部分我們都是手動(dòng)在dist目錄下創(chuàng)建index.html引入js文件查看打包結(jié)果,這樣會(huì)很麻煩。我們可以使用html-webpack-plugin來自動(dòng)生產(chǎn)index.html,并且能夠自動(dòng)引入打包好的文件,直接打開生產(chǎn)的html就可以看到打包結(jié)構(gòu)。
1.html-webpack-plugin的使用
安裝
npm i html-webpack-plugin -D
在webpack.config.js中配置plugins配置項(xiàng)
const path = require('path')
const htmlWebpackPlugin = require('html-webpack-plugin')
module.exports={
//mode development: 開發(fā)環(huán)境 production:生產(chǎn)環(huán)境
mode: 'development',
//entry 入口文件配置
entry: {
index: './src/index.js'
},
//打包完成后文件輸出位置配置
output: {
//filename 設(shè)置打包后文件的名字
//如果不設(shè)置filename,則文件的名字跟入口文件路徑的屬性名一樣
filename: 'bundle.js',
//path 設(shè)置打包完成后文件輸出路徑
path: path.resolve(__dirname,'dist')
},
module: { },
plugins: [
new htmlWebpackPlugin({
template: './index.html'
})
]
}
在demo2目錄下新建index.html作為模板
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>模板</title>
</head>
<body>
<div id="root"></div>
<script type="text/javascript" src="bundle.js"></script></body>
</html>
運(yùn)行npm run build,可以看到dist目錄下自動(dòng)生產(chǎn)index.html,并且還自動(dòng)引入js文件
2.clean-webpack-plugin的使用
每次打包生成的dist目錄,如果改一次代碼,都得要?jiǎng)h除一次dist目錄,這樣很麻煩,可以通過clean-webpack-plugin在每次打包的前自動(dòng)清空dist目錄。
安裝
npm i clean-webpack-plugin -D
在webpack.config.js的plugins中配置如下
const path = require('path')
const htmlWebpackPlugin = require('html-webpack-plugin')
const cleanWebpackPlugin = require('clean-webpack-plugin')
module.exports={
//mode development: 開發(fā)環(huán)境 production:生產(chǎn)環(huán)境
mode: 'development',
//entry 入口文件配置
entry: {
index: './src/index.js'
},
//打包完成后文件輸出位置配置
output: {
//filename 設(shè)置打包后文件的名字
//如果不設(shè)置filename,則文件的名字跟入口文件路徑的屬性名一樣
filename: 'bundle.js',
//path 設(shè)置打包完成后文件輸出路徑
path: path.resolve(__dirname,'dist')
},
module: { },
plugins: [
new htmlWebpackPlugin({
template: './index.html'
}),
new cleanWebpackPlugin()
]
}
運(yùn)行npm run build,可以自己測試,每次打包前都會(huì)把dist目錄下的文件刪掉。
3.entry和output多入口配置
module.exports={
//mode development: 開發(fā)環(huán)境 production:生產(chǎn)環(huán)境
mode: 'development',
//entry 入口文件配置
entry: {
index: './src/index.js',
main: './src/index.js'
},
//打包完成后文件輸出位置配置
output: {
//filename 設(shè)置打包后文件的名字
//如果不設(shè)置filename,則文件的名字跟入口文件路徑的屬性名一樣
// 占位符
filename: '[name].js',
//path 設(shè)置打包完成后文件輸出路徑
path: path.resolve(__dirname,'dist')
},
}
當(dāng)有多入口的時(shí)候,需要修改filename的屬性值為'[name].js'
運(yùn)行npm run build,就會(huì)在dist目錄下生產(chǎn)index.js和main.js
4.配置devtool
devtool決定源代碼與打包后的代碼之間的映射關(guān)系,方便對代碼進(jìn)行調(diào)試。
開發(fā)環(huán)境推薦: cheap-module-eval-source-map
生產(chǎn)環(huán)境推薦: cheap-module-source-map
devtool具體內(nèi)容請查閱:文檔:devtool
module.exports={
devtool: 'cheap-module-eval-source-map',
//開發(fā)環(huán)境推薦: cheap-module-eval-source-map
//生產(chǎn)環(huán)境推薦: cheap-module-source-map
}
5.配置devServer
安裝
webpack-dev-server
npm i webpack-dev-server -D
在webpack.config.js中添加以下內(nèi)容
module.exports={
devServer: {
contentBase: './dist',
// open: true, //自動(dòng)打開瀏覽器
// port: 8080, //默認(rèn)8080
}
}
修改package.json的script,添加 "start": "webpack-dev-server"
"scripts": {
"start": "webpack-dev-server"
},
執(zhí)行npm run start后打開瀏覽器就可以看到效果,當(dāng)我們修改代碼的時(shí)候頁面就會(huì)重新刷新。
有時(shí)我們希望頁面只更新我們修改的那一部分就可以了,而并不是刷新頁面,所以需要啟用webpack的熱模塊替換功能。
6.啟用webpack的熱模塊替換功能
首先修改index.js
import './css/style.css'
var btn = document.createElement('button')
btn.innerHTML='新增'
document.body.appendChild(btn)
btn.onclick=function(){
var div=document.createElement('div')
div.innerHTML='items'
document.body.appendChild(div)
}
修改style.css,刪掉index.scss
//style.css
body{
background: yellow;
}
div:nth-of-type(odd){
background: chartreuse;
font-size: 18px;
}
在webpack.config.js中
引入webpack:const webpack=require('webpack')
添加內(nèi)容如下:
const webpack=require('webpack')
module.exports={
plugins: [
new webpack.HotModuleReplacementPlugin() //啟用HMR
],
devServer: {
contentBase: './dist',
// open: true, //自動(dòng)打開瀏覽器
// port: 8080,
hot: true, //啟用webpack的熱模塊替換功能
hotOnly: true
//devServer.hot在沒有頁面刷新的情況下啟用熱模塊替換作為構(gòu)建失敗時(shí)的后備
}
}
hot:true啟用HotModuleReplacementPlugin(HMR)
執(zhí)行npm run start,在瀏覽器打開以后,修改div的背景顏色,只有改變的地方才發(fā)生變化,但是頁面并沒有刷新。
在demo2的src目錄下新建number.js
number.js
var number=function(){
var div=document.createElement('div')
div.setAttribute("id","number")
div.innerHTML=103
document.body.appendChild(div)
}
export default number
修改index.js
import number from './number'
number()
運(yùn)行npm run start,在瀏覽器中打開看結(jié)果,然后在number.js中修改內(nèi)容,但是頁面并沒有顯示修改后的內(nèi)容
這是因?yàn)樵谝雑s文件的時(shí)候,熱模塊替換的實(shí)現(xiàn)方式有點(diǎn)區(qū)別。
js要達(dá)到熱模塊替換的效果,得要if(module.hot){}這一部分代碼,否則就算改了代碼,頁面不刷新,修改的地方在頁面上頁面變化。
css樣式因?yàn)閏ss-loader已經(jīng)實(shí)現(xiàn)if(module.hot){}這一部分,所以不需要單獨(dú)實(shí)現(xiàn)這一部分。
再次修改index.js
import number from './number'
number()
if(module.hot){
module.hot.accept('./number.js',function(){
number()
document.body.removeChild(document.getElementById('number'))
})
}
運(yùn)行npm run start,在瀏覽器中打開看結(jié)果,然后在number.js中修改內(nèi)容,發(fā)現(xiàn)頁面顯示修改后的內(nèi)容
三、使用Babel處理js文件
Babel是一個(gè)廣泛使用的轉(zhuǎn)碼器,可以將ES6代碼轉(zhuǎn)為ES5代碼,從而在現(xiàn)有環(huán)境執(zhí)行。
Babel總共分為三個(gè)階段:解析(parse),轉(zhuǎn)換(transform),生成(generate)。
Babel本身不具有任何轉(zhuǎn)化功能,它把轉(zhuǎn)化的功能都分解到一個(gè)個(gè)plugin里面。因此當(dāng)我們不配置任何插件時(shí),經(jīng)過Babel輸出的代碼和輸入是相同的。
Babel插件的使用
將插件的名字增加到配置文件中:項(xiàng)目根目錄下創(chuàng)建
.babelrc配置文件或是webapck.config.js中配置,一般都是在.babelrc中配置。使用 npm install xxx 進(jìn)行安裝
Babel的配置文件是.babelrc,存放在項(xiàng)目的根目錄下。使用Babel的第一步,就是配置這個(gè)文件。
該文件用來設(shè)置轉(zhuǎn)碼規(guī)則和插件,基本格式如下。
{
"presets": [],
"plugins": []
}
Babel簡單介紹
preset
preset(預(yù)設(shè))就是一系列插件的集合
@babel/preset-env包含所有ES6轉(zhuǎn)譯為ES5的插件集合
core-js
轉(zhuǎn)換一些內(nèi)置類(Promise, Symbols等等)和靜態(tài)方法(Array.from等)。
@babel/core
是作為Babel的核心存在,Babel的核心api都在這個(gè)模塊里面。
babel-loader
babel-loader在webpack中使用,是webpack和Babel之間的通訊橋梁
@babel/polyfill介紹
@babel/preset-env默認(rèn)只轉(zhuǎn)譯js語法,而不轉(zhuǎn)譯新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局對象,以及一些定義在全局對象上的方法(比如Object.assign)都不會(huì)轉(zhuǎn)譯。這時(shí)就必須使用@babel/polyfill(內(nèi)部集成了core-js和regenerator)。
使用時(shí),在所有代碼運(yùn)行之前增加import "@babel/polyfill"
或者是在webpack.config.js入口配置
module.exports = {
entry: ["@babel/polyfill", "./app/js"],
}
因此必須把@babel/polyfill作為dependencies而不是devDependencies
@babel/polyfill主要有兩個(gè)缺點(diǎn):
1.使用@babel/polyfill需要做些額外配置,實(shí)現(xiàn)打包的時(shí)候按需引入,否則會(huì)把@babel/polyfill全部注入代碼中會(huì)導(dǎo)致打出來的包非常大。
2.@babel/polyfill會(huì)污染全局變量。
Babel7的一個(gè)重大變化就是npm package 名稱的變化,把所有babel-*重命名為@babel/*,例如:
-
babel-polyfill重命名為@babel/polyfill -
babel-preset-env重命名為@babel/preset-env
Babel在webpack中的用法
首先實(shí)現(xiàn)對ES6語法的轉(zhuǎn)譯
在webpack4目錄下新建demo3文件夾,將demo2目錄下的所有東西復(fù)制到demo3中
安裝babel-loader、 @babel/core、@babel/preset-env
npm i babel-loader -Dnpm i @babel/core -Dnpm i @babel/preset-env -D
babel-loader@8需要安裝@babel/core7.x版本。
在webpack.config.js配置
module.exports={
module: {
rules:[
{
test: /\.js$/,
exclude: /node_modules/,
use:{
loader: 'babel-loader',
options:{
presets: [
["@babel/preset-env",{
//targets:表示編譯出的代碼想要支持的瀏覽器版本
targets: {
chrome: "67"
}
}]
]
}
}
}
]
}
}
執(zhí)行npm run build或npx webpack就可以看到dist目錄下的打包文件,但是只是將ES6的語法進(jìn)行轉(zhuǎn)譯,并沒有對ES6新API進(jìn)行轉(zhuǎn)譯,所以我們需要配置@babel/polyfill解決這個(gè)問題。
安裝
@babel/polyfill
npm i @babel/polyfill --save
在index.js中引入@babel/polyfill
index.js
//index.js
import '@babel/polyfill'
let arr=[
new Promise(()=>{}),
new Promise(()=>{}),
2
]
arr.map((item)=>{
console.log(item)
})
引入@babel/polyfill前,main.js的大小為29.5KB
引入@babel/polyfill后,main.js的大小為1MB
注意:以上對比都是在沒有targets這個(gè)選項(xiàng)的情況下,因?yàn)橛行g覽器幾乎都支持ES6,在這種情況下,@babel/preset-env將不會(huì)對代碼進(jìn)行處理。
這是因?yàn)榘?code>@babel/polyfill對所有API的實(shí)現(xiàn)都注入到打包文件中,但是里面很多的API我們在代碼中并沒有用到,所以需要修改配置,按需引入對應(yīng)的API。
修改webpack.config.js配置
添加"useBuiltIns": "usage"以后,需要安裝core-js@2,并且添加"corejs": 2配置項(xiàng),這時(shí)配置選項(xiàng)比較多,需要在項(xiàng)目根目錄下新建.babelrc文件,在這個(gè)文件中配置。
.babelrc配置如下:
"useBuiltIns"屬性值為"usage"時(shí),會(huì)自動(dòng)引入@babel/polyfill,必須保證已經(jīng)安裝了@babel/polyfill"useBuiltIns"屬性值為"usage"時(shí),需要添加"corejs": 2配置項(xiàng),否則報(bào)錯(cuò),需要安裝core-js
首先刪掉index.js中的import '@babel/polyfill'
安裝
core-js
npm i --save core-js@2或npm i --save core-js@3
{
"presets": [["@babel/preset-env",{
"useBuiltIns": "usage", //不需要把polly都打包到代碼中,根據(jù)代碼按需轉(zhuǎn)譯
// core-js@3和core-js@2二選一
//"corejs": 3, //npm i --save core-js@3
"corejs": 2 //npm i --save core-js@2
}]]
}
修改webpack.config.js,刪除options對象
module.exports={
module: {
rules:[
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
}
}
執(zhí)行npm run build,打包后的文件大小為165KB
但是,在開發(fā)類庫或是第三方模塊時(shí)不適合使用@babel/polyfill,所以接下來使用@babel/plugin-transform-runtime來解決這個(gè)問題。
@babel/plugin-transform-runtime、@babel/runtime和@babel/runtime-corejs2的用法
@babel/runtime-corejs2:是一個(gè)包含Babel modular runtime helpers和regenerator-runtime以及core-js的庫。
@babel/runtime:是一個(gè)包含Babel modular runtime helpers和regenerator-runtime的庫。
在配置項(xiàng)中corejs屬性值為默認(rèn)為false,如果需要將Promise等API進(jìn)行轉(zhuǎn)譯,則需要設(shè)置屬性值為2時(shí),并且安裝@babel/runtime-corejs2
安裝:
npm i @babel/plugin-transform-runtime -Dnpm i --save @babel/runtimenpm i --save @babel/runtime-corejs2
修改.babelrc文件
{
"plugins": [
["@babel/plugin-transform-runtime",{
"helpers": true,
"regenerator": true,
"useESModules": false,
"corejs": 2
}]
]
}
我們把presets配置項(xiàng)去掉了,然后npm run build打包,打開打包后的main.js查看,雖然把轉(zhuǎn)譯了Promise,但是ES6新語法并沒被轉(zhuǎn)譯,例如:let沒有被轉(zhuǎn)譯為var。
所以還是需要配置presets,因?yàn)?code>"@babel/preset-env"包含了對所有ES6語法轉(zhuǎn)譯為ES5插件。
再次修改.babelrc文件
{
"presets": ["@babel/preset-env"],
"plugins": [
["@babel/plugin-transform-runtime",{
"helpers": true,
"regenerator": true,
"useESModules": false,
"corejs": 2
}]
]
}
添加presets配置項(xiàng),然后npm run build打包,打開打包后的main.js查看,可以看到let和箭頭函數(shù)都被轉(zhuǎn)譯為ES5語法了。
四、Tree Shaking使用
首先,在webpack4目錄下新建demo4文件夾,將demo3目錄下的所有東西復(fù)制到demo4中
Tree Shaking可以用來剔除JavaScript中用不上的死代碼。它依賴靜態(tài)的ES6模塊化語法,例如通過import和export導(dǎo)入導(dǎo)出。
需要注意的是要讓Tree Shaking正常工作的前提是JavaScript代碼必須采用ES6模塊化語法,因?yàn)?code>ES6模塊化語法是靜態(tài)的,這讓Webpack可以簡單的分析出哪些export的被import過了。
接下來配置Webpack讓Tree Shaking生效
webpack4默認(rèn)保留ES6模塊化語句,并沒有通過Babel將其轉(zhuǎn)換
修改.babelrc文件為如下:
//.babelrc
{
"presets": [["@babel/preset-env",{
"useBuiltIns": "usage",
"corejs": 2,
"modules":false //關(guān)閉 Babel 的模塊轉(zhuǎn)換功能,保留原本的 ES6 模塊化語法
//默認(rèn)是auto,取值還可以是 amd, umd, systemjs, commonjs,auto等
}]]
}
修改webapck.config.js,添加
optimization: {
usedExports: true
}
到module.exports{}中
module.exports={
mode: 'development',
optimization: {
//開發(fā)壞境使用tree shaking時(shí)加usedExports: true
usedExports: true
},
}
還需通過package.json的"sideEffects"屬性來告訴webpack哪些模塊是可以忽略掉,如果沒有則設(shè)置為false,來告知webpack,它可以安全地刪除未用到的export。
修改package.json
{
"name": "your-project",
"sideEffects": false
}
在demo4下的src新建math.js
index.js
//tree shaking import export
import {cube} from './math.js'
let component = () => {
let element = document.createElement('pre')
element.innerHTML = [
'Hello webpack!',
'2 cubed is equal to ' + cube(2)
].join('\n\n');
console.log(cube)
return element;
}
document.body.appendChild(component());
math.js
export let square= (x) => {
console.log(x)
return x * x;
}
export let cube = (x) => {
console.log(x)
return x * x * x;
}
運(yùn)行npm run build,然后打開打包后的js文件:main.js找到下面這段文字
/*!*********************!*\
!*** ./src/math.js ***!
\*********************/
/*! exports provided: square, cube */
/*! exports used: cube */
/***/
從上面這段文字可以看出Tree Shaking生效了,但是在開發(fā)環(huán)境下,并沒有把沒有用的代碼刪掉,因?yàn)樵陂_發(fā)環(huán)境下還需要對代碼進(jìn)行調(diào)試。
我們已經(jīng)找出需要?jiǎng)h除的“未引用代碼(dead code)”,然而,不僅僅是要找出,還要?jiǎng)h除它們。為此,我們需要將mode配置選項(xiàng)設(shè)置為production,將optimization對象刪掉,修改devtool配置選項(xiàng)
webpack.config.js
module.exports = {
mode: 'production',
devtool: 'cheap-module-source-map'
}
運(yùn)行npm run build,查看打包結(jié)果就可以看到?jīng)]有用的代碼被刪掉了。
五、Develoment和Production不同環(huán)境的配置
在webpack4下新建demo5,將demo4下的所有文件復(fù)制到demo5中
因?yàn)樵诓煌沫h(huán)境下,webpack的配置稍微有點(diǎn)區(qū)別,如果我們需要在不同的換將下切換,就得修改webpack配置,這是很麻煩而且還容易改錯(cuò),所以我們需要把配置文件進(jìn)行拆分。
在項(xiàng)目根目錄下新建build文件夾,然后在build文件夾中新建webpack.dev.js、webpack.prod.js和webpack.base.js三個(gè)文件
webpack.dev.js:是開發(fā)環(huán)境
webpack.prod.js:是生產(chǎn)環(huán)境
webpack.base.js:是開發(fā)環(huán)境和生產(chǎn)環(huán)境都用到的配置
這幾個(gè)文件之間的結(jié)合靠'webpack-merge'這個(gè)插件。
安裝
npm i webpack-merge -D
webpack.dev.js
//webpack.dev.js
const webpack=require('webpack')
const merge = require('webpack-merge')
const baseConfig=require('./webpack.base')
const devConfig={
mode: 'development',
devtool: 'cheap-module-eval-source-map',
plugins: [
new webpack.HotModuleReplacementPlugin()
],
optimization: {
usedExports: true
},
devServer: {
contentBase: './dist',
// open: true, //自動(dòng)打開瀏覽器
// port: 8080,
hot: true, //啟用webpack的熱模塊替換功能
//hotOnly: true
//devServer.hot在沒有頁面刷新的情況下啟用熱模塊替換作為構(gòu)建失敗時(shí)的后備
}
}
module.exports=merge(baseConfig,devConfig)
webapck.prod.js
//webapck.prod.js
const merge = require('webpack-merge')
const baseConfig=require('./webpack.base')
const prodConfig={
mode: 'production',
devtool: 'cheap-module-source-map'
}
module.exports=merge(baseConfig,prodConfig)
但是這兩個(gè)文件還有大量重復(fù)的代碼,新建webpack.base.js
//webpack.base.js
const path = require('path')
const htmlWebpackPlugin = require('html-webpack-plugin')
const cleanWebpackPlugin = require('clean-webpack-plugin')
module.exports={
entry: {
main: './src/index.js'
},
output: {
filename: '[name].js',
path: path.resolve(__dirname,'dist')
},
module: {
rules:[
{
test: /\.(png|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/',
limit: 2048
}
}
},
{
test: /\.css$/,
use:[
'style-loader',
'css-loader',
'postcss-loader'
]
},
{
test: /\.scss$/,
use:[
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
modules: true
}
},
'sass-loader',
'postcss-loader'
]
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
},
plugins: [
new htmlWebpackPlugin({
template: './index.html'
}),
new cleanWebpackPlugin(),
]
}
修改package.json的script:
{
"scripts": {
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js"
},
}
開發(fā)環(huán)境:運(yùn)行npm run dev,打開瀏覽器訪問http://localhost:8080/就可以看到結(jié)果
生產(chǎn)環(huán)境:運(yùn)行npm run build
六、SplitChunksPlugin插件的用法
1.SplitChunksPlugin插件介紹
webpack 4移除CommonsChunkPlugin,取而代之的是SplitChunksPlugin。下面介紹SplitChunksPlugin的用法。
在webpack4目錄下新建demo6目錄,將demo5目錄下的所有文件都復(fù)制到demo6中。
安裝 lodash
npm i lodash --save
在package.json添加
"scripts": {
"dev-build": "webpack --config ./build/webpack.dev.js"
}
修改index.js
import _ from 'lodash'
console.log(_.join(['lodash', 'babel', 'webpack'], '-'))
運(yùn)行npm run dev-build后,demo6目錄結(jié)構(gòu)如下
demo6
├── demo6/build
│ ├── demo6/build/webpack.base.js
│ ├── demo6/build/webpack.dev.js
│ └── demo6/build/webpack.prod.js
├── demo6/dist
│ ├── demo6/dist/index.html
│ └── demo6/dist/main.js
├── demo6/index.html
├── demo6/package.json
├── demo6/package-lock.json
├── demo6/postcss.config.js
└── demo6/src
└── demo6/src/index.js
dist目錄下只有main.js一個(gè)js文件,lodash庫也一起打包到main.js里面,這種情況下會(huì)導(dǎo)致文件變大,導(dǎo)致頁面加載速度變慢,我們需要把第三庫或是需要單獨(dú)打包的代碼給分割出來。
這時(shí)需要使用webpack4自帶的插件SplitChunksPlugin,默認(rèn)情況下它將只會(huì)影響按需加載的代碼塊
在webpack.config.js添加optimization.splitChunks.chunks
optimization: {
splitChunks: {
//chunks: all, async, initial.
//async針對異步加載的chunk做切割,initial針對初始chunk,all針對所有chunk。
chunks: 'async'
}
}
運(yùn)行npm run dev-build后,打包后的代碼并沒有分割。
修改optimization.splitChunks.chunks為all
optimization: {
splitChunks: {
//chunks: all, async, initial.
//async針對異步加載的chunk做切割,initial針對初始chunk,all針對所有chunk。
chunks: 'all'
}
}
運(yùn)行npm run dev-build后,demo6目錄結(jié)構(gòu)如下
demo6
├── demo6/build
│ ├── demo6/build/webpack.base.js
│ ├── demo6/build/webpack.dev.js
│ └── demo6/build/webpack.prod.js
├── demo6/dist
│ ├── demo6/dist/index.html
│ ├── demo6/dist/main.js
│ └── demo6/dist/vendors~main.js
├── demo6/index.html
├── demo6/package.json
├── demo6/package-lock.json
├── demo6/postcss.config.js
└── demo6/src
└── demo6/src/index.js
可以看到dist目錄下多了vendors~main.js文件,說明SplitChunksPlugin插件生效了
接下來先看optimization.splitChunks的默認(rèn)配置
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
}
chunks: 表示將對哪些塊進(jìn)行優(yōu)化,可選async, initial, all,async對異步加載的模塊進(jìn)行分割,initial對初始模塊,all對所有模塊
minSize: 加載的模塊不小于30kb才進(jìn)行分割
minChunks: 生成的
chunk,共享該模塊的chunk必須不小于1時(shí)才分割maxAsyncRequests:按需加載時(shí)的最大并行請求數(shù)
maxInitialRequests:入口處的最大并行請求數(shù)
automaticNameDelimiter:默認(rèn)情況下,webpack將使用塊的名稱和名稱生成名稱(例如vendors~main.js)。此選項(xiàng)指定用于生成的名稱的分隔符
name:生成chunk的名字,如果設(shè)成true,將根據(jù)模塊和緩存組配置結(jié)合生成名稱
cacheGroups: 緩存組可以繼承和/或覆蓋任何選項(xiàng)splitChunks.*; 但是test,priority并且reuseExistingChunk只能在高速緩存組級別配置。要禁用任何默認(rèn)緩存組,請將其設(shè)置為false。
test:控制此緩存組選擇的模塊
priority:模塊可以屬于多個(gè)緩存組,模塊則歸于緩存組priority高的
reuseExistingChunk: 如果當(dāng)前塊包含已拆分的模塊,則將重用它而不是生成新的塊。
從以上可以看出來,默認(rèn)的配置只對異步加載的模塊有效
修改index.js,異步加載lodash
function getComponent(){
return import('lodash').then(({default: _})=>{
var element=document.createElement('div')
element.innerHTML=_.join(['lodash', 'babel', 'webpack'], '-')
return element
})
}
getComponent().then(element=>{
document.body.appendChild(element)
})
這時(shí)運(yùn)行npm run dev-build會(huì)報(bào)錯(cuò),需要下載安裝@babel/plugin-syntax-dynamic-import
npm i @babel/plugin-syntax-dynamic-import -D
在.babelrc中添加
"plugins": ["@babel/plugin-syntax-dynamic-import"]
再次運(yùn)行npm run dev-build,此時(shí)打包成功,目錄結(jié)構(gòu)如下
demo6
├── demo6/build
│ ├── demo6/build/webpack.base.js
│ ├── demo6/build/webpack.dev.js
│ └── demo6/build/webpack.prod.js
├── demo6/dist
│ ├── demo6/dist/0.js
│ ├── demo6/dist/index.html
│ └── demo6/dist/main.js
├── demo6/index.html
├── demo6/package.json
├── demo6/package-lock.json
├── demo6/postcss.config.js
└── demo6/src
└── demo6/src/index.js
可以看到dist目錄下0.js,就是對lodash打包后的文件,有時(shí)我們希望能夠改變0.js的名字
修改index.js,添加/* webpackChunkName:"lodash" */
function getComponent(){
return import(/* webpackChunkName:"lodash" */'lodash').then(({default: _})=>{
var element=document.createElement('div')
element.innerHTML=_.join(['lodash', 'babel', 'webpack'], '-')
return element
})
}
getComponent().then(element=>{
document.body.appendChild(element)
})
運(yùn)行npm run dev-build,發(fā)現(xiàn)0.js變?yōu)?code>vendors~lodash.js
也可以通過設(shè)置optimization.splitChunks.cacheGroups.vendors.name來修改打包后的文件名字
修改optimization.splitChunks配置
optimization: {
splitChunks: {
chunks: 'all',
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
name: 'vendors'
},
default: {
minChunks: 1,
priority: -20,
reuseExistingChunk: true
}
}
}
},
運(yùn)行npm run dev-build,發(fā)現(xiàn)打包后的文件名字變?yōu)?code>vendors.js
以上就是SplitChunksPlugin的簡單用法
七、css代碼的分割
MiniCssExtractPlugin插件將CSS提取到單獨(dú)的文件中。它為每個(gè)包含CSS的JS文件創(chuàng)建一個(gè)CSS文件,在webpack 4才能使用
在webpack目錄下新建demo7,將demo6下的所有文件都復(fù)制到demo7中,進(jìn)入demo7
安裝
npm install --save-dev mini-css-extract-plugin
官網(wǎng)提示,這個(gè)插件應(yīng)該在生產(chǎn)環(huán)境中使用,所以修改webpack的相關(guān)配置。
首先將webpack.base.js中對css和scss處理的loader配置分別復(fù)制粘貼(在webpack.base.js中刪掉這一部分)到webpack.prod.js和webpack.dev.js中。
在webpack.prod.js中引入
const miniCssExtractPlugin = require('mini-css-extract-plugin')
修改后的相關(guān)配置如下:
webpack.base.js
const path = require('path')
const htmlWebpackPlugin = require('html-webpack-plugin')
const cleanWebpackPlugin = require('clean-webpack-plugin')
module.exports = {
entry: {
main: './src/index.js' //對應(yīng)filename
//入口文件引入的模塊,分割打包的名字對應(yīng)chunkFilename
},
output: {
filename: '[name].js',
chunkFilename: '[name].chunk.js',
path: path.resolve(__dirname, '../dist')
},
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/',
limit: 2048
}
}
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
},
plugins: [
new htmlWebpackPlugin({
template: './index.html'
}),
new cleanWebpackPlugin(),
],
optimization: {
usedExports: true,
splitChunks: {
//chunks: all, async, initial.
//async 只對異步分割有效
// initial 同步
//all 要配置cacheGroups
chunks: 'all',
// 引入的包或是庫大于30kb才對代碼進(jìn)行分割
minSize: 30000,
maxSize: 0, //沒多大意義
minChunks: 1, //當(dāng)一個(gè)模塊至少引入多少次時(shí)才會(huì)進(jìn)行代碼分割
maxAsyncRequests: 5, //同時(shí)加載的模塊數(shù)最多是5個(gè)
maxInitialRequests: 3,
automaticNameDelimiter: '~', //打包后的文件名字之間的連接符
name: true,
cacheGroups: { //緩存組
// vendors: false,
vendors: {
//同步 檢查是否在node_modules里面
test: /[\\/]node_modules[\\/]/,
priority: -10, //優(yōu)先級
name: 'vendors'
},
// default: false
default: {
minChunks: 1,
priority: -20,
reuseExistingChunk: true, //如果模塊已經(jīng)打包過了就引用之前打包好的模塊
// filename: 'common.js'
}
}
}
}
}
webpack.prod.js
const merge = require('webpack-merge')
const baseConfig=require('./webpack.base')
const miniCssExtractPlugin = require('mini-css-extract-plugin')
const prodConfig={
mode: 'production',
devtool: 'cheap-module-source-map',
module: {
rules: [
{
test: /\.css$/,
use: [
miniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
},
{
test: /\.scss$/,
use: [
miniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 2,
modules: true
}
},
'postcss-loader',
'sass-loader'
]
},
]
},
plugins: [
new miniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[name].chunk.css'
})
]
}
module.exports=merge(baseConfig,prodConfig)
webapck.dev.js
const webpack=require('webpack')
const merge = require('webpack-merge')
const baseConfig=require('./webpack.base')
const devConfig={
mode: 'development',
// devtool: 'cheap-module-eval-source-map',
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
},
{
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
modules: true
}
},
'sass-loader',
'postcss-loader'
]
},
]
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
devServer: {
contentBase: './dist',
// open: true, //自動(dòng)打開瀏覽器
// port: 8080,
hot: true, //啟用webpack的熱模塊替換功能
//hotOnly: true
//devServer.hot在沒有頁面刷新的情況下啟用熱模塊替換作為構(gòu)建失敗時(shí)的后備
}
}
module.exports=merge(baseConfig,devConfig)
需要注意的一點(diǎn)就是,在前面的時(shí)候,我們配置tree shaking的時(shí)候,在package.json添加了sideEffects配置項(xiàng),需要修改這個(gè)配置項(xiàng)為
"sideEffects": [
"*.css"
],
否則通過import './*.css'方式引入的css文件會(huì)被刪除掉。
運(yùn)行npm run build,我們就可以看到dist目錄下,css文件被單獨(dú)分割出來了。
官網(wǎng)還提供一個(gè)插件可以對css文件進(jìn)行壓縮
安裝
npm i optimize-css-assets-webpack-plugin -D
修改webpack.prod.js
//引入
const optimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
//添加
optimization: {
minimizer: [new optimizeCSSAssetsPlugin({})],
},
再次運(yùn)行npm run build,打開打包后的css文件,就發(fā)現(xiàn)css文件被壓縮了。
代碼都已經(jīng)上傳到github:github:webpack4