從零學腳手架(二)---初識webpack

莫問今朝·發表於2021-03-09

webpack是什麼

打包器

在使用 webpack 之前,首先需要明白 webpack 到底是個什麼東西。

幾乎所有文章(包括官網)中都說webpack是一個 打包器 ,用於打包所有資源。

webpack確實是一個 打包器 ,但是對於不知道打包器的朋友來說還是會有些迷惑。

拋開 webpack 去看問題本質。

當前時代,前端的作用越來越大,對於寫過MVC或者更早的JSPASP.NET的朋友可能更有體會,那時代的前端只是作為展示作用。

隨著 移動端Node.JS 的崛起,前端進入了新的時代。

前端與後端進行了分離,前端開始獨立部署,逐漸走入了工程化的步伐。

首先對於獨立部署,就會有很多的工作需要完成,最容易想到的就是壓縮。

MVC時代,壓縮工作都是由後端進行完成。但是對於已經獨立部署的前端,這項工作只能由前端自行完成。

當然還有逐漸更新的JSCSS和瀏覽器相容性等一系列問題。

這些問題其實可以總結為 可部署環境程式碼開發環境程式碼 之間的衝突。

那麼能不能提供一個橋樑來連線兩種環境程式碼呢?最好能夠提供一個 黑匣子 ,能夠讓我們使用一個命令將 開發環境程式碼 編譯為 可部署環境程式碼

打包器 就是這麼一個 黑匣子

從零學腳手架(二)---初識webpack

打包器是個 黑匣子 這是對於大部分寫業務的程式設計師來說的,他們只需要完成業務模組。

但是對於專案管理者,打包器 就必須掌握,畢竟由於前端的特殊性,導致需要自己配置屬於自己專案的 打包器 。所以對於 打包器 的瞭解也基本屬於前端必修課。

webpack

webpack 就屬於一個 打包器 工具。目前市面上也有好多其它 打包器 工具:gruntgulprollup、還有尤大新開發vite。每一個 打包器 都有各自的優缺點。

不過截止到目前最流行的還是webpack 。所以在此也是以webpack作為學習、

webpack是一個優秀的 JavaScript應用程式 的靜態模組打包器,具有高度可配置的優勢,因此也被業界稱為最麻煩之一。

前面說過:打包器 就是將 開發環境程式碼 進行編譯為 可部署環境程式碼

而不同的專案對 可部署環境程式碼 的要求又不一致。所以 webpack 並沒有提供一個全而大功能,而只是提供了一個 核心引擎, 只負責 JS 檔案的依賴打包,其它功能使用 外掛化 進行配置 。

??? 這裡說的 外掛化 並不是指的 webpack 中的 plugin ,而是 擴充套件 的意思,為了 避免和 webpackplugin 翻譯歧義。 webpackplugin 就不做翻譯。

一般開發人員使用 webpack 實現某個功能時,只需要尋找符合自己需求的外掛就可以。外掛則由強大的社群維護。

社群中具有海量的 外掛 ,相同功能的都有好多。所以,在學習 webpack 時,

我個人建議轉換一下思想: 不要想這個東西是什麼,而要想我們需要什麼 。根據自己需求去尋找合適的 外掛

經過了這麼多年的發展,大部分功能的 外掛 已經具有了最優解,形成了 外掛 固定化。就像現在大部分語言執行環境的 GC演算法 都是 引用跟蹤演算法 一樣。

在上一篇文章中講到 package.json 檔案中的 devDependencies 留了一個問題:什麼是開發環境依賴

其實就可以總結出:構建工程化依賴環境  時使用的依賴庫。

? 構建工程化依賴環境 包括 打包器、還有 eslint單元測試庫

webpack 基本使用

webpack安裝

在之前已經安裝了 webpack@5.24.0 依賴庫,

在這裡只需要安裝 webpack-cli 即可,webpack-cli 類似一個簡易的客戶端,用來以 webpack 連線對應服務。

如果不安裝 webpack-cli 執行 webpack 命令時會提示安裝 webpack-cli

yarn add -D webpack-cli@4.5.0 // 安裝到devDependencies依賴。

package.json 檔案 scripts 屬性中加入 build:webpack 命令

從零學腳手架(二)---初識webpack

此時執行 yarn build 就會執行 webpack 命令

雖然會因為沒有配置項而失敗,但 webpack 成功執行了。

? webpack 可以直接使用命令列引數打包檔案,不過在此就不贅述,有興趣的朋友可以參考官網

webpack.config.js

在根目錄中建立一個 webpack.config.js,此檔案是 webpack 配置項檔案

webpack.config.js 檔案必須丟擲一個 模組 ,這個 模組 可以是一個 Object ,也可以是一個返回值為 Object 的函式, webpack 屬性就配置在這個Object

webpack 執行時會讀取 webpack.config.js 檔案模組,根據此 Object 中的配置資訊進行打包編譯

從零學腳手架(二)---初識webpack

根目錄 webpack.config.js 檔名稱是一個 約定檔名稱

在不指定配置檔案情況下,webpack 會讀取根目錄 webpack.config.js 檔案。

當然也可以使用引數指定配置檔案,也推薦這樣做,引數指令可以更改配置檔案的目錄和名稱

從零學腳手架(二)---初識webpack
工作目錄

接下來建立 /src 目錄、 /src/index.js 檔案

從零學腳手架(二)---初識webpack

在使用vue-clireact-cli時都會具有一個 /src 目錄,這是一個 約定的工作目錄

專案中的程式碼檔案都存放此目錄下。

當然此目錄名稱可以隨意設定,只不過約定為 /src

/src/index.js 檔案,是一個 entry(入口) 檔案。

打包器 作為一個將 開發環境程式碼 編譯可部署環境程式碼 的橋樑,那麼就必須 至少擁有一個 entry(開發環境程式碼) 和一個 output(可部署環境程式碼)

/src/index.js 就作為這麼一個 entry(入口)檔案

? index.js 檔名稱也是約定名稱。

wbepack簡單配置

entry、output

既然 打包器 必須有 entry(入口)output(輸出) ,那麼就由這兩個屬性開始。

webpack  中就是使用 entryoutput 作為兩者的屬性名稱,這兩個屬性都可以靈活配置。

const path = require('path')

const modules = {
  //  入口檔案
  //  字串形式
   entry:path.join(__dirname, 'src/index.js'),
  //  物件形式
  // entry:{
  //   'index':path.join(__dirname, 'src/index.js')
  // },

  //  輸出檔案
  //  字串形式
  // output:path.join(__dirname, 'dist/[name].js')
  //物件形式
  output:{
    //  輸出檔案的目錄地址
    path:path.join(__dirname, 'dist'),
    //  輸出檔名稱,contenthash代表一種快取,只有檔案更改才會更新hash值,重新打包
    filename: '[name]_[contenthash].js'
  }
}

//  使用node.js的匯出,將配置進行匯出
module.exports = modules
  • entry :入口檔案。

    屬性可設定為:StringObjectArray

    屬性值為 String :直接設定一個入口檔案地址

    屬性值為 Object :對入口檔案進行詳細設定和 可以設定多個入口檔案

    屬性值為 Array :設定多個入口檔案

    ? webpack 允許設定多個 entry(入口) 檔案,打包編譯出多個檔案。一般用於 多頁面配置

    至於多頁面開發配置,就不在此贅述,有興趣的朋友可以去查閱下 參考資料,單頁面程式直接設定 String 即可

  • output:輸出檔案。

    屬性可設定為:StringObject

    屬性值為 String :直接設定一個輸出檔案地址

    屬性值為 Object :輸出檔案地址和檔名稱詳細設定

    ? webpack在設定 output.filename 時,允許使用 [name] 保留 entry 屬性設定的檔名稱。

    entryString 時, [name] 為檔名稱

    entryObject 時, [name] 為物件的 key

    ? output 屬性中的 [contenthash] ,是 webpack 提供的一種打包快取的機制。 webpack 會為打包編譯生成一個 hash 值。只有更改原始檔後才會重新打包編譯,生成新的 hash 。 快取機制一般只會在 生產模式(production) 使用。

    webpack還提供了兩個屬性 [hash][chunkhash] 設定快取,具體區別請參考:webpack中hash、chunkhash、contenthash區別


? :__dirname 屬性是 Node.JS 基礎庫中屬性,表示當前檔案的絕對路徑。等同於path.dirname()

? webpack.json 檔案中使用了 Node.JS 基礎庫(require('path')) 獲取檔案絕對地址,更準確的保證檔案目錄的完整性。當然也可以只使用相對地址,不過不推薦

此時使用yarn build命令就可以執行 webpack 。 最終會在根目錄建立 /dist,並在 /dist 目錄下生成一個 .js 檔案。

檔案中就是 /src/index.js 中的內容

從零學腳手架(二)---初識webpack

以上就是一個最簡單的 webpack 配置,只是將 entry(入口) 檔案打包編譯到 output(輸出)

不過 webpack 本身是一個強大的 JavaScript應用程式

