webpack是一個靜態模塊打包器,此處的模塊能夠是任意文件,包括Sass、TypeScript、模板和圖像等。webpack可根據輸入文件的依賴關係,打包輸出瀏覽器可識別的JavaScript、CSS和HTML等文件,而且能對圖像作優化處理,如圖1所示。html
圖1 webpack打包node
目前,webpack的最新版本是4.33,其配置文件(webpack.config.js)的基本結構以下所示,包含了它的4個核心概念:入口(entry)、輸出(output)、加載器(loader)和插件(plugin)。webpack
module.exports = { entry: {}, //入口 output: {}, //輸出 module: { rules: [] }, //加載器 plugins: [] //插件 };
若是要安裝最新版本的webpack,那麼能夠運行下面這條命令。web
npm install --save-dev webpack
當使用的是webpack 4+版本時,還須要再安裝它的命令行工具,安裝命令以下所示。正則表達式
npm install --save-dev webpack-cli
接下來就能夠運行webpack的打包命令了,以下所示,其中「--config」參數後面會跟着配置文件的路徑。npm
npx webpack --config webpack.config.js
經過package.json的scripts字段可聲明自定義的腳本任務(以下代碼所示),從而就能快捷的執行打包命令,例如「npm run build」。json
{ scripts: { build: "npx webpack --config webpack.config.js" } }
在webpack.config.js中,entry字段是一個入口,記錄着須要處理的模塊。從這個入口開始,webpack會遞歸地構建出模塊之間的依賴關係。數組
1)單個入口瀏覽器
當entry字段是一個字符串類型時,其值就是模塊的相對或絕對路徑,以下所示。babel
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屬性。