前言
本文主要從webpack4.x入手窗市,會對平時常用的Webpack配置一一講解栗菜,各個功能點都有對應的詳細例子,所以本文也比較長像棘,但如果你能動手跟著本文中的例子完整寫一次稽亏,相信你會覺得Webpack也不過如此。
?
一缕题、什么是webpack截歉,為什么使用它?
?
1.1 什么是webpack烟零?
簡單來說瘪松,它其實就是一個模塊打包器。
?
1.2 為什么使用它锨阿?
如果像以前開發(fā)時一個html文件可能會引用十幾個js文件,而且順序還不能亂宵睦,因為它們存在依賴關系,同時對于ES6+等新的語法墅诡,less, sass等CSS預處理都不能很好的解決……壳嚎,此時就需要一個處理這些問題的工具。
Webpack就是為處理這些問題而生的末早,它就是把你的項目當成一個整體烟馅,通過一個入口主文件(如:index.js),從這個文件開始找到你的項目所有的依賴文件并處理它們,最后打包成一個(或多個)瀏覽器可識別的JavaScript文件然磷。
?
二焙糟、一個簡單的打包例子
2.1 準備工作
首先新建一個空文件夾,用于創(chuàng)建項目样屠,在終端中進入文件夾穿撮,如下我在桌面建了一個名為webpack-project的文件夾缺脉,使用終端進入文件夾后(如果對命令行不太熟悉,可參考我的博客:前端常用命令行)悦穿,使用npm init
命令創(chuàng)建一個package.json文件攻礼。
npm init
輸入這個命令后,終端會問你一系列諸如項目名稱栗柒,項目描述礁扮,作者等信息,不過如果你不打算發(fā)布這個模塊瞬沦,直接一路回車就好太伊。(也可以使用npm init -y
這個命令來一次生成package.json文件,這樣終端不會詢問你問題)逛钻。
?
2.2 安裝webpack
如果你想一步到位的話僚焦,就把全局webpack和本地項目webpack全都先裝了,因為后面一些模塊會用到曙痘。安裝本地項目webapck時把webpack-cli也裝上芳悲,因為webpack模塊把一些功能分到了webpack-cli模塊,安裝方法如下:
npm install webpack --global //這是安裝全局webpack命令
npm install webpack webpack-cli --save-dev //這是安裝本地項目模塊
?
tips:
上述命令可采用簡寫边坤,
install
可簡寫為i
,--global
可簡寫為-g
,--save-dev
可簡寫為-D
(這個命令是用于把配置添加到package.json的開發(fā)環(huán)境配置列表中名扛,后面會提到),--save
可簡寫為-S
茧痒,同時國內我們可以采用cnpm肮韧,配置方法可去這里查看,這樣安裝速度會相對較快。如下:
cnpm i webpack -g //這是安裝全局webpack命令
cnpm i webpack webpack-cli -D //這是安裝本地項目模塊
?
2.3 新建文件
在webpack-project
文件夾中新建兩個文件夾,分別為src文件夾和dist文件夾咬展,接下來再創(chuàng)建三個文件:
-
index.html
--放在dist文件夾中; -
hello.js
--放在src文件夾中桩蓉; -
index.js
--放在src文件夾中;
此時劳闹,項目結構如下:
我們在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文件,我們暫時命名為bundle.js-->
</body>
</html>
我們在hello.js
中導出一個模塊:
// hello.js
module.exports = function() {
let hello = document.createElement('div');
hello.innerHTML = "Long time no see!";
return hello;
};
然后在index.js
中引入這個模塊(hello.js
):
//index.js
const hello = require('./hello.js');
document.querySelector("#root").appendChild(hello());
上述操作就相當于我們把hello.js
模塊合并到了index.js
模塊本涕,之后我們打包時就只需把index.js
模塊打包成bundle.js
业汰,然后供index.html
引用即可,這就是最簡單的webpack打包原理菩颖。
?
2.4 開始進行webpack打包
在終端中使用如下命令進行打包:
// webpack全局安裝的情況下
webpack src/index.js --output dist/bundle.js
// --output可簡寫為-o
上述就相當于把src
文件夾下的index.js
文件打包到dist
文件下的bundle.js
样漆,這時就生成了bundle.js
供index.html
文件引用。
結果如下:
可以看出webpack同時編譯了index.js
和hello.js
,現在打開index.html
,可以看到如下結果:
沒錯晦闰,我們已經成功使用webpack進行打包放祟,原來webpack也不過如此嘛鳍怨!但是,每次都在終端中輸入這么長的命令跪妥,感覺好煩啊鞋喇,還好有懶人方法,讓我們看看眉撵。
?
2.5 通過配置文件來使用webpack
其實webpack是有很多功能的侦香,也是很方便的,我們可以在當前項目的根目錄下新建一個配置文件webpack.config.js
纽疟,我們寫下如下簡單配置代碼罐韩,目前只涉及入口配置(相當于我們的index.js
,從它開始打包)和出口配置(相當于我們打包生成的bundle.js
)污朽。
// webpack.config.js
module.exports = {
entry: __dirname + "/src/index.js", // 入口文件
output: {
path: __dirname + "/dist", //打包后的文件存放的地方
filename: "bundle.js" //打包后輸出文件的文件名
}
}
注:
__dirname
是node.js中的一個全局變量散吵,它指向當前執(zhí)行腳本所在的目錄,即C:\Users\sjt\DeskTop\webpack-project(這是我當前的目錄)
但平時我們看到的腳手架配置也比較喜歡采用node.js的path
模塊來處理絕對路徑州藕,所以我們也可以采用如下的寫法,和上述的效果是一樣的:
// 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
的功能是拼接路徑片段缨该。
有了這個配置文件询一,我們只需在終端中運行webpack
命令就可進行打包,這條命令會自動引用webpack.config.js文件中的配置選項势木,示例如下:
搞定又跛,是不是這樣更方便了静暂,感覺沒那么low了,但還能不能更便捷智能呢戳玫?那必須的!
?
2.6 更智能的打包方式
我們現在只在終端中使用webpack
命令來進行打包诉瓦,要是以后在打包的同時還有更多的操作呢川队,那不是還得寫上更多的命令力细?所以我們得想辦法把這些命令都集成起來睬澡,這時候之前的package.json
文件就派上用場了。
現在的package.json文件大概就是如下這樣:
{
"name": "webpack-project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1" //我們要修改的是這里眠蚂,JSON文件不支持注釋煞聪,引用時請清除
},
"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", //改成這樣,注意使用時把注釋刪掉
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.23.1",
"webpack-cli": "^3.1.2"
}
}
注:package.json中的script會按你設置的命令名稱來執(zhí)行對應的命令逝慧。
這樣我們就可以在終端中直接執(zhí)行npm start
命令來進行打包昔脯,start
命令比較特殊,可以直接npm
加上start
就可以執(zhí)行笛臣,如果我們想起其他的名稱云稚,如build
時,就需要使用npm run
加上build
沈堡,即npm run build
命令静陈。
現在我們執(zhí)行npm start
命令:
OK,搞定,是不是很簡單鲸拥,但webpack的功能遠不止于此拐格,下面我們繼續(xù)。
?
三刑赶、構建本地服務器
現在我們是通過打開本地文件來查看頁面的捏浊,看起來總感覺比較low,看別人用vue撞叨,react框架時都是運行在本地服務器上的金踪,那我們能不能也那樣呢?那必須的牵敷!
?
3.1 webpack-dev-server配置本地服務器
Webpack提供了一個可選的本地開發(fā)服務器热康,這個本地服務器基于node.js構建,它是一個單獨的組件劣领,在webpack中進行配置之前需要單獨安裝它作為項目依賴:
cnpm i webpack-dev-server -D
devServer作為webpack配置選項中的一項姐军,以下是它的一些配置選項:
-
contentBase
:設置服務器所讀取文件的目錄,當前我們設置為"./dist" -
port
:設置端口號尖淘,如果省略奕锌,默認為8080
-
inline
:設置為true
,當源文件改變時會自動刷新頁面 -
historyApiFallback
:設置為true
村生,所有的跳轉將指向index.html
現在我們把這些配置加到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", // 本地服務器所加載文件的目錄
port: "8088", // 設置端口號為8088
inline: true, // 文件修改后實時刷新
historyApiFallback: true, //不跳轉
}
}
我們繼續(xù)在package.json
文件中添加啟動命令:
{
"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
,這樣比較語義化趁桃,平時的腳手架也多數采用這個名稱辽话,我們用dev
(development的縮寫,意指開發(fā)環(huán)境)來啟動本地服務器卫病,webpack-dev-server
就是啟動服務器的命令油啤,--open
是用于啟動完服務器后自動打開瀏覽器,這時候我們自定義命令方式的便捷性就體現出來了蟀苛,可以多個命令集成在一起運行益咬,即我們定義了一個dev
命令名稱就可以同時運行了webpack-dev-server
和--open
兩個命令。
現在在終端輸入npm run dev
運行服務器:
這樣我們即可在http://localhost:8088/中查看頁面(退出服務器帜平,可使用ctrl+c
后幽告,再按y
確認,即可退出服務器運行)
?
3.2 Source Maps調試配置
作為開發(fā)裆甩,代碼調試當然少不了冗锁,那么問題來了,經過打包后的文件嗤栓,你是不容易找到出錯的地方的冻河,Source Map
就是用來解決這個問題的。
通過如下配置,我們會在打包時生成對應于打包文件的.map
文件芋绸,使得編譯后的代碼可讀性更高媒殉,更易于調試。
// 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", // 本地服務器所加載文件的目錄
port: "8088", // 設置端口號為8088
inline: true, // 文件修改后實時刷新
historyApiFallback: true, //不跳轉
},
devtool: 'source-map' // 會生成對于調試的完整的.map文件摔敛,但同時也會減慢打包速度
}
配置好后廷蓉,我們再次運行npm run build
進行打包,這時我們會發(fā)現在dist
文件夾中多出了一個bundle.js.map
文件如下:
如果我們的代碼有bug马昙,在瀏覽器的調試工具中會提示錯誤出現的位置桃犬,這就是
devtool: 'source-map'
配置項的作用。?
四行楞、Loaders
loaders是webpack最強大的功能之一攒暇,通過不同的loader,webpack有能力調用外部的腳本或工具子房,實現對不同格式的文件的處理形用,例如把scss
轉為css
,將ES66证杭、ES7等語法轉化為當前瀏覽器能識別的語法田度,將JSX轉化為js等多項功能。
Loaders需要單獨安裝并且需要在webpack.config.js
中的modules
配置項下進行配置解愤,Loaders的配置包括以下幾方面:
-
test
:一個用以匹配loaders所處理文件的拓展名的正則表達式(必須) -
loader
:loader的名稱(必須) -
include/exclude
:手動添加必須處理的文件(文件夾)或屏蔽不需要處理的文件(文件夾)(可選)镇饺; -
options
:為loaders提供額外的設置選項(可選)
?
4.1 配置css-loader
如果我們要加載一個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", // 本地服務器所加載文件的目錄
port: "8088", // 設置端口號為8088
inline: true, // 文件修改后實時刷新
historyApiFallback: true, //不跳轉
},
devtool: 'source-map', // 會生成對于調試的完整的.map文件送讲,但同時也會減慢打包速度
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結尾的文件
use: ['style-loader', 'css-loader'] // 需要用的loader奸笤,一定是這個順序,因為調用loader是從右往左編譯的
}
]
}
}
我們在src文件夾下新建css
文件夾哼鬓,該文件夾內新建style.css
文件:
/* style.css */
body {
background: gray;
}
在index.js
中引用它:
//index.js
import './css/style.css'; //導入css
const hello = require('./hello.js');
document.querySelector("#root").appendChild(hello());
這時我們運行npm run dev
监右,會發(fā)現頁面背景變成了灰色。
如果是要編譯sass文件呢魄宏?
?
4.2 配置sass
cnpm i sass-loader node-sass -D // 因為sass-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", // 本地服務器所加載文件的目錄
port: "8088", // 設置端口號為8088
inline: true, // 文件修改后實時刷新
historyApiFallback: true, //不跳轉
},
devtool: 'source-map', // 會生成對于調試的完整的.map文件存筏,但同時也會減慢打包速度
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結尾的文件
use: ['style-loader', 'css-loader'] // 需要用的loader宠互,一定是這個順序,因為調用loader是從右往左編譯的
},
{
test: /\.(scss|sass)$/, // 正則匹配以.scss和.sass結尾的文件
use: ['style-loader', 'css-loader', 'sass-loader'] // 需要用的loader椭坚,一定是這個順序予跌,因為調用loader是從右往左編譯的
}
]
}
}
在css文件夾中新建blue.scss
文件:
/* blue.scss */
$blue: blue;
body{
color: $blue;
}
在index.js
中引入blue.scss
:
//index.js
import './css/style.css'; // 導入css
import './css/blue.scss'; // 導入scss
const hello = require('./hello.js');
document.querySelector("#root").appendChild(hello());
這時npm run dev
重新啟動服務器,應該會出現如下結果:
還有諸如圖片loader善茎、字體loader等就不一一列出來了券册,感興趣的可前往webpack官網查看,都是一樣的套路。
?
五烁焙、Babel
Babel其實是一個編譯JavaScript的平臺航邢,它可以編譯代碼幫你達到以下目的:
- 讓你能使用最新的JavaScript代碼(ES6,ES7...)骄蝇,而不用管新標準是否被當前使用的瀏覽器完全支持膳殷;
- 讓你能使用基于JavaScript進行了拓展的語言,比如React的JSX九火;
?
5.1 Babel的安裝與配置
Babel其實是幾個模塊化的包赚窃,其核心功能位于稱為babel-core
的npm包中,webpack可以把其不同的包整合在一起使用岔激,對于每一個你需要的功能或拓展勒极,你都需要安裝單獨的包(用得最多的是解析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表示是對當前環(huán)境的預處理虑鼎,而不是像以前使用babel-preset-es2015只能針對某個環(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", // 本地服務器所加載文件的目錄
port: "8088", // 設置端口號為8088
inline: true, // 文件修改后實時刷新
historyApiFallback: true, //不跳轉
},
devtool: 'source-map', // 會生成對于調試的完整的.map文件辱匿,但同時也會減慢打包速度
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結尾的文件
use: ['style-loader', 'css-loader'] // 需要用的loader,一定是這個順序炫彩,因為調用loader是從右往左編譯的
},
{
test: /\.(scss|sass)$/, // 正則匹配以.scss和.sass結尾的文件
use: ['style-loader', 'css-loader', 'sass-loader'] // 需要用的loader掀鹅,一定是這個順序,因為調用loader是從右往左編譯的
},
{ // jsx配置
test: /(\.jsx|\.js)$/,
use: { // 注意use選擇如果有多項配置媒楼,可寫成這種對象形式
loader: "babel-loader",
options: {
presets: [
"env", "react"
]
}
},
exclude: /node_modules/
}
]
}
}
現在我們已經可以支持ES6及JSX的語法了乐尊,我們用react來試試,但使用react還得先安裝兩個模塊react
和react-dom
划址。
cnpm i react react-dom -D
接下來我們把hello.js
文件修改一下:
// hello.js
import React, {Component} from 'react'; // 這兩個模塊必須引入
let name = Alan;
export default class Hello extends Component{
render() {
return (
<div>
{name}
</div>
);
}
}
修改index.js
文件:
//index.js
import './css/style.css'; // 導入css
import './css/blue.scss'; // 導入scss
import React from 'react';
import {render} from 'react-dom';
import Hello from './hello'; // 可省略.js后綴名
render(<Hello />, document.getElementById('root'));
此時運行npm run dev
后你可能會發(fā)現如下結果:
這是因為官方默認babel-loader | babel
對應的版本需要一致: 即babel-loader
需要搭配最新版本babel
,詳細可參考這篇博客夺颤。
兩種解決方案:
- 回退低版本
cnpm i babel-loader@7 babel-core babel-preset-env -D
- 更新到最高版本:
cnpm i babel-loader @babel/core @babel/preset-env webpack -D
我這里采取的是第一個方案痢缎,回退后,再此運行npm run dev
世澜,得到如下結果:
到這里了是不是感覺很爽独旷,不就是配置嘛,想要使用什么就配置什么寥裂。
?
5.2 優(yōu)化babel配置
雖然babel完全可以在webpack.config.js
中進行配置嵌洼,但現在不是都提倡模塊化嘛,也許之后babel膨脹了封恰,增加了更多的配置項呢麻养?
那我們不如把它提取出來,把它放到根目錄下的.babelrc
文件下(webpack會自動調用.babelrc
里的babel配置選項)诺舔。
我們在項目根目錄下新建.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", // 本地服務器所加載文件的目錄
port: "8088", // 設置端口號為8088
inline: true, // 文件修改后實時刷新
historyApiFallback: true, //不跳轉
},
devtool: 'source-map', // 會生成對于調試的完整的.map文件,但同時也會減慢打包速度
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結尾的文件
use: ['style-loader', 'css-loader'] // 需要用的loader,一定是這個順序允粤,因為調用loader是從右往左編譯的
},
{
test: /\.(scss|sass)$/, // 正則匹配以.scss和.sass結尾的文件
use: ['style-loader', 'css-loader', 'sass-loader'] // 需要用的loader,一定是這個順序类垫,因為調用loader是從右往左編譯的
},
{ // jsx配置
test: /(\.jsx|\.js)$/,
use: { // 注意use選擇如果有多項配置,可寫成這種對象形式
loader: "babel-loader"
},
exclude: /node_modules/ // 排除匹配node_modules模塊
}
]
}
}
// .babelrc 使用時把注釋刪掉榆俺,該文件不能添加注釋
{
"presets": ["env", "react"]
}
此時不出問題的話應該一切運行正常售躁,接下來讓我們進入強大的插件模塊。
?
六茴晋、插件(Plugins)
插件(Plugins)是用來拓展Webpack功能的陪捷,它們會在整個構建過程中生效,執(zhí)行相關的任務诺擅。
Loaders和Plugins常常被弄混市袖,但是他們其實是完全不同的東西,可以這么來說烁涌,loaders是在打包構建過程中用來處理源文件的(JSX苍碟,Scss,Less..)撮执,一次處理一個微峰,插件并不直接操作單個文件,它直接對整個構建過程其作用抒钱。
?
6.1 插件如何使用
使用某個插件蜓肆,需要通過npm
進行安裝,然后在webpack.config.js
配置文件的plugins
(是一個數組)配置項中添加該插件的實例谋币,下面我們先來使用一個簡單的版權聲明插件仗扬。
// webpack.config.js
const webpack = require('webpack'); // 這個插件不需要安裝,是基于webpack的瑞信,需要引入webpack模塊
module.exports = {
...
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結尾的文件
use: ['style-loader', 'css-loader'] // 需要用的loader厉颤,一定是這個順序,因為調用loader是從右往左編譯的
},
{
test: /\.(scss|sass)$/, // 正則匹配以.scss和.sass結尾的文件
use: ['style-loader', 'css-loader', 'sass-loader'] // 需要用的loader,一定是這個順序捉蚤,因為調用loader是從右往左編譯的
},
{ // jsx配置
test: /(\.jsx|\.js)$/,
use: { // 注意use選擇如果有多項配置拇惋,可寫成這種對象形式
loader: "babel-loader"
},
exclude: /node_modules/ // 排除匹配node_modules模塊
}
]
},
plugins: [
new webpack.BannerPlugin('版權所有,翻版必究') // new一個插件的實例
]
}
運行npm run build
打包后我們看到bundle.js
文件顯示如下:
?
6.2 自動生成html文件(HtmlWebpackPlugin)
到目前為止我們都是使用一開始建好的index.html
文件帜乞,而且也是手動引入bundle.js
,要是以后我們引入不止一個js文件筐眷,而且更改js文件名的話黎烈,也得手動更改index.html
中的js文件名,所以能不能自動生成index.html
且自動引用打包后的js呢匀谣?HtmlWebpackPlugin
插件就是用來解決這個問題的:
首先安裝該插件
cnpm i html-webpack-plugin -D
然后我們對項目結構進行一些更改:
- 把
dist
整個文件夾刪除照棋; - 在
src
文件夾下新建一個index.template.html
(名稱自定義)文件模板(當然這個是可選的,因為就算不設置模板武翎,HtmlWebpackPlugin
插件也會生成默認html
文件烈炭,這里我們設置模塊會讓我們的開發(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
插件宝恶,并配置了引用了我們設置的模板符隙,如下:
// webpack.config.js
const path = require('path'); // 路徑處理模塊
const webpack = require('webpack'); // 這個插件不需要安裝,是基于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", // 本地服務器所加載文件的目錄
port: "8088", // 設置端口號為8088
inline: true, // 文件修改后實時刷新
historyApiFallback: true, //不跳轉
},
devtool: 'source-map', // 會生成對于調試的完整的.map文件霹疫,但同時也會減慢打包速度
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結尾的文件
use: ['style-loader', 'css-loader'] // 需要用的loader,一定是這個順序综芥,因為調用loader是從右往左編譯的
},
{
test: /\.(scss|sass)$/, // 正則匹配以.scss和.sass結尾的文件
use: ['style-loader', 'css-loader', 'sass-loader'] // 需要用的loader丽蝎,一定是這個順序,因為調用loader是從右往左編譯的
},
{ // jsx配置
test: /(\.jsx|\.js)$/,
use: { // 注意use選擇如果有多項配置膀藐,可寫成這種對象形式
loader: "babel-loader"
},
exclude: /node_modules/ // 排除匹配node_modules模塊
}
]
},
plugins: [
new webpack.BannerPlugin('版權所有征峦,翻版必究'), // new一個插件的實例
new HtmlWebpackPlugin({
template: path.join(__dirname, "/src/index.template.html")// new一個這個插件的實例,并傳入相關的參數
})
]
}
然后我們使用npm run build
進行打包消请,你會發(fā)現栏笆,dist
文件夾和html
文件都會自動生成,如下:
為什么會自動生成dist
文件夾呢臊泰?因為我們在output
出口配置項中定義了出口文件所在的位置為dist
文件夾蛉加,且出口文件名為bundle.js
,所以HtmlWebpackPlugin
會自動幫你在index.html
中引用名為bundle.js
文件缸逃,如果你在webpack.config.js
文件中更改了出口文件名针饥,index.html
中也會自動更改該文件名,這樣以后修改起來是不是方便多了需频?
?
6.3 清理/dist
文件夾(CleanWebpackPlugin)
你可能已經注意到丁眼,在我們刪掉/dist
文件夾之前,由于前面的代碼示例遺留昭殉,導致我們的/dist
文件夾比較雜亂苞七。webpack
會生成文件藐守,然后將這些文件放置在/dist
文件夾中,但是webpack
無法追蹤到哪些文件是實際在項目中用到的蹂风。
通常卢厂,在每次構建前清理/dist
文件夾,是比較推薦的做法惠啄,因此只會生成用到的文件慎恒,這時候就用到CleanWebpackPlugin
插件了。
cnpm i clean-webpack-plugin -D
// webpack.config.js
...
const CleanWebpackPlugin = require('clean-webpack-plugin'); // 引入CleanWebpackPlugin插件
module.exports = {
...
plugins: [
new webpack.BannerPlugin('版權所有撵渡,翻版必究'), // new一個插件的實例
new HtmlWebpackPlugin({
template: path.join(__dirname, "/src/index.template.html")// new一個這個插件的實例融柬,并傳入相關的參數
}),
new CleanWebpackPlugin(['dist']), // 所要清理的文件夾名稱
]
}
插件的使用方法都是一樣的,首先引入趋距,然后new一個實例粒氧,實例可傳入參數。
現在我們運行npm run build
后就會發(fā)現棚品,webpack會先將/dist
文件夾刪除靠欢,然后再生產新的/dist
文件夾。
?
6.4 熱更新(HotModuleReplacementPlugin)
HotModuleReplacementPlugin
(HMR)是一個很實用的插件铜跑,可以在我們修改代碼后自動刷新預覽效果门怪。
?
方法:
-
devServer
配置項中添加hot: true
參數。 - 因為
HotModuleReplacementPlugin
是webpack
模塊自帶的锅纺,所以引入webpack
后掷空,在plugins
配置項中直接使用即可。
// webpack.config.js
...
const webpack = require('webpack'); // 這個插件不需要安裝囤锉,是基于webpack的坦弟,需要引入webpack模塊
module.exports = {
...
devServer: {
contentBase: "./dist", // 本地服務器所加載文件的目錄
port: "8088", // 設置端口號為8088
inline: true, // 文件修改后實時刷新
historyApiFallback: true, //不跳轉
hot: true // 熱更新
},
...
plugins: [
new webpack.BannerPlugin('版權所有,翻版必究'), // new一個插件的實例
new HtmlWebpackPlugin({
template: path.join(__dirname, "/src/index.template.html")// new一個這個插件的實例官地,并傳入相關的參數
}),
new CleanWebpackPlugin(['dist']), // 傳入所要清理的文件夾名稱
new webpack.HotModuleReplacementPlugin() // 熱更新插件
]
}
此時我們重新啟動項目npm run dev
后酿傍,修改hello.js
的內容,會發(fā)現瀏覽器預覽效果會自動刷新(也許反應會比較慢驱入,因為我們使用了source-map
和其他配置的影響赤炒,后面代碼分離的時候我們再處理)。
?
七亏较、項目優(yōu)化及拓展
7.1 代碼分離
在當前的開發(fā)環(huán)境都是提倡模塊化莺褒,webpack自然不例外,我們前面的webpack.config.js
配置文件雪情,其實也沒配置多少東西就這么多了遵岩,要是以后增加了更多配置,豈不是看得眼花繚亂巡通,所以最好的方法就是把它拆分尘执,方便管理:
1. 我們在根目錄下新建三個文件舍哄,分別為webpack.common.js
、webpack.dev.js
正卧、webpack.prod.js
蠢熄,分別代表公共配置文件跪解、開發(fā)環(huán)境配置文件炉旷、生產環(huán)境(指項目上線時的環(huán)境)配置文件。
2. 安裝一個合并模塊插件:
cnpm i webpack-merge -D
3. 將webpack.config.js
的代碼拆分到上述新建的三個文件中叉讥,然后把webpack.config.js
文件刪除窘行,具體如下:
// webpack.common.js
const path = require('path'); // 路徑處理模塊
const webpack = require('webpack'); // 這個插件不需要安裝,是基于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結尾的文件
use: ['style-loader', 'css-loader'] // 需要用的loader罐盔,一定是這個順序,因為調用loader是從右往左編譯的
},
{
test: /\.(scss|sass)$/, // 正則匹配以.scss和.sass結尾的文件
use: ['style-loader', 'css-loader', 'sass-loader'] // 需要用的loader救崔,一定是這個順序惶看,因為調用loader是從右往左編譯的
},
{ // jsx配置
test: /(\.jsx|\.js)$/,
use: { // 注意use選擇如果有多項配置,可寫成這種對象形式
loader: "babel-loader"
},
exclude: /node_modules/ // 排除匹配node_modules模塊
}
]
},
plugins: [
new webpack.BannerPlugin('版權所有六孵,翻版必究'), // new一個插件的實例
new HtmlWebpackPlugin({
template: path.join(__dirname, "/src/index.template.html")// new一個這個插件的實例纬黎,并傳入相關的參數
}),
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合并到當前文件
devServer: {
contentBase: "./dist", // 本地服務器所加載文件的目錄
port: "8088", // 設置端口號為8088
inline: true, // 文件修改后實時刷新
historyApiFallback: true, //不跳轉
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合并到當前文件
devtool: 'source-map', // 會生成對于調試的完整的.map文件,但同時也會減慢打包速度
plugins: [
new CleanWebpackPlugin(['dist']), // 所要清理的文件夾名稱
]
})
此時我們的項目目錄如下:
4. 設置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
配置文件本今,而之前我們只需要使用一個webpack
命令為什么就可以運行了?因為webpack
命令是默認指向webpack.config.js
這個文件名稱了主巍,現在我們把文件名稱改了冠息,所以就需要自定義指向新的文件,dev
命令中的指令也同理孕索。
然后我們運行npm run build
和npm run dev
逛艰,效果應該和我們分離代碼前是一樣的。
注:說道
package.json
文件搞旭,順便就多提幾句散怖,因為也許有些朋友可能對我們安裝模塊時加的-D
、-S
或-g
命令存在一些疑惑选脊,因為不知道什么時候加什么尾綴杭抠。
其實這個package.json
文件是用于我們安裝依賴的,可以把它當成一份依賴安裝說明表恳啥,就是如果我們把項目上傳或者發(fā)給其他的開發(fā)同事偏灿,肯定不會把/node_modules
文件夾也發(fā)送過去,因為這太大了钝的,不現實也沒必要翁垂。
開發(fā)同事只需要有這份package.json
文件铆遭,然后npm install
就可以把我們所需要的依賴都安裝下來,但前提是package.json
文件上有記錄沿猜,這就是安裝模塊時加上-D
,-S
命令的原因枚荣。
-D
的全稱是--save-dev
指開發(fā)環(huán)境時需要用到的依賴,會記錄在package.json
文件中的devDependencies
選項中啼肩,而-S
是--save
是指生產環(huán)境也就是上線環(huán)境中需要用到的依賴橄妆,會記錄在package.json
文件中的dependencies
選項中,-g
的全稱是--global
指安裝全局命令祈坠,就是我們在本電腦的任何項目中都能使用到的命令害碾,比如安裝cnpm
這個淘寶鏡像命令就會用到-g
命令。
所以我們在安裝模塊時一定不要忘了加上對應的尾綴命令赦拘,讓我們的模塊有跡可循慌随,否則其他的開發(fā)同事接手你的項目的話,會不會下班后(放學后)在門口等你就不知道了躺同。
扯遠了阁猜,希望不要嫌棄,也是想講得更詳細嘛蹋艺!
?
7.2 多入口多出口
到目前為止我們都是一個入口文件和一個出口文件剃袍,要是我不止一個入口文件呢?下面我們來試試:
在webpack.common.js
中的entry
入口有三種寫法车海,分別為字符串笛园、數組和對象,平時我們用得比較多的是對象侍芝,所以我們把它改為對象的寫法研铆,首先我們在src
文件夾下新建two.js
文件,名稱任意州叠。因為有多個入口棵红,所以肯定得多個出口來進行一一對應了,所以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 = '我是第二個入口文件';
return element;
}
document.getElementById('root').appendChild(two());
然后我們運行npm run build
打包后發(fā)現/dist
文件夾下會多出two.js
文件咧栗,同時index.html
也會自動將two.js
引入逆甜,然后我們運行npm run dev
顯示如下:
?
7.3 增加css前綴、分離css致板、消除冗余css交煞、分離圖片
?
1.增加css前綴
平時我們寫css時,一些屬性需要手動加上前綴斟或,比如-webkit-border-radius: 10px;
素征,在webpack中我們能不能讓它自動加上呢?那是必須的,首先肯定得安裝模塊了:
cnpm i postcss-loader autoprefixer -D
安裝好這兩個模塊后御毅,在項目根目錄下新建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); /* 這個屬性會產生前綴 */
}
修改webpack.common.js
文件中的css-loader
配置:
...
module.exports = {
...
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結尾的文件
use: [
{loader: 'style-loader'}, // 這里采用的是對象配置loader的寫法
{loader: 'css-loader'},
{loader: 'postcss-loader'} // 使用postcss-loader
]
},
...
]
},
...
}
然后我們運行npm run dev
后css樣式中會自動添加前綴,顯示如下:
?
2.分離css
雖然webpack的理念是把css端蛆、js全都打包到一個文件里凤粗,但要是我們想把css分離出來該怎么做呢?
cnpm i extract-text-webpack-plugin@next -D // 加上@next是為了安裝最新的今豆,否則會出錯
安裝完以上插件后在webpack.common.js
文件中引入并使用該插件:
// webpack.common.js
...
const ExtractTextPlugin = require('extract-text-webpack-plugin') //引入分離插件
module.exports = {
...
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結尾的文件
use: ExtractTextPlugin.extract({ // 這里我們需要調用分離插件內的extract方法
fallback: 'style-loader', // 相當于回滾嫌拣,經postcss-loader和css-loader處理過的css最終再經過style-loader處理
use: ['css-loader', 'postcss-loader']
})
},
...
]
},
plugins: [
...
new ExtractTextPlugin('css/index.css') // 將css分離到/dist文件夾下的css文件夾中的index.css
]
}
運行npm run build
后會發(fā)現/dist
文件夾內多出了/css
文件夾及index.css
文件。
?
3.消除冗余css
有時候我們css寫得多了晚凿,可能會不自覺的寫重復了一些樣式亭罪,這就造成了多余的代碼瘦馍,上線前又忘了檢查歼秽,對于這方面,我們應該盡量去優(yōu)化它情组,webpack就有這個功能燥筷。
cnpm i purifycss-webpack purify-css glob -D
安裝完上述三個模塊后,因為正常來說是在生產環(huán)境中優(yōu)化代碼院崇,所以我們應該是在webpack.prod.js
文件中進行配置肆氓,引入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合并到當前文件
devtool: 'source-map', // 會生成對于調試的完整的.map文件,但同時也會減慢打包速度
plugins: [
new CleanWebpackPlugin(['dist']), // 所要清理的文件夾名稱
new PurifyCssWebpack({
paths: glob.sync(path.join(__dirname, 'src/*.html')) // 同步掃描所有html文件中所引用的css
})
]
})
我們在style.css
文件中增加一些多余的代碼試試:
/* style.css */
body {
background: #999;
}
#root div{
width: 200px;
margin-top: 50px;
transform: rotate(45deg); /* 這個屬性會產生前綴 */
}
.a{ /* 冗余css */
color: black;
}
.b{ /* 冗余css */
width: 50px;
height: 50px;
background: yellow;
}
然后我們運行npm run build
后發(fā)現打包后的index.css
中是沒有多余的.a
和.b
代碼的:
/* index.css */
body {
background: #999;
}
#root div {
width: 200px;
margin-top: 50px;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
/* 這個屬性會產生前綴 */
}
/*# sourceMappingURL=index.css.map*/
?
4.處理圖片
到目前為止我們還沒講到圖片的問題底瓣,如果要使用圖片谢揪,我們得安裝兩個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結尾的文件
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; /* 設為圖片背景 */
}
#root div{
width: 200px;
margin-top: 50px;
transform: rotate(45deg); /* 這個屬性會產生前綴 */
}
.a{
color: black;
}
.b{
width: 50px;
height: 50px;
background: yellow;
}
運行npm run dev
后顯示如下:
但是背景圖片變成了base64
,因為webpack會自動優(yōu)化圖片茁肠,減少發(fā)送請求患民,但是如果我想把它變成路徑的該怎么做?
我們可以把webpack.common.js
的loader配置更改一下垦梆,增加options
選項:
// webpack.common.js
...
module.exports = {
...
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結尾的文件
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'postcss-loader']
})
},
{
test: /\.(png|jpg|svg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1000 // 限制只有小于1kb的圖片才轉為base64匹颤,例子圖片為1.47kb,所以不會被轉化
}
}
]
},
...
]
},
...
}
然后我們運行npm run build
后,再運行npm run dev
托猩,額印蓖,圖片是沒有轉成base64了,但是圖片怎么不顯示了京腥?
問題就出在路徑上赦肃,我們之前圖片的路徑是在../images
文件夾下,但是打包出來后沒有這個路徑了,圖片直接和文件同級了摆尝,所以我們需要在webpack.common.js
中給它設置一個文件夾:
// webpack.common.js
...
module.exports = {
...
module: {
rules: [
...
{
test: /\.(png|jpg|svg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1000, // 限制只有小于1kb的圖片才轉為base64温艇,例子圖片為1.47kb,所以不會被轉化
outputPath: 'images' // 設置打包后圖片存放的文件夾名稱
}
}
]
},
...
]
},
...
}
繼續(xù)npm run build
打包再npm run dev
運行,我的天堕汞!圖片還是不顯示勺爱!
調試工具上看圖片路徑有images
文件夾了,但是我的../
呢讯检?
這又涉及到配置路徑的問題上了琐鲁,我們還需要在css-loader中給背景圖片設置一個公共路徑publicPath: '../'
,如下:
// webpack.common.js
...
module.exports = {
...
module: {
rules: [
{
test: /\.css$/, // 正則匹配以.css結尾的文件
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'postcss-loader'],
publicPath: '../' // 給背景圖片設置一個公共路徑
})
},
{
test: /\.(png|jpg|svg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1000, // 限制只有小于1kb的圖片才轉為base64人灼,例子圖片為1.47kb,所以不會被轉化
outputPath: 'images' // 設置打包后圖片存放的文件夾名稱
}
}
]
},
...
]
},
...
}
現在再npm run build
打包再npm run dev
啟動围段,OK!沒毛餐斗拧奈泪!
是不是很熱鬧?到現在我們不知不覺中也同時解決了圖片分離的問題灸芳,偷偷高興一下吧涝桅!
?
7.4 壓縮代碼
在webpack4.x版本中當你打包時會自動把js壓縮了,而且npm run dev
運行服務器時烙样,當你修改代碼時冯遂,熱更新很慢,這是因為你修改后webpack又自動為你打包谒获,這就導致了在開發(fā)環(huán)境中效率很慢蛤肌,所以我們需要把開發(fā)環(huán)境和生產環(huán)境區(qū)分開來,這時就體現出我們代碼分離的便捷性了批狱,webpack.dev.js
代表開發(fā)環(huán)境的配置裸准,webpack.prod.js
代表生產環(huán)境的配置,這時我們只要在package.json
文件中配置對應環(huán)境的命令即可:
{
...
"scripts": {
"build": "webpack --config webpack.prod.js --mode production",
"dev": "webpack-dev-server --open --config webpack.dev.js --mode development"
},
...
}
}
--mode production
表示打包時是生產環(huán)境精耐,會自己將js進行壓縮狼速,而--mode development
表示當前是開發(fā)環(huán)境,不需要進行壓縮卦停。這同時也解決了之前一直遺留的警告問題:
?
總結
好了向胡,到現在我們基本把webapck常用的功能都走了一遍,寫得有點長惊完,感謝你能仔細的看到這里僵芹,希望能對你有所幫助,如果有發(fā)現不對的地方小槐,也請多多指教拇派。其實webpack還有很多功能荷辕,這里也沒講述完全,但相信你現在對webpack也有了一定的了解件豌,更多的webpack探索一定難不倒你疮方!
完整代碼請前往github,如果能有所幫助茧彤,希望能給個star(偷笑)