原文 https://segmentfault.com/a/1190000005089993
Webpack是目前基于React和Redux開發(fā)的應(yīng)用的主要打包工具,。我想使用Angular 2或其他框架開發(fā)的應(yīng)用也有很多在使用Webpack。
當(dāng)我第一次看到Webpack的配置文件時,,它看起來非常的陌生,,我非常的疑惑。經(jīng)過一段時間的嘗試之后我認(rèn)為這是因為Webpack只是使用了比較特別的語法和引入了新的原理,,因此會讓使用者感到疑惑,。這些也是導(dǎo)致Webpack不被人熟悉的原因。
因為剛開始使用Webpack很讓人疑惑,,我覺得有必要寫幾篇介紹Webpack的功能和特性的文章以幫助初學(xué)者快速理解,。此文是最開始的一篇。
Webpack的核心原理
Webpack的兩個最核心的原理分別是:
1. 一切皆模塊
正如js文件可以是一個“模塊(module)”一樣,,其他的(如css,、image或html)文件也可視作模 塊。因此,,你可以require('myJSfile.js') 亦可以require('myCSSfile.css') ,。這意味著我們可以將事物(業(yè)務(wù))分割成更小的易于管理的片段,從而達(dá)到重復(fù)利用等的目的,。
2. 按需加載
傳統(tǒng)的模塊打包工具(module bundlers)最終將所有的模塊編譯生成一個龐大的bundle.js 文件,。但是在真實的app里邊,“bundle.js”文件可能有10M到15M之大可能會導(dǎo)致應(yīng)用一直處于加載中狀態(tài),。因此Webpack使用許多特性來分割代碼然后生成多個“bundle”文件,,而且異步加載部分代碼以實現(xiàn)按需加載。
好了,,下面來看看那些令人困惑的部分吧,。
1. 開發(fā)模式和生產(chǎn)模式
首先要知道的是Webpack有許許多多的特性,一些是”開發(fā)模式“下才有的,,一些是”生產(chǎn)模式“下才有的,,還有一些是兩種模式下都有的。
通常使用到Webpack如此多特性的項目都會有兩個比較大的Webpack配置文件
為了生成bundles文件你可能在package.json 文件加入如下的scripts項:
"scripts": {
// 運行npm run build 來編譯生成生產(chǎn)模式下的bundles
"build": "webpack --config webpack.config.prod.js",
// 運行npm run dev來生成開發(fā)模式下的bundles以及啟動本地server
"dev": "webpack-dev-server"
}
2. webpack CLI 和webpack-dev-server
值得注意的是,,Webpack作為模塊打包工具,,提供兩種用戶交互接口:
-
Webpack CLI tool:默認(rèn)的交互方式(已隨Webpack本身安裝到本地)
-
webpack-dev-server:一個Node.js服務(wù)器(需要開發(fā)者從npm自行安裝)
Webpack CLI(有利于生產(chǎn)模式下打包)
這種方式可以從命令行獲取參數(shù)也可以從配置文件(默認(rèn)叫webpack.config.js)獲取,將獲取到的參數(shù)傳入Webpack來打包,。
當(dāng)然你也可以從命令行(CLI)開始學(xué)習(xí)Webpack,,以后你可能主要在生產(chǎn)模式下使用到它。
用法:
方式1:
// 全局模式安裝webpack
npm install webpack --g
// 在終端輸入
$ webpack // <--使用webpack.config.js生成bundle
方式 2 :
// 費全局模式安裝webpack然后添加到package.json依賴?yán)镞?/span>
npm install webpack --save
// 添加build命令到package.json的scripts配置項
"scripts": {
"build": "webpack --config webpack.config.prod.js -p",
...
}
// 用法:
"npm run build"
webpack-dev-server(有利于在開發(fā)模式下編譯)
這是一個基于Express.js框架開發(fā)的web server,默認(rèn)監(jiān)聽8080端口,。server內(nèi)部調(diào)用Webpack,,這樣做的好處是提供了額外的功能如熱更新“Live Reload”以及熱替換“Hot Module Replacement”(即HMR),。
用法:
方式 1:
// 全局安裝
npm install webpack-dev-server --save
// 終端輸入
$ webpack-dev-server --inline --hot
用法 2:
// 添加到package.json scripts
"scripts": {
"start": "webpack-dev-server --inline --hot",
...
}
// 運行:
$ npm start
// 瀏覽器預(yù)覽:
http://localhost:8080
Webpack VS Webpack-dev-server選項
注意像inline 和hot 這些選項是Webpack-dev-server特有的,,而另外的如hide-modules 則是CLI模式特有的選項。
webpack-dev-server CLI選項和配置項
另外值得注意的是你可以通過以下兩種方式向webpack-dev-server傳入?yún)?shù):
-
通過webpack.config.js文件的"devServer"對象
-
通過CLI選項
// 通過CLI傳參
webpack-dev-server --hot --inline
// 通過webpack.config.js傳參
devServer: {
inline: true,
hot:true
}
我發(fā)現(xiàn)有時devServer配置項(hot: true 和inline: true)不生效,,我更偏向使用如下的方式向CLI傳遞參數(shù):
// package.json
{
"scripts": "webpack-dev-server --hot --inline"
}
注意:確定你沒有同時傳入hot:true 和-hot
webpack-dev-server的“hot” 和 “inline”選項
“inline”選項會為入口頁面添加“熱加載”功能,,“hot”選項則開啟“熱替換(Hot Module Reloading)”,即嘗試重新加載組件改變的部分(而不是重新加載整個頁面),。如果兩個參數(shù)都傳入,,當(dāng)資源改變時,webpack-dev-server將會先嘗試HRM(即熱替換),,如果失敗則重新加載整個入口頁面,。
// 當(dāng)資源發(fā)生改變,以下三種方式都會生成新的bundle,,但是又有區(qū)別:
// 1. 不會刷新瀏覽器
$ webpack-dev-server
//2. 刷新瀏覽器
$ webpack-dev-server --inline
//3. 重新加載改變的部分,,HRM失敗則刷新頁面
$ webpack-dev-server --inline --hot
3. “entry”:值分別是字符串、數(shù)組和對象的情況
Enter配置項告訴Webpack應(yīng)用的根模塊或起始點在哪里,,它的值可以是字符串,、數(shù)組或?qū)ο蟆_@看起來可能令人困惑,,因為不同類型的值有著不同的目的,。
像絕大多數(shù)app一樣,倘若你的應(yīng)用只有一個單一的入口,,enter項的值你可以使用任意類型,,最終輸出的結(jié)果都是一樣的。
enter:數(shù)組類型
但是,,如果你想添加多個彼此不互相依賴的文件,,你可以使用數(shù)組格式的值。
例如,,你可能在html文件里引用了“googleAnalytics.js”文件,,可以告訴Webpack將其加到bundle.js的最后。
enter:對象
現(xiàn)在,,假設(shè)你的應(yīng)用是多頁面的(multi-page application)而不是SPA,,有多個html文件(index.html和profile.html)。然后你通過一個對象告訴Webpack為每一個html生成一個bundle文件,。
以下的配置將會生成兩個js文件:indexEntry.js和profileEntry.js分別會在index.html和profile.html中被引用,。
用法:
//profile.html
<script src=”dist/profileEntry.js”></script>
//index.html
<script src=”dist/indexEntry.js”></script>
注意:文件名取自“entry”對象的鍵名。
enter:混合類型
你也可以在enter對象里使用數(shù)組類型,例如下面的配置將會生成3個文件:vender.js(包含三個文件),,index.js和profile.js文件,。
4. output:“path”項和“publicPath”項
output項告訴webpack怎樣存儲輸出結(jié)果以及存儲到哪里。output的兩個配置項“path”和“publicPath”可能會造成困惑,。
“path”僅僅告訴Webpack結(jié)果存儲在哪里,,然而“publicPath”項則被許多Webpack的插件用于在生產(chǎn)模式下更新內(nèi)嵌到css、html文件里的url值,。
例如,,在localhost(譯者注:即本地開發(fā)模式)里的css文件中邊你可能用“./test.png”這樣的url來加載圖片,但是在生產(chǎn)模式下“test.png”文件可能會定位到CDN上并且你的Node.js服務(wù)器可能是運行在HeroKu上邊的,。這就意味著在生產(chǎn)環(huán)境你必須手動更新所有文件里的url為CDN的路徑,。
然而你也可以使用Webpack的“publicPath”選項和一些插件來在生產(chǎn)模式下編譯輸出文件時自動更新這些url。
// 開發(fā)環(huán)境:Server和圖片都是在localhost(域名)下
.image {
background-image: url('./test.png');
}
// 生產(chǎn)環(huán)境:Server部署下HeroKu但是圖片在CDN上
.image {
background-image: url('https://someCDN/test.png');
}
5. 模塊加載和鏈?zhǔn)侥K加載
模塊加載器是可自由添加的Node模塊,,用于將不同類型的文件“l(fā)oad”或“import”并轉(zhuǎn)換成瀏覽器可以識別的類型,,如js、Stylesheet等,。更高級的模塊加載器甚至可以支持使用ES6里邊的“require”或“import”引入模塊,。
例如,你可以使用babel-loader 來將使用ES6語法寫成的文件轉(zhuǎn)換成ES5:
module: {
loaders: [{
test: /\.js$/, // 匹配.js文件,,如果通過則使用下面的loader
exclude: /node_modules/, // 排除node_modules文件夾
loader: 'babel' // 使用babel(babel-loader的簡寫)作為loader
}]
鏈?zhǔn)剑ü艿朗剑┑募虞d器(從右往左執(zhí)行)
多個loader可以用在同一個文件上并且被鏈?zhǔn)秸{(diào)用,。鏈?zhǔn)秸{(diào)用時從右到左執(zhí)行且loader之間用“!”來分割。
例如,,假設(shè)我們有一個名為“myCssFile.css”的css文件,,然后我們想將它的內(nèi)容使用style標(biāo)簽內(nèi)聯(lián)到最終輸出的html里邊。我們可以使用css-loader和style-loader兩個loader來達(dá)到目的,。
module: {
loaders: [{
test: /\.css$/,
loader: 'style!css' //(short for style-loader!css-loader)
}]
這里展示它是如何工作的:
-
Webpack在模塊顳部搜索在css的依賴項,,即Webpack檢查js文件是否有“require('myCssFile.css')”的引用,如果它發(fā)現(xiàn)有css的依賴,,Webpack將css文件交給“css-loader”去處理
-
css-loader加載所有的css文件以及css自身的依賴(如,,@import 其他css)到JSON對象里,Webpack然后將處理結(jié)果傳給“style-loader”
-
style-loader接受JSON值然后添加一個style標(biāo)簽并將其內(nèi)嵌到html文件里
6. loader自身可以配置
模塊加載器(loader)自身可以根據(jù)傳入不同的參數(shù)進(jìn)行配置,。
在下面的例子中,,我們可以配置url-loader來將小于1024字節(jié)的圖片使用DataUrl替換而大于1024字節(jié)的圖片使用url,我們可以用如下兩種方式通過傳入“l(fā)imit“參數(shù)來實現(xiàn)這一目的:
7. .babelrc 文件
babal-loader使用”presets“配置項來標(biāo)識如何將ES6語法轉(zhuǎn)成ES5以及如何轉(zhuǎn)換React的JSX成js文件,。我們可以用如下的方式使用”query“參數(shù)傳入配置:
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel',
query: {
presets: ['react', 'es2015']
}
}
]
}
然而在很多項目里babal的配置可能比較大,,因此你可以把babal-loader的配置項單獨保存在一個名為”.babelrc“的文件中,在執(zhí)行時babal-loader將會自動加載.babelrc文件,。
所以在很多例子里,,你可能會看到:
//webpack.config.js
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel'
}
]
}
//.bablerc
{
presets: ['react', 'es2015']
}
8. 插件
插件一般都是用于輸出bundle的node模塊,。
例如,uglifyJSPlugin獲取bundle.js然后壓縮和混淆內(nèi)容以減小文件體積,。
類似的extract-text-webpack-plugin內(nèi)部使用css-loader和style-loader來收集所有的css到一個地方最終將結(jié)果提取結(jié)果到一個獨立的”styles.css“文件,,并且在html里邊引用style.css文件。
//webpack.config.js
// 獲取所有的.css文件,,合并它們的內(nèi)容然后提取css內(nèi)容到一個獨立的”styles.css“里
var ETP = require("extract-text-webpack-plugin");
module: {
loaders: [
{test: /\.css$/, loader:ETP.extract("style-loader","css-loader") }
]
},
plugins: [
new ExtractTextPlugin("styles.css") //Extract to styles.css file
]
}
注意:如果你只是想把css使用style標(biāo)簽內(nèi)聯(lián)到html里,,你不必使用extract-text-webpack-plugin,僅僅使用css loader和style loader即可:
module: {
loaders: [{
test: /\.css$/,
loader: 'style!css' // (short for style-loader!css-loader)
}]
9. 加載器(loader)和插件
你可能已經(jīng)意識到了,,Loader處理單獨的文件級別并且通常作用于包生成之前或生成的過程中,。
而插件則是處理包(bundle)或者chunk級別,,且通常是bundle生成的最后階段,。一些插件如commonschunkplugin甚至更直接修改bundle的生成方式。
10. 處理文件的擴(kuò)展名
很多Webpack的配置文件都有一個resolve 屬性,,然后就像下面代碼所示有一個空字符串的值,。空字符串在此是為了resolve一些在import文件時不帶文件擴(kuò)展名的表達(dá)式,,如require('./myJSFile') 或者import
myJSFile from './myJSFile' (譯者注:實際就是自動添加后綴,,默認(rèn)是當(dāng)成js文件來查找路徑)
{
resolve: {
extensions: ['', '.js', '.jsx']
}
}
就這么多。
|