最近一直在學習webpack的基礎知識,從以下5個大的點進行總結(jié)。
1.什么是webpack?
2.學習webpack需要掌握的基本知識點和概念
3.從頭到尾做一個webpack的小demo
4.webpack在項目中的使用技巧和對項目的優(yōu)化

什么是Webpack
webpack是近期最火的一款模塊加載器兼打包工具,它能把各種資源,例如JS(含JSX)、coffee、樣式(含less/sass)、圖片等都作為模塊來使用和處理,它能有Grunt或Gulp所有基本功能。webpack的官網(wǎng)是 https://webpack.github.io/ ,文檔地址是https://webpack.github.io/docs,官網(wǎng)對webpack的定義是MODULE BUNDLER,他的目的就是把有依賴關系的各種文件打包成一系列的靜態(tài)資源。
一、webpack 的優(yōu)勢
其優(yōu)勢主要可以歸類為如下幾個:
(1) webpack 是以 commonJS 的形式來書寫腳本滴,但對 AMD/CMD 的支持也很全面,方便舊項目進行代碼遷移。
(2)支持很多模塊加載器的調(diào)用,可以使模塊加載器靈活定制,比如babel-loader加載器,該加載器能使我們使用ES6的語法來編寫代碼;less-loader加載器,可以將less編譯成css文件;
(3)開發(fā)便捷,能替代部分 grunt/gulp 的工作,比如打包、壓縮混淆、圖片轉(zhuǎn)base64等。
可以通過配置打包成多個文件,有效的利用瀏覽器的緩存功能提升性能。
(4) Loader,加載器可以將其他資源整合到JS文件中,通過這種方式,可以講所有的源文件形成一個模塊.
(5)優(yōu)秀的語法分析能力,支持 CommonJs AMD 規(guī)范
(6)有豐富的開源插件庫,可以根據(jù)自己的需求自定義webpack的配置
二、webpack的工作原理
Webpack的工作方式是:把你的項目當做一個整體,通過一個給定的主文件(如:index.js),Webpack將從這個文件開始找到你的項目的所有依賴文件,使用loaders處理它們,最后打包為一個(或多個)瀏覽器可識別的JavaScript文件。

簡單的說就是分析代碼,找到“require”、“exports”、“define”等關鍵詞,并替換成對應模塊的引用。
在一個配置文件中,指明對某些文件進行編譯、壓縮、組合等任務。把你的項目當成一個整體,通過一個給定的主文件 (index.js),webpack將從這個文件開始找到你的項目的所有的依賴文件,使用loaders處理他們,最后打包為一個瀏覽器可
以識別的js文件。
三、使用場景
在沒有使用webpack之前:
舉個例子:index.html里面有一大堆的css和js文件,如a.js b.js c.js等等
(1)a.js要用到b.js里面的某一個函數(shù),則a.js要放在b.js后面
(2)c.js要用到a.js里面的一個函數(shù),則c.js要放在a.js后面
(3)b.js又要用到某個js文件里面的函數(shù),則b.js就要放在其后面
如果有N多個js文件,需要手動處理他們的關系,即容易出錯。
使用webpack:
webpack的理念就是一切皆模塊化,把一堆的css文件和js文件放在一個總的入口文件,通過require引入,剩下的事情webpack會處理,包括所有模塊的前后依賴關系,打包、壓縮、合并成一個js文件,公共代碼抽離成一個js文件、某些自己指定的js單獨打包,模塊可以是css/js/imsge/font等等。
(1)根據(jù)模板生成HTML,并自動處理上面的css/js引用路徑
(2)自動處理<img>里面的圖片路徑,css里面背景圖的路徑,字體引用
(3)開啟本地服務器,一邊改寫代碼,一邊自動更新頁面內(nèi)容
(4)編譯jsx es6 sass less coffescript等,并添加md5、sourcemap等輔助
(5)異步加載內(nèi)容,不需要時不加載到DOM
(6)配合vue.js react.js等框架開發(fā)
四、WebPack和Grunt以及Gulp相比有什么特性
其實Webpack和另外兩個并沒有太多的可比性,Gulp/Grunt是一種能夠優(yōu)化前端的開發(fā)流程的工具,而WebPack是一種模塊化的解決方案,不過Webpack的優(yōu)點使得Webpack在很多場景下可以替代Gulp/Grunt類的工具。
Grunt和Gulp的工作方式是:在一個配置文件中,指明對某些文件進行類似編譯,組合,壓縮等任務的具體步驟,工具之后可以自動替你完成這些任務。

