# webpack
說起 Webpack,大家都知道這是一個**模塊化構建(打包)工具**,那么究竟什么是模塊化呢?
## 模塊化
> 模塊化是指解決一個復雜問題時自頂向下逐層把系統劃分成若干模塊的過程,有多種屬性,分別反映其內部特性。(百度百科)
模塊化被越來越多的應用到我們的日常生活中,在我的印象中,小時候家電(比如收音機、電視)壞了都是拿到店里去找老師傅維修,現在家電都是模塊化的,檢測下哪里壞了直接換個新模塊就可以了,由此可見,模塊化不僅僅是個前端概念(相反,前端模塊化也是這些年剛剛被得到重視的),我們生活場景中大量的充斥著模塊化,讓我們的生活效率更高。
前端模塊化一般指得是 JavaScript 的模塊,最常見的是 Nodejs 的 NPM 包,每個模塊可能是最小甚至是最優的代碼組合,也可能是解決某些問題有很多特定模塊組成的大模塊。如果沒有模塊化,可能大家編寫代碼當中遇見最多的就是復制(copy),當我們需要某個功能的代碼時,自己由之前在哪個項目寫過,那么我們就會 copy 過來,copy 多了,自然代碼的可維護性就會下降。
我們所接觸的模塊化開發有`CommonJS`和`ES6 Module`規范。此外還有`AMD`、`CMD`、`UMD`(兼容`AMD`和`CMD`)
**說起模塊化又不得不提到一個詞叫做組件化**
## 組件化和模塊化的區別
> Tips:大家可能也經常聽到組件化這個名詞,模塊化一般指的是可以被抽象封裝的最小/最優代碼集合,模塊化解決的是功能耦合問題;組件化則更像是模塊化進一步封裝,根據業務特點或者不同的場景封裝出具有一定功能特性的獨立整體;另外,前端提到組件化更多的是具有模板、樣式和 js 交互的 UI 組件。
## 工程化
當我們開發的 Web 應用越來越復雜的時候,會發現我們面臨的問題會逐漸增多:
1. 模塊多了,依賴管理怎么做;
2. 頁面復雜度提升之后,多頁面、多系統、多狀態怎么辦;
3. 團隊擴大之后,團隊合作怎么做;
4. 怎么解決多人研發中的性能、代碼風格等問題;
5. 權衡研發效率和產品迭代的問題。
這些問題就是軟件工程需要解決的問題。工程化的問題需要運用工程化工具來解決,得益于 Nodejs 的發展,前端這些年在工程化上取得了不俗的成績。前端工程化早期,是以 Grunt、Gulp 等構建工具為主的階段,這個階段解決的是重復任務的問題,它們將某些功能拆解成固定步驟的任務,然后編寫工具來解決,比如:圖片壓縮、地址添加 hash、替換等,都是固定套路的**重復你性工作**。
而現階段的 Webpack 則更像是從一套解決 JavaScript 模塊化依賴打包開始,利用強大的插件機制,逐漸解決前端資源依賴管理問題,依附社區力量逐漸進化成一套前端工程化解決方案。
## 什么是 webpack
> 本質上,Webpack 是一個現代 JavaScript 應用程序的靜態模塊打包器(static module bundler)。在 Webpack 處理應用程序時,它會在內部創建一個依賴圖(dependency graph),用于映射到項目需要的每個模塊,然后將所有這些依賴生成到一個或多個 bundle。
像 Grunt、Gulp 這類構建工具,打包的思路是:`遍歷源文件`→`匹配規則`→`打包`,這個過程中**做不到按需加載**,即對于打包起來的資源,到底頁面用不用,打包過程中是不關心的。
webpack 跟其他構建工具本質上不同之處在于:**webpack 是從入口文件開始,經過模塊依賴加載、分析和打包三個流程完成項目的構建**。在加載、分析和打包的三個過程中,可以針對性的做一些解決方案,比如`code split`(拆分公共代碼等)。
當然,Webpack 還可以輕松的解決傳統構建工具解決的問題:
- 模塊化打包,一切皆模塊,JS 是模塊,CSS 等也是模塊;
- 語法糖轉換:比如 ES6 轉 ES5、TypeScript;
- 預處理器編譯:比如 Less、Sass 等;
- 項目優化:比如壓縮、CDN;
- 解決方案封裝:通過強大的 Loader 和插件機制,可以完成解決方案的封裝;
# webpack-cli體驗零配置打包
> 溫故而知新,可以為師矣。
本節默認認為已經安裝過Node和Npm.開發IDE使用的是[WebStorm 2018 3.6](http://www.jetbrains.com/webstorm/download/previous.html)
打造一個可以轉換ES6為ES5 以及 支持圖片(png,jpg,gif,webp)、less、sass/scss的webpack配置項,支持修改項目文件自動更新的功能。
## 全局安裝
`npm install webpack webpackcli -g`
## 初始化項目
`npm init -y`
```
{
"name": "demo1",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
}
}
```
這里面包含了`dependencies`為項目的依賴在通過`npm install XXX -S`或者`npm install yyy --save`會將xxx及版本號顯示在這個位置
`devdependencies`為開發環境依賴通過`npm install yyy -D`或者`npm install yyy --dev-save`
## 創建src/main.js和src/index.html
需求描述,通過jquery實現 ul>li無序列表中li的隔行換色功能。
**安裝jquery 項目中需要使用**
`npm install jquery -S`
**實現隔行換色**
`src/main.js`
```js
import $ from "jquery"
$("ul li:even").css({background:'red'})
$("ul li:odd").css({background:'pink'})
```
## 執行命令
`webpack src/main.js -o dist/bundle.js`
**目錄結構**
```
dist
| ├─bundle.js
src
| ├─main.js
| ├─index.html
package.json
```
## 修改index.html引入打包后的js
```
……
<body>
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
<script src="../dist/bundle.js"></script>
……
```
## 章結
發現,我們要手動把js代碼引入,并且要手動打開瀏覽器去查看網頁。等我們后面學到`webpack-dev-server`中的`html-webpack-plugin`插件去解決。
## 項目源碼
`git clone https://github.com/highh5/webpack.git -b lesson-01`
# webpack結合npm scripts
我們后面會不斷的修改項目文件,每次修改完要重新執行`webpack src/main.js -o dist/bundle.js`命令。感覺 命令比較長,比較繁瑣。
## 安裝本地項目依賴
`npm install webpack webpack-cli -D`
```json
{
"name": "demo1",
"version": "1.0.0",
"dependencies": {
"jquery": "^3.4.1"
},
"devDependencies": {
"webpack": "^4.35.0",
"webpack-cli": "^3.3.5"
}
}
```
## npm scripts
我們可以結合`npm scripts`來方便我們書寫命令。修改`package.json`文件如下:
```
{
"name": "demo1",
"version": "1.0.0",
"scripts": {
"start": "webpack src/main.js -o dist/bundle.js"
},
"dependencies": {
"jquery": "^3.4.1"
},
"devDependencies": {
"webpack": "^4.35.0",
"webpack-cli": "^3.3.5"
}
}
```
## 啟動命令
`npm start 或者npm run start`
## 解釋
`npm run xxx`
這其中的`xxx`就是我們在scripts中定義的key值。
這樣一句命令代表著我們將xxx的值執行。即執行 `webpack src/main.js -o dist/bundle.js`。
這里面的`webpack`命令其實是利用的我們項目的開發環境的的`webpack-cli`模塊。如果本地項目未完裝`webpack`、`wepback-cli`則去找全局命令。
## 注意
> <font color="red">start這個命令可以省去run其它都不可以</font>
## 問題
上面命令執行后是可以進行打包,不過會產生一個警告提示。按照提示添加mode屬性要么為production(生產環境)或者development 開發環境 。我們在這里了解一下開發環境。所以將mode屬性的值設置為development;
```
> demo1@1.0.0 start C:\Users\zhaoy\Desktop\react\01\demo1
> webpack src/main.js -o dist/bundle.js
Hash: 4e6df5b73ef9caaba844
Version: webpack 4.35.0
Time: 329ms
Built at: 2019-06-24 7:36:41 PM
Asset Size Chunks Chunk Names
bundle.js 87.6 KiB 0 [emitted] main
Entrypoint main = bundle.js
[1] ./src/main.js 106 bytes {0} [built]
+ 1 hidden module
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option t
o 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configurati
on/mode/
```
**修改npm scripts**
```
webpack src/main.js -o dist/bundle.js --mode development
```
```json
{
"name": "demo1",
"version": "1.0.0",
"scripts": {
"start": "webpack src/main.js -o dist/bundle.js --mode development"
},
"dependencies": {
"jquery": "^3.4.1"
},
"devDependencies": {
"webpack": "^4.35.0",
"webpack-cli": "^3.3.5"
}
}
```
## 項目源碼
`git clone https://github.com/highh5/webpack.git -b lesson-02`
# webpack配置文件
`webpack`的配置項除了`--output`和`--mode`之外還有非常多的選項。官方也提供了一個更簡便的方式來進行操作。
## 配置文件
在項目根目錄上新建`webpack.config.js`
```
const path = require('path');
module.exports = {
entry:path.join(__dirname,'./src/main.js'),
output:{
path:path.join(__dirname,'./dist'),
filename:'bundle.js'
}
}
```
我們利用`path.join(__dirname,'path')`將文件的入口文件變為絕對地址。將輸出文件地址也改為絕對地址。這樣更利于管理。
相應的`npm scripts`也要做一些修改`"start": "webpack --config webpack.config.js"`或者`"start": "webpack"`完整的文件如下。
```
{
"name": "demo1",
"version": "1.0.0",
"scripts": {
"start": "webpack --config webpack.config.js"
},
"dependencies": {
"jquery": "^3.4.1"
},
"devDependencies": {
"webpack": "^4.35.0",
"webpack-cli": "^3.3.5"
}
}
```
## 執行命令
`npm start` 那么會執行`webpack --config webpack.config.js`那么上面的代碼的執行順序為`webpack` 讀取到`webconfig.js``的配置就可以得知具體的事務`。
## 項目源碼
`git clone https://github.com/highh5/webpack.git -b lesson-03`
# webpack-dev-server
我們現在修改需求,把main.js中的”pink“改為"purple" 即粉色改為紫色。發現并沒有自動打包文件,我們需要手動執行npm start來重新打包。
我們在去看gulp的時候我們在改項目文件時候,只要項目文件發生變化 ,那么會自動打包,并且會刷新瀏覽器。其實webpack也有相應的功能,而且這個功能較gulp更強大。
修改`src/main.js`
```
import $ from "jquery"
$("ul li:even").css({background:'red'})
$("ul li:odd").css({background:'purple'})
```
安裝`npm install webpack-devserver -D`
修改`npm scripts`
```
"start": "webpack-dev-server --config webpack.config.js --open --port 3000 --hot"
```
```json
{
"name": "demo1",
"version": "1.0.0",
"scripts": {
"start": "webpack-dev-server --config webpack.config.js --open --port 3000 --hot"
},
"dependencies": {
"jquery": "^3.4.1"
},
"devDependencies": {
"webpack": "^4.35.0",
"webpack-cli": "^3.3.5",
"webpack-dev-server": "^3.7.2"
}
}
```
## 解釋配置
```
--open 自動打開瀏覽器 相當于 --open true
--port 打開的服務端口號 3000 --port 3000
--hot 自動更新 --hot true
```
## 執行命令
`npm start`
執行命令我們會發現會自動打開瀏覽器,但是我們發現打開的并不是我們的index.html中的文件。它其它打開的是一個靜態文件服務器
同時我們也發現并不會在我們本地去創建一個`dist/bundle.js`那么它是怎么一回事?
其實它是將打包的信息全部放在緩存(內存)中,我們是看不到的,那究竟打包的文件在哪里?打開這個地址就可以找到。<http://localhost:3000/bundle.js>
那么如何將我們的模板`index.html`和`bunddle.js`文件結合?請查看下面
## `html-webpack-plugin`插件
安裝`npm install html-webpack-plugin -D`
使用`webpack.config.js`
```js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry:path.join(__dirname,'./src/main.js'),
output:{
path:path.join(__dirname,'./dist'),
filename:'bundle.js'
},
plugins:[
new HtmlWebpackPlugin({
template: path.join(__dirname,'./src/index.html'),
filename: 'index.html'
})
]
}
```
注意插都是一個構造函數,都需要new,所以在引入`html-webpack-plugin`模塊時我們會使用大駝峰命名法。
利用上面的方式我們重新啟動一下服務即可完成。`npm start`

# 除js外的其它模塊
## 使用css
webpack將所有的文件都認為是模塊所以CSS也不例外。
創建`src/index.css`
```css
body{
background:gold;
}
```
**使用`css`**
在`main.js`項目入口文件 通過 `import './index.css';`
```js
import $ from "jquery"
$("ul li:even").css({background:'red'})
$("ul li:odd").css({background:'purple'})
import './index.css'
```
### 報錯
```cmd
ERROR in ./src/index.css 1:4
Module parse failed: Unexpected token (1:4)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
> body{
| background:gold;
| }
@ ./src/main.js 5:0-20
```
`webpack`默認是不識別`.css`文件作為結尾的模塊,需要我們通過`loader`加載器將`.css`文件進行解釋成正確的模塊
### css-loader
安裝 `npm install css-loader -D`
配置`webpack.config.js`
```js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry:path.join(__dirname,'./src/main.js'),
output:{
path:path.join(__dirname,'./dist'),
filename:'bundle.js'
},
plugins:[
new HtmlWebpackPlugin({
template: path.join(__dirname,'./src/index.html'),
filename: 'index.html'
})
],
module:{
rules:[
{test:/\.css$/,use:['css-loader']}
]
}
}
```
module表示是模塊的意思,rules是規則的意思,每個規則對應的都是一個對象,其中test字段表示是書寫正則以哪個文件名結尾,use是一個數組,表示 當前類型文件用哪種加載器解釋。
### 疑問
這次我們重新執行命令`npm start`不再報錯,但是對于css的樣式并未加載成功原因是為什么?缺失`style-loader`
為什么說是缺失`style-loader?`
css-loader 是將index.css正確解釋 為webpack的模塊,進行打包到了bundle.js,我們可以佐證,但是該樣式并未成功的顯示到瀏覽器中。
那么style-loader的作用其實就是將打包到bundle.js中的css樣式輸出到瀏覽器中,以style標簽的形式顯示。


### style-loader
安裝`npm install style-loader -D`
配置 `{test:/\.css$/,use:['style-loader','css-loader']}`
注意loader的加載順序是從右向左, 即先將css文件通過css-loader 作為正確的模塊進行解釋,然后再通過style-loader進行顯示到瀏覽器中。
至此CSS文件已經完成。
### 使用less文件
安裝`npm install less-loader less -D`
配置:`{test:/\.less$/,use:['style-loader','css-loader','less-loader']}`
注意,我們需要借助于less編譯。因為less-loader 依賴less。
### 使用sass文件
安裝`npm install sass-loader node-sass -D`
配置:`{test:/\.scss$/,use:['style-loader','css-loader','sass-loader']}`
```js
rules:[
{test:/\.css$/,use:['style-loader','css-loader']},
{test:/\.less$/,use:['style-loader','css-loader','less-loader']},
{test:/\.scss$/,use:['style-loader','css-loader','sass-loader']},
]
```
### 注意
<font color="red">對正則中的.號表示任意字符,千萬小心最好把它轉義一下,再來使用。</font>
### 項目源碼
`git clone https://github.com/highh5/webpack.git -b lesson-04`
## 圖片處理
安裝`npm install url -D`
配置:`{test:/\.(jpg|jpeg|gif|png|webp)$/,use:['url-loader']},`
放置一張圖片到src目錄圖片大小為13.54kb

我們給a.scss指定body的背景是這張圖片
```scss
$color:green;
body{
background: url("./avatar.jpg");
}
```
打開瀏覽器后發現圖片是以base64進行編碼了。什么是base64編譯的圖片呢?通俗的講,圖片不再像以前通過外鏈的形式打開,而是內嵌在網頁上了。圖片越大base64的編碼信息越多。也就是說越大的圖片會導致我們的代碼越來越長。這是不是好事呢?
答案: 肯定不是很好,我們期望網頁打開越快越好,如果網站當中所有的圖片全是以base64來進行編碼,那么會導致首頁代碼體積越大,那么打開速度也不會很快,相反可能會很卡。
我們一般可以讓小圖片使用base64進行加載,這樣既能讓圖片快速加載,又會減少http請求。至于大圖片我們一般就直接以往常的外鏈形式存在即可。
### 解決問題
修改loader配置項,limit表示小于后面的1000 byte即接近1kb,用base64顯示。超過的限制的話通過外鏈來讀取圖片(需要安裝file-loader 文件加載器)
`npm install file-loader -D`
```json
{
test: /\.(jpg|jpeg|gif|png|webp)$/, use: [{
loader: 'url-loader',
options:{
limit:1000,
name:'[name].[hash:8].[ext]'
}
}]
},
```
最終圖片被正確解釋出來
```css
body {
background: url(avatar.9d23d463.jpg);
}
```
# ES6 轉 ES5
## 安裝
`npm install babel-core babel-loader@7.1.5 babel-plugin-transform-runtime babel-preset-env babel-preset-stage-0 -D`
## 配置loader
```json
{test:/\.js/,use:['babel-loader'],exclude:/node_modules/}
```
exclude表示排除掉 node_modules下載的依賴項。這樣可以加速網站開發,而且我們也只需要對我們的項目src源文件進行編譯即可。
## 新增.babelrc文件
```json
{
"presets":[“env","stage-0"],
"plugins":["transform-runtime"]
}
```
## 修改main.js使用es6語法
```js
class Person {
constructor(){
}
}
var p = new Person();
```
## 執行命令編譯
`npm start`
編譯后的結果接近在1335行
```js
\n\n var Person = function Person() {\n (0, _classCallCheck3.default)(this, Person);\n};\n\nvar p = new Person();\n\n
```
**至此關于webpack的基本配置已經到這里。**
## 項目源碼
`git clone https://github.com/highh5/webpack.git -b lesson-05`
# 小結
介紹了模塊化、工程化相關的概念和發展現狀,最后介紹了 Webpack 的應用場景,以及復習了webpack如何使用等。
# 面試題(拓展)
>學而不思則罔,思而不學則殆
**Webpack 與 Grunt、Gulp 這類打包工具有什么不同?**
簡單解答:一個是模塊化打包化工具,一個是流程化任務工具。
webpack 的工作方式是: 把你的項目當做一個整體,通過一個指定的主文件名(index.js, 一般是入口文件),webpack 將從這個文件開始找到你的項目所依賴的文件,使 用loaders 來處理它們,最后打包為一個瀏覽器可識別的js 文件。

Gulp的工作方式是:stream流
grunt的工作方式是:在一個配置文件中,指明對某些文件進行壓縮、組合、檢查等任務的具體步驟,然后在運行中輸入相應的命令。

**與 Webpack 類似的工具還有哪些?談談你為什么選擇(或放棄)使用 Webpack?**
**同樣是基于入口的打包工具還有以下幾個主流的:**
- webpack
- [rollup](http://rollupjs.org/guide/en/)
- [parcel](https://parceljs.org/)
**從應用場景上來看:**
- webpack適用于大型復雜的前端站點構建
- rollup適用于基礎庫的打包,如vue、react
- parcel適用于簡單的實驗性項目,他可以滿足低門檻的快速看到效果
由于parcel在打包過程中給出的調試信息十分有限,所以一旦打包出錯難以調試,所以不建議復雜的項目使用parcel
- webpack復習
- React基礎
- 前端三大主流框架對比
- React中幾個核心的概念
- React基礎語法
- React JSX語法
- React組件
- 普通組件
- 組件通信-父向子傳遞
- 組件拆成單個文件
- 面向對象復習
- Class組件基礎
- Class組件的私有狀態(私有數據)
- 案例:實現評論列表組件
- 組件樣式管理
- 組件樣式分離-樣式表
- CSS模塊化
- 生命周期
- React組件生命周期
- Counter組件來學習組件生命周期
- 生命周期總結
- 生命周期案例
- React評論列表
- React雙向數據綁定
- React版todolist
- 其它提高(了解)
- 組件默認值和數據類型驗證
- 綁定this并傳參的三種方式
- 祖孫級和非父子組件傳遞數據(了解)
- React路由
- 路由基礎
- 動態路由
- 路由嚴格模式
- 路由導航定位
- 路由重定向
- 路由懶加載
- WolfMovie項目
- 項目初始化
- AntDesign使用
- 其它相關了解