1. 程式人生 > >前端利器躬行記(3)——webpack基礎

前端利器躬行記(3)——webpack基礎

  webpack是一個靜態模組打包器,此處的模組可以是任意檔案,包括Sass、TypeScript、模板和影象等。webpack可根據輸入檔案的依賴關係,打包輸出瀏覽器可識別的JavaScript、CSS和HTML等檔案,並且能對影象做優化處理,如圖1所示。

圖1  webpack打包

  目前,webpack的最新版本是4.33,其配置檔案(webpack.config.js)的基本結構如下所示,包含了它的4個核心概念:入口(entry)、輸出(output)、載入器(loader)和外掛(plugin)。

module.exports = {
  entry: {},               //入口
  output: {},              //輸出
  module: { rules: [] },    //載入器
  plugins: []              //外掛
};

一、安裝

  如果要安裝最新版本的webpack,那麼可以執行下面這條命令。

npm install --save-dev webpack

  當使用的是webpack 4+版本時,還需要再安裝它的命令列工具,安裝命令如下所示。

npm install --save-dev webpack-cli

  接下來就可以執行webpack的打包命令了,如下所示,其中“--config”引數後面會跟著配置檔案的路徑。

npx webpack --config webpack.config.js

  通過package.json的scripts欄位可宣告自定義的指令碼任務(如下程式碼所示),從而就能快捷的執行打包命令,例如“npm run build”。

{
  scripts: {
    build: "npx webpack --config webpack.config.js"
  }
}

二、入口

  在webpack.config.js中,entry欄位是一個入口,記錄著需要處理的模組。從這個入口開始,webpack會遞迴地構建出模組之間的依賴關係。

1)單個入口

  當entry欄位是一個字串型別時,其值就是模組的相對或絕對路徑,如下所示。

module.exports = {
  entry: "./index.js"
};

  這其實是一種簡寫,等價於下面的物件形式。物件的鍵就是chunk的名稱,chunk是webpack的特定術語,通常一個chunk對應或多個chunk組成一個bundle(即打包生成的檔案)。

module.exports = {
  entry: {
    main: "./index.js"
  }
};

  entry欄位的值既可以是物件和字串,也可以是由模組路徑組成的陣列,如下程式碼所示,其中index.js和list.js兩個檔案會被合併成一個chunk。

module.exports = {
  entry: ["./index.js", "./list.js"]
};

2)多個入口

  只要在entry的物件中新增多個屬性就能設定多個入口,如下所示。

module.exports = {
  entry: {
    index: "./index.js",
    list: __dirname + "/list.js"
  }
};

  兩個chunk會被分別命名為index和list,其中__dirname是Node.js內建的全域性變數,儲存著當前檔案所處目錄的絕對路徑。

三、輸出

  在webpack.config.js中,output欄位是一個物件,用於配置輸出的資訊。它的filename屬性可宣告輸出的檔名,而另一個path屬性可配置輸出目錄的絕對路徑。與入口不同,在配置檔案中只能存在一個輸出。

1)filename

  如果在入口中聲明瞭多個chunk,那麼在配置輸出時得用佔位符來表示對應的bundle名稱,如下所示。

module.exports = {
  entry: {
    index: "./index.js",
    list:  "./list.js"
  },
  output: {
    filename: "[name].bundle.js"
  }
};

  [name]表示chunk的名稱,例如index和list,還有另外三個常用的佔位符,如表3所示。

表3  filename中的佔位符

佔位符 描述
[id] chunk的唯一識別符號
[hash] chunk的唯一識別符號的hash值
[chunkhash] chunk內容的hash值

  [hash]常與[name]配合使用(如下所示),在執行打包命令後,預設會在配置檔案所處的位置建立dist目錄,並且把生成的bundle檔案放置其中。

module.exports = {
  output: {
    filename: "[name].[hash].bundle.js"
  }
};

2)path

  如果要更改預設的輸出路徑(即不想在dist目錄下生成bundle檔案),那麼可以通過設定path實現。但要注意,它的值必須是絕對路徑,不能是相對路徑,如下所示。

module.exports = {
  output: {
    path: __dirname + "/build"
  }
};

四、載入器

  載入器(loader)能在webpack載入模組時對其進行預處理,即對模組的原始碼進行轉換,下面列出載入器的幾個比較典型的用途。

(1)將瀏覽器無法識別的JSX、Sass等語言轉換成JavaScript、CSS等語言。

(2)把影象轉換成Data URI格式嵌入到JavaScript檔案中。

(3)用ES6的import關鍵字將CSS檔案匯入到JavaScript中。

1)配置

  載入器不僅需要單獨安裝,還得在webpack.config.js中配置module欄位。下面是一個簡單的配置示例,其作用是讓file-loader載入器處理四種類型的影象。

module.exports = {
  module: {
    rules: [{
        test: /\.(png|svg|jpg|gif)$/,
        use: [ "file-loader" ]
    }]
  }
};

  module的值是一個物件,包含一個rules屬性,記錄了模組匹配的規則,而這些規則又會以物件的形式組成一個數組,作為rules屬性的值。每條規則必須包含test和use兩個屬性,前者是一個正則表示式,可設定匹配條件;後者是一個由字串或物件組成的陣列,可指定要使用的載入器。只有當test屬性中的條件匹配成功後,才會讓use屬性中的載入器處理相應的模組。

  接下來將展示載入器的用法,以上面的file-loader為例,首先通過命令將其安裝,如下所示。

npm install --save-dev file-loader

  然後建立一個index.js檔案,其內容就是引入一張avatar.jpg影象,並將例項化的Image物件新增到頁面的<body>元素內,如下所示。

import src from "./avatar.jpg";
var img = new Image();
img.src = src;
document.body.appendChild(img);

  再建立一張index.html頁面,引用打包生成的index.bundle.js檔案,如下所示。

<body>
  <script src="dist/index.bundle.js"></script>
</body>

  最後執行webpack的打包命令,就會在index.html的<body>元素中新增下面的<img>元素。

<img src="68fd51ab711118f323bdddf6de7a0175.jpg" />

  注意,預設情況下,影象會隨著bundle檔案一起被放置到dist目錄下。這樣的話,上述<img>元素將無法讀取到影象,得為其src屬性加上路徑。為了解決該問題,可以利用file-loader提供的配置引數。下面將use屬性中的規則修改成物件形式,用options引數記錄publicPath選項,即定義影象的釋出目錄。

module.exports = {
  module: {
    rules: [{
        test: /\.(png|svg|jpg|gif)$/,
        use: [{
            loader: "file-loader",
            options: { publicPath: "./dist" }
        }]
    }]
  }
};

  當一個條件對應多個載入器時,其執行順序是從右到左。以下面的載入器為例,先執行url-loader,後執行file-loader。

module.exports = {
  module: {
    rules: [{
        test: /\.(png|svg|jpg|gif)$/,
        use: [ "file-loader", "url-loader" ]
    }]
  }
};

  由於目前市面上的載入器有可能無法滿足實際需求,因此官方提供了自定義載入器的方法,具體可參考相關文件。

2)Babel

  在之前的第2篇中,對Babel做了專門的講解,現在利用載入器可以將Babel的配置寫到webpack.config.js中,接下來將演示在webpack中使用Babel。

  首先安裝babel-loader、Babel的核心包以及集合了ES語法的預設,命令如下所示。

npm install --save-dev babel-loader @babel/core @babel/preset-env

  然後在webpack.config.js的module欄位中配置規則,如下所示。

module.exports = {
  module: {
    rules: [{
        test: /\.js$/,
        exclude: /node_modules/,
        use: [{
            loader: "babel-loader",
            options: {
              presets: ['@babel/preset-env']
            }
        }]
    }]
  }
};

  rules中的exclude屬性定義了忽略的條件,即不會對node_modules目錄中的指令碼檔案執行babel-loader。在babel-loader的presets選項中聲明瞭所使用的預設。當執行打包命令後,就會對下面這樣的ES6語法進行編譯,轉換成低版本的ES語法。

let fn = () => true;

五、外掛

  外掛能夠藉助webpack引擎的能力,將自定義的行為注入到webpack的構建流程中,解決載入器無法實現的功能,例如分離打包、壓縮檔案等。外掛不僅能處理模組和編譯過的資源,還能監控檔案的變化。與載入器一樣,外掛也可根據特定需求實現自定義,具體可參考官方文件。

  外掛的配置被放在了plugins欄位中,它的值是一個由外掛例項組成的陣列。接下來將通過html-webpack-plugin來演示外掛的用法。

1)配置

  html-webpack-plugin不僅能根據模板生成一個HTML檔案,還能自動引入所需的bundle檔案,對於那些隨著編譯而發生名稱變化的bundle檔案特別有用。

  要使用它,首先需要將其安裝,相關的命令如下所示。

npm install --save-dev html-webpack-plugin

  然後在webpack.config.js中宣告外掛的例項,如下配置所示,在初始化外掛時,沒有為其傳遞任何引數。

var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  entry: {
    index: "./index.js"
  },
  output: {
    filename: "[name].[hash].bundle.js"
  },
  plugins: [
    new HtmlWebpackPlugin()
  ]
};

  最後執行打包命令,生成的HTML檔案如下所示,其中指令碼檔案的名稱包含了一個hash值。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Webpack App</title>
  </head>
  <body>
    <script src="index.4ee657c406f9babd171a.bundle.js"></script>
  </body>
</html>

2)自定義模板

  除了上面所使用的預設模板之外,html-webpack-plugin還提供了自定義模板的功能。預設情況下,html-webpack-plugin採用的是EJS模板引擎,宣告的載入器是ejs-loader。如果要使用其它模板引擎,那麼必須得把相應的載入器新增到配置檔案中。

  下面利用EJS模板的語法插入頁面標題,首先建立一個template.html模板檔案,如下所示。

<!DOCTYPE html>
<html>
  <head>
    <title><%= htmlWebpackPlugin.options.title %></title>
    <meta charset="utf-8" />
  </head>
  <body>
    <div>模板內容</div>
  </body>
</html>

  然後向外掛傳遞兩個引數:title和template,前者就是頁面標題,後者是模板路徑。如此設定之後,就能渲染出所需要的頁面了。

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({ 
      title: "模板", 
      template: "./template.html" 
    })
  ]
};

六、模組化

  webpack實現了一套相容所有模組化方案的機制,這讓任意檔案皆有可能成為模組,而構建依賴圖和按需打包等功能則都由webpack內部完成。

  在webpack中,能夠表達模組之間依賴關係的方式有多種,例如下面所列的。

(1)CommonJS規範的require()函式。

(2)AMD規範的define()函式。

(3)ES6標準的import語句。

(4)Sass和Less中的@import語句。

(5)CSS中引用影象的url()函式。

(6)<img>元素用於載入影象的src屬性。

&n