如果實在要把二者進行比較,Webpack的處理速度更快更直接,能打包更多不同類型的文件。
五、webpack為什么要將所有資源放在一個文件里面?
我們知道,對于瀏覽器來說,加載的資源越少,響應的速度也就越快,所以有時候我們?yōu)榱藘?yōu)化瀏覽器的性能,會盡可能的將資源合并到一個主文件app.js里面。但是這導致的很大的缺點:
當你的項目十分龐大的時候,不同的頁面不能做到按需加載,而是將所有的資源一并加載,耗費時間長,性能降低。
會導致依賴庫之間關系的混亂,特別是大型項目時,會變得難以維護和跟蹤。比如:哪些文件是需要A模塊加載完后才能執(zhí)行的?哪些頁面會受到多個樣式表同時影響的? 等許多問題。
而webpack可以很好的解決以上缺點,因為它是一個十分聰明的模塊打包系統(tǒng),當你正確配置后,它會比你想象中的更強大,更優(yōu)秀。
webpack需要掌握的基本知識點和概念
一個完整的webpack基本配置如下:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const extractPlugin = require('extract-text-webpack-plugin');
module.exports = {
devtool: 'eval-source-map',
entry: { //入口文件配置
main: './src/main.js'
},
output: {
path: path.resolve(__dirname, 'dist'), //打包后的文件存放的地方
filename: 'js/[name].bundle.js', //打包后輸出文件的文件名
},
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' },
{ //js loader
test: /\.js$/,
exclude: /(node_modules|bower_components)/, //避免轉(zhuǎn)義node_modules 目錄或者其他不需要的源代碼。
use: {
loader: 'babel-loader'
}
},
{
test: /\.(png|jpg)$/,
use: {
loader: 'url-loader',
},
},
{
test: /\.css$/,
use: [{
loader: 'style-loader/url'
},
{
loader: 'file-loader',
options: {
name: '[path][name].[ext]',
}
}
]
}
]
},
plugins: [ //相關插件配置
new HtmlWebpackPlugin({
title: 'test page', //html標題
filename: 'index.html', //打包后的html文件名,默認index.html
template: './index.html', // html的源文件
inject: true, //默認true,意為script標簽位于html文件的 body 底部
cache: true, //默認true,表示內(nèi)容變化的時候生成一個新的文件
chunks: ['index'], //表示編譯時用到的入口文件
date: new Date(),
// excludeChunks: ['index']//表示編譯時排除的入口文件
}),
new extractPlugin("css/[name].css")
],
devServer: {
contentBase: path.resolve(__dirname, 'dist'), //最好設置成絕對路徑
historyApiFallback: true, //true默認打開index.html,false會出現(xiàn)一個目錄,一會演示
hot: true,
inline: true,
stats: 'errors-only',
host: '127.0.0.1',
port: '8080',
overlay: true, //出現(xiàn)錯誤之后會在頁面中出現(xiàn)遮罩層提示
open: true //運行之后自動打開本地瀏覽器
}
}
entry: 是指入口文件的配置項,它是一個數(shù)組的原因是webpack允許多個入口點。
entry的概念和用法具體可參考entry配置,寫的很詳細。
output:是指輸出文件的配置項
output的概念和用法具體可參考output配置,寫的很詳細。
filename : 表示輸出文件的文件名
path :配置輸出文件存放在本地的目錄
publicPath,配置CDN的路徑
chunkFilename ,處理異步加載時的命名規(guī)則
hash、chunkhash和contenthash三者的區(qū)別
hash是項目級別的,每次構(gòu)建得出的hash都是相同的,這可能不利于文件的緩存
chunkhash是文件級別的,值是變動修改的文件的chunkhash值
contenthash是文件級別的,在拆分css文件時記得使用處理css的緩存
Resolve: Webpack 在啟動后會從配置的入口模塊出發(fā)找出所有依賴的模塊,Resolve 配置 Webpack 如何尋找模塊所對應的文件。 Webpack 內(nèi)置 JavaScript 模塊化語法解析功能,默認會采用模塊化標準里約定好的規(guī)則去尋找,但你也可以根據(jù)自己的需要修改默認的規(guī)則。
alias: resolve.alias 配置項通過別名來把原導入路徑映射成一個新的導入路徑。例如使用以下配置:
// Webpack alias 配置
resolve:{
alias:{
components: './src/components/'
}
}
當你通過 import Button from 'components/button 導入時,實際上被 alias 等價替換成了 import Button from './src/components/button' 。
以上 alias 配置的含義是把導入語句里的 components 關鍵字替換成 ./src/components/ 。
這樣做可能會命中太多的導入語句,alias 還支持 $ 符號來縮小范圍到只命中以關鍵字結(jié)尾的導入語句:
resolve:{
alias:{
'react$': '/path/to/react.min.js'
}
}
extensions: 在導入語句沒帶文件后綴時,Webpack 會自動帶上后綴后去嘗試訪問文件是否存在。 resolve.extensions 用于配置在嘗試過程中用到的后綴列表,默認是:
extensions: ['.js', '.json']
也就是說當遇到 require('./data') 這樣的導入語句時,Webpack 會先去尋找 ./data.js 文件,如果該文件不存在就去尋找 ./data.json 文件, 如果還是找不到就報錯。
假如你想讓 Webpack 優(yōu)先使用目錄下的 TypeScript 文件,可以這樣配置:
extensions: ['.ts', '.js', '.json']
plugins :顧名思義,使用插件可以給webpack添加更多的功能,使webpack更加的靈活和強大,webpack有兩種類型的插件:
(1)webpack內(nèi)置的插件
// 首先要先安裝webpack模塊
var webpack = require("webpack");
module.exports = {
new webpack.optimize.UglifyJsPlugin({
compressor: {
warnings: false,
},
})
};
(2)webpack外置插件
//npm install component-webpack-plugin 先要在安裝該模版
var ComponentPlugin = require("component-webpack-plugin");
module.exports = {
plugins: [
new ComponentPlugin()
]
}
plugins的概念和用法具體可參考plugins的使用,寫的很詳細。
module 配置處理文件的選項
loaders: 一個含有wepback中能處理不同文件的加載器的數(shù)組。告訴webpack要利用哪種加載器來處理test所匹配的文件
loaders的概念和用法具體可參考loaders的使用,寫的很詳細。
loaders 的安裝方法
$ npm install xxx-loader --save-dev
以less為例的話,先要安裝less、less-loader、css-loader、style-loader模塊。loader如下:
{
test: /\.less$/,
use: [
"style-loader",
"css-loader",
"less-loader"
]
}
test:用來匹配相對應文件的正則表達式
use: 一個數(shù)組,里面放一個個的loader,執(zhí)行順序從右到左。上面幾個loader的作用依次為:less-loader將代碼轉(zhuǎn)成css、css-loader用于解析(可以用來做css-moudle、壓縮等)、style-loader則將解析后的樣式嵌入js代碼。
exclude:排除不滿足條件的文件夾(這樣可以排除webpack查找不必要的文件)
include:需要被loader 處理的文件或文件夾。
dev-server :
Webpack提供一個可選的本地開發(fā)服務器,這個本地服務器基于node.js構(gòu)建,可以實現(xiàn)你想要的這些功能之后會使用到;
詳細的教程可以參考深入淺出的webpack構(gòu)建工具---DevServer配置項
一個完整的dev-server如下:
devServer: {
contentBase: path.resolve(__dirname, 'dist'), //最好設置成絕對路徑
historyApiFallback: true, //true默認打開index.html,false會出現(xiàn)一個目錄,一會演示
hot: true,
inline: true,
stats: 'errors-only',
host: '127.0.0.1',
port: '8080',
overlay: true, //出現(xiàn)錯誤之后會在頁面中出現(xiàn)遮罩層提示
open: true //運行之后自動打開本地瀏覽器
}
contentBase:該配置項指定了服務器資源的根目錄,如果不配置contentBase的話,那么contentBase默認是當前執(zhí)行的目錄,一般是項目的根目錄。
port:該配置屬性指定了開啟服務器的端口號。
host:該配置項用于配置 DevServer的服務器監(jiān)聽地址。比如想讓局域網(wǎng)的其他設備訪問自己的本地服務,則可以在啟動DevServer時帶上 --host 0.0.0.0.
headers:該配置項可以在HTTP響應中注入一些HTTP響應頭
historyApiFallback:該配置項屬性是用來應對返回404頁面時定向跳轉(zhuǎn)到特定頁面的。一般是應用在 HTML5中History API 的單頁應用,比如在訪問路由時候,訪問不到該路由的時候,會跳轉(zhuǎn)到index.html頁面。
hot:該配置項是指模塊替換換功能,DevServer 默認行為是在發(fā)現(xiàn)源代碼被更新后通過自動刷新整個頁面來做到實時預覽的,
但是開啟模塊熱替換功能后,它是通過在不刷新整個頁面的情況下通過使用新模塊替換舊模塊來做到實時預覽的。inline: webpack-dev-server 有兩種模式可以實現(xiàn)自動刷新和模塊熱替換機制。inline: false;頁面是被嵌入到一個iframe頁面,并且在模塊變化的時候重載頁面。inline:true;它在構(gòu)建變化后的代碼會通過代理客戶端來控制網(wǎng)頁刷新。
open: 該屬性用于DevServer啟動且第一次構(gòu)建完成時,自動使用我們的系統(tǒng)默認瀏覽器去打開網(wǎng)頁。
overlay:該屬性是用來在編譯出錯的時候,在瀏覽器頁面上顯示錯誤。該屬性值默認為false,需要的話,設置該參數(shù)為true。
stats(字符串):該屬性配置是用來在編譯的時候再命令行中輸出的內(nèi)容
compress:該屬性是一個布爾型的值,默認為false,當他為true的時候,它會對所有服務器資源采用gzip進行壓縮。
proxy: 用來解決跨域。假如現(xiàn)在我們本地訪問的域名是 http://localhost:8081, 但是我現(xiàn)在調(diào)用的是百度頁面中的一個接口,該接口地址是:http://news.baidu.com/widget?ajax=json&id=ad?,F(xiàn)在我們只需要在devServer中的proxy的配置就可以了:
如下配置:
proxy: {
'/api': {
target: 'http://news.baidu.com', // 目標接口的域名
// secure: true, // https 的時候 使用該參數(shù)
changeOrigin: true, // 是否跨域
pathRewrite: {
'^/api' : '' // 重寫路徑
}
}
}
然后我們在需要請求接口的js里面編寫如下代碼:
import axios from 'axios';
axios.get('/api/widget?ajax=json&id=ad').then(res => {
console.log(res);
});
resolve:其它解決方案配置;
resolve.root,絕對路徑, 查找module的話從這里開始查找(可選)
resolve.modulesDirectories,取相對路徑,所以比起 root ,所以會多 parse 很多路徑。查找module(可選)
resolve.extensions,自動擴展文件后綴名,意味著我們require模塊可以省略不寫后綴名
resolve.alias,模塊別名定義,方便后續(xù)直接引用別名,無須多寫長長的地址
從頭到尾做一個webpack的小demo
初步了解了Webpack基本知識點后,我們一步步的開始學習使用Webpack。
1.初始化項目
Webpack可以使用npm安裝,新建一個空的練習文件夾(此處命名為webpack-test),在終端中轉(zhuǎn)到該文件夾后執(zhí)行下述指令就可以完成安裝。
npm init
在上述練習文件夾中創(chuàng)建一個package.json文件,這是一個標準的npm說明文件,里面蘊含了豐富的信息,包括當前項目的依賴模塊,自定義的腳本任務等等。在終端中使用npm init命令可以自動創(chuàng)建這個package.json文件。
輸入這個命令后,終端會問你一系列諸如項目名稱,項目描述,作者等信息,不過不用擔心,如果你不準備在npm中發(fā)布你的模塊,這些問題的答案都不重要,回車默認即可。初始化完成。
2.安裝webpack
全局安裝(如果之前沒安裝的話,就全局安裝一下,之前安裝過了的話,就可以直接跳過這一步)
npm install -g webpack //全局安裝webpack
npm install --save-dev webpack //局部安裝
注意:如果使用的是4.0+的版本,還需要安裝webpack-cli
cnpm i webpack-cli -s
npm安裝注意兩點:
(1)安裝時如未指定版本號,則按最新的版本安裝,這里webpack安裝的是最新的4.17.1的版本
(2)npm install --save和npm install --save-dev的區(qū)別:
--save 會把依賴包名稱添加到 package.json 文件 dependencies 鍵下,--save-dev 則添加到 package.json 文件 devDependencies 鍵下。dependencies是發(fā)布后還依賴的,devDependencies是開發(fā)時的依賴。
webpack和webpack-cli安裝完成后在剛才創(chuàng)建的文件夾里面創(chuàng)建兩個文件夾,分別起名為src文件夾和dist文件夾,src文件夾用來存放原始數(shù)據(jù)和我們將寫的JavaScript模塊,dist文件夾用來存放打包之后供瀏覽器讀取的文件(包括使用webpack打包生成的js文件以及一個index.html文件)。接下來我們再創(chuàng)建幾個文件價和文件,最后的層級如下圖所示:

我們在index.html文件中寫入最基礎的html代碼,它在這里目的在于引入打包后的js文件(這里我們先把之后打包后的js文件命名為./dist/js/index.bundle.js,之后我們還會詳細講述)。
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Webpack Sample Project</title>
</head>
<body>
<div id='root'>
</div>
<script src="bundle.js"></script>
</body>
</html>
我們在Greeter.js中定義一個返回包含問候信息的html元素的函數(shù),并依據(jù)CommonJS規(guī)范導出這個函數(shù)為一個模塊:
// Greeter.js
module.exports = function() {
var greet = document.createElement('div');
greet.textContent = "Hi there and greetings!";
return greet;
};
main.js文件中我們寫入下述代碼,用以把Greeter模塊返回的節(jié)點插入頁面。
//main.js
const greeter = require('./Greeter.js');
document.querySelector("#root").appendChild(greeter());
3. 配置webpack文件
Webpack擁有很多其它的比較高級的功能(比如說本文后面會介紹的loaders和plugins),這些功能其實都可以通過命令行模式實現(xiàn),但是正如前面提到的,這樣不太方便且容易出錯的,更好的辦法是定義一個配置文件,這個配置文件其實也是一個簡單的JavaScript模塊,我們可以把所有的與打包相關的信息放在里面。
繼續(xù)上面的例子來說明如何寫這個配置文件,在當前練習文件夾的根目錄下新建一個名為webpack.config.js的文件,我們在其中寫入如下所示的簡單配置代碼,目前的配置主要涉及到的內(nèi)容是入口文件路徑和打包后文件的存放路徑。
module.exports = {
entry: __dirname + "/src/main.js", //已多次提及的唯一入口文件
output: {
path: __dirname + '/dist',//打包后的文件存放的地方
filename: 'js/[name].bundle.js',//打包后輸出文件的文件名
}
}
注:“__dirname”是node.js中的一個全局變量,它指向當前執(zhí)行腳本所在的目錄。
有了這個配置之后,再打包文件,只需在終端里運行webpack(非全局安裝需使用node_modules/.bin/webpack)命令就可以了,這條命令會自動引用webpack.config.js文件中的配置選項,示例如下:

更快捷的執(zhí)行打包任務
在命令行中輸入命令需要代碼類似于node_modules/.bin/webpack這樣的路徑其實是比較煩人的,不過值得慶幸的是npm可以引導任務執(zhí)行,對npm進行配置后可以在命令行中使用簡單的npm start命令來替代上面略微繁瑣的命令。在package.json中對scripts對象進行相關設置即可,設置方法如下。
{
"name": "webpack-sample-project",
"version": "1.0.0",
"description": "Sample webpack project",
"scripts": {
"start": "webpack" // 修改的是這里,JSON文件不支持注釋,引用時請清除
},
"author": "zhang",
"license": "ISC",
"devDependencies": {
"webpack": "3.10.0"
}
}
注:package.json中的script會安裝一定順序?qū)ふ颐顚恢?,本地的node_modules/.bin路徑就在這個尋找清單中,所以無論是全局還是局部安裝的Webpack,你都不需要寫前面那指明詳細的路徑了。
npm的start命令是一個特殊的腳本名稱,其特殊性表現(xiàn)在,在命令行中使用npm start就可以執(zhí)行其對于的命令,如果對應的此腳本名稱不是start,想要在命令行中運行時,需要這樣用npm run {script name}如npm run build,我們在命令行中輸入npm start試試,輸出結(jié)果如下:

現(xiàn)在只需要使用npm start就可以打包文件了,有沒有覺得webpack也不過如此嘛,不過不要太小瞧webpack,要充分發(fā)揮其強大的功能我們需要修改配置文件的其它選項,一項項來看。
Webpack的強大功能
(1)生成Source Maps(使調(diào)試更容易)
開發(fā)總是離不開調(diào)試,方便的調(diào)試能極大的提高開發(fā)效率,不過有時候通過打包后的文件,你是不容易找到出錯了的地方,對應的你寫的代碼的位置的,Source Maps就是來幫我們解決這個問題的。
通過簡單的配置,webpack就可以在打包時為我們生成的source maps,這為我們提供了一種對應編譯文件和源文件的方法,使得編譯后的代碼可讀性更高,也更容易調(diào)試。
在webpack的配置文件中配置source maps,需要配置devtool,它有以下四種不同的配置選項,各具優(yōu)缺點,描述如下:

正如上表所述,上述選項由上到下打包速度越來越快,不過同時也具有越來越多的負面作用,較快的打包速度的后果就是對打包后的文件的的執(zhí)行有一定影響。
對小到中型的項目中,eval-source-map是一個很好的選項,再次強調(diào)你只應該開發(fā)階段使用它,我們繼續(xù)對上文新建的webpack.config.js,進行如下配置:
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/src/main.js", //已多次提及的唯一入口文件
output: {
path: __dirname + '/dist',//打包后的文件存放的地方
filename: 'js/[name].bundle.js',//打包后輸出文件的文件名
}
}
cheap-module-eval-source-map方法構(gòu)建速度更快,但是不利于調(diào)試,推薦在大型項目考慮時間成本時使用。
(2)使用webpack構(gòu)建本地服務器
想不想讓你的瀏覽器監(jiān)聽你的代碼的修改,并自動刷新顯示修改后的結(jié)果,其實Webpack提供一個可選的本地開發(fā)服務器,這個本地服務器基于node.js構(gòu)建,可以實現(xiàn)你想要的這些功能,不過它是一個單獨的組件,在webpack中進行配置之前需要單獨安裝它作為項目依賴
npm install --save-dev webpack-dev-server
devserver作為webpack配置選項中的一項,它的一些配置選項,更多配置可參考devServer詳細配置
把這些命令加到webpack的配置文件中,現(xiàn)在的配置文件webpack.config.js如下所示
module.exports = {
devServer: {
// contentBase: path.join(__dirname, "dist"),
headers: {
'X-foo': '112233'
},
// hot: true,
port: '8081',
inline: true,
open: true,
overlay: true,
stats: 'errors-only',
proxy: {
'/api': {
target: 'http://news.baidu.com', // 目標接口的域名
// secure: true, // https 的時候 使用該參數(shù)
changeOrigin: true, // 是否跨域
pathRewrite: {
'^/api' : '' // 重寫路徑
}
}
}
}
}
在package.json中的scripts對象中添加如下命令,用以開啟本地服務器:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack",
"server": "webpack-dev-server --open"
},
在終端中輸入npm run server即可在本地的8080端口查看結(jié)果

(3)Loaders
鼎鼎大名的Loaders登場了!
loader是webpack的核心概念之一,它的基本工作流是將一個文件以字符串的形式讀入,對其進行語法分析及轉(zhuǎn)換(或者直接在loader中引入現(xiàn)成的編譯工具,例如sass-loader中就引入了node-sass將SCSS代碼轉(zhuǎn)換為CSS代碼,再交由css-loader處理),然后交由下一環(huán)節(jié)進行處理,所有載入的模塊最終都會經(jīng)過moduleFactory處理,轉(zhuǎn)成javascript可以識別和運行的代碼,從而完成模塊的集成。
loader支持鏈式調(diào)用,所以開發(fā)上需要嚴格遵循“單一職責”原則,即每個loader只負責自己需要負責的事情:將輸入信息進行處理,并輸出為下一個loader可識別的格式。
實際開發(fā)中,很少會出現(xiàn)需要自己寫loader來實現(xiàn)復雜需求的場景,如果某個擴展名的文件無法快速集成到自動化構(gòu)建工具里,估計很快就會被拋棄了,大家都那么忙是吧。但是了解loader的基本原理和編譯器的基本原理卻是非常有必要的。
常用的loaders有:
樣式
css-loader : 解析css文件中代碼
style-loader : 將css模塊作為樣式導出到DOM中
less-loader : 加載和轉(zhuǎn)義less文件
sass-loader : 加載和轉(zhuǎn)義sass/scss文件
腳本轉(zhuǎn)換編譯
script-loader : 在全局上下文中執(zhí)行一次javascript文件,不需要解析
babel-loader : 加載ES6 代碼后使用Babel轉(zhuǎn)義為ES5后瀏覽器才能解析
Files文件
file-loader:將要加載的文件復制到指定目錄,生成請求文件資源URL
url-loader : 多數(shù)用于加載圖片資源,超過文件大小顯示則返回data URL
raw-loader : 加載文件原始內(nèi)容(utf-8格式)
加載框架
vue-loader : 加載和轉(zhuǎn)義vue組件
react-hot-loader : 動態(tài)刷新和轉(zhuǎn)義react組件中修改的部分
loader的使用方法:
1.安裝需要的loader
npm install --save-dev xxx-loader
//比如
npm install --save-dev less
npm install --save-dev less-loader
npm install --save-dev css-loader
npm install --save-dev style-loader
2.配置loader
Loaders需要單獨安裝并且需要在webpack.config.js中的modules關鍵字下進行配置,Loaders的配置包括以下幾方面:
Rule.test 是 Rule.resource.test 的簡寫。如果你提供了一個 Rule.test 選項,就不能再提供 Rule.resource。詳細請查看 Rule.resource 和 Condition.test。
-
Rule.use
應用于模塊的 UseEntries 列表。每個入口(entry)指定使用一個 loader。
傳遞字符串(如:use: [ "style-loader" ])是 loader 屬性的簡寫方式(如:use: [ { loader: "style-loader "} ])。
例如:
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1
}
},
{
loader: 'less-loader',
options: {
noIeCompat: true
}
}
]
3.exclude:排除不滿足條件的文件夾(這樣可以排除webpack查找不必要的文件)
4.include:需要被loader 處理的文件或文件夾
例如:
{
module: {
loaders: [{
test: /\.js$/,
loader: 'babel-loader',
include: [
path.resolve(__dirname, "app/src"),
path.resolve(__dirname, "app/test")
],
exclude: /node_modules/
}]
}
}
(4)Babel
Babel其實是一個編譯JavaScript的平臺,它可以編譯代碼幫你達到以下目的:
- 讓你能使用最新的JavaScript代碼(ES6,ES7...),而不用管新標準是否被當前使用的瀏覽器完全支持;
- 讓你能使用基于JavaScript進行了拓展的語言,比如React的JSX;
Babel的安裝
Babel其實是幾個模塊化的包,其核心功能位于稱為babel-core的npm包中,webpack可以把其不同的包整合在一起使用,對于每一個你需要的功能或拓展,你都需要安裝單獨的包(用得最多的是解析Es6的babel-env-preset包和解析JSX的babel-preset-react包)。
我們先來一次性安裝這些依賴包
// npm一次性安裝多個依賴模塊,模塊之間用空格隔開
npm install --save-dev babel-core babel-loader babel-preset-env babel-preset-react
當我們使用babel-loader處理js文件的時候,實際上這個babel-loader只是webpack和babel做通信的一個橋梁,用了他之后,webpack和babel做了打通,但實際上,babel-loader并不會幫助我們把es6語法翻譯成es5語法,還需要借助一些其它的模塊才能夠幫助我們把es6語法翻譯成es5語法。babel/preset-env就是這樣的一個模塊,這里面包含了所有把es6轉(zhuǎn)化成es5的規(guī)則。裝好之后,還需要在webpack里面配置一下
在webpack中配置Babel的方法如下:
module.exports = {
entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
output: {
path: __dirname + "/public",//打包后的文件存放的地方
filename: "bundle.js"http://打包后輸出文件的文件名
},
devtool: 'eval-source-map',
devServer: {
contentBase: "./public",//本地服務器所加載的頁面所在的目錄
historyApiFallback: true,//不跳轉(zhuǎn)
inline: true//實時刷新
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
exclude: /(node_modules|bower_components)/, //避免轉(zhuǎn)義node_modules 目錄或者其他不需要的源代碼。
use: {
loader: "babel-loader",
options: {
presets: [
"env", "react"
]
}
},
exclude: /node_modules/
}
]
}
};
現(xiàn)在你的webpack的配置已經(jīng)允許你使用ES6以及JSX的語法了。繼續(xù)用上面的例子進行測試,不過這次我們會使用React,記得先安裝 React 和 React-DOM
npm install --save react react-dom
接下來我們使用ES6的語法,更新Greeter.js并返回一個React組件
//Greeter,js
import React, {Component} from 'react'
import config from './config.json';
class Greeter extends Component{
render() {
return (
<div>
{config.greetText}
</div>
);
}
}
export default Greeter
修改main.js如下,使用ES6的模塊定義和渲染Greeter模塊
// main.js
import React from 'react';
import {render} from 'react-dom';
import Greeter from './Greeter';
render(<Greeter />, document.getElementById('root'));
重新使用npm start打包,如果之前打開的本地服務器沒有關閉,你應該可以在localhost:8080下看到與之前一樣的內(nèi)容,這說明react和es6被正常打包了。

