細說前端自動化打包工具--webpack

記得2004年的時候,互聯網開發就是做網頁,那時也沒有前端和后端的區分,有時一個網站就是一些純靜態的html,通過鏈接組織在一起。用過Dreamweaver的都知道,做網頁就像用word編輯文檔一樣。一個html頁面,夾雜著css,javascript是再常見不過的事了。

隨著前端的不斷發展,特別是單頁應用的興起,這種所見即所得的IDE工具,就漸漸地退出了前端的主流。一個應用,通常只有一個靜態頁面(index.html),甚至這個頁面的body只有一個或少數幾個div組成。這時有大量的css和javascript代碼需要編寫。如何組織他們,就是現在前端所面臨和要解決的問題。

一些很好的前端框架(像angularjs,React,Vue)可以幫我們如何合理的組織代碼,保持代碼的可維護性和擴展性。這在開發階段是很有用的,可是要把應用發布到線上的時候,需要把代碼進行合并壓縮,以減小代碼體積,和文件數量,人為的對代碼進行丑化。于是就有了grunt,gulp,webpack等前端工程化打包工具。

如何使用webpack

使用webpack之前,需要安裝node.js,然后通過npm 安裝webpack.具體的安裝過程移步官網。本著從入門到精通的順序,先來看一個最簡單的應用。

場景一:

在demo1目錄下,有兩個文件,app.js,cats.js,需要把它們合并成一個bundle.js文件.demo01

cats.js:

1

2varcats = ['dave','henry','martha'];

module.exports = cats;

app.js:

1

2cats = require('./cats.js');

console.log(cats);

如果是全局安裝的webpack,那么直接在命令行窗口中輸入webpack app.js bundle.js就可以了:

要得到壓縮版的也很容易,在后面追加一個-p參數:

bundle.js由原來的1.58kb縮小到304b.

如果每改一次代碼就要輸一次命令,就太沒意思了,這時就需要追加一個" -w " 參數 (watch) 監視代碼的改動。

1

webpack app.js bundle.js -p -w

注意:如果是clone的代碼,試驗時,請移除目錄下的webpack.config.js文件。

雖然簡單,但是這里有一個重要的概念要說一下:官方文檔中把app.js這個文件稱為“entry point”,即“入口”。代表著webpack從哪開始。webpack會順著這個入口文件自動尋找里邊所依賴的文件,比如demo01中的cats.js會自動被載入。而bundle.js 是我們指定打包之后輸出的文件名,默認的輸出目錄就是命令運行時所在的目錄,也可以在指定輸出目錄,如./dist/bundle.js ,這樣webpack就會自動創建dist目錄,然后把bundle.js寫在dist目錄下。由于app.js這個入口文件是純js,webpack直接就可以支持,如果是其它類型的文件,比如css,就需要用到"loader",即“加載器”,后面會有詳細介紹。

除了直接用webpack命令指定入口文件打包之外,還可以通過配置webpack.config.js文件實現同樣的功能:

webpack.config.js :

1

2

3

4

5

6

7

8

9//最簡單的webpack配置

module.exports = {

entry:'./app.js',//入口文件地址

output: {

filename:'bundle.js',//打包后的文件名

}

};

通過配置webpack.config.js之后,在命令行下只需要簡單的輸入webpack就可以了。如果是這么簡單的應用,顯然體現不出webpack.config.js存在的價值。通常我們的網站都會有多個頁面,比如index,home,about等等,每個頁面都是一個獨立的入口,于是就產生了多入口的情況,下面就看看多入口的情況下,webpack怎么輸出不同的打包文件。demo02

1

2

3

4

5

6

7

8

9

10

11//webpack.config.js

//多入口示例

module.exports = {

entry: {

bundle1:'./main1.js',//入口1

bundle2:'./main2.js'//入口2

},

output: {

filename:'[name].js'// [name]是一個變量,會自動替換成entry的key

}

};

和demo01相比,這次的入口(entry)是一個對象, 用鍵值對的形式指定了多個入口文件,輸出的文件名用了變量表示。事實上,入口文件的值還可以是數組。如:

1

2

3

4

5

6

7

8

9

10

11//webpack.config.js

//多入口示例

module.exports = {

entry: {

bundle1: ['./main1.js'],//入口1

bundle2: ['./main2.js']//入口2

},

output: {

filename:'[name].js'// [name]是一個變量,會自動替換成entry的key

}

};

