Webpack 再深刻再總結

前言

本文爲做者第二次專門對 Webpack 的知識點進行深刻和實踐,根據理解和實踐的結果進行總結的;javascript

文章內容參考書籍《深刻淺出 Webpack》,由於該書籍基於 Webpack 3.4.0 版本,本文的實踐基於 Webpack 4.28.2 版本,因此也踩了很多因爲模塊版本問題出現的坑,已經彙總到第 6 章節 踩坑彙總,你們記得避免踩坑;也印證了那句哲理:紙上得來終覺淺,絕知此事要躬行 ...css

博客 github地址爲:github.com/fengshi123/… ,彙總了做者的全部博客,也歡迎關注及 star ~html

本文實踐 demo 的 github地址java

1、Webpack 原理

一、構建做用

構建工具就是將源代碼轉換成可執行的 JavaScript、CSS、HTML 代碼,包括如下內容:node

  • 代碼轉換:將 TypeScript 編譯成 JavaScript、將 SCSS 編譯成 CSS 等;webpack

  • 文件優化:壓縮 JavaScript、CSS、HTML 代碼,壓縮合並圖片等;git

  • 代碼分割:提取多個頁面的公共代碼,提取首屏不須要執行部分的代碼,讓其異步加載;github

  • 模塊合併:在採用模塊化的項目裏會有不少個模塊和文件,須要經過構建功能將模塊分類合併成一個文件;web

  • 自動刷新:監聽本地源代碼的變化,自動從新構建、刷新瀏覽器;npm

  • 代碼校驗:在代碼被提交到倉庫前須要校驗代碼是否符合規範,以及單元測試是否經過;

  • 自動發佈:更新代碼後,自動構建出線上發佈代碼並傳輸給發佈系統;

二、核心概念

Webpack 有如下幾個核心概念:

  • Entry :入口,Webpack 執行構建的第一步將從 entry 開始,可抽象成輸入;

  • Module:模塊,配置處理模塊的規則;在 Webpack 裏一切皆模塊,一個模塊對應一個文件;Webpack 會從配置的 Entry 開始遞歸找出全部依賴的模塊;

  • Loader:模塊轉換器,用於將模塊的原內容按照需求轉換成新內容;

  • Resolve:配置尋找模塊的規則;

  • Plugin:擴展插件,在 Webpack 構建流程中的特定時機會廣播對應的事件,插件能夠監聽這些事情的發生,在特定的時機作對應的事情;

  • Output:輸出結果,在 Webpack 通過一系列處理並得出最終想要的代碼後輸出結果;

  • Chunk:代碼塊,一個 Chunk 由多個模塊組合而成,用於代碼合併與分割;

三、流程概述

(1)初始化參數:從配置文件和 Shell 語句中讀取與合併參數,得出最終的參數;

(2)開始編譯:用上一步獲得的參數初始化 Compiler 對象,加載全部配置的插件,經過執行對象的 run 方法開始執行編譯;

(3)肯定入口:根據配置中的 entry 找出全部入口文件;

(4)編譯模塊:從入口文件出發,調用全部配置的 Loader 對模塊進行翻譯,再找出該模塊依賴的模塊,再遞歸本步驟直到全部入口依賴的文件都通過了本步驟的處理;

(5)完成模塊編譯:在通過第 4 步使用 Loader 翻譯完全部模塊後,獲得了每一個模塊被翻譯後的最終內容及它們之間的依賴關係;

(6)輸出資源:根據入口和模塊之間的依賴關係,組裝成一個個包含多個模塊的 Chunk,再將每一個 Chunk 轉換成一個單獨的文件加入輸出列表中,這是能夠修改輸出內容的最後機會;

(7)輸出完成:在肯定好輸出內容後,根據配置肯定輸出的路徑和文件名,將文件的內容寫入文件系統中;

在以上過程當中,Webpack 會在特定的時間點廣播特定的事件,插件在監聽到感興趣的事件後會執行特定的邏輯,而且插件能夠調用 Webpack 提供的 API 改變 Webpack 的運行結果;

2、Webpack 配置

一、Webpack 項目初始化

一、新建 Web 項目

新建一個目錄,再進入項目根目錄執行 npm init 來初始化最簡單的採用了模塊化開發的項目;最終生成 package.json 文件;

$ npm init
複製代碼

二、安裝 Webpack 到本項目

(1)查看 Webpack 版本

運行如下命令能夠查看 Webpack 的版本號

$ npm view webpack versions
複製代碼

(2)安裝 Webpack

能夠選擇(1)步驟羅列獲得的 Webpack 版本號,也能夠安裝最新穩定版、最新體驗版本,相關命令以下所示,我選擇安裝 4.28.2 版本(沒有爲何,就想裝個 4.x 的版本);

// 安裝指定版本
npm i -D webpack@4.28.2

// 安裝最新穩定版
npm i -D webpack

// 安裝最新體驗版本
npm i -D webpack@beta
複製代碼

(3)安裝 Webpack 腳手架

須要安裝 Webpack 腳手架,才能在命令窗口執行 Webpack 命令,運行如下命令安裝 Webpack 腳手架;

$ npm i -D webpack-cli
複製代碼

三、使用 Webpack

使用 Webpack 構建一個採用 CommonJS 模塊化編寫的項目;

(1)新建頁面入口文件 index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Webpack</title>
</head>
<body>
<!--導入 Webpack 輸出的 JavaScript 文件-->
<script src="./dist/bundle.js"></script>  
</body>
</html>
複製代碼

(2)新建須要用到的 JS 文件

show.js 文件

// 操做 DOM 元素,把 content 顯示到網頁上
function show(content) {
  window.document.getElementById('app').innerText = 'Hello,' + content;
}

// 經過 CommonJS 規範導出 show 函數
module.exports = show;
複製代碼

main.js 文件

// 經過 CommonJS 規範導入 show 函數
const show = require('./show.js');
// 執行 show 函數
show('Webpack');
複製代碼

(3)新建 Webpack 配置文件 webpack.config.js

const path = require('path');

module.exports = {
  // JavaScript 執行入口文件
  entry: './main.js',
  output: {
    // 把全部依賴的模塊合併輸出到一個 bundle.js 文件
    filename: 'bundle.js',
    // 輸出文件都放到 dist 目錄下
    path: path.resolve(__dirname, './dist'),
  }
};
複製代碼

(4)執行 webpack 命令進行構建

在 package.json 文件中配置編譯命令,以下所示:

"scripts": {
    "build": "webpack --config webpack.config.js",
  },
複製代碼

執行如下命令進行項目的 Webpack 編譯,成功後會在項目根目錄下生成編譯目錄 dist ;

$ npm run build
複製代碼

(5)運行 index.html

編譯成功後,咱們用瀏覽器打開 index.html 文件,能看到頁面成功顯示 「Hello Webpack」;

二、Loader 配置

本節經過爲以前的例子添加樣式,來嘗試使用 Loader;

(1)新建樣式文件 main.css

#app{
  text-align: center;
  color:'#999';
}
複製代碼

(2)將 main.css 文件引入入口文件 main.js 中,以下所示:

// 經過 CommonJS 規範導入 CSS 模塊
require('./main.css');

// 經過 CommonJS 規範導入 show 函數
const show = require('./show.js');
// 執行 show 函數
show('Webpack');
複製代碼

(3)Loader 配置

以上修改後去執行 Webpack 構建是會報錯的,由於 Webpack 不原生支持解析 CSS 文件。要支持非 JavaScript 類型的文件,須要使用 Webpack 的 Loader 機制;

(3.1)運行如下命令,安裝 style-loader 和 css-loader,其中:

  • css-loader 用於讀取 CSS 文件;
  • style-loader 把 CSS 內容注入到 JavaScript 中;
$ npm i -D style-loader css-loader
複製代碼

(3.2)進行如下配置

module:{
rules:[
  {
	// 用正則去匹配要用該 loader 轉換的 CSS 文件
	test:/\.css$/,
	use:['style-loader','css-loader']
  }
 ]
}
複製代碼

(4)查看結果

編譯後,刷新 index.html ,查看剛剛的樣式 loader 已經起做用;

1

三、Plugin 配置

(1)安裝樣式提取插件 extract-text-webpack-plugin

$ npm i -D extract-text-webpack-plugin@next
複製代碼

(2)plugin 文件配置以下

module:{
    rules:[
      {
        // 用正則去匹配要用該 loader 轉換的 CSS 文件
        test:/\.css$/,
        use:ExtractTextPlugin.extract({
          use:['css-loader']
        }),
      }
    ]
  },
  plugins:[
    new ExtractTextPlugin({
       // 從 .js 文件中提取出來的 .css 文件的名稱
       filename:`[name]_[hash:8].css`
    }),
  ]
複製代碼

(3)查看結果

經過以上配置後,執行 Webapack 的執行命令,發如今 dist 目錄下,生成對應的 css 文件;存在的坑點:

  • 咱們須要手動將生成的 css 文件引入到 index.html 中;
  • 修改 css 文件後,會生成新的 css 文件,原先的不會刪除;

四、使用 DevServer

(1)執行如下命令安裝 webpack-dev-server