Babel的配置
Babel其實可以完全在 webpack.config.js 中進行配置,但是考慮到babel具有非常多的配置選項,在單一的webpack.config.js文件中進行配置往往使得這個文件顯得太復雜,因此一些開發(fā)者支持把babel的配置選項放在一個單獨的名為 ".babelrc" 的配置文件中。我們現(xiàn)在的babel的配置并不算復雜,不過之后我們會再加一些東西,因此現(xiàn)在我們就提取出相關部分,分兩個配置文件進行配置(webpack會自動調(diào)用.babelrc里的babel配置選項),如下:
module.exports = {
entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
output: {
path: __dirname + "/public",//打包后的文件存放的地方
filename: "bundle.js"http://打包后輸出文件的文件名
},
devtool: 'eval-source-map',
devServer: {
contentBase: "./public",//本地服務器所加載的頁面所在的目錄
historyApiFallback: true,//不跳轉(zhuǎn)
inline: true//實時刷新
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader"
},
exclude: /node_modules/
}
]
}
};
//.babelrc
{
"presets": ["react", "env"]
}
到目前為止,我們已經(jīng)知道了,對于模塊,Webpack能提供非常強大的處理功能,那哪些是模塊呢。
一切皆模塊
Webpack有一個不可不說的優(yōu)點,它把所有的文件都都當做模塊處理,JavaScript代碼,CSS和fonts以及圖片等等通過合適的loader都可以被處理。
(5)處理 CSS
webpack提供兩個工具處理樣式表,css-loader 和 style-loader,二者處理的任務不同,css-loader使你能夠使用類似@import 和 url(...)的方法實現(xiàn) require()的功能,style-loader將所有的計算后的樣式加入頁面中,二者組合在一起使你能夠把樣式表嵌入webpack打包后的JS文件中。
繼續(xù)上面的例子
//安裝
npm install --save-dev style-loader css-loader
//使用
module.exports = {
...
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader"
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader"
}
]
}
]
}
};
請注意這里對同一個文件引入多個loader的方法。因為weback對loader的執(zhí)行是自下而上的,所以css-loader要放到style-loader的下面。
接下來,在app文件夾里創(chuàng)建一個名字為"main.css"的文件,對一些元素設置樣式
/* main.css */
html {
box-sizing: border-box;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
margin: 0;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
h1, h2, h3, h4, h5, h6, p, ul {
margin: 0;
padding: 0;
}
我們這里例子中用到的webpack只有單一的入口,其它的模塊需要通過 import, require, url等與入口文件建立其關聯(lián),為了讓webpack能找到”main.css“文件,我們把它導入”main.js “中,如下
//main.js
import React from 'react';
import {render} from 'react-dom';
import Greeter from './Greeter';
import './main.css';//使用require導入css文件
render(<Greeter />, document.getElementById('root'));
通常情況下,css會和js打包到同一個文件中,并不會打包為一個單獨的css文件,不過通過合適的配置webpack也可以把css打包為單獨的文件的。
上面的代碼說明webpack是怎么把css當做模塊看待的,咱們繼續(xù)看一個更加真實的css模塊實踐。
CSS module
在過去的一些年里,JavaScript通過一些新的語言特性,更好的工具以及更好的實踐方法(比如說模塊化)發(fā)展得非常迅速。模塊使得開發(fā)者把復雜的代碼轉(zhuǎn)化為小的,干凈的,依賴聲明明確的單元,配合優(yōu)化工具,依賴管理和加載管理可以自動完成。
不過前端的另外一部分,CSS發(fā)展就相對慢一些,大多的樣式表卻依舊巨大且充滿了全局類名,維護和修改都非常困難。
被稱為CSS modules的技術(shù)意在把JS的模塊化思想帶入CSS中來,通過CSS模塊,所有的類名,動畫名默認都只作用于當前模塊。Webpack對CSS模塊化提供了非常好的支持,只需要在CSS loader中進行簡單配置即可,然后就可以直接把CSS的類名傳遞到組件的代碼中,這樣做有效避免了全局污染。具體的代碼如下
module.exports = {
...
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader"
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader",
options: {
modules: true, // 指定啟用css modules
localIdentName: '[name]__[local]--[hash:base64:5]' // 指定css的類名格式
}
}
]
}
]
}
};
我們在app文件夾下創(chuàng)建一個Greeter.css文件來進行一下測試
/* Greeter.css */
.root {
background-color: #eee;
padding: 10px;
border: 3px solid #ccc;
}
導入.root到Greeter.js中
import React, {Component} from 'react';
import config from './config.json';
import styles from './Greeter.css';//導入
class Greeter extends Component{
render() {
return (
<div className={styles.root}> //使用cssModule添加類名的方法
{config.greetText}
</div>
);
}
}
export default Greeter
放心使用把,相同的類名也不會造成不同組件之間的污染。

