前言
本文主要從webpack4.x入手,會(huì)對(duì)平時(shí)常用的Webpack配置一一講解,各個(gè)功能點(diǎn)都有對(duì)應(yīng)的詳細(xì)例子,所以本文也比較長(zhǎng),但如果你能動(dòng)手跟著本文中的例子完整寫一次,相信你會(huì)覺(jué)得Webpack也不過(guò)如此。
?
一、什么是webpack,為什么使用它?

?
1.1 什么是webpack?
簡(jiǎn)單來(lái)說(shuō),它其實(shí)就是一個(gè)模塊打包器。
?
1.2 為什么使用它?
如果像以前開(kāi)發(fā)時(shí)一個(gè)html文件可能會(huì)引用十幾個(gè)js文件,而且順序還不能亂,因?yàn)樗鼈兇嬖谝蕾囮P(guān)系,同時(shí)對(duì)于ES6+等新的語(yǔ)法,less, sass等CSS預(yù)處理都不能很好的解決……,此時(shí)就需要一個(gè)處理這些問(wèn)題的工具。
Webpack就是為處理這些問(wèn)題而生的,它就是把你的項(xiàng)目當(dāng)成一個(gè)整體,通過(guò)一個(gè)入口主文件(如:index.js),從這個(gè)文件開(kāi)始找到你的項(xiàng)目所有的依賴文件并處理它們,最后打包成一個(gè)(或多個(gè))瀏覽器可識(shí)別的JavaScript文件。
?
二、一個(gè)簡(jiǎn)單的打包例子
2.1 準(zhǔn)備工作
首先新建一個(gè)空文件夾,用于創(chuàng)建項(xiàng)目,在終端中進(jìn)入文件夾,如下我在桌面建了一個(gè)名為webpack-project的文件夾,使用終端進(jìn)入文件夾后(如果對(duì)命令行不太熟悉,可參考我的博客:前端常用命令行),使用npm init命令創(chuàng)建一個(gè)package.json文件。
npm init

輸入這個(gè)命令后,終端會(huì)問(wèn)你一系列諸如項(xiàng)目名稱,項(xiàng)目描述,作者等信息,不過(guò)如果你不打算發(fā)布這個(gè)模塊,直接一路回車就好。(也可以使用npm init -y這個(gè)命令來(lái)一次生成package.json文件,這樣終端不會(huì)詢問(wèn)你問(wèn)題)。
?
2.2 安裝webpack
如果你想一步到位的話,就把全局webpack和本地項(xiàng)目webpack全都先裝了,因?yàn)楹竺嬉恍┠K會(huì)用到。安裝本地項(xiàng)目webapck時(shí)把webpack-cli也裝上,因?yàn)閣ebpack模塊把一些功能分到了webpack-cli模塊,安裝方法如下:
npm install webpack --global //這是安裝全局webpack命令
npm install webpack webpack-cli --save-dev //這是安裝本地項(xiàng)目模塊
?
tips:
上述命令可采用簡(jiǎn)寫,
install可簡(jiǎn)寫為i,--global可簡(jiǎn)寫為-g,--save-dev可簡(jiǎn)寫為-D(這個(gè)命令是用于把配置添加到package.json的開(kāi)發(fā)環(huán)境配置列表中,后面會(huì)提到),--save可簡(jiǎn)寫為-S,同時(shí)國(guó)內(nèi)我們可以采用cnpm,配置方法可去這里查看,這樣安裝速度會(huì)相對(duì)較快。如下:
cnpm i webpack -g //這是安裝全局webpack命令
cnpm i webpack webpack-cli -D //這是安裝本地項(xiàng)目模塊
?
2.3 新建文件
在webpack-project文件夾中新建兩個(gè)文件夾,分別為src文件夾和dist文件夾,接下來(lái)再創(chuàng)建三個(gè)文件:
-
index.html--放在dist文件夾中; -
hello.js--放在src文件夾中; -
index.js--放在src文件夾中;
此時(shí),項(xiàng)目結(jié)構(gòu)如下:

我們?cè)?code>index.html中寫下html代碼,它的作用是為了引入我們打包后的js文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Webpack Project</title>
</head>
<body>
<div id='root'></div>
<script src="bundle.js"></script> <!--這是打包之后的js文件,我們暫時(shí)命名為bundle.js-->
</body>
</html>
我們?cè)?code>hello.js中導(dǎo)出一個(gè)模塊:
// hello.js
module.exports = function() {
let hello = document.createElement('div');
hello.innerHTML = "Long time no see!";
return hello;
};
然后在index.js中引入這個(gè)模塊(hello.js):
//index.js
const hello = require('./hello.js');
document.querySelector("#root").appendChild(hello());
上述操作就相當(dāng)于我們把hello.js模塊合并到了index.js模塊,之后我們打包時(shí)就只需把index.js模塊打包成bundle.js,然后供index.html引用即可,這就是最簡(jiǎn)單的webpack打包原理。
?
2.4 開(kāi)始進(jìn)行webpack打包
在終端中使用如下命令進(jìn)行打包:
// webpack全局安裝的情況下
webpack src/index.js --output dist/bundle.js
// --output可簡(jiǎn)寫為-o
上述就相當(dāng)于把src文件夾下的index.js文件打包到dist文件下的bundle.js,這時(shí)就生成了bundle.js供index.html文件引用。
結(jié)果如下:

可以看出webpack同時(shí)編譯了index.js和hello.js,現(xiàn)在打開(kāi)index.html,可以看到如下結(jié)果:

沒(méi)錯(cuò),我們已經(jīng)成功使用webpack進(jìn)行打包,原來(lái)webpack也不過(guò)如此嘛!但是,每次都在終端中輸入這么長(zhǎng)的命令,感覺(jué)好煩啊,還好有懶人方法,讓我們看看。
?
2.5 通過(guò)配置文件來(lái)使用webpack
其實(shí)webpack是有很多功能的,也是很方便的,我們可以在當(dāng)前項(xiàng)目的根目錄下新建一個(gè)配置文件webpack.config.js,我們寫下如下簡(jiǎn)單配置代碼,目前只涉及入口配置(相當(dāng)于我們的index.js,從它開(kāi)始打包)和出口配置(相當(dāng)于我們打包生成的bundle.js)。
// webpack.config.js
module.exports = {
entry: __dirname + "/src/index.js", // 入口文件
output: {
path: __dirname + "/dist", //打包后的文件存放的地方
filename: "bundle.js" //打包后輸出文件的文件名
}
}
注:
__dirname是node.js中的一個(gè)全局變量,它指向當(dāng)前執(zhí)行腳本所在的目錄,即C:\Users\sjt\DeskTop\webpack-project(這是我當(dāng)前的目錄)
但平時(shí)我們看到的腳手架配置也比較喜歡采用node.js的path模塊來(lái)處理絕對(duì)路徑,所以我們也可以采用如下的寫法,和上述的效果是一樣的:
// webpack.config.js
const path = require('path');
module.exports = {
entry: path.join(__dirname, "/src/index.js"), // 入口文件
output: {
path: path.join( __dirname, "/dist"), //打包后的文件存放的地方
filename: "bundle.js" //打包后輸出文件的文件名
}
}
注:
path.join的功能是拼接路徑片段。
有了這個(gè)配置文件,我們只需在終端中運(yùn)行webpack命令就可進(jìn)行打包,這條命令會(huì)自動(dòng)引用webpack.config.js文件中的配置選項(xiàng),示例如下:

搞定,是不是這樣更方便了,感覺(jué)沒(méi)那么low了,但還能不能更便捷智能呢?那必須的!
?
2.6 更智能的打包方式
我們現(xiàn)在只在終端中使用webpack命令來(lái)進(jìn)行打包,要是以后在打包的同時(shí)還有更多的操作呢,那不是還得寫上更多的命令?所以我們得想辦法把這些命令都集成起來(lái),這時(shí)候之前的package.json文件就派上用場(chǎng)了。
現(xiàn)在的package.json文件大概就是如下這樣:
{
"name": "webpack-project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1" //我們要修改的是這里,JSON文件不支持注釋,引用時(shí)請(qǐng)清除
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.23.1",
"webpack-cli": "^3.1.2"
}
}
修改如下:
{
"name": "webpack-project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack", //改成這樣,注意使用時(shí)把注釋刪掉
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.23.1",
"webpack-cli": "^3.1.2"
}
}
注:package.json中的script會(huì)按你設(shè)置的命令名稱來(lái)執(zhí)行對(duì)應(yīng)的命令。
這樣我們就可以在終端中直接執(zhí)行npm start命令來(lái)進(jìn)行打包,start命令比較特殊,可以直接npm加上start就可以執(zhí)行,如果我們想起其他的名稱,如build時(shí),就需要使用npm run加上build,即npm run build命令。
現(xiàn)在我們執(zhí)行npm start命令:

OK,搞定,是不是很簡(jiǎn)單,但webpack的功能遠(yuǎn)不止于此,下面我們繼續(xù)。
?
三、構(gòu)建本地服務(wù)器
現(xiàn)在我們是通過(guò)打開(kāi)本地文件來(lái)查看頁(yè)面的,看起來(lái)總感覺(jué)比較low,看別人用vue,react框架時(shí)都是運(yùn)行在本地服務(wù)器上的,那我們能不能也那樣呢?那必須的!
?
3.1 webpack-dev-server配置本地服務(wù)器
Webpack提供了一個(gè)可選的本地開(kāi)發(fā)服務(wù)器,這個(gè)本地服務(wù)器基于node.js構(gòu)建,它是一個(gè)單獨(dú)的組件,在webpack中進(jìn)行配置之前需要單獨(dú)安裝它作為項(xiàng)目依賴:
cnpm i webpack-dev-server -D
devServer作為webpack配置選項(xiàng)中的一項(xiàng),以下是它的一些配置選項(xiàng):
-
contentBase:設(shè)置服務(wù)器所讀取文件的目錄,當(dāng)前我們?cè)O(shè)置為"./dist" -
port:設(shè)置端口號(hào),如果省略,默認(rèn)為8080 -
inline:設(shè)置為true,當(dāng)源文件改變時(shí)會(huì)自動(dòng)刷新頁(yè)面 -
historyApiFallback:設(shè)置為true,所有的跳轉(zhuǎn)將指向index.html
現(xiàn)在我們把這些配置加到webpack.config.js文件上,如下:
// webpack.config.js
const path = require('path');
module.exports = {
entry: path.join(__dirname, "/src/index.js"), // 入口文件
output: {
path: path.join( __dirname, "/dist"), //打包后的文件存放的地方
filename: "bundle.js" //打包后輸出文件的文件名
},
devServer: {
contentBase: "./dist", // 本地服務(wù)器所加載文件的目錄
port: "8088", // 設(shè)置端口號(hào)為8088
inline: true, // 文件修改后實(shí)時(shí)刷新
historyApiFallback: true, //不跳轉(zhuǎn)
}
}
我們繼續(xù)在package.json文件中添加啟動(dòng)命令:
{
"name": "webpack-project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack",
"dev": "webpack-dev-server --open"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.23.1",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10"
}
}
我們把start命令名稱改為了build,這樣比較語(yǔ)義化,平時(shí)的腳手架也多數(shù)采用這個(gè)名稱,我們用dev(development的縮寫,意指開(kāi)發(fā)環(huán)境)來(lái)啟動(dòng)本地服務(wù)器,webpack-dev-server就是啟動(dòng)服務(wù)器的命令,--open是用于啟動(dòng)完服務(wù)器后自動(dòng)打開(kāi)瀏覽器,這時(shí)候我們自定義命令方式的便捷性就體現(xiàn)出來(lái)了,可以多個(gè)命令集成在一起運(yùn)行,即我們定義了一個(gè)dev命令名稱就可以同時(shí)運(yùn)行了webpack-dev-server和--open兩個(gè)命令。
現(xiàn)在在終端輸入npm run dev運(yùn)行服務(wù)器:

這樣我們即可在http://localhost:8088/中查看頁(yè)面(退出服務(wù)器,可使用ctrl+c后,再按y確認(rèn),即可退出服務(wù)器運(yùn)行)
?
3.2 Source Maps調(diào)試配置
作為開(kāi)發(fā),代碼調(diào)試當(dāng)然少不了,那么問(wèn)題來(lái)了,經(jīng)過(guò)打包后的文件,你是不容易找到出錯(cuò)的地方的,Source Map就是用來(lái)解決這個(gè)問(wèn)題的。
通過(guò)如下配置,我們會(huì)在打包時(shí)生成對(duì)應(yīng)于打包文件的.map文件,使得編譯后的代碼可讀性更高,更易于調(diào)試。
// webpack.config.js
const path = require('path');
module.exports = {
entry: path.join(__dirname, "/src/index.js"), // 入口文件
output: {
path: path.join( __dirname, "/dist"), //打包后的文件存放的地方
filename: "bundle.js" //打包后輸出文件的文件名
},
devServer: {
contentBase: "./dist", // 本地服務(wù)器所加載文件的目錄
port: "8088", // 設(shè)置端口號(hào)為8088
inline: true, // 文件修改后實(shí)時(shí)刷新
historyApiFallback: true, //不跳轉(zhuǎn)
},
devtool: 'source-map' // 會(huì)生成對(duì)于調(diào)試的完整的.map文件,但同時(shí)也會(huì)減慢打包速度
}
配置好后,我們?cè)俅芜\(yùn)行npm run build進(jìn)行打包,這時(shí)我們會(huì)發(fā)現(xiàn)在dist文件夾中多出了一個(gè)bundle.js.map文件如下:

如果我們的代碼有bug,在瀏覽器的調(diào)試工具中會(huì)提示錯(cuò)誤出現(xiàn)的位置,這就是
devtool: 'source-map'配置項(xiàng)的作用。?
四、Loaders
loaders是webpack最強(qiáng)大的功能之一,通過(guò)不同的loader,webpack有能力調(diào)用外部的腳本或工具,實(shí)現(xiàn)對(duì)不同格式的文件的處理,例如把scss轉(zhuǎn)為css,將ES66、ES7等語(yǔ)法轉(zhuǎn)化為當(dāng)前瀏覽器能識(shí)別的語(yǔ)法,將JSX轉(zhuǎn)化為js等多項(xiàng)功能。
Loaders需要單獨(dú)安裝并且需要在webpack.config.js中的modules配置項(xiàng)下進(jìn)行配置,Loaders的配置包括以下幾方面:
-
test:一個(gè)用以匹配loaders所處理文件的拓展名的正則表達(dá)式(必須) -
loader:loader的名稱(必須) -
include/exclude:手動(dòng)添加必須處理的文件(文件夾)或屏蔽不需要處理的文件(文件夾)(可選); -
options:為loaders提供額外的設(shè)置選項(xiàng)(可選)
?
4.1 配置css-loader
如果我們要加載一個(gè)css文件,需要安裝配置style-loader和css-loader:
cnpm i style-loader css-loader -D
// webpack.config.js
const path = require('path');
module.exports = {
entry: path.join(__dirname, "/src/index.js"), // 入口文件
output: {
path: path.join( __dirname, "/dist"), //打包后的文件存放的地方
filename: "bundle.js" //打包后輸出文件的文件名
},
devServer: {
contentBase: "./dist", // 本地服務(wù)器所加載文件的目錄
port: "8088", // 設(shè)置端口號(hào)為8088
inline: true, // 文件修改后實(shí)時(shí)刷新
historyApiFallback: true, //不跳轉(zhuǎn)
},
devtool: 'source-map', // 會(huì)生成對(duì)于調(diào)試的完整的.map文件,但同時(shí)也會(huì)減慢打包速度
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結(jié)尾的文件
use: ['style-loader', 'css-loader'] // 需要用的loader,一定是這個(gè)順序,因?yàn)檎{(diào)用loader是從右往左編譯的
}
]
}
}
我們?cè)趕rc文件夾下新建css文件夾,該文件夾內(nèi)新建style.css文件:
/* style.css */
body {
background: gray;
}
在index.js中引用它:
//index.js
import './css/style.css'; //導(dǎo)入css
const hello = require('./hello.js');
document.querySelector("#root").appendChild(hello());
這時(shí)我們運(yùn)行npm run dev,會(huì)發(fā)現(xiàn)頁(yè)面背景變成了灰色。
如果是要編譯sass文件呢?
?
4.2 配置sass
cnpm i sass-loader node-sass -D // 因?yàn)閟ass-loader依賴于node-sass,所以還要安裝node-sass
增加sass的rules:
// webpack.config.js
const path = require('path');
module.exports = {
entry: path.join(__dirname, "/src/index.js"), // 入口文件
output: {
path: path.join( __dirname, "/dist"), //打包后的文件存放的地方
filename: "bundle.js" //打包后輸出文件的文件名
},
devServer: {
contentBase: "./dist", // 本地服務(wù)器所加載文件的目錄
port: "8088", // 設(shè)置端口號(hào)為8088
inline: true, // 文件修改后實(shí)時(shí)刷新
historyApiFallback: true, //不跳轉(zhuǎn)
},
devtool: 'source-map', // 會(huì)生成對(duì)于調(diào)試的完整的.map文件,但同時(shí)也會(huì)減慢打包速度
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結(jié)尾的文件
use: ['style-loader', 'css-loader'] // 需要用的loader,一定是這個(gè)順序,因?yàn)檎{(diào)用loader是從右往左編譯的
},
{
test: /\.(scss|sass)$/, // 正則匹配以.scss和.sass結(jié)尾的文件
use: ['style-loader', 'css-loader', 'sass-loader'] // 需要用的loader,一定是這個(gè)順序,因?yàn)檎{(diào)用loader是從右往左編譯的
}
]
}
}
在css文件夾中新建blue.scss文件:
/* blue.scss */
$blue: blue;
body{
color: $blue;
}
在index.js中引入blue.scss:
//index.js
import './css/style.css'; // 導(dǎo)入css
import './css/blue.scss'; // 導(dǎo)入scss
const hello = require('./hello.js');
document.querySelector("#root").appendChild(hello());
這時(shí)npm run dev重新啟動(dòng)服務(wù)器,應(yīng)該會(huì)出現(xiàn)如下結(jié)果:

還有諸如圖片loader、字體loader等就不一一列出來(lái)了,感興趣的可前往webpack官網(wǎng)查看,都是一樣的套路。
?
五、Babel
Babel其實(shí)是一個(gè)編譯JavaScript的平臺(tái),它可以編譯代碼幫你達(dá)到以下目的:
- 讓你能使用最新的JavaScript代碼(ES6,ES7...),而不用管新標(biāo)準(zhǔn)是否被當(dāng)前使用的瀏覽器完全支持;
- 讓你能使用基于JavaScript進(jìn)行了拓展的語(yǔ)言,比如React的JSX;
?
5.1 Babel的安裝與配置
Babel其實(shí)是幾個(gè)模塊化的包,其核心功能位于稱為babel-core的npm包中,webpack可以把其不同的包整合在一起使用,對(duì)于每一個(gè)你需要的功能或拓展,你都需要安裝單獨(dú)的包(用得最多的是解析ES6的babel-preset-env包和解析JSX的babel-preset-react包)。
cnpm i babel-core babel-loader babel-preset-env babel-preset-react -D
// babel-preset-env的env表示是對(duì)當(dāng)前環(huán)境的預(yù)處理,而不是像以前使用babel-preset-es2015只能針對(duì)某個(gè)環(huán)境
// webpack.config.js
const path = require('path');
module.exports = {
entry: path.join(__dirname, "/src/index.js"), // 入口文件
output: {
path: path.join( __dirname, "/dist"), //打包后的文件存放的地方
filename: "bundle.js" //打包后輸出文件的文件名
},
devServer: {
contentBase: "./dist", // 本地服務(wù)器所加載文件的目錄
port: "8088", // 設(shè)置端口號(hào)為8088
inline: true, // 文件修改后實(shí)時(shí)刷新
historyApiFallback: true, //不跳轉(zhuǎn)
},
devtool: 'source-map', // 會(huì)生成對(duì)于調(diào)試的完整的.map文件,但同時(shí)也會(huì)減慢打包速度
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結(jié)尾的文件
use: ['style-loader', 'css-loader'] // 需要用的loader,一定是這個(gè)順序,因?yàn)檎{(diào)用loader是從右往左編譯的
},
{
test: /\.(scss|sass)$/, // 正則匹配以.scss和.sass結(jié)尾的文件
use: ['style-loader', 'css-loader', 'sass-loader'] // 需要用的loader,一定是這個(gè)順序,因?yàn)檎{(diào)用loader是從右往左編譯的
},
{ // jsx配置
test: /(\.jsx|\.js)$/,
use: { // 注意use選擇如果有多項(xiàng)配置,可寫成這種對(duì)象形式
loader: "babel-loader",
options: {
presets: [
"env", "react"
]
}
},
exclude: /node_modules/
}
]
}
}
現(xiàn)在我們已經(jīng)可以支持ES6及JSX的語(yǔ)法了,我們用react來(lái)試試,但使用react還得先安裝兩個(gè)模塊react和react-dom。
cnpm i react react-dom -D
接下來(lái)我們把hello.js文件修改一下:
// hello.js
import React, {Component} from 'react'; // 這兩個(gè)模塊必須引入
let name = Alan;
export default class Hello extends Component{
render() {
return (
<div>
{name}
</div>
);
}
}
修改index.js文件:
//index.js
import './css/style.css'; // 導(dǎo)入css
import './css/blue.scss'; // 導(dǎo)入scss
import React from 'react';
import {render} from 'react-dom';
import Hello from './hello'; // 可省略.js后綴名
render(<Hello />, document.getElementById('root'));
此時(shí)運(yùn)行npm run dev后你可能會(huì)發(fā)現(xiàn)如下結(jié)果:

這是因?yàn)楣俜侥J(rèn)babel-loader | babel對(duì)應(yīng)的版本需要一致: 即babel-loader需要搭配最新版本babel,詳細(xì)可參考這篇博客。
兩種解決方案:
- 回退低版本
cnpm i babel-loader@7 babel-core babel-preset-env -D
- 更新到最高版本:
cnpm i babel-loader @babel/core @babel/preset-env webpack -D
我這里采取的是第一個(gè)方案,回退后,再此運(yùn)行npm run dev,得到如下結(jié)果:

到這里了是不是感覺(jué)很爽,不就是配置嘛,想要使用什么就配置什么。
?
5.2 優(yōu)化babel配置
雖然babel完全可以在webpack.config.js中進(jìn)行配置,但現(xiàn)在不是都提倡模塊化嘛,也許之后babel膨脹了,增加了更多的配置項(xiàng)呢?
那我們不如把它提取出來(lái),把它放到根目錄下的.babelrc文件下(webpack會(huì)自動(dòng)調(diào)用.babelrc里的babel配置選項(xiàng))。
我們?cè)陧?xiàng)目根目錄下新建.babelrc文件:

// webpack.config.js
const path = require('path');
module.exports = {
entry: path.join(__dirname, "/src/index.js"), // 入口文件
output: {
path: path.join( __dirname, "/dist"), //打包后的文件存放的地方
filename: "bundle.js" //打包后輸出文件的文件名
},
devServer: {
contentBase: "./dist", // 本地服務(wù)器所加載文件的目錄
port: "8088", // 設(shè)置端口號(hào)為8088
inline: true, // 文件修改后實(shí)時(shí)刷新
historyApiFallback: true, //不跳轉(zhuǎn)
},
devtool: 'source-map', // 會(huì)生成對(duì)于調(diào)試的完整的.map文件,但同時(shí)也會(huì)減慢打包速度
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結(jié)尾的文件
use: ['style-loader', 'css-loader'] // 需要用的loader,一定是這個(gè)順序,因?yàn)檎{(diào)用loader是從右往左編譯的
},
{
test: /\.(scss|sass)$/, // 正則匹配以.scss和.sass結(jié)尾的文件
use: ['style-loader', 'css-loader', 'sass-loader'] // 需要用的loader,一定是這個(gè)順序,因?yàn)檎{(diào)用loader是從右往左編譯的
},
{ // jsx配置
test: /(\.jsx|\.js)$/,
use: { // 注意use選擇如果有多項(xiàng)配置,可寫成這種對(duì)象形式
loader: "babel-loader"
},
exclude: /node_modules/ // 排除匹配node_modules模塊
}
]
}
}
// .babelrc 使用時(shí)把注釋刪掉,該文件不能添加注釋
{
"presets": ["env", "react"]
}
此時(shí)不出問(wèn)題的話應(yīng)該一切運(yùn)行正常,接下來(lái)讓我們進(jìn)入強(qiáng)大的插件模塊。
?
六、插件(Plugins)
插件(Plugins)是用來(lái)拓展Webpack功能的,它們會(huì)在整個(gè)構(gòu)建過(guò)程中生效,執(zhí)行相關(guān)的任務(wù)。
Loaders和Plugins常常被弄混,但是他們其實(shí)是完全不同的東西,可以這么來(lái)說(shuō),loaders是在打包構(gòu)建過(guò)程中用來(lái)處理源文件的(JSX,Scss,Less..),一次處理一個(gè),插件并不直接操作單個(gè)文件,它直接對(duì)整個(gè)構(gòu)建過(guò)程其作用。
?
6.1 插件如何使用
使用某個(gè)插件,需要通過(guò)npm進(jìn)行安裝,然后在webpack.config.js配置文件的plugins(是一個(gè)數(shù)組)配置項(xiàng)中添加該插件的實(shí)例,下面我們先來(lái)使用一個(gè)簡(jiǎn)單的版權(quán)聲明插件。
// webpack.config.js
const webpack = require('webpack'); // 這個(gè)插件不需要安裝,是基于webpack的,需要引入webpack模塊
module.exports = {
...
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結(jié)尾的文件
use: ['style-loader', 'css-loader'] // 需要用的loader,一定是這個(gè)順序,因?yàn)檎{(diào)用loader是從右往左編譯的
},
{
test: /\.(scss|sass)$/, // 正則匹配以.scss和.sass結(jié)尾的文件
use: ['style-loader', 'css-loader', 'sass-loader'] // 需要用的loader,一定是這個(gè)順序,因?yàn)檎{(diào)用loader是從右往左編譯的
},
{ // jsx配置
test: /(\.jsx|\.js)$/,
use: { // 注意use選擇如果有多項(xiàng)配置,可寫成這種對(duì)象形式
loader: "babel-loader"
},
exclude: /node_modules/ // 排除匹配node_modules模塊
}
]
},
plugins: [
new webpack.BannerPlugin('版權(quán)所有,翻版必究') // new一個(gè)插件的實(shí)例
]
}
運(yùn)行npm run build打包后我們看到bundle.js文件顯示如下:

?
6.2 自動(dòng)生成html文件(HtmlWebpackPlugin)
到目前為止我們都是使用一開(kāi)始建好的index.html文件,而且也是手動(dòng)引入bundle.js,要是以后我們引入不止一個(gè)js文件,而且更改js文件名的話,也得手動(dòng)更改index.html中的js文件名,所以能不能自動(dòng)生成index.html且自動(dòng)引用打包后的js呢?HtmlWebpackPlugin插件就是用來(lái)解決這個(gè)問(wèn)題的:
首先安裝該插件
cnpm i html-webpack-plugin -D
然后我們對(duì)項(xiàng)目結(jié)構(gòu)進(jìn)行一些更改:
- 把
dist整個(gè)文件夾刪除; - 在
src文件夾下新建一個(gè)index.template.html(名稱自定義)文件模板(當(dāng)然這個(gè)是可選的,因?yàn)榫退悴辉O(shè)置模板,HtmlWebpackPlugin插件也會(huì)生成默認(rèn)html文件,這里我們?cè)O(shè)置模塊會(huì)讓我們的開(kāi)發(fā)更加靈活),如下:
<!-- index.template.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Here is Template</title>
</head>
<body>
<div id='root'>
</div>
</body>
</html>
webpack.config.js中我們引入了HtmlWebpackPlugin插件,并配置了引用了我們?cè)O(shè)置的模板,如下:
// webpack.config.js
const path = require('path'); // 路徑處理模塊
const webpack = require('webpack'); // 這個(gè)插件不需要安裝,是基于webpack的,需要引入webpack模塊
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 引入HtmlWebpackPlugin插件
module.exports = {
entry: path.join(__dirname, "/src/index.js"), // 入口文件
output: {
path: path.join( __dirname, "/dist"), //打包后的文件存放的地方
filename: "bundle.js" //打包后輸出文件的文件名
},
devServer: {
contentBase: "./dist", // 本地服務(wù)器所加載文件的目錄
port: "8088", // 設(shè)置端口號(hào)為8088
inline: true, // 文件修改后實(shí)時(shí)刷新
historyApiFallback: true, //不跳轉(zhuǎn)
},
devtool: 'source-map', // 會(huì)生成對(duì)于調(diào)試的完整的.map文件,但同時(shí)也會(huì)減慢打包速度
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結(jié)尾的文件
use: ['style-loader', 'css-loader'] // 需要用的loader,一定是這個(gè)順序,因?yàn)檎{(diào)用loader是從右往左編譯的
},
{
test: /\.(scss|sass)$/, // 正則匹配以.scss和.sass結(jié)尾的文件
use: ['style-loader', 'css-loader', 'sass-loader'] // 需要用的loader,一定是這個(gè)順序,因?yàn)檎{(diào)用loader是從右往左編譯的
},
{ // jsx配置
test: /(\.jsx|\.js)$/,
use: { // 注意use選擇如果有多項(xiàng)配置,可寫成這種對(duì)象形式
loader: "babel-loader"
},
exclude: /node_modules/ // 排除匹配node_modules模塊
}
]
},
plugins: [
new webpack.BannerPlugin('版權(quán)所有,翻版必究'), // new一個(gè)插件的實(shí)例
new HtmlWebpackPlugin({
template: path.join(__dirname, "/src/index.template.html")// new一個(gè)這個(gè)插件的實(shí)例,并傳入相關(guān)的參數(shù)
})
]
}
然后我們使用npm run build進(jìn)行打包,你會(huì)發(fā)現(xiàn),dist文件夾和html文件都會(huì)自動(dòng)生成,如下:

為什么會(huì)自動(dòng)生成dist文件夾呢?因?yàn)槲覀冊(cè)?code>output出口配置項(xiàng)中定義了出口文件所在的位置為dist文件夾,且出口文件名為bundle.js,所以HtmlWebpackPlugin會(huì)自動(dòng)幫你在index.html中引用名為bundle.js文件,如果你在webpack.config.js文件中更改了出口文件名,index.html中也會(huì)自動(dòng)更改該文件名,這樣以后修改起來(lái)是不是方便多了?
?
6.3 清理/dist文件夾(CleanWebpackPlugin)
你可能已經(jīng)注意到,在我們刪掉/dist文件夾之前,由于前面的代碼示例遺留,導(dǎo)致我們的/dist文件夾比較雜亂。webpack會(huì)生成文件,然后將這些文件放置在/dist文件夾中,但是webpack無(wú)法追蹤到哪些文件是實(shí)際在項(xiàng)目中用到的。
通常,在每次構(gòu)建前清理/dist文件夾,是比較推薦的做法,因此只會(huì)生成用到的文件,這時(shí)候就用到CleanWebpackPlugin插件了。
cnpm i clean-webpack-plugin -D
// webpack.config.js
...
const CleanWebpackPlugin = require('clean-webpack-plugin'); // 引入CleanWebpackPlugin插件
module.exports = {
...
plugins: [
new webpack.BannerPlugin('版權(quán)所有,翻版必究'), // new一個(gè)插件的實(shí)例
new HtmlWebpackPlugin({
template: path.join(__dirname, "/src/index.template.html")// new一個(gè)這個(gè)插件的實(shí)例,并傳入相關(guān)的參數(shù)
}),
new CleanWebpackPlugin(['dist']), // 所要清理的文件夾名稱
]
}
插件的使用方法都是一樣的,首先引入,然后new一個(gè)實(shí)例,實(shí)例可傳入?yún)?shù)。
現(xiàn)在我們運(yùn)行npm run build后就會(huì)發(fā)現(xiàn),webpack會(huì)先將/dist文件夾刪除,然后再生產(chǎn)新的/dist文件夾。
?
6.4 熱更新(HotModuleReplacementPlugin)
HotModuleReplacementPlugin(HMR)是一個(gè)很實(shí)用的插件,可以在我們修改代碼后自動(dòng)刷新預(yù)覽效果。
?
方法:
-
devServer配置項(xiàng)中添加hot: true參數(shù)。 - 因?yàn)?code>HotModuleReplacementPlugin是
webpack模塊自帶的,所以引入webpack后,在plugins配置項(xiàng)中直接使用即可。
// webpack.config.js
...
const webpack = require('webpack'); // 這個(gè)插件不需要安裝,是基于webpack的,需要引入webpack模塊
module.exports = {
...
devServer: {
contentBase: "./dist", // 本地服務(wù)器所加載文件的目錄
port: "8088", // 設(shè)置端口號(hào)為8088
inline: true, // 文件修改后實(shí)時(shí)刷新
historyApiFallback: true, //不跳轉(zhuǎn)
hot: true // 熱更新
},
...
plugins: [
new webpack.BannerPlugin('版權(quán)所有,翻版必究'), // new一個(gè)插件的實(shí)例
new HtmlWebpackPlugin({
template: path.join(__dirname, "/src/index.template.html")// new一個(gè)這個(gè)插件的實(shí)例,并傳入相關(guān)的參數(shù)
}),
new CleanWebpackPlugin(['dist']), // 傳入所要清理的文件夾名稱
new webpack.HotModuleReplacementPlugin() // 熱更新插件
]
}
此時(shí)我們重新啟動(dòng)項(xiàng)目npm run dev后,修改hello.js的內(nèi)容,會(huì)發(fā)現(xiàn)瀏覽器預(yù)覽效果會(huì)自動(dòng)刷新(也許反應(yīng)會(huì)比較慢,因?yàn)槲覀兪褂昧?code>source-map和其他配置的影響,后面代碼分離的時(shí)候我們?cè)偬幚恚?br>
?
七、項(xiàng)目?jī)?yōu)化及拓展
7.1 代碼分離
在當(dāng)前的開(kāi)發(fā)環(huán)境都是提倡模塊化,webpack自然不例外,我們前面的webpack.config.js配置文件,其實(shí)也沒(méi)配置多少東西就這么多了,要是以后增加了更多配置,豈不是看得眼花繚亂,所以最好的方法就是把它拆分,方便管理:
1. 我們?cè)诟夸浵滦陆ㄈ齻€(gè)文件,分別為webpack.common.js、webpack.dev.js、webpack.prod.js,分別代表公共配置文件、開(kāi)發(fā)環(huán)境配置文件、生產(chǎn)環(huán)境(指項(xiàng)目上線時(shí)的環(huán)境)配置文件。
2. 安裝一個(gè)合并模塊插件:
cnpm i webpack-merge -D
3. 將webpack.config.js的代碼拆分到上述新建的三個(gè)文件中,然后把webpack.config.js文件刪除,具體如下:
// webpack.common.js
const path = require('path'); // 路徑處理模塊
const webpack = require('webpack'); // 這個(gè)插件不需要安裝,是基于webpack的,需要引入webpack模塊
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 引入HtmlWebpackPlugin插件
module.exports = {
entry: path.join(__dirname, "/src/index.js"), // 入口文件
output: {
path: path.join( __dirname, "/dist"), //打包后的文件存放的地方
filename: "bundle.js" //打包后輸出文件的文件名
},
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結(jié)尾的文件
use: ['style-loader', 'css-loader'] // 需要用的loader,一定是這個(gè)順序,因?yàn)檎{(diào)用loader是從右往左編譯的
},
{
test: /\.(scss|sass)$/, // 正則匹配以.scss和.sass結(jié)尾的文件
use: ['style-loader', 'css-loader', 'sass-loader'] // 需要用的loader,一定是這個(gè)順序,因?yàn)檎{(diào)用loader是從右往左編譯的
},
{ // jsx配置
test: /(\.jsx|\.js)$/,
use: { // 注意use選擇如果有多項(xiàng)配置,可寫成這種對(duì)象形式
loader: "babel-loader"
},
exclude: /node_modules/ // 排除匹配node_modules模塊
}
]
},
plugins: [
new webpack.BannerPlugin('版權(quán)所有,翻版必究'), // new一個(gè)插件的實(shí)例
new HtmlWebpackPlugin({
template: path.join(__dirname, "/src/index.template.html")// new一個(gè)這個(gè)插件的實(shí)例,并傳入相關(guān)的參數(shù)
}),
new webpack.HotModuleReplacementPlugin()
]
}
// webpack.dev.js
const merge = require('webpack-merge'); // 引入webpack-merge功能模塊
const common = require('./webpack.common.js'); // 引入webpack.common.js
module.exports = merge(common, { // 將webpack.common.js合并到當(dāng)前文件
devServer: {
contentBase: "./dist", // 本地服務(wù)器所加載文件的目錄
port: "8088", // 設(shè)置端口號(hào)為8088
inline: true, // 文件修改后實(shí)時(shí)刷新
historyApiFallback: true, //不跳轉(zhuǎn)
hot: true //熱加載
}
})
// webpack.prod.js
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const CleanWebpackPlugin = require('clean-webpack-plugin'); // 引入CleanWebpackPlugin插件
module.exports = merge(common, { // 將webpack.common.js合并到當(dāng)前文件
devtool: 'source-map', // 會(huì)生成對(duì)于調(diào)試的完整的.map文件,但同時(shí)也會(huì)減慢打包速度
plugins: [
new CleanWebpackPlugin(['dist']), // 所要清理的文件夾名稱
]
})
此時(shí)我們的項(xiàng)目目錄如下:

4. 設(shè)置package.json的scripts命令:
{
"name": "webpack-project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack --config webpack.prod.js",
"dev": "webpack-dev-server --open --config webpack.dev.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"clean-webpack-plugin": "^0.1.19",
"css-loader": "^1.0.0",
"html-webpack-plugin": "^3.2.0",
"node-sass": "^4.9.4",
"react": "^16.6.0",
"react-dom": "^16.6.0",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"webpack": "^4.23.1",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10",
"webpack-merge": "^4.1.4"
}
}
我們把build命令改為了webpack --config webpack.prod.js,意思是把打包配置指向webpack.prod.js配置文件,而之前我們只需要使用一個(gè)webpack命令為什么就可以運(yùn)行了?因?yàn)?code>webpack命令是默認(rèn)指向webpack.config.js這個(gè)文件名稱了,現(xiàn)在我們把文件名稱改了,所以就需要自定義指向新的文件,dev命令中的指令也同理。
然后我們運(yùn)行npm run build和npm run dev,效果應(yīng)該和我們分離代碼前是一樣的。
注:說(shuō)道
package.json文件,順便就多提幾句,因?yàn)橐苍S有些朋友可能對(duì)我們安裝模塊時(shí)加的-D、-S或-g命令存在一些疑惑,因?yàn)椴恢朗裁磿r(shí)候加什么尾綴。
其實(shí)這個(gè)package.json文件是用于我們安裝依賴的,可以把它當(dāng)成一份依賴安裝說(shuō)明表,就是如果我們把項(xiàng)目上傳或者發(fā)給其他的開(kāi)發(fā)同事,肯定不會(huì)把/node_modules文件夾也發(fā)送過(guò)去,因?yàn)檫@太大了,不現(xiàn)實(shí)也沒(méi)必要。
開(kāi)發(fā)同事只需要有這份package.json文件,然后npm install就可以把我們所需要的依賴都安裝下來(lái),但前提是package.json文件上有記錄,這就是安裝模塊時(shí)加上-D,-S命令的原因。
-D的全稱是--save-dev指開(kāi)發(fā)環(huán)境時(shí)需要用到的依賴,會(huì)記錄在package.json文件中的devDependencies選項(xiàng)中,而-S是--save是指生產(chǎn)環(huán)境也就是上線環(huán)境中需要用到的依賴,會(huì)記錄在package.json文件中的dependencies選項(xiàng)中,-g的全稱是--global指安裝全局命令,就是我們?cè)诒倦娔X的任何項(xiàng)目中都能使用到的命令,比如安裝cnpm這個(gè)淘寶鏡像命令就會(huì)用到-g命令。
所以我們?cè)诎惭b模塊時(shí)一定不要忘了加上對(duì)應(yīng)的尾綴命令,讓我們的模塊有跡可循,否則其他的開(kāi)發(fā)同事接手你的項(xiàng)目的話,會(huì)不會(huì)下班后(放學(xué)后)在門口等你就不知道了。
扯遠(yuǎn)了,希望不要嫌棄,也是想講得更詳細(xì)嘛!
?
7.2 多入口多出口
到目前為止我們都是一個(gè)入口文件和一個(gè)出口文件,要是我不止一個(gè)入口文件呢?下面我們來(lái)試試:
在webpack.common.js中的entry入口有三種寫法,分別為字符串、數(shù)組和對(duì)象,平時(shí)我們用得比較多的是對(duì)象,所以我們把它改為對(duì)象的寫法,首先我們?cè)?code>src文件夾下新建two.js文件,名稱任意。因?yàn)橛卸鄠€(gè)入口,所以肯定得多個(gè)出口來(lái)進(jìn)行一一對(duì)應(yīng)了,所以entry和output配置如下:
// webpack.common.js
...
module.exports = {
entry: {
index: path.join(__dirname, "/src/index.js"),
two: path.join(__dirname, "/src/two.js")
},
output: {
path: path.join( __dirname, "/dist"), //打包后的文件存放的地方
filename: "[name].js" //打包后輸出文件的文件名
},
...
}
// two.js
function two() {
let element = document.createElement('div');
element.innerHTML = '我是第二個(gè)入口文件';
return element;
}
document.getElementById('root').appendChild(two());
然后我們運(yùn)行npm run build打包后發(fā)現(xiàn)/dist文件夾下會(huì)多出two.js文件,同時(shí)index.html也會(huì)自動(dòng)將two.js引入,然后我們運(yùn)行npm run dev顯示如下:

?
7.3 增加css前綴、分離css、消除冗余css、分離圖片
?
1.增加css前綴
平時(shí)我們寫css時(shí),一些屬性需要手動(dòng)加上前綴,比如-webkit-border-radius: 10px;,在webpack中我們能不能讓它自動(dòng)加上呢?那是必須的,首先肯定得安裝模塊了:
cnpm i postcss-loader autoprefixer -D
安裝好這兩個(gè)模塊后,在項(xiàng)目根目錄下新建postcss.config.js文件:
// postcss.config.js
module.exports = {
plugins: [
require('autoprefixer') // 引用autoprefixer模塊
]
}
在style.css中增加以下樣式:
/* style.css */
body {
background: #999;
}
#root div{
width: 200px;
margin-top: 50px;
transform: rotate(45deg); /* 這個(gè)屬性會(huì)產(chǎn)生前綴 */
}
修改webpack.common.js文件中的css-loader配置:
...
module.exports = {
...
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結(jié)尾的文件
use: [
{loader: 'style-loader'}, // 這里采用的是對(duì)象配置loader的寫法
{loader: 'css-loader'},
{loader: 'postcss-loader'} // 使用postcss-loader
]
},
...
]
},
...
}
然后我們運(yùn)行npm run dev后css樣式中會(huì)自動(dòng)添加前綴,顯示如下:

?
2.分離css
雖然webpack的理念是把css、js全都打包到一個(gè)文件里,但要是我們想把css分離出來(lái)該怎么做呢?
cnpm i extract-text-webpack-plugin@next -D // 加上@next是為了安裝最新的,否則會(huì)出錯(cuò)
安裝完以上插件后在webpack.common.js文件中引入并使用該插件:
// webpack.common.js
...
const ExtractTextPlugin = require('extract-text-webpack-plugin') //引入分離插件
module.exports = {
...
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結(jié)尾的文件
use: ExtractTextPlugin.extract({ // 這里我們需要調(diào)用分離插件內(nèi)的extract方法
fallback: 'style-loader', // 相當(dāng)于回滾,經(jīng)postcss-loader和css-loader處理過(guò)的css最終再經(jīng)過(guò)style-loader處理
use: ['css-loader', 'postcss-loader']
})
},
...
]
},
plugins: [
...
new ExtractTextPlugin('css/index.css') // 將css分離到/dist文件夾下的css文件夾中的index.css
]
}
運(yùn)行npm run build后會(huì)發(fā)現(xiàn)/dist文件夾內(nèi)多出了/css文件夾及index.css文件。
?
3.消除冗余css
有時(shí)候我們css寫得多了,可能會(huì)不自覺(jué)的寫重復(fù)了一些樣式,這就造成了多余的代碼,上線前又忘了檢查,對(duì)于這方面,我們應(yīng)該盡量去優(yōu)化它,webpack就有這個(gè)功能。
cnpm i purifycss-webpack purify-css glob -D
安裝完上述三個(gè)模塊后,因?yàn)檎?lái)說(shuō)是在生產(chǎn)環(huán)境中優(yōu)化代碼,所以我們應(yīng)該是在webpack.prod.js文件中進(jìn)行配置,引入clean-webpack-plugin及glob插件并使用它們:
// webpack.prod.js
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const CleanWebpackPlugin = require('clean-webpack-plugin'); // 引入CleanWebpackPlugin插件
const path = require('path');
const PurifyCssWebpack = require('purifycss-webpack'); // 引入PurifyCssWebpack插件
const glob = require('glob'); // 引入glob模塊,用于掃描全部html文件中所引用的css
module.exports = merge(common, { // 將webpack.common.js合并到當(dāng)前文件
devtool: 'source-map', // 會(huì)生成對(duì)于調(diào)試的完整的.map文件,但同時(shí)也會(huì)減慢打包速度
plugins: [
new CleanWebpackPlugin(['dist']), // 所要清理的文件夾名稱
new PurifyCssWebpack({
paths: glob.sync(path.join(__dirname, 'src/*.html')) // 同步掃描所有html文件中所引用的css
})
]
})
我們?cè)?code>style.css文件中增加一些多余的代碼試試:
/* style.css */
body {
background: #999;
}
#root div{
width: 200px;
margin-top: 50px;
transform: rotate(45deg); /* 這個(gè)屬性會(huì)產(chǎn)生前綴 */
}
.a{ /* 冗余css */
color: black;
}
.b{ /* 冗余css */
width: 50px;
height: 50px;
background: yellow;
}
然后我們運(yùn)行npm run build后發(fā)現(xiàn)打包后的index.css中是沒(méi)有多余的.a和.b代碼的:
/* index.css */
body {
background: #999;
}
#root div {
width: 200px;
margin-top: 50px;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
/* 這個(gè)屬性會(huì)產(chǎn)生前綴 */
}
/*# sourceMappingURL=index.css.map*/
?
4.處理圖片
到目前為止我們還沒(méi)講到圖片的問(wèn)題,如果要使用圖片,我們得安裝兩個(gè)loader:
// 雖然我們只需使用url-loader,但url-loader是依賴于file-loader的,所以也要安裝
cnpm i url-loader file-loader -D
然后在webpack.common.js中配置url-loader:
// webpack.common.js
...
module.exports = {
...
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結(jié)尾的文件
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'postcss-loader']
})
},
{
test: /\.(png|jpg|svg|gif)$/, // 正則匹配圖片格式名
use: [
{
loader: 'url-loader' // 使用url-loader
}
]
},
...
]
},
...
}
我們修改一下style.css,把背景改為圖片背景:
/* style.css */
body {
background: url(../images/coffee.png) top right repeat-y; /* 設(shè)為圖片背景 */
}
#root div{
width: 200px;
margin-top: 50px;
transform: rotate(45deg); /* 這個(gè)屬性會(huì)產(chǎn)生前綴 */
}
.a{
color: black;
}
.b{
width: 50px;
height: 50px;
background: yellow;
}
運(yùn)行npm run dev后顯示如下:

但是背景圖片變成了base64,因?yàn)閣ebpack會(huì)自動(dòng)優(yōu)化圖片,減少發(fā)送請(qǐng)求,但是如果我想把它變成路徑的該怎么做?
我們可以把webpack.common.js的loader配置更改一下,增加options選項(xiàng):
// webpack.common.js
...
module.exports = {
...
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結(jié)尾的文件
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'postcss-loader']
})
},
{
test: /\.(png|jpg|svg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1000 // 限制只有小于1kb的圖片才轉(zhuǎn)為base64,例子圖片為1.47kb,所以不會(huì)被轉(zhuǎn)化
}
}
]
},
...
]
},
...
}
然后我們運(yùn)行npm run build后,再運(yùn)行npm run dev,額,圖片是沒(méi)有轉(zhuǎn)成base64了,但是圖片怎么不顯示了?

問(wèn)題就出在路徑上,我們之前圖片的路徑是在../images文件夾下,但是打包出來(lái)后沒(méi)有這個(gè)路徑了,圖片直接和文件同級(jí)了,所以我們需要在webpack.common.js中給它設(shè)置一個(gè)文件夾:
// webpack.common.js
...
module.exports = {
...
module: {
rules: [
...
{
test: /\.(png|jpg|svg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1000, // 限制只有小于1kb的圖片才轉(zhuǎn)為base64,例子圖片為1.47kb,所以不會(huì)被轉(zhuǎn)化
outputPath: 'images' // 設(shè)置打包后圖片存放的文件夾名稱
}
}
]
},
...
]
},
...
}
繼續(xù)npm run build打包再npm run dev運(yùn)行,我的天!圖片還是不顯示!
調(diào)試工具上看圖片路徑有images文件夾了,但是我的../呢?

這又涉及到配置路徑的問(wèn)題上了,我們還需要在css-loader中給背景圖片設(shè)置一個(gè)公共路徑publicPath: '../',如下:
// webpack.common.js
...
module.exports = {
...
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結(jié)尾的文件
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'postcss-loader'],
publicPath: '../' // 給背景圖片設(shè)置一個(gè)公共路徑
})
},
{
test: /\.(png|jpg|svg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1000, // 限制只有小于1kb的圖片才轉(zhuǎn)為base64,例子圖片為1.47kb,所以不會(huì)被轉(zhuǎn)化
outputPath: 'images' // 設(shè)置打包后圖片存放的文件夾名稱
}
}
]
},
...
]
},
...
}
現(xiàn)在再npm run build打包再npm run dev啟動(dòng),OK!沒(méi)毛??!

是不是很熱鬧?到現(xiàn)在我們不知不覺(jué)中也同時(shí)解決了圖片分離的問(wèn)題,偷偷高興一下吧!
?
7.4 壓縮代碼
在webpack4.x版本中當(dāng)你打包時(shí)會(huì)自動(dòng)把js壓縮了,而且npm run dev運(yùn)行服務(wù)器時(shí),當(dāng)你修改代碼時(shí),熱更新很慢,這是因?yàn)槟阈薷暮體ebpack又自動(dòng)為你打包,這就導(dǎo)致了在開(kāi)發(fā)環(huán)境中效率很慢,所以我們需要把開(kāi)發(fā)環(huán)境和生產(chǎn)環(huán)境區(qū)分開(kāi)來(lái),這時(shí)就體現(xiàn)出我們代碼分離的便捷性了,webpack.dev.js代表開(kāi)發(fā)環(huán)境的配置,webpack.prod.js代表生產(chǎn)環(huán)境的配置,這時(shí)我們只要在package.json文件中配置對(duì)應(yīng)環(huán)境的命令即可:
{
...
"scripts": {
"build": "webpack --config webpack.prod.js --mode production",
"dev": "webpack-dev-server --open --config webpack.dev.js --mode development"
},
...
}
}
--mode production表示打包時(shí)是生產(chǎn)環(huán)境,會(huì)自己將js進(jìn)行壓縮,而--mode development表示當(dāng)前是開(kāi)發(fā)環(huán)境,不需要進(jìn)行壓縮。這同時(shí)也解決了之前一直遺留的警告問(wèn)題:

?
總結(jié)
好了,到現(xiàn)在我們基本把webapck常用的功能都走了一遍,寫得有點(diǎn)長(zhǎng),感謝你能仔細(xì)的看到這里,希望能對(duì)你有所幫助,如果有發(fā)現(xiàn)不對(duì)的地方,也請(qǐng)多多指教。其實(shí)webpack還有很多功能,這里也沒(méi)講述完全,但相信你現(xiàn)在對(duì)webpack也有了一定的了解,更多的webpack探索一定難不倒你!
完整代碼請(qǐng)前往github,如果能有所幫助,希望能給個(gè)star(偷笑)