前端利器躬行記(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