CSS modules 也是一個很大的主題,有興趣的話可以去其官方文檔了解更多。
CSS預處理器
Sass 和 Less 之類的預處理器是對原生CSS的拓展,它們允許你使用類似于variables, nesting, mixins, inheritance等不存在于CSS中的特性來寫CSS,CSS預處理器可以這些特殊類型的語句轉(zhuǎn)化為瀏覽器可識別的CSS語句,
你現(xiàn)在可能都已經(jīng)熟悉了,在webpack里使用相關loaders進行配置就可以使用了,以下是常用的CSS 處理loaders:
Less LoaderSass LoaderStylus Loader
不過其實也存在一個CSS的處理平臺-PostCSS,它可以幫助你的CSS實現(xiàn)更多的功能,在其官方文檔可了解更多相關知識。
舉例來說如何使用PostCSS,我們使用PostCSS來為CSS代碼自動添加適應不同瀏覽器的CSS前綴。
首先安裝postcss-loader 和 autoprefixer(自動添加前綴的插件)
npm install --save-dev postcss-loader autoprefixer
接下來,在webpack配置文件中添加postcss-loader,在根目錄新建postcss.config.js,并添加如下代碼之后,重新使用npm start打包時,你寫的css會自動根據(jù)Can i use里的數(shù)據(jù)添加不同前綴了。
//webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader"
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader",
options: {
modules: true
}
}, {
loader: "postcss-loader"
}
]
}
]
}
}
// postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
至此,本文已經(jīng)談論了處理JS的Babel和處理CSS的PostCSS的基本用法,它們其實也是兩個單獨的平臺,配合webpack可以很好的發(fā)揮它們的作用。接下來介紹Webpack中另一個非常重要的功能-Plugins
(6)插件(Plugins)
插件(Plugins)是用來拓展Webpack功能的,它們會在整個構(gòu)建過程中生效,執(zhí)行相關的任務。
Loaders和Plugins常常被弄混,但是他們其實是完全不同的東西,可以這么來說,loaders是在打包構(gòu)建過程中用來處理源文件的(JSX,Scss,Less..),一次處理一個,插件并不直接操作單個文件,它直接對整個構(gòu)建過程其作用。
Webpack有很多內(nèi)置插件,同時也有很多第三方插件,可以讓我們完成更加豐富的功能。
使用插件的方法
要使用某個插件,我們需要通過npm安裝它,然后要做的就是在webpack配置中的plugins關鍵字部分添加該插件的一個實例(plugins是一個數(shù)組)繼續(xù)上面的例子,我們添加1個插件,
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const extractPlugin = require('extract-text-webpack-plugin');
module.exports = {
....
plugins: [ //相關插件配置
new HtmlWebpackPlugin({
title: 'test page', //html標題
filename: 'index.html', //打包后的html文件名,默認index.html
template: './index.html', // html的源文件
inject: true, //默認true,意為script標簽位于html文件的 body 底部
cache: true, //默認true,表示內(nèi)容變化的時候生成一個新的文件
chunks: ['index'], //表示編譯時用到的入口文件
date: new Date(),
// excludeChunks: ['index']//表示編譯時排除的入口文件
})
]
}
這就是webpack插件的基礎用法了,下面給大家推薦幾個常用的插件
HtmlWebpackPlugin
這個plugin曝光率很高,他主要有兩個作用
1.為html文件中引入的外部資源如script、link。動態(tài)添加每次compile后的hash,防止引用緩存的外部文件問題
2.可以生成創(chuàng)建html的入口文件,比如單頁面可以生成一個html文件入口,配置N個html-webpack-plugin可以生成N個頁面入口
安裝
npm install --save-dev html-webpack-plugin
配置可參考html-webpack-plugin的配置
再次執(zhí)行npm start你會發(fā)現(xiàn),build文件夾下面生成了index.html,并且頁面body標簽的最后面添加了生成的script.


Hot Module Replacement
Hot Module Replacement(HMR)也是webpack里很有用的一個插件,它允許你在修改組件代碼后,自動刷新實時預覽修改后的效果。
在webpack中實現(xiàn)HMR也很簡單,只需要做兩項配置
- 在webpack配置文件中添加HMR插件;
- 在Webpack Dev Server中添加“hot”參數(shù);
不過配置完這些后,JS模塊其實還是不能自動熱加載的,還需要在你的JS模塊中執(zhí)行一個Webpack提供的API才能實現(xiàn)熱加載,雖然這個API不難使用,但是如果是React模塊,使用我們已經(jīng)熟悉的Babel可以更方便的實現(xiàn)功能熱加載。
整理下我們的思路,具體實現(xiàn)方法如下
-
Babel和webpack是獨立的工具 - 二者可以一起工作
- 二者都可以通過插件拓展功能
- HMR是一個webpack插件,它讓你能瀏覽器中實時觀察模塊修改后的效果,但是如果你想讓它工作,需要對模塊進行額外的配額;
- Babel有一個叫做
react-transform-hrm的插件,可以在不對React模塊進行額外的配置的前提下讓HMR正常工作;
還是繼續(xù)上例來實際看看如何配置
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
output: {
path: __dirname + "/build",
filename: "bundle.js"
},
devtool: 'eval-source-map',
devServer: {
contentBase: "./public",//本地服務器所加載的頁面所在的目錄
historyApiFallback: true,//不跳轉(zhuǎn)
inline: true,
hot: true
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader"
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader",
options: {
modules: true
}
}, {
loader: "postcss-loader"
}
]
}
]
},
plugins: [
new webpack.BannerPlugin('版權(quán)所有,翻版必究'),
new HtmlWebpackPlugin({
template: __dirname + "/app/index.tmpl.html"http://new 一個這個插件的實例,并傳入相關的參數(shù)
}),
new webpack.HotModuleReplacementPlugin()//熱加載插件
],
};
安裝react-transform-hmr
npm install --save-dev babel-plugin-react-transform react-transform-hmr
配置Babel
// .babelrc
{
"presets": ["react", "env"],
"env": {
"development": {
"plugins": [["react-transform", {
"transforms": [{
"transform": "react-transform-hmr",
"imports": ["react"],
"locals": ["module"]
}]
}]]
}
}
}
現(xiàn)在當你使用React時,可以熱加載模塊了,每次保存就能在瀏覽器上看到更新內(nèi)容。
(6)產(chǎn)品階段的構(gòu)建
目前為止,我們已經(jīng)使用webpack構(gòu)建了一個完整的開發(fā)環(huán)境。但是在產(chǎn)品階段,可能還需要對打包的文件進行額外的處理,比如說優(yōu)化,壓縮,緩存以及分離CSS和JS。
對于復雜的項目來說,需要復雜的配置,這時候分解配置文件為多個小的文件可以使得事情井井有條,以上面的例子來說,我們創(chuàng)建一個webpack.production.config.js的文件,在里面加上基本的配置,它和原始的webpack.config.js很像,如下
// webpack.production.config.js
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: __dirname + "/app/main.js", //已多次提及的唯一入口文件
output: {
path: __dirname + "/build",
filename: "bundle.js"
},
devtool: 'null', //注意修改了這里,這能大大壓縮我們的打包代碼
devServer: {
contentBase: "./public", //本地服務器所加載的頁面所在的目錄
historyApiFallback: true, //不跳轉(zhuǎn)
inline: true,
hot: true
},
module: {
rules: [{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader"
},
exclude: /node_modules/
}, {
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [{
loader: "css-loader",
options: {
modules: true
}
}, {
loader: "postcss-loader"
}],
})
}]
},
plugins: [
new webpack.BannerPlugin('版權(quán)所有,翻版必究'),
new HtmlWebpackPlugin({
template: __dirname + "/app/index.tmpl.html" //new 一個這個插件的實例,并傳入相關的參數(shù)
}),
new webpack.HotModuleReplacementPlugin() //熱加載插件
],
};
//package.json
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack",
"server": "webpack-dev-server --open",
"build": "NODE_ENV=production webpack --config ./webpack.production.config.js --progress"
},
"author": "",
"license": "ISC",
"devDependencies": {
...
},
"dependencies": {
"react": "^15.6.1",
"react-dom": "^15.6.1"
}
}
注意:如果是window電腦,
build需要配置為"build": "set NODE_ENV=production && webpack --config ./webpack.production.config.js --progress".
好的,至此一個完整的webpack項目已經(jīng)構(gòu)建完畢。接下來總結(jié)一些項目中常用的優(yōu)化和技巧