如果你稍微了解過 HTTP,肯定知道一大堆文件請求對(duì)于瀏覽器是什么樣的災(zāi)難。然而,數(shù)以百計(jì)的 JS 文件和 CSS 文件在現(xiàn)在的 Web Applications 中已經(jīng)是常態(tài)了,這么多的文件如果一個(gè)個(gè)通過 HTML 標(biāo)簽引入,別說瀏覽器,自己都要瘋掉。
簡單來說,Webpack 就是一個(gè)打包工具,它可以讓你把眾多文件,合并成一個(gè)。就像 Webpack 官網(wǎng)圖里展示的那樣。

對(duì)一個(gè)沒有引入 Webpack 的項(xiàng)目來說,如果要實(shí)現(xiàn)這種效果,最先遇到的問題就是:
- 要怎么打包?
- 打包之后的文件會(huì)在哪?
解決了這兩個(gè)問題,我們就能在項(xiàng)目里開始使用 Webpack 了。
所有問題的答案,都可以在 Webpack 的配置文件 webpack.config.js 中找到。
基礎(chǔ)概念
對(duì)于第一個(gè)問題,要怎么打包?
這是 Webpack 要考慮的問題,但同時(shí)我們也需要考慮 -- 怎么讓 Webpack 知道把 哪些文件 打成一個(gè) bundle?
Entry
以 JS 文件為例,通常來說,項(xiàng)目中的 JS 文件之間都是相互依賴的。你極有可能需要把 util.js 通過 import 引入另一個(gè)業(yè)務(wù)相關(guān)的文件 work.js 。那么 work.js 在被打包的時(shí)候, 它依賴的 util.js 也需要被打包在同一個(gè) JS 文件中, 程序的邏輯才不會(huì)被破壞。
如果通過引用一直向上溯源的話,通常會(huì)找到命名類似 src/index.js 的文件,它直接或間接的引用了幾乎所有的 JS 文件(Webpack 可以將文件打包為多個(gè),這里我們只考慮打包為一個(gè)的情況,認(rèn)為 index.js 引用了所有文件)。那么我們?nèi)绻麖?index.js 出發(fā),將它所有直接或間接通過 import 引用的文件全部都打包進(jìn)一個(gè) JS 文件 ,那 index.js 就是我們打包的開始、起點(diǎn)、入口,Webpack 中稱為:entry。
module.exports = {
entry: './src/index.js'
};
entry 可以接收字符串、對(duì)象和數(shù)組。
Webpack 認(rèn)為,如果一個(gè)文件依賴于另一個(gè)文件(非代碼資源,像是圖片,Web 字體等),就存在 dependency。Webpack 從 entry 開始處理,遞歸地建立一個(gè) dependency graph,包含工程所需的所有文件,最終將它們打包為一個(gè)文件供瀏覽器加載。
那這個(gè)文件最終會(huì)在哪,文件名又是什么呢?知道這些信息我們就可以在 index.html 中直接引用它。
Output
這個(gè)問題的答案當(dāng)然是自己定,我們可以通過 output 屬性指定將來打包完畢的文件所在的路徑,以及文件名。Webpack 4 中的默認(rèn)值為 ./dist/main.js ( dist : distribution ),配置方式:
const path = require('path');
module.exports = {
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
}
};
path 和 filename 分別指定 bundle 文件所在文件夾的絕對(duì)路徑的和文件名。
似乎問題已經(jīng)解決了?是的,目前為止我們已經(jīng)可以打包 JS 了。
只不過還有一些問題,比如我們需要打包的 JS 文件中不能使用 ES6 的特性,不能在 JS 中引入 CSS 文件(包括 SASS 文件),打包后文件數(shù)量數(shù)量少了但 bundle 文件所占空間還是比較大。
前面兩個(gè)問題是打包途中就需要考慮的,如果 Webpack 不能處理引入的文件(CSS 文件 和 瀏覽器不能辨識(shí)的 ES6 JS 文件),那打包的過程勢必會(huì)收到影響。
最后一個(gè)問題在 bundle 文件生成后處理就可以,比如壓縮一下文件。
這兩類問題都需要額外的工具協(xié)助。
對(duì)于第一類問題,我需要可以解析引入到 JS 的 CSS 文件的工具(css-loader 和 style-loader),以及將 ES6 標(biāo)準(zhǔn)下的 JS 轉(zhuǎn)為瀏覽器能識(shí)別版本的工具( babel-loader )。 Webpack 將這些解析工具稱為 loader。
Loaders
Webpack 只理解 javascript 和 json,Loaders 可以讓 Webpack 將其他文件轉(zhuǎn)化為 module,隨后就可以加入 denpendency graph。
Loaders 有兩個(gè)屬性可以在 config 文件中配置:
-
test: 指定要轉(zhuǎn)化的文件 -
use:指定轉(zhuǎn)化文件使用的 loader
對(duì)于上面的問題:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader'
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
};
翻譯一下:
- 當(dāng) webpack 的編譯器遇到某個(gè)文件中
require/import以js結(jié)尾的文件時(shí), 先使用babel-loader轉(zhuǎn)化再打包。 - 當(dāng) webpack 的編譯器遇到某個(gè)文件中
require/import以css結(jié)尾的文件時(shí), 先使用css-loader和style-loader轉(zhuǎn)化再打包。
對(duì)于第二類問題,對(duì)打包好的 bundle 文件的處理,比如優(yōu)化、壓縮等等。通常通過一些插件進(jìn)行操作。
Plugins
比如常用的壓縮,Webpack 內(nèi)置這個(gè)插件。
這里我們更進(jìn)一步,通過 Webpack 插件生成 HTML 文件,并且自動(dòng)引入 bundle 之后的文件。顯然,Webpack 需要知道生成的 HTML 文件是什么樣子的,也就是它需要一個(gè)樣板、模板( template ),我們假設(shè)程序的入口 HTML 文件是 src/index.html,通過插件 html-webpack-plugin 來進(jìn)行這個(gè)操作。
使用 plugin 需要先通過 require 引入,再加入到 plugins 數(shù)組中。
const HtmlWebpackPlugin = require('html-webpack-plugin'); //installed via npm
const webpack = require('webpack'); //to access built-in plugins
module.exports = {
plugins: [
new webpack.optimize.UglifyJsPlugin({minimize: true}),
new HtmlWebpackPlugin({template: './src/index.html'})
]
};
現(xiàn)在我們直接使用 dist/index.html 就好了, Webpack 已經(jīng)打包了 JS、CSS 文件并一并注入了這個(gè) HTML 文件。
Plugin 還可以執(zhí)行更廣泛的任務(wù),如打包的優(yōu)化,資源管理和環(huán)境變量注入。大多 plugin 都可以定制化。
匯總
一份包含上面所有概念的 webpack.config.js 看起來差不多是這樣:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader'
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin({minimize: true}),
new HtmlWebpackPlugin({template: './src/index.html'})
]
};
打包入口 entry ,輸出路徑 output,解析文件的 loader ,打包文件處理的插件 plugin。
今天就到這里了。