這種用法,對于入口文件需要指定多種類型的文件時比較有用。比如['./main1.js','./main1.css'],后面用到再細講。小結一下:對于entry一共展示了三種形式:

1. entry:'app.js' ?直接寫入口文件

2. entry:{bundle:'./main1.js'} 對象形式

3. entry:{bundle:['./main1.js']} 對象中的值用數組表示

接下來的demo03將展示webpack在jsx, es6 中的用法。這一節內容會稍稍有點多。首先是package.json文件,它不是webpack的組成部分,但是常和webpack項目出雙入對,先看一下它的大概模樣:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24{

"name":"demo01",

"version":"1.0.0",

"description":"sample to use webpack",

"main":"index.html",

"scripts": {

"test":"echo \"Error: no test specified\" && exit 1",

"start":"webpack"

},

"keywords": [

"webpack"

],

"author":"frog",

"license":"MIT",
? ”dependencies":{},

"devDependencies": {

"babel-core": "^6.20.0",

"babel-loader": "^6.2.10",

"babel-preset-es2015": "^6.18.0",

"babel-preset-react": "^6.16.0",

"react": "^15.4.1",

"react-dom": "^15.4.1",

"webpack": "^1.13.0"

}

}

關于這個文件的更多介紹,請移步官方內容這里我只重點介紹一下以下內個內容:

1. scripts 命令行腳本通過key:value的方式描述。key是腳本名,value是腳本執行的內容,通過在命令行中輸入npm run 腳本名 就可以執行。這一塊的內容是實際開發中很實用的,這里不詳情展開,參考地址

常見的腳本名有:npm run start , npm run test 。 內置的腳本名(比如start),可以省略run。

2. devDependencies 開發依賴,相應的還有一個dependencies(可以理解為生產環境依賴)

通過npm install 包名 --save-dev (保存到devDependencies),或 --save 保存到(dependencies)

package.json是用來配合包的管理和發布用的,如果你不想發布這個項目,似乎以上內容對項目開發并沒有什么好處,但是作為團隊協作,它可以方便自己和同事快速搭建項目,管理項目中用到的第三方包。

下面回到webpack.config.js這個文件來。由于jsx是react專用的語法,超出了js的語法范圍,要想加載jsx文件,需要借助一個loader(加載器), 不同類型的文件有不同的加載器,比如jsx,es6要用到babel-loader

加載css要用到css-loader,加載html要用到html-loader等等. 下面是具體的用法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18module.exports = {

entry:'./main.jsx',

output: {

filename:'bundle.js'

},

module: {

loaders:[

{

test: /\.js[x]?$/,

exclude: /node_modules/,

loader:'babel-loader',

query:{

presets:['react','es2015']

}

},

]

}

};

所有的loader都放在module下面的loaders里邊.通常有以下內容:

1. ?test:是對該類文件的正則表達式,用來判斷采用這個loader的條件。

2. ?exclude是排除的目錄,比如node_modules中的文件,通常都是編譯好的js,可以直接加載,因此為了優化打包速度,可以排除。作為優化手段它不是必須的。

3. loader: 加載器的名稱,每一個加載器都有屬于它自己的用法,具體要參考官方說明。

4. query: 傳遞給加載器的附加參數或配置信息,有些也可以通過在根目錄下生成特殊的文件來單獨配置,比如.babelrc

這里配置好,還不能用,需要安裝對應的加載器到項目中來,安裝方式很簡單,通過命令行,輸入npm install 加載器的名稱 --save-dev 或 --save

加--save或--save-dev的目的是為了把該插件記錄到package.json中去,方便通過npm install的時候自動安裝。

通過npm3.0+版本安裝的時候,它不會自動安裝依賴,需要手動去安裝,比如安裝這個babel-loader的時候,它提示要安裝babel-core和webpack,依次安裝即可。demo03比較激進,直接用了jsx和es6的語法,所以要安裝的插件比較多,但這也是實際開發中經常用到的。

1

2

3

4

5

6

7

8

9"devDependencies": {

"babel-core":"^6.20.0",

"babel-loader":"^6.2.10",

"babel-preset-es2015":"^6.18.0",//es6轉普通js用

"babel-preset-react":"^6.16.0",//解析jsx用

"react":"^15.4.1",

"react-dom":"^15.4.1",

"webpack":"^1.13.0"

}

由于我們在package.json的script中加了一個start腳本,所以這次,我不打算老套的用法,這次來點新鮮的嘗試。直接運行npm start,看看是否大力出奇跡。

這和直接運行webpack是一樣的結果,但是顯得更高大上一些。如果你一半會不覺用不到react或es6這么新潮的東西,那就請忽略前面的內容,下面看一點更加簡單更加常用的加載器

demo04css-loader樣式加載器

1

2

3

4

5

6

7

8

9

10

11module.exports = {

entry:'./main.js',

output: {

filename:'bundle.js'

},

module: {

loaders:[

{ test: /\.css$/, loader:'style-loader!css-loader'},

]

}

};

這里有兩個要注意的地方:

1。 對于有多個加載器串聯的情況,webpack,它是從右向左依賴加載的,也就是說先用css-loader,再用style-loader.

2. ?為什么會有一個style-loader, 因為webpack默認是把css文件插在html文件內,通過style標簽加載樣式的。所以需要用style-loader這個加載器。

如果想要把css用文件的形式link到html中,也是可以的,后面會講到。

由于我們用了css加載器,所以入口文件其實也可以直接寫成:entry:'./app.css'; 效果是一樣的。這就體現了,入口文件,不一定要是js格式,只要有對應的加載器,就可以直接在入口中使用,甚至多種類型混合使用,比如['./app.js','app.css'],都是可以的。

樣式中,常常會用到圖片,比如background:url('../images/logo.jpg'); 如果沒有指定加載器,就會報錯(you may need an appropriate loader to handle this file type),這時,就需要用到圖片加載器了,不要以為,只有在入口中用到的文件才要加載器,只要是在webpack工作期間加載到的文件,只要不是js文件,就需要指定加載器,并在webpack.config.js中正確配置。

1

2

3

4

5

6

7

8

9

10

11module.exports = {

entry:'./main.js',

output: {

filename:'bundle.js'

},

module: {

loaders:[

{ test: /\.(png|jpg)$/, loader:'url-loader?limit=8192&name=[name][ext]'}

]

}

};

圖片加載-demo05示例中用的是url-loader,并不是期望的image-loader, 原因是url-loader就可以加載圖片字體這些文件了,因此不需要重復造輪子,事實上,url-loader還不是最終的加載器,它只不過是對file-loader的進一步封裝。通過在url-loader后面加?來掛載更多的配置參數,可以實現定制化的需求,比如對于圖片小于8192字節的圖片,采用base64的方式,直接輸出在css中,可以減少http請求。對于大這個限制的圖片,通過name指定輸出的文件名,在前面指定路徑也是可以的。比如/images/[name][ext] ,這里的[name]和[ext]都是變量的表示,前面有講過,用在這里,表示用原來輸入時的文件名和擴展名。需要注意的是,這個路徑是參考默認的輸出路徑的來的。如果要指定輸出路徑怎么處理呢?

請參考以下方法:

1. 通過在webpack.config.js 中指定,output:{path:'./dist',...}

1

2

3

4

5

6

7module.exports = {

entry:'./src/app.js',

output: {

path:'./dist',//新的輸出路徑

filename:'app.bundle.js'

}

};

'./'代表項目的當前目錄,通常指根目錄,這是一種相對路徑的表示,也可以用絕對路徑,通過path.resolve(__dirname,'./')來指定,這時,webpack所生成的js,css文件都會變成./dist目錄下,而對于本例中的圖片,則還是在./目錄下,

1


并沒有把圖片生成在dist目錄下,試試 loader:'url?publicPath=./dist/'

1

2

3

4

5

6

7

8

9

10

11

12

13module.exports = {

entry: ['./main.js','./icon.css'],

output: {

path:'./dist',

filename:'bundle.js'

},

module: {

loaders:[

{ test: /\.(png|jpg)$/, loader:'url-loader?limit=8192&publicPath=./dist/'},

{ test: /\.css$/, loader:'style-loader!css-loader'},

]

}

};

通過指定這個publicPath實現了圖片生成到指定的目錄。同樣的,通過在output中指定這個值也是同樣的作用。

1

2

3

4

