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


從開發文件到生產文件

 
有一天我突然意識到一個問題,在使用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,控制台輸出:
 
 
圖解上述過程:
 

 

  【完】--喜歡這篇文章的話不妨關注一下我喲


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM