1. 程式人生 > 其它 >淺析webpack基礎核心概念、Loader作用及執行流程理解及常用Loader介紹、Plugin作用及執行流程理解及常用Plugin介紹、Loader與Plugin區別

淺析webpack基礎核心概念、Loader作用及執行流程理解及常用Loader介紹、Plugin作用及執行流程理解及常用Plugin介紹、Loader與Plugin區別

  Webpack 是一個用於現代JS應用程式的靜態模組打包工具。當 webpack 處理應用程式時,它會在內部從一個或多個入口點構建一個依賴圖(dependency graph),然後將你專案中所需的每一個模組組合成一個或多個 bundles,它們均為靜態資源,用於展示你的內容。

  我們看一下Webpack一些核心概念:

1、Entry:入口,指示 Webpack 應該使用哪個模組,來作為構建其內部依賴圖(dependency graph) 的開始

2、Output:輸出結果,告訴 Webpack 在哪裡輸出它所建立的 bundle,以及如何命名這些檔案

3、Module:模組,在 Webpack 裡一切皆模組,一個模組對應著一個檔案。Webpack 會從配置的 Entry 開始遞迴找出所有依賴的模組

4、Chunk:程式碼塊,一個 Chunk 由多個模組組合而成,用於程式碼合併與分割

5、Loader:模組程式碼轉換器,讓webpack能夠去處理除了JS、JSON之外的其他型別的檔案,並將它們轉換為有效模組,以供應用程式使用,以及被新增到依賴圖中

6、Plugin:擴充套件外掛。在webpack執行的生命週期中會廣播出許多事件,plugin可以監聽這些事件,在合適的時機通過webpack提供的api改變輸出結果。常見的有:打包優化,資源管理,注入環境變數

6、Mode:模式,告知 webpack 使用相應模式的內建優化

7、Browser Compatibility:瀏覽器相容性,Webpack 支援所有符合 ES5 標準的瀏覽器(IE8以上版本)

  Webpack的作用非常多,並且我們還可以通過loader和plugin機制去進一步擴充套件能力,按照專案需要去實現個性化的功能。

  Loader 和 Plugin 在 Webpack 裡是支柱能力。在整個構建流程中,Loader 和 Plugin 對編譯結果起著決定性的作用,下面主要講一下 Webpack 中一些常用的 Loader 和 Plugin。

一、Loader 介紹

1、Loader 是什麼?Loader 用於對模組的"原始碼"進行轉換,在 import 或"載入"模組時預處理檔案

  webpack中提供了一種處理多種檔案格式的機制,這便是Loader,我們可以把Loader當成一個轉換器,它可以將某種格式的檔案轉換成Webpack支援打包的模組。

  在Webpack中,一切皆模組,我們常見的Javascript、CSS、Less、Typescript、Jsx、圖片等檔案都是模組,不同模組的載入是通過模組載入器來統一管理的,當我們需要使用不同的 Loader 來解析不同型別的檔案時,我們可以在 module.rules 欄位下配置相關規則。

  Webpack做的事情,僅僅是分析出各種模組的依賴關係,然後形成資源列表,最終打包生成到指定的檔案中。如下圖所示

  左邊是“模組依賴”,然後打包成右邊的“靜態資源”。

  在Webpack內部中,任何檔案都是模組,不僅僅只是JS檔案。預設情況下,在遇到 import 或者 require 載入模組的時候,Webpack只支援對JS和JSON檔案打包,像 css、sass、png 等這些型別的檔案的時候,Webpack則無能為力,這時候就需要配置對應的 Loader 進行檔案內容的解析。

  在載入模組的時候,執行順序如下:entry  ->  loaders  ->  output

  當 Webpack 碰到不識別的模組的時候,Webpack會在配置的中查詢該檔案解析規則。關於配置 Loader 的方式有三種:

(1)配置方式(推薦):在 webpack.config.js 檔案中指定 loader

(2)內聯方式:在每個 import 語句中顯式指定 loader

(3)CLI 方式:在 shell 命令中指定它們

2、配置方式:

// 配置方式:關於loader的配置,是寫在module.rules屬性中,屬性介紹如下:
// rules是一個數組的形式,因此我們可以配置很多個loader
// 每一個loader對應一個物件的形式,物件屬性test為匹配的規則,一般情況為正則表示式
// 屬性use針對匹配到檔案型別,呼叫對應的 loader 進行處理
module.exports = {
  module: {
    rules: [
      {
        test: /.css$/,
        use: [
          { loader: 'style-loader' },
          {
            loader: 'css-loader',
            options: {
              modules: true
            }
          },
          { loader: 'sass-loader' }
        ]
      }
    ]
  }
};

3、Loader的特性

  拿上述程式碼,來講講 Loader 的特性,從上述程式碼可以看到,在處理 css 模組的時候,use 屬性中配置了三個Loader分別處理 css 檔案

  因為Loader支援鏈式呼叫,鏈中的每個Loader會處理之前已處理過的資源,最終變為JS程式碼。順序為相反的順序執行,即上述執行方式為sass-loadercss-loaderstyle-loader

  除此之外,Loader 的特性還有如下:

(1)loader 本質上是一個函式,output=loader(input) // input可為工程原始檔的字串,也可是上一個loader轉化後的結果;

(2)第一個 loader 的傳入引數只有一個:資原始檔(resource file)的內容;

(3)loader支援鏈式呼叫,webpack打包時是按照陣列從後往前的順序將資源交給loader處理的。

(4)支援同步或非同步函式。

  可以通過 loader 的預處理函式,為JS生態系統提供更多能力。使用者現在可以更加靈活地引入細粒度邏輯,例如:壓縮、打包、語言翻譯和更多其他特性

3、程式碼結構通常如下:
// source:資源輸入,對於第一個執行的loader為資原始檔的內容;後續執行的loader則為前一個loader的執行結果
// sourceMap: 可選引數,程式碼的sourcemap結構
// data: 可選引數,其它需要在Loader鏈中傳遞的資訊,比如 posthtml/posthtml-loader 就會通過這個引數傳遞引數的AST物件
const loaderUtils = require('loader-utils');
module.exports = function(source, sourceMap?, data?) {
  // 獲取到使用者給當前 Loader 傳入的 options
  const options = loaderUtils.getOptions(this);
  // TODO: 此處為轉換source的邏輯
  return source;
};

二、常用的 Loader

1、babel-loader基於babel,用於解析JS檔案。babel有豐富的預設和外掛,babel的配置可以直接寫到options裡或者單獨寫道配置檔案裡。

  Babel是一個Javscript編譯器,可以將高階語法(主要是ECMAScript 2015+ )編譯成瀏覽器支援的低版本語法,它可以幫助你用最新版本的JS寫程式碼,提高開發效率。

(1)常用預設:

@babel/preset-env              ES2015+ 語法
@babel/preset-typescript     TypeScript
@babel/preset-react            React

(2)外掛和預設的執行順序:

外掛比預設先執行
外掛執行順序是外掛陣列從前向後執行
預設執行順序是預設陣列從後向前執行

(3)webpack配置程式碼

// webpack.config.js
module: {
  rules: [
    {
      test: /\.m?js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: [
            ['@babel/preset-env', { targets: "defaults" }]
          ],
          plugins: ['@babel/plugin-proposal-class-properties'],
          // 快取 loader 的執行結果到指定目錄,預設為node_modules/.cache/babel-loader,之後的 webpack 構建,將會嘗試讀取快取
          cacheDirectory: true,
        }
      }
    }
  ]
}

  以上options引數也可單獨寫到配置檔案裡,許多其他工具都有類似的配置檔案:ESLint (.eslintrc)、Prettier (.prettierrc)。

  配置檔案我們一般只需要配置 presets(預設陣列) 和 plugins(外掛陣列) ,其他一般也用不到,程式碼示例如下:
// babel.config.js
module.exports = (api) => {
    return {
        presets: [
            '@babel/preset-react',
            [
                '@babel/preset-env', {
                    useBuiltIns: 'usage',
                    corejs: '2',
                    targets: {
                        chrome: '58',
                        ie: '10'
                    }
                }
            ]
        ],
        plugins: [
            '@babel/plugin-transform-react-jsx',
            '@babel/plugin-proposal-class-properties'
        ]
    };
};

2、ts-loader 為webpack提供的 TypeScript loader,打包編譯Typescript

// 1、安裝依賴:
npm install ts-loader --save-dev
npm install typescript --dev

// 2、webpack配置如下:webpack.config.json
module.exports = {
  mode: "development",
  devtool: "inline-source-map",
  entry: "./app.ts",
  output: {
    filename: "bundle.js"
  },
  resolve: {
    // Add `.ts` and `.tsx` as a resolvable extension.
    extensions: [".ts", ".tsx", ".js"]
  },
  module: {
    rules: [
      // all files with a `.ts` or `.tsx` extension will be handled by `ts-loader`
      { test: /\.tsx?$/, loader: "ts-loader" }
    ]
  }
};
// 3、還需要typescript編譯器的配置檔案tsconfig.json:
{
  "compilerOptions": {
    // 目標語言的版本
    "target": "esnext",
    // 生成程式碼的模板標準
    "module": "esnext",
    "moduleResolution": "node",
    // 允許編譯器編譯JS,JSX檔案
    "allowJS": true,
    // 允許在JS檔案中報錯,通常與allowJS一起使用
    "checkJs": true,
    "noEmit": true,
    // 是否生成source map檔案
    "sourceMap": true,
    // 指定jsx模式
    "jsx": "react"
  },
  // 編譯需要編譯的檔案或目錄
  "include": [
    "src",
    "test"
  ],
  // 編譯器需要排除的檔案或資料夾
  "exclude": [
    "node_modules",
    "**/*.spec.ts"
  ]
}

3、markdown-loader:markdown編譯器和解析器

4、raw-loader 可將檔案作為字串匯入

5、file-loader 用於處理檔案型別資源,如jpg,png等圖片。返回值為publicPath為準

// file.js
import img from './webpack.png';
console.log(img); // 編譯後:https://www.tencent.com/webpack_605dc7bf.png
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {        test: /\.(png|jpe?g|gif)$/i,
        loader: 'file-loader',
        options: {
          name: '[name]_[hash:8].[ext]',
          publicPath: "https://www.tencent.com",
        },
      },
    ],
  },
};
// css檔案裡的圖片路徑變成如下:
/* index.less */
.tag {
  background-color: red;
  background-image: url(./webpack.png);
}
/* 編譯後:*/
background-image: url(https://www.tencent.com/webpack_605dc7bf.png);

6、url-loader:它與 file-loader 作用相似,也是處理圖片的,只不過 url-loader 可以設定一個根據圖片大小進行不同的操作,如果該圖片大小大於指定的大小,則將圖片進行打包資源,否則將圖片轉換為base64字串合併到 js 檔案裡。

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|jpeg)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              name: '[name]_[hash:8].[ext]',
              // 這裡單位為(b) 10240 => 10kb
              // 這裡如果小於10kb則轉換為base64打包進js檔案,如果大於10kb則打包到對應目錄
              limit: 10240,
            }
          }
        ]
      }
    ]
  }
}

7、style-loader 通過注入<style>標籤將CSS插入到DOM中

8、css-loader 僅處理css的各種載入語法(@import和url()函式等),就像 js 解析 import/require() 一樣

9、postcss-loader

  PostCSS 是一個允許使用 JS 外掛轉換樣式的工具。 這些外掛可以檢查你的 CSS,支援 CSS Variables 和 Mixins, 編譯尚未被瀏覽器廣泛支援的先進的 CSS 語法,內聯圖片,以及其它很多優秀的功能。PostCSS 在業界被廣泛地應用。PostCSS 的 autoprefixer 外掛是最流行的 CSS 處理工具之一。

10、less-loader:解析less,轉換為css

三、Plugin 介紹

1、Plugin 是什麼?以下來自於《深入淺出 Webpack》

1、Webpack 就像一條生產線,要經過一系列處理流程後才能將原始檔轉換成輸出結果。

這條生產線上的每個處理流程的職責都是單一的,多個流程之間又存在依賴關係,只有完成當前處理後才能交給下一個流程去處理。

外掛就像是一個插入到生產線中的一個功能,在特定的時機對生產線上的資源做處理。

2、Webpack 通過 Tapable 來組織這條複雜的生產線。

Webpack 在執行過程中會廣播事件,外掛只需要監聽它所關心的事件,就能加入到這條生產線中,去改變生產線的運作。

Webpack 的事件流機制保證了外掛的有序性,使得整個系統擴充套件性很好。

2、Plugin(Plug-in)是一種計算機應用程式,它和主應用程式互相互動,以提供特定的功能。它是一種遵循一定規範的應用程式介面編寫出來的程式,只能執行在程式規定的系統下,因為其需要呼叫原純淨系統提供的函式庫或者資料

  webpack中的Plugin也是如此,Plugin賦予其各種靈活的功能,例如打包優化、資源管理、環境變數注入等,它們會執行在webpack的不同階段(鉤子 / 生命週期),貫穿了webpack整個編譯週期。目的在於解決 Loader 無法實現的其他事。

3、配置方式

  這裡講述檔案的配置方式,一般情況,通過配置檔案匯出物件中 plugins 屬性傳入 new 例項物件。如下所示

const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通過 npm 安裝
const webpack = require('webpack'); // 訪問內建的外掛
module.exports = {
  ...
  plugins: [
    new webpack.ProgressPlugin(),
    new HtmlWebpackPlugin({ template: './src/index.html' }),
  ],
};

四、常用 Plugin

1、html-webpack-plugin 基本作用是生成html檔案

(1)單頁應用可以生成一個html入口,多頁應用可以配置多個html-webpack-plugin例項來生成多個頁面入口

(2)為html引入外部資源如script、link,將entry配置的相關入口chunk以及mini-css-extract-plugin抽取的css檔案插入到基於該外掛設定的template檔案生成的html檔案裡面,具體的方式是link插入到head中,script插入到head或body中。

2、clean-webpack-plugin

  預設情況下,這個外掛會刪除webpack的output.path中的所有檔案,以及每次成功重新構建後所有未使用的資源。

  這個外掛在生產環境用的頻率非常高,因為生產環境經常會通過 hash 生成很多 bundle 檔案,如果不進行清理的話每次都會生成新的,導致資料夾非常龐大。

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
    plugins: [
        new CleanWebpackPlugin(),
    ]
};

3、mini-css-extract-plugin:該外掛會將 CSS 提取到單獨的檔案中,為每個包含 CSS 的 JS 檔案建立一個 CSS 檔案。

// 建議 mini-css-extract-plugin 與 css-loader 一起使用
// 將 loader 與 plugin 新增到 webpack 配置檔案中
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
  plugins: [new MiniCssExtractPlugin()],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      }
    ],
  },
};

4、webpack.HotModuleReplacementPlugin:模組熱替換外掛,又稱為 HMR

  該功能會在應用程式執行過程中,替換、新增或刪除 模組,而無需重新載入整個頁面。

  啟動方式有2種:

(1)引入外掛 webpack.HotModuleReplacementPlugin,並且設定 devServer.hot: true

(2)命令列加 --hot 引數

// package.json配置:
{
  "scripts": {
    "start": "NODE_ENV=development webpack serve --progress --mode=development --config=scripts/dev.config.js --hot"
  }
}
// webpack的配置
plugins: [
  // 大多數情況下不需要任何配置
  new webpack.HotModuleReplacementPlugin(),
],
devServer: {
  hot: true,
}

  注意:HMR 絕對不能被用在生產環境。

5、webpack.DefinePlugin 建立一個在編譯時可以配置的全域性常量。這會對開發模式和生產模式的構建允許不同的行為非常有用。

  因為這個外掛直接執行文字替換,給定的值必須包含字串本身內的實際引號。

6、webpack-bundle-analyzer 可以看到專案各模組的大小,可以按需優化

  一個webpack的bundle檔案分析工具,將bundle檔案以可互動縮放的treemap的形式展示。

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

7、SplitChunksPlugin 程式碼分割

五、Loader和Plugin的區別

  上面我們有提到 loader plugin 對應的概念,先來回顧下:

1、loader 是檔案載入器,能夠載入資原始檔,並對這些檔案進行一些處理,諸如編譯、壓縮等,最終一起打包到指定的檔案中

2、plugin 賦予了 webpack 各種靈活的功能,例如打包優化、資源管理、環境變數注入等,目的是解決 loader 無法實現的其他事

  從整個執行時機上來看,如下圖所示:

  可以看到,兩者在執行時機上的區別:

1、loader 執行在打包檔案之前

2、plugins 在整個編譯週期都起作用

  在 webpack 執行的生命週期中會廣播出許多事件,Plugin 可以監聽這些事件,在合適的時機通過 webpack 提供的 api 改變輸出結果

  對於 loader,實質是一個轉換器,將A檔案進行編譯形成B檔案,操作的是檔案,比如將A.scssA.less轉變為B.css,單純的檔案轉換過程