【webpack】流行的前端模塊化工具webpack初探

從開發文件到生產文件javascript

 
有一天我忽然意識到一個問題,在使用react框架搭建應用時,我使用到了sass/less,JSX模版以及ES6的語法在編輯器下進行開發,使用這些寫法是能夠提升開發的效率。但是瀏覽器它自己是並不可以「理解」這些語法的呀。就像下面這張圖:
 
 
 
在開發代碼文件 --> 生產代碼文件的轉換過程當中,咱們到底須要作些什麼呢?沒錯,這一切都和webpack(或gulp)有關:
 
 
 
轉一張webpack官網的圖,webpack能把less/sass文件,json文件,乃至css文件,全都打包成js文件和靜態資源文件(圖片)
 

 

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 = '彭湖灣'
這樣作的好處:
  • 減小文件體積,加快傳輸速度,提升頁面性能
  • 實現代碼混淆,破壞其可讀性,保護創做者的知識產權
 (注:這一過程不可逆!須要事先作好備份工做)
爲何要用webpack實現sass,less的編譯和JSX模版文件的轉換?
 
也就是上文提到的,經過webpack的轉換,從瀏覽器沒法「理解」的開發代碼生成一份瀏覽器可以「理解」的生產代碼
 
commonJS和AMD規範
 
從大量<script>的寫法到webpack的普遍使用,實際上就是前端模塊化發展的過程,而其間有兩個主要的模塊化標準commonJS和AMD,webpack是基於commonJS的,(固然也兼容寫AMD,不過不推薦)下面是commonJS 的模塊寫法:
 
const moduleInput = require('moduleInpu')
//輸入模塊
module.exports = {
//輸出模塊
  ...
}
 
下面我就一一來介紹如何用webpack實現上述三種功能:
 
首先你得建立一個文件webpackTest,在終端進入目錄,寫入$ npm install webpack -g,安裝成功
 
1文件打包(SPA-單頁面應用程序)
 
1-1安裝好webpack後建立這樣一個目錄:
 
 
1-2:向component各文件和dist/index.html文件寫入內容
 
dist表示的是生產目錄,component是開發目錄,咱們平時開發時只在component目錄下完成。dist/index.html是咱們手動建立的,內容以下:
 
<!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回車
 
 
component下的三個文件都被打包好了,再回來看看咱們的目錄
 

 

多了一個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) - 一般只有一個包 - 可由瀏覽器加載
 
 
2多入口文件
 
2-1上述例子中,咱們只在entry中寫入了一個入口文件,那咱們能不能一次寫入多個入口文件呢?這固然是能夠的,首先修改咱們在component的文件結構:
 

 

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,輸出:
 
 
 
用圖解描述上述過程以下,因爲引用關係創建的依賴書,a/b/ab和c/d/cd分別被打包爲兩個bundle並被引入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
 

 

用圖解描述上述過程:
 
 
6用webpack打包多頁面應用程序(MPA)
談談SPA(sing page application)與MPA(mutiple page aplication),SPA和MPA 指的是單頁面應用程序和多頁面應用程序,以前咱們打包的都是SPA,那麼怎麼打包MPA呢。很簡單,在plugins中寫入多個HtmlWebpackPlugin對象即可,這時候須要指明不一樣文件的filename屬性值,以及chunks屬性值——它們對應的bundle文件
6-1改寫一下咱們的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({
        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,控制檯輸出:
 
 
瀏覽器打開cd.html,控制檯輸出:
 
 
圖解上述過程:
 

 

  【完】--喜歡這篇文章的話不妨關注一下我喲
相關文章
相關標籤/搜索