接下來做小測試,在 /src 目錄中建立 index2.js ,並在 index.js 中匯入

從零學腳手架(二)---初識webpack

此時再進行yarn build/dist 目錄下會自動再建立一個 .js 檔案,這個 .js 檔案就是這次打包的輸出問題。

從檔案中具有 index.jsindex2.js 內容

從零學腳手架(二)---初識webpack

?? 打包後檔案會進行壓縮,並且程式碼會多出許多 webpack 構建的程式碼。

程式碼壓縮是因為 webpack 預設使用的是 生產模式(production)

如果想要不壓縮程式碼可以在 webpack.json 檔案中新增一個 mode:'development' 屬性,至於這兩個屬性意思下一篇再詳細講解。

這就是webpack 的強大功能之一。 webpack 在打包處理檔案時,會遞迴的構建一個 依賴圖(dependency graph) ,根據這個 依賴圖 將所有使用到的 JS模組 進行打包。

?如果在 index.js 將引入 index2.js 的程式碼註釋或者刪除,那麼 index2.js 檔案內容便不會被打包。有興趣的朋友可以自行測試。

plugins

在前面說過,webpack 只提供了一個 核心引擎。大部分功能使用 外掛化 進行管理

webpack 配置項提供了一個 plugins 屬性,該屬性是一個 Array 型別,用於設定 plugin

webpack 在執行時會順序執行 plugins 中的 plugin

const modules = {
    plugins:[

    ],
}

//  使用node.js的匯出,將配置進行匯出
module.exports = modules

?? webpack 預設只是一個 JavaScript應用程式 打包器,所以不會處理CSSImageTypeScript非js模組

webpack提供了一個 loader 屬性處理 非JS模組(將非JS模組轉換為JS模組), 而 plugins 則為 webpack 打包時提供其他擴充套件。兩者結合了促成了 webpack外掛化 ,使 webpack 可以高度擴充套件。至於兩者的分工和不同,之後會慢慢了解。

? webpack 提供了自定義 plugin 的方法,具體編寫規則,有興趣的朋友可以去參考 官網

html-webpack-plugin

眾所周知,執行在瀏覽器根源是 HTML 檔案,HTML 作為 JS 檔案的承載容器,哪怕將所有業務邏輯都交給 JS(Document型別) 完成,依然需要一個 HTML 容器承載,那麼就需要提供一個提供的 HTML 檔案的功能

webpack html-webpack-plugin 就是完成這個功能的

?? HTML 容器也是一個 非JS模組,那麼為什麼使用 plugin處理 而不使用loader呢? 我個人的理解為 HTML 的提供是作為一個 JS 容器存在,而並非要轉換為 JS模組 處理

? webpack社群中 以 -plugin 為單詞為字尾的庫,都為 plugin。而以 -loader 單詞為字尾的庫,都為 loader。 這也是一種約定規則。

yarn add -D html-webpack-plugin@5.2.0

安裝之後,需要在webpack檔案中進行引用

const HtmlWebpackPlugin = require('html-webpack-plugin')

const modules = {
  plugins: [
    new HtmlWebpackPlugin()
  ]
}

//  使用node.js的匯出,將配置進行匯出
module.exports = modules

此時執行yarn build/dist 目錄便會多出一個 HTML 檔案,此 HTML 還引用此次打包的 JS 檔案。

在瀏覽器中也可以執行此 HTML ,開發者控制檯中會列印 JS 檔案中的 Console 語句。

從零學腳手架(二)---初識webpack

真實專案中,HTML 容器也要設定很多東西。例如: TitleMeta 或者 icon 等。

並且在vue-cli中,會有一個 HTML 模板檔案。在此 HTML 檔案新增資訊,並且以此檔案進行作為容器。

這些資訊都是由 html-webpack-plugin 構造引數提供的。