5output: {

path:'./dist',

publicPath:'./dist/',//在這里指定同樣生效

filename:'bundle.js'

},

這個publicPath原本是用來配置虛擬目錄用的,也就是通過http方式訪問時的路徑,或者通過webpack HMR方式加載時的輸出目錄。在這里只能算是一種hack用法。說到output,就要提一下文件緩存[hash]的用法:

1

2

3

4

5output: {

path:'./dist',

publicPath:'./dist/',

filename:'bundle_[hash:8].js'//通過:8截取has值的前8位

},

這個[hash]作用很少被提到,在實際開發中,是很常見的功能,原樣輸出的hash我覺得太長,可以通過[hash:加數字]的方式進行截取,很方便。

對于webpack.config.js ,前面已經介紹了entry,output,module,下面以代碼丑化為例,說說plugins,webpack的插件的用法:demo07

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15varwebpack = require('webpack');

varuglifyJsPlugin = webpack.optimize.UglifyJsPlugin;

module.exports = {

entry:'./main.js',

output: {

filename:'bundle.js'

},

plugins: [

newuglifyJsPlugin({

compress: {

warnings:false

}

})

]

}

plugins:的值是一個數組,所有插件都通過npm install進行安裝,然后在plugins數組中添加對應的插件配置。有三個插件需要提一下:

1.?HtmlwebpackPlugin 這個插件可以把生成的css ,js 插入到html頁中的head,body中,這對于加了hash值的輸出很有用。這個功能也可以用gulp的insject插件做。不過既然用webpack了,就暫時忘了gulp吧。在具有相似功能的不同的工具之間切換,并不是一個好主意。不過html這款插件有一個小小的問題,它對html中的img不會像css中那樣解析。造成dist目錄下的html文件,img下的src報錯。解決辦法是添加html-withimg-loader這個插件。

1

2

3

4{

test:/.html$/,

loader:'html-withimg-loader?min=false'

},

2. CommonsChunkPlugin 提取公共代碼,這個不需要安裝,webpack集成有。

1

2

3

4

5newwebpack.optimize.CommonsChunkPlugin({

name:'vendor',

filename:'js/vendor.js',

chunks:['chunk1','chunk2','chunk3']//不寫為所有的chunk,

}),

chunk (塊), webpack中另一個非常重要的概念,和entry對應。有三種情況:

2.1 ?如果entry通過字符串的方式指定的入口文件,那么chunk就是指入口文件,比如entry: './app.js'; 那么可以肯定chunk和'./app.js'一一對應。

2.2 如果是entry:['./app1.js','app2.js']那么chunk就是這兩個文件之和。

* ?以上chunk的[name]就是默認的"main".

2.3 ?如果是下面這種形式:

1

2

3

4

5entry:{

index:'./index.js',

error:'./error.js',

vendor:['react','redux']

}

那么就會產生多個chunk,[name]分別和index,error,vendor對應。

3.?ExtractTextPlugin 這個插件就是開頭提到的,從html中分離css的插件。npm install?extract-text-webpack-plugin --save-dev

1

2

3plugins: [

newExtractTextPlugin("[name].css"),

]

需要注意的是,如果在[name].css前面加了子路徑,如css/[name].css ?那么就要小心樣式中的圖片路徑出錯,特別是在沒有指定publicPah的情況下。background:url(這個地方的圖片默認是和chunk的輸出路徑同級的,如果指定了publicPath,則以publicPath代替,不存在這個問題),但是由于我們人為的指定了打包后的樣式放在css/目錄下,而圖片默認還在原來的目錄,這就導致css中引用的圖片路徑失效。看下面的例子:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18varExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {

entry: ['./main.js','./icon.css'],

output: {

path:'./dist',

//publicPath:'./dist/',

filename:'bundle_[hash:8].js'

},

module: {

loaders:[

{ test: /\.(png|jpg)$/, loader:'url-loader?limit=8192'},

{ test: /\.css$/, loader: ExtractTextPlugin.extract('style','css') },

]

},

plugins: [

newExtractTextPlugin("css/[name].css"),

]

};

從圖上看到,添了css子目錄之后,樣式中的圖片,還是原來的路徑,而圖片并不存在css目錄下。

解決辦法,要么不加子目錄,保持樣式文件和圖片文件始終在同一層級,要么添加publicPath,相當于使用絕對路徑。由于這個是在webpack.config.js中配置的,要更換也很容易。

看起來一切都很美好,可是當我們的html中也用了img標簽的時候,問題就來了,還記得html-withimg-loader這個插件嗎?它際實上也是調用的url-loader,所以,它最終的圖片輸出路徑也樣受publicPath的影響。考慮一下這樣的目錄結構:

樣式文件是位于css子目錄,而html則是和圖片保持同級的。樣式中的圖片需要指定為"../",而html中的圖片需要指定成"./",這在同一個publicPath中,顯示是沖突的。這時就需要權衡一下,要么所有的文件都堆在根目錄下,要么html中的圖片用別的插件進行處理。總之,不能讓這種相沖突的情況發生。

最后再簡單說一下webpack.config.js 中的 resolve;

1

2

3resolve: {

extensions: ['','.js','.vue','.json']

},

通常我們都知道通過配置這個屬性下的extensions,可以省略擴展名,似乎沒有什么可以介紹的,直到有一次我在項目中通過npm install vue --save 安裝了vue ,然后我在代碼中用import Vue from 'vue' 導入了vue。到這一步都是正常的,可是當我打算進一步使用vue的時候,代碼就報錯了,然后去查官方文檔(出錯的時候不要驚慌,大多數情況都會在官方找到解決的辦法,比如github中的issue等)。官方介紹如下

“ There are two builds available, the standalone build and the runtime-only build. The difference being that the former includes the?template compiler?and the latter does not.By default, the NPM package exports the?runtime-only?build. To use the standalone build, add the following alias to your Webpack config:”

1

2

3

4

5resolve: {

alias: {

'vue$':'vue/dist/vue.common.js'

}

},

大意是說:vue有兩種構建方式,獨立構建和運行構建。它們的區別在于前者包含模板編譯器而后者不包含。而默認 NPM 包導出的是?運行時?構建。為了使用獨立構建,要在 webpack 配置中添加下面的別名:添加alias:{'vue$':'vue/dist/vue.common.js'}. 這個別名,同樣適用于jquery,zepto這些庫。

對于vue來說,如果不用別名,也可以從node_modules/vue/dist/復制vue.common.js到開發目錄下,比如./src/vue下面,然后像普通的js文件一樣引用, import vue from './src/js/vue.common.js' 這也是可以的。只是和使用別名相比,顯的很lower

小結

使用webpack,分為兩種方式,一種是CLI(命令行方式),一種是API方式(new webpack(config)),兩種方式都可以通過webpack.config.js 來配置。所以學習webpack,就是掌握webpack.config.js配置的過程。我相繼介紹了entry,output,module,plugins,resolve,本來還想寫寫webpack的熱加載HMR, 以及webpack-dev-server,browser-sync結合webpack的用法,感覺要寫的內容有點多,這些內容也是實際開發中非常有用的技術。越寫到后面,越覺得難于下筆。想起一句話,要想給別人一滴水,自己至少要有一桶水。前端工程自動化方案更新很快,webpack還沒有來的極普及,webpack2,rollup等又出來了. 學習這些工具,是為了減輕重復勞動,提高效率。選擇適合自己的方案,而不是在追尋技術的路上迷失了方向。

轉載出處? 點擊http://www.cnblogs.com/afrog/p/6247665.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 一.什么是 WebpackWebpack 是一個模塊打包器。它將根據模塊的依賴關系進行靜態分析,然后將這些模塊按照...
    nightZing閱讀 1,013評論 1 13
  • GitChat技術雜談 前言 本文較長,為了節省你的閱讀時間,在文前列寫作思路如下: 什么是 webpack,它要...
    蕭玄辭閱讀 12,710評論 7 110
  • webpack 介紹 webpack 是什么 為什么引入新的打包工具 webpack 核心思想 webpack 安...
    yxsGert閱讀 6,501評論 2 71
  • 無意中看到zhangwnag大佬分享的webpack教程感覺受益匪淺,特此分享以備自己日后查看,也希望更多的人看到...
    小小字符閱讀 8,227評論 7 35
  • 每天最重要的兩個小時這本書,我覺得寫得非常好,和你充滿電了,那本書有很多相似之處。這就證明了,大道至簡。 ...
    小粒_9beb閱讀 145評論 0 0