$ npm i -D  webpack-dev-server
複製代碼

在 package.json 中配置啓動命令

"scripts": {
    "build": "webpack --config webpack.config.js",
    "dev": "webpack-dev-server",
  },
複製代碼

運行命令後,就能夠啓動 HTTP 服務

$ npm run dev
複製代碼

啓動結果以下所示,咱們能夠經過 http://localhost:8080/ 訪問咱們的 index.html 的demo

1

(2)實時預覽

咱們在運行命令後面添加參數 --watch 實現實時預覽,配置以下所示:

"scripts": {
    "dev": "webpack-dev-server --watch"
  },
複製代碼

而後咱們修改 main.js 的傳入參數,發現並不能實時預覽,也沒有報錯!!! why?

踩坑:

在 index.html 中須要將 js 的路徑修改成:

<script src="bundle.js"></script>  
複製代碼

而不能是以前的(由於這個是編譯生成的,並非經過 devServer 生成放在內存的)

<script src="./dist/bundle.js"></script> 
複製代碼

(3)模塊熱替換

能夠經過配置 -- hot 進行模塊熱替換;

3、Webpack 優化

關於優化的實踐以前有進行過實踐了,這裏再也不累述,感興趣的童鞋能夠查看做者寫的另外一篇文章《Vue項目Webpack優化實踐,構建效率提升50%

4、編寫 Loader

一、Loader 要點總結

(1)Loader 爲模塊轉換器,用於將模塊的原內容按照需求轉換成新內容;

(2)Loader 的職責是單一的,只須要完成一種轉換,遵照單一職責原則;

(3)Webpack 爲 Loader 提供了一系列 API 供 Loader 調用,例如:

  • loader-utils.getOptions( this ) 獲取用戶傳入的 options,
  • this.callback( ) 自定義返回結果,
  • this.async( ) 支持異步操做;
  • this.context 當前文件所在的目錄;
  • this.resource 當前處理文件的完整請求路徑;
  • 其它等等

二、編寫 loader 源碼

手寫一個 loader 源碼,其功能是將 /hello/gi 轉換成 HELLO,固然這個 loader 其實沒啥實際意義,純碎是爲了寫 loader 而寫 loader;固然若是你實際業務有須要編寫 loader 需求,那就要反思這個業務的合理性,由於龐大的社區,通常合理的需求都能找到對應的 loader。

(1)源碼編寫

在原有的項目底下,新建目錄 custom-loader 做爲咱們編寫 loader 的名稱,執行 npm init 命令,新建一個模塊化項目,而後新建 index.js 文件,相關源碼以下:

function convert(source){
  return source && source.replace(/hello/gi,'HELLO');
}

module.exports = function(content){
  return convert(content);
}
複製代碼

(2)Npm link 模塊註冊

正常咱們安裝 Loader 是從 Npm 公有倉庫安裝,也即將 Loader 發佈到 Npm 倉庫,而後再安裝到本地使用;可是咱們可使用 Npm link 作到在不發佈模塊的狀況下,將本地的一個正在開發的模塊的源碼連接到項目的 node_modules 目錄下,讓項目能夠直接使用本地的 Npm 模塊;

在 custom-loader 目錄底下,運行如下命令,將本地模塊註冊到全局:

$ npm link
複製代碼

成功結果以下:

1

而後在項目根目錄執行如下命令,將註冊到全局的本地 Npm 模塊連接到項目的 node_modules 下:

$ npm link custom-loader
複製代碼

成功結果以下,而且在 node_modules 目錄下能查找到對應的 loader;

1

三、Webpack 中配置編寫的 loader

該配置跟第一章節的 Webpack 配置並無任何區別,這裏再也不詳述,配置參考以下:

module:{
    rules:[
      {
        test:/\.js/,
        use:['custom-loader'],
        include:path.resolve(__dirname,'show')
      }
    ]
  }
複製代碼

執行運行 or 編譯命令,就能看到咱們的 loader 起做用了。

5、編寫 Plugin

Webpack 就像一條生產線,要通過一系列處理流程後才能將源文件轉換成輸出結果,這條生產線上的每一個處理流程的職責都是單一的,多個流程之間存在依賴關係,只有在完成當前處理後才能提交給下一個流程去處理。插件就像生產線中的某個功能,在特定的時機對生產線上的資源進行處理。

Webpack 經過 Tapable 來組織這條複雜的生產線。 Webpack 在運行過程當中會廣播事件,插件只須要監聽它所關心的事件,就能加入到這條生產線中,去改變生產線的運做。 Webpack 的事件流機制保證了插件的有序性,使得整個系統擴展性很好。

一、Plugin 要點總結

  • Webpack 在編譯過程當中,會廣播不少事件,例如 run、compile、done、fail 等等,能夠查看官網;
  • Webpack 的事件流機制應用了觀察者模式,咱們編寫的插件能夠監聽 Webpack 事件來觸發對應的處理邏輯;
  • 插件中可使用不少 Webpack 提供的 API,例如讀取輸出資源、代碼塊、模塊及依賴等;

二、編寫 Plugin 源碼

手寫一個 plugin 源碼,其功能是在 Webpack 編譯成功或者失敗時輸出提示;固然這個 plugin 其實沒啥實際意義,純碎是爲了寫 plugin 而寫 plugin;固然若是你實際業務有須要編寫 plugin 需求,那就要反思這個業務的合理性,由於龐大的社區,通常合理的需求都能找到對應的 plugin。

(1)源碼編寫

在原有的項目底下,新建目錄 custom-plugin 做爲咱們編寫 plugin 的名稱,執行 npm init 命令,新建一個模塊化項目,而後新建 index.js 文件,相關源碼以下:

class CustomPlugin{
  constructor(doneCallback, failCallback){
     // 保存在建立插件實例時傳入的回調函數
     this.doneCallback = doneCallback;
     this.failCallback = failCallback;
  }
  apply(compiler){
    // 成功完成一次完整的編譯和輸出流程時,會觸發 done 事件
    compiler.plugin('done',(stats)=>{
      this.doneCallback(stats);
    })
    // 在編譯和輸出的流程中遇到異常時,會觸發 failed 事件
    compiler.plugin('failed',(err)=>{
      this.failCallback(err);
    })
  }
}
module.exports = CustomPlugin;
複製代碼

(2)Npm link 模塊註冊

跟 Loader 註冊同樣,咱們使用 npm link 進行註冊;

在 custom-plugin 目錄底下,運行如下命令,將本地模塊註冊到全局:

$ npm link
複製代碼

而後在項目根目錄執行如下命令,將註冊到全局的本地 Npm 模塊連接到項目的 node_modules 下:

$ npm link custom-plugin
複製代碼

若是一切順利,能夠在 node_modules 目錄下能查找到對應的 plugin;

三、Webpack 中配置編寫的 plugin

該配置跟第一章節的 Webpack 配置並無任何區別,這裏再也不詳述,配置參考以下:

plugins:[
    new CustomPlugin(
     stats => {console.info('編譯成功!')},
     err => {console.error('編譯失敗!')}
   ),
  ],
複製代碼

執行運行 or 編譯命令,就能看到咱們的 plugin 起做用了。

6、踩坑彙總

一、css-loader 如下配置

rules:[
  {
	// 用正則去匹配要用該 loader 轉換的 CSS 文件
	test:/\.css$/,
	use:['style-loader','css-loader?minimize']
  }
]
複製代碼

報如下錯誤:

- options has an unknown property 'minimize'. These properties are valid:
   object { url?, import?, modules?, sourceMap?, importLoaders?, localsConventio               n?, onlyLocals?, esModule? }
複製代碼

緣由:

minimize 屬性在新版本已經被移除,

解決:

先去掉 minimize 選項;

二、ExtractTextPlugin 編譯如下錯誤:

1

緣由:

extract-text-webpack-plugin 版本號問題

參考連接:github.com/webpack/web…

解決:

從新安裝 extract-text-webpack-plugin

$ npm i -D extract-text-webpack-plugin@next
複製代碼

三、修復第2個坑以後,ExtractTextPlugin 編譯繼續報如下錯誤:

1

緣由:

不存在 contenthash 這個變量

解決:

更改 extract-text-webpack-plugin 的配置:

plugins:[
    new ExtractTextPlugin({
       // 從 .js 文件中提取出來的 .css 文件的名稱
       filename:`[name]_[hash:8].css`
    }),
  ]
複製代碼

四、添加 HappyPack 後,編譯 CSS 文件時報如下錯誤:

1

緣由:

css-loader 版本的問題

解決:

從新安裝 css-loader@3.2.0

7、總結

本文主要基於 Webpack 的做用、核心概念、流程,Webpack 的基礎配置,Webpack 優化,編寫 Loader,編寫 Plugin ,從理論到實踐,從基礎到較難,對 Webpack 進行總結掌握,但願對你也有幫助。仍是那句話:紙上得來終覺淺,絕知此事要躬行 ...,若是你沒有手敲過,必定要多動動手 !

博客 github地址爲:github.com/fengshi123/… ,彙總了做者的全部博客,也歡迎關注及 star ~

本文實踐 demo 的 github地址

相關文章
相關標籤/搜索