plugins: [
    new HtmlWebpackPlugin({
      //  HTML的標題,
      //  template的title優先順序大於當前資料
      title:'my-cli',
        
      //  輸出的html檔名稱
      filename:'index.html',
        
      //  本地HTML模板檔案地址
      template:path.join(__dirname, 'src/index.html'),
        
      // 引用JS檔案的目錄路徑
      publicPath:'./',
        
      //  引用JS檔案的位置
      //  true或者body將打包後的js指令碼放入body元素下,head則將指令碼放到中
      //  預設為true
      inject:'body',
        
      //  載入js方式,值為defer/blocking
      //  預設為blocking, 如果設定了defer,則在js引用標籤上加上此屬性,進行非同步載入
      scriptLoading:'blocking',
        
      //  是否進行快取,預設為true,在開發環境可以設定成false
      cache:false,
        
      //  新增mate屬性
      meta:{}
    })
  ]
  • titleHTML 的標題,

    屬性可設定為: String

    此屬性在設定 template 屬性後會失效

  • filename:輸出的 HTML 檔名稱,

    屬性可設定為:String

    預設值為: index.html

  • template:本地 HTML 模板檔案地址。

    屬性可設定為:String

    使用HTML模板檔案時,會將 HTML 模板檔案內容原封不動的 copy

    例如下面 HTML 模板引用了jquery,打包後的 HTML 依然存在jquery

  • publicPath: 引用 JS 檔案的目錄路徑。

    屬性可設定為:String

    例如設定路徑為 ./ ,那麼在 HTML 檔案引用 JS 時就會為 <script src="./main_XXXXXX.js"></script>

    此屬性提供了更靈活的專案管理,可以將 HTML 檔案和 JS 檔案打包到不同目錄。

  • inject: 引用編譯後 JS 檔案的位置。

    屬性可設定為:Booleanheadbody

    屬性值為 false :代表不引用編譯後 JS 檔案

    屬性值為 truebody :在 body 元素最後引用編譯後 JS 檔案 。推薦

    屬性值為 head:在 head 元素中引用編譯後 JS 檔案,不推薦

    預設值為 true

  • scriptLoading:設定載入 JS 的方法

    屬性可設定為:blockingdefer

    預設值為 blocking

    具體 blockingdefer 差別可參考defer和async的區別

  • cache:是否快取 HTML 檔案。

    屬性可設定為:Boolean

    預設值為 true

    開發環境(development)時可以設定為 false

  • meta:設定 meta 屬性


在此列舉了html-webpack-plugin主要部分屬性, 更多屬性可以在npm中檢視

template 屬性需要是一個本地的html路徑。

從零學腳手架(二)---初識webpack
clean-webpack-plugin

之前每次打包編譯都會在 /dist 目錄建立 .js 檔案,久而久之, /dist 就會具有好多無用的檔案

從零學腳手架(二)---初識webpack

/dist 目錄往往希望它是一個乾淨的目錄,目錄內只具有最新一次的打包生成的檔案。這樣就可以直接以此目錄進行釋出。

針對這個需求,社群有大佬開發了clean-webpack-plugin

clean-webpack-plugin 會在每次打包編譯時,清空輸出目錄。

這個求 --- clean-webpack-plugin

yarn add -D clean-webpack-plugin@3.0.0

將此庫直接新增到 plugins 屬性即可

const { CleanWebpackPlugin } = require('clean-webpack-plugin')
 plugins: [
    new CleanWebpackPlugin()
  ]

此時執行yarn build /dist 目錄就只剩下本地打包結果。

從零學腳手架(二)---初識webpack

??

clean-webpack-plugin庫使用時為:const { CleanWebpackPlugin } =

html-webpack-plugin庫: const HtmlWebpackPlugin = 不同。

這是因為html-webpack-plugin使用了預設匯出: export default

clean-webpack-plugin匯出的是指定型別: CleanWebpackPlugin

預設匯出方式 可以使用任意變數名稱進行匯入:const H = require('html-webpack-plugin')

clean-webpack-plugin也可以從建構函式中傳參進行自定義設定

 new CleanWebpackPlugin({
      // 是否假裝刪除檔案
      //  如果為false則代表真實刪除,如果為true,則代表不刪除
      dry:false,
     
      //  是否將刪除日誌列印到控制檯 預設為false
      verbose: true,
     
      //  允許保留本次打包的檔案
      //  true為允許,false為不允許,保留本次打包結果,也就是會刪除本次打包的檔案
      //  預設為true
      protectWebpackAssets:true,
     
      //  每次打包之前刪除匹配的檔案
      cleanOnceBeforeBuildPatterns:["*.html"],

      //  每次打包之後刪除匹配的檔案
      cleanAfterEveryBuildPatterns:["*.js"],
    })
  • dry:是否假裝刪除檔案,官方文件的描述是:Simulate the removal of files

    屬性可設定為:Boolean

    此屬性當為 true 時,則不清空 /dist 目錄;當為 false 時,會清空 /dist 目錄

    預設值: false

  • verbose:是否將刪除日誌列印到控制檯

    屬性可設定為:Boolean

    預設值: false

  • protectWebpackAssets:是否保留本次打包的檔案

    屬性可設定為:Boolean

    屬性值為 false 時,本次打包檔案也會被清除掉

    預設值: true

  • cleanOnceBeforeBuildPatterns:設定打包之前刪除的檔案

    屬性可設定為:Array

    此屬性類似一個鉤子,在打包執行之前,刪除此屬性匹配到的檔案

    預設值: ['**/*']

  • cleanAfterEveryBuildPatterns:設定打包之後刪除的檔案

    屬性可設定為:Array

    此屬性與 cleanOnceBeforeBuildPatterns 類似,觸發時機是打包執行完畢後

    預設值: []

使用clean-webpack-plugin時其實不需要配置屬性,預設就已經足夠使用。

?? 測試 clean-webpack-plugin 時,需要每次都修改 index.js 檔案資料,因為 output 設定快取,如果不修改原始檔,不會重新打包

總結

???

  • 打包器開發環境程式碼 編譯可部署環境程式碼“編譯器”
  • webpack執行在Node.JS環境中, 所以寫的 webpack配置項 其實是Node.JS
  • webpack配置檔案需要丟擲一個模組,匯出模組必須為 Object 或返回 Object 的函式
  • webpack只提供一個 核心引擎 ,而其它功能使用 外掛化 方式管理
  • webpack是一個 JavaScript應用程式 ,預設只支援 JS模組 的打包,對於 非JS模組 需要使用 loader 轉換為 JS模組
  • 打包器不一定非要使用 Node.JS,也可以使用JAVA.NET等任何一門語言
  • 打包器其實就是 IO 操作,將entry(入口)檔案資料讀取並經過一系列操作最終寫入到output(輸出)檔案

如果此篇對您有所幫助,在此求一個star。專案地址: OrcasTeam/my-cli

本文參考

本文依賴

package.json

{
  "name": "my-cli",
  "version": "1.0.0",
  "main": "index.js",
  "author": "mowenjinzhao<yanzhangshuai@126.com>",
  "license": "MIT",
  "devDependencies": {
    "clean-webpack-plugin": "3.0.0",
    "html-webpack-plugin": "5.2.0",
    "webpack": "5.24.0",
    "webpack-cli": "4.5.0"
  },
  "dependencies": {
    "jquery": "3.5.1"
  },
  "scripts": {
    "start": "node",
    "build": "webpack --config webpack.config.js"
  }
}

webpack.config.js

const path = require('path')

const modules = {
  //  入口檔案
  //  字串形式
  entry:path.join(__dirname, 'src/index.js'),
  //  物件形式
  // entry:{
  //   'index':path.join(__dirname, 'src/index.js')
  // },

  //  輸出檔案
  //  字串形式
  // output:path.join(__dirname, 'dist/[name].js')
  //物件形式
  output:{
    //  輸出檔案的目錄地址
    path:path.join(__dirname, 'dist'),
    //  輸出檔名稱,contenthash代表一種快取,只有檔案更改才會更新hash值,重新打包
    filename: '[name]_[contenthash].js'
  },
    
  plugins: [
    new HtmlWebpackPlugin({
       //  HTML的標題,
        //  template的title優先順序大於當前資料
        title: 'my-cli',

        //  輸出的html檔名稱
        filename: 'index.html',

        //  本地HTML模板檔案地址
        template: path.join(config.root, 'src/index.html'),

        // 引用JS檔案的目錄路徑
        publicPath: './',

        //  引用JS檔案的位置
        //  true或者body將打包後的js指令碼放入body元素下,head則將指令碼放到中
        //  預設為true
        inject: 'body',

        //  載入js方式,值為defer/blocking
        //  預設為blocking, 如果設定了defer,則在js引用標籤上加上此屬性,進行非同步載入
        scriptLoading: 'blocking',

        //  是否進行快取,預設為true,在開發環境可以設定成false
        cache: false,

        //  新增mate屬性
        meta: {}
    }),
    new CleanWebpackPlugin({

        // 是否假裝刪除檔案
        //  如果為false則代表真實刪除,如果為true,則代表不刪除
        dry: false,

        //  是否將刪除日誌列印到控制檯 預設為false
        verbose: true,

        //  允許保留本次打包的檔案
        //  true為允許,false為不允許,保留本次打包結果,也就是會刪除本次打包的檔案
        //  預設為true
        protectWebpackAssets: true,

        //  每次打包之前刪除匹配的檔案
        cleanOnceBeforeBuildPatterns: ['**/*'],

        //  每次打包之後刪除匹配的檔案
        cleanAfterEveryBuildPatterns:["*.js"],
    })
  ]
}

//  使用node.js的匯出,將配置進行匯出
module.exports = modules

相關文章