簡介:講解webpack如何實現模塊編程,以及為什麼我們需要打包,壓縮js文件,實現sass/less編譯以及JSX等模版的轉換(版本控制),然後用實例說明如何用webpack實現SPA和MPA(單頁面應用程式和多頁面應用程式,包含詳細圖解)(在以後的文章中還將介紹如何實現js壓縮和sass/les... ...
從開發文件到生產文件
有一天我突然意識到一個問題,在使用react框架搭建應用時,我使用到了sass/less,JSX模版以及ES6的語法在編輯器下進行開發,使用這些寫法是可以提高開發的效率。可是瀏覽器它本身是並不能夠“理解”這些語法的呀。就像下麵這張圖:

webpack和gulp的共同作用及兩者的區別: webpack和gulp本質上並不是同一類型工具,但它們都能完成以下任務:

webpack:一個模塊化工具(a module bundle) gulp:一個任務運行器(a Task Runner) 在用react/vue/angular搭建單頁面應用時,我們可以用webpack代替gulp的工作,方便而快捷。兩者具體的區別,在這裡不多贅述,大家自行查閱資料。下麵我主要介紹一下webpack的使用 除了利用webpack實現開發代碼 --> 生產代碼的轉換,我們為什麼要用它做其他一些工作,比如文件打包(文件合併),JS/css壓縮呢? 為什麼要用webpack實現文件打包? 為什麼我們要做文件打包的工作,這樣做有什麼意義嗎?這要從我們曾經喜聞樂見的<script>標簽說起。對於許多初學者來說,在每個HTML頁面里寫入大量的<script>標簽是再正常不過的事情。然後在部署上線時就會生成這樣的HTML文件
<html> <body> <script src = 'http:// ... a.js' /> <script src = 'http:// ... b.js' /> <script src = 'http:// ... c.js' /> <script src = 'http:// ... d.js' /> </body> </html>咋看一下似乎也沒什麼不對,但是仔細想想,每個頁面都發起如此多的http請求,大量的頁面疊加在一起,這將極大降低頁面的性能,使頁面載入得很慢。那麼我們想,能不能將無數個script文件合為一個(或幾個)文件,這樣請求數不就大大減少了嗎?沒錯,webpack打包做的就是這樣的作用 為什麼要用webpack實現JS壓縮? 和打包一樣,壓縮文件也是為了提高頁面性能,(大家可結合自己對那些打開極慢的網站的體驗感受一下頁面性能的重要性)。使用webpack壓縮文件時,它會做以下操作:
- 刪除註釋
- 刪除空格 (所以我們偶爾會看到沒有間隔或只有一行的JS代碼)
- 縮短變數名,函數名和函數參數名(var myName = '彭湖灣')-->var a = '彭湖灣'
- 減少文件體積,加快傳輸速度,提高頁面性能
- 實現代碼混淆,破壞其可讀性,保護創作者的知識產權

<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> </head> <body> <script type="text/javascript" src="bundle.js"></script></body> </html>
我們希望通過webpack的文件打包,將component中的所有文件合併到dist/ab.js中來然後dist/index.html就可以引用dist/ab.js文件了。 component/a.js內容:
console.log('我是a.js文件');
component/b.js內容:
console.log("我是b.js文件");
component/ab.js內容:
require('./a')
require('./b')
console.log('我是ab.js,我require了a.js文件和b.js文件');
1-3向webpack.config.js中寫入內容:
var path = require('path') module.exports = { entry:{ ab:'./component/ab.js', }, output:{ filename:'bundle.js', path:path.resolve(__dirname,'dist'), }, }
webpack要求webpack.config.js的輸出模塊為一個對象,且包含兩大基本屬性:entry和output。entry,顧名思義,入口文件,上面代碼表示component/ab.js是入口文件,output/bundle.js是輸出文件。由於component/ab.js引入(require)了a.js文件和b.js文件,這三個文件會被一起打包dist/bundle.js中,(註:entry中可以寫入相對路徑)
var path = require('path')
path.resolve(__dirname,'dist')
這段代碼什麼意思?path是node的內置模塊,resolve是它的一個方法,__dirname表示當前目錄在磁碟中的絕對路徑,path.resolve(__dirname,'dist') = __dirname + '/dist' ,在我的mac里它相當於Users/penghuwan/myprogram/webpackTest/dist(註意:必須為絕對路徑,不能為相對路徑!)
1- 4 OK!該寫的都寫好了,接下來,在終端進入目錄,寫入webpack回車


多了一個dist/bundle.js的文件!讓我們看看裡面有什麼:

就是我們獨立寫的a.js,b.js和ab.js打包後的dist/ab.js。 與此同時,我們之前在dist/index.html里的 <script type="text/javascript" src="ab.js"></script></body>不就可以起到作用了嗎?讓我們在磁碟里找到該文件打開,發現控制台輸出了:

用圖解描述上述過程,,webpack 遞歸地構建一個依賴樹,這個依賴樹包括你應用所需的每個模塊,然後將所有模塊打包為少量的包(bundle) - 通常只有一個包 - 可由瀏覽器載入。


c.js/d.js/cd.js和a.js/b.js/ab.js結構上完全一致,只是輸出的文本不同,這裡不多贅述,然後修改我們的webpack.config.js
var path = require('path')
module.exports = {
entry:{
ab:'./component/ab.js',
cd:'./component/cd.js'
},
output:{
filename:'[name].js',
path:path.resolve(__dirname,'dist'),
},
}
這裡的name是占位符[name]分別對應entry中寫入的[ab]和[cd],這表示,在dist下生成的將不再是上文提到的bundle.js,而是ab.js和cd.js兩個JS文件
2-2再修改一下我們的dist/index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript" src="./ab.js"></script></body>
<script type="text/javascript" src="./cd.js"></script></body>
</html>
2-3然後同樣是進入終端,寫入webpack再回車,回到目錄,此時已經生成了dist/ab.js和dist/cd.js兩個文件

2-4在瀏覽器中打開dist/index.html,輸出:


3為輸出文件添加哈希值標記,避免相同文件重新載入 在前後兩次在終端輸入webpack打包時,即使component中的所有文件都沒有變化,資源是要重新載入一遍的。同理,在生產中,每次需要在代碼中更新內容時,伺服器都必須重新部署,然後再由所有客戶端重新下載。 這顯然是低效的,因為通過網路獲取資源可能會很慢。 那麼我們怎麼才能避免這個問題呢———給output中的bundle文件提供hash值標記: 每次構建輸出文件時,如果代碼發生變化,輸出的文件將生成不同的hash值,這時將重新載入資源,但如果代碼無變化,輸出文件hash值也不變化,系統就會預設使用原來緩存的輸出文件 3-1修改我們的webpack.config.js:
var path = require('path') module.exports = { entry:{ ab:'./component/ab.js', cd:'./component/cd.js' }, output:{ filename:'[name]-[hash].js', path:path.resolve(__dirname,'dist'), }, }
打包後dist目錄:(上個例子中的dist/ab.js和dist/cd.js已刪掉)

寫入hash值帶來的新問題——每次都要更改dist/index.html中JS的src 因為我們生成的hash是不斷變化的,與此同時index.html必須不斷更改<script>標簽中的src的值 4解決hash值帶來的新問題 4-1使用html-webpack-plugin插件,webpack.config.js的輸出模塊對象有一個plugins屬性,它是一個數組,數組項是創建的plugin對象 在終端寫入npm install html-webpack-plugin --save-dev,安裝完畢後修改webpack.config.js的配置:
var path = require('path') var HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry:{ ab:'./component/ab.js', cd:'./component/cd.js' }, output:{ filename:'[name]-[hash].js', path:path.resolve(__dirname,'dist'), }, plugins:[ new HtmlWebpackPlugin() ] }
4-2在終端里輸入webpack回車,打開我們的dist/index,居然已經自動寫入了src帶hash值的script標簽!

【註意】這次的dist/index.html是webpack自動生成的,而以前的例子都是我們手動寫入的 5為生成的index.html指定模版 5-1但讓我們想一想另外一個問題,這個dist/html是自動生成的,我們能不能做一些改造,比如指定一個模版。用開發開發文件中的component/index.html為模版生成dist.html呢?先創建一個component/index.html文件,寫入:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>這是開發文件中的模版HTML</title> </head> <body> </html>
5-2修改我們的webpack.config.js:
var path = require('path')
var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry:{
ab:'./component/ab.js',
cd:'./component/cd.js'
},
output:{
filename:'[name]-[hash].js',
path:path.resolve(__dirname,'dist'),
},
plugins:[
new HtmlWebpackPlugin({
template:'./component/index.html'
})
]
}
5-3在HtmlWebpackPlugin的參數對象中寫入template屬性,指定為component/index.js,回到終端,寫入webpack,然後讓我們看一看dist/index.html

用圖解描述上述過程:

var path = require('path') var HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry:{ ab:'./component/ab.js', cd:'./component/cd.js' }, output:{ filename:'[name]-[hash].js', path:path.resolve(__dirname,'dist'), }, plugins:[ new HtmlWebpackPlugin({ filename:'ab.html', template:'./component/index.html', chunks:['ab'] }), new HtmlWebpackPlugin({ filename:'cd.html', template:'./component/index.html', chunks:['cd'] }) ] }
6-2打包後在dist中生成了dist/ab.html和dist/cd.html,瀏覽器打開ab.html,控制台輸出:


