webpack 是什麼?我們為什麼要用它?
首先貼出官方解釋:
本質上,webpack 是一個現代 JavaScript 應用程式的靜態模組打包器(module bundler)。當 webpack 處理應用程式時,它會遞迴地構建一個依賴關係圖(dependency graph),其中包含應用程式需要的每個模組,然後將所有這些模組打包成一個或多個 bundle。
通俗的講,webpack 是一個程式碼加工機器,我們把自己的程式碼丟給他,它經過加工之後再還給我們。
以上所說的加工,包括但不限於以下幾點:
- 程式碼打包壓縮
- 程式碼編譯(ES6轉ES5、ts 轉 js 、less\sass 轉 css 等)
- 程式碼優化,提取公共模組
webpack 還可以為我們提供前端靜態服務,極大地方便了我們日常的開發除錯。
綜上,我們之所以使用 webpack ,主要是為了提高開發效率,優化自身程式碼。
webpack 核心概念
當我們把自身程式碼交給 webpack 加工時,必然會需要一個入口,而加工過後的程式碼需要還給我們,肯定也會需要出口,而在加工的過程中 webpack 需要進行一系列的工序。如同麵粉變成麵包,不只是烘烤這麼簡單。
由上,我們來理解 webpack 核心的四個概念:
- entry : 檔案入口
- output : 檔案出口
- module : 工序一 (通過配置,對不同的檔案型別做處理,完成模組程式碼的轉換)
- plugins : 工序二 (通過外掛,來獲取更加強大的加工處理能力,完成更復雜的構建任務)
到這裡,我們便可以寫出一份簡單的配置檔案骨架:
module.exports = {
entry: {
··· 檔案入口
},
output: {
··· 檔案出口
},
module: {
rules: [
··· 一些列的 loader
],
},
plugins: [
··· 一系列的外掛
]
}
複製程式碼
webpack 的基本配置使用
在 webpack 4.0以上版本中為我們提供了 --mode
這個命令配置項,mode 分為 development 和 production 兩個選項,預設為production。它為我們提供了一些簡單的基本配置,對於簡單的專案,我們不再需要配置檔案,mode 預設入口檔案為 src 資料夾 下的 index.js,出口為 dist 資料夾,打包時,我們只需要選擇 mode 的引數即可。
webpack --mode development/production
複製程式碼
在 mode 的預設配置中,已經為我們處理了一些常見的用法。但是,這些預設配置,無法處理非 js 檔案內容。
下面這段話來自知乎:
development預設值會給你最好的開發體驗,它注重:
瀏覽器除錯工具
快速開發週期中的快速增量編譯
在執行過程中提供有效的錯誤資訊
而production預設值會給你提供一系列有效的預設值以便部署你的應用,它注重:
小的輸出體積
執行快速的程式碼
忽略僅在開發時需要的程式碼
不暴露原始碼和檔案路徑
易於使用的輸出產物
複製程式碼
實際上,就目前看來,在專案開發中,我們仍需配置自己的配置檔案。
在日常的開發中,我們一般需要 webpack 為我們解決下面這些問題:
- 構建我們釋出需要的 HTML、CSS、JS 檔案
- 使用 CSS 前處理器來編寫樣式
- 處理和壓縮圖片
- 使用 Babel 來支援 ES 新特性
- 本地提供靜態服務以方便開發除錯
根據以上需求,我們可以這樣配置自己 webpack.config.js :
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin'); // 注意版本號 webpack 4 以上版本請下載 @next 版本
const path = require('path');
module.exports = {
entry: {
index: './src/index.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].min.js'
},
module:{
rules: [
{
test: /\.jsx?$/,// 匹配檔案路徑的正規表示式,通常我們都是匹配檔案型別字尾
exclude: /(node_modules|bower_components)/, // 過濾掉不需要處理的檔案
use: { loader: 'babel-loader' } // 指定使用的 loader
},
{
test: /\.less/,
use: ExtractTextPlugin.extract({ // 使用外掛抽離 css ,生成單獨的 css 檔案
fallback: 'style-loader',
use: ['css-loader', 'less-loader']
})
},
{
test: /\.html$/,
use: [
{
loader: 'html-loader',
options: { // 壓縮 html
minimize: true
}
}
]
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
// 自定義配置,圖片壓縮、處理等
}
}
]
}
]
},
// 提供靜態服務
devServer:{
port: 9999,
headers: { // 新增頭部資訊
"X-Custom-Foo": "bar"
},
proxy: { // 請求代理
"/api": {
target: "http://localhost:3000"
}
}
},
plugins: [
// 每次打包前清除 dist 下的檔案
new CleanWebpackPlugin('dist'),
// 提取樣式,生成單獨檔案
new ExtractTextPlugin("styles.css"),
// 生成新的 html 檔案
new HtmlWebpackPlugin({
filename: 'index.html', // 如果檔名不是 index , 開發時要在 url 處新增檔名
template: path.resolve(__dirname + '/src/index.html'), // 注意路徑,
})
]
}
複製程式碼
package.json (請注意版本號,因為版本更新會造成不可預期的錯誤)
{
"name": "webpack-share",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "webpack-dev-server --open --inline --mode development --progress",
"build": "webpack --mode production --progress"
},
"author": "",
"license": "ISC",
"devDependencies": {
"babel": "^6.23.0",
"babel-core": "^6.26.0",
"babel-loader": "^7.1.4",
"babel-preset-env": "^1.6.1",
"clean-webpack-plugin": "^0.1.19",
"css-loader": "^0.28.11",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.11",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"less": "^3.0.1",
"less-loader": "^4.1.0",
"path": "^0.12.7",
"style-loader": "^0.20.3",
"webpack": "^4.5.0",
"webpack-cli": "^2.0.14",
"webpack-dev-server": "^3.1.2"
}
}
複製程式碼
這樣,一個簡單的前端開發環境就已經配置好了,這份配置檔案解決了之前我們所提出的問題:
1、構建我們釋出需要的 HTML、CSS、JS 檔案
- 使用 html-loader 、 html-webpack-plugin 處理 html 檔案,引入處理後的 css , js 檔案,使用 extract-text-webpack-plugin 抽離單獨的 css 檔案(注意版本號,在 webpack 4 中需要下載 @next 版本)
2、使用 CSS 前處理器來編寫樣式
- 使用 less-loader、 css-loader 、style-loader 處理樣式檔案
3、處理和壓縮圖片
- 使用 file-loader 處理圖片資源
4、使用 Babel 來支援 ES 新特性
- 使用 babel-loader 對 js 編譯
5、本地提供靜態服務以方便開發除錯
- 使用 devServer 提供靜態服務,配置請求代理,避免產生跨域安全問題
如果在專案中引入 react ,我們要做一下簡單修改:
{
test: /\.jsx?$/,// 匹配檔案路徑的正規表示式,通常我們都是匹配檔案型別字尾
exclude: /(node_modules|bower_components)/, // 過濾掉不需要處理的檔案
use: { // 指定使用的 loader
loader: 'babel-loader',
options: {
// presets: ['es2015','react'] webpack 3
presets: ['@babel/preset-react','@babel/preset-es2015'] // webpack 4
}
}
},
複製程式碼
package.json :
"@babel/preset-es2015": "^7.0.0-beta.44",
"@babel/preset-react": "^7.0.0-beta.44",
複製程式碼
如果要引入 antd , 在 less-loader 中還需要做一下修改:
{
test: /\.less/,
use: ExtractTextPlugin.extract({ // 使用外掛抽離 css ,生成單獨的 css 檔案
fallback: 'style-loader',
use: [
{
loader: 'css-loader'
},
{
loader: 'less-loader',
options: { // 引入 js
javascriptEnabled: true
}
}
]
})
}
複製程式碼
一些常用的其他配置
resolve
這裡只做簡單介紹,更多詳情,請看官網
在 webpack 中,和模組路徑解析相關的配置都在 resolve 欄位下,我們可以通過配置 resolve 來提高自身開發的體驗,例如常用的 alias 別名設定,簡化了我們引入檔案的路徑。 首先我們來了解一下在 webpack 中的解析規則:
- 解析相對路徑
1、如果是檔案,直接載入
2、如果是資料夾,則查詢資料夾下 package.json 檔案
- 找到 package.json: 則一般情況會對照 main 屬性,獲取檔案路徑來解析,如果找不到 main 欄位,一般情況話會尋找資料夾下的 index.js
- 找不到 package.json : 會尋找 index.js
- 解析絕對路徑
直接查詢檔案
- 解析模組
由下自上查詢 node_modules 中的模組
alias 別名
alias 可以為一段路徑建立一個別名,使一些較為常用的冗長路徑得到簡化:
題外話:
path.join([path1][, path2][, ...])用於連線路徑。該方法的主要用途在於,會正確使用當前系統的路徑分隔符,Unix系統是/,Windows系統是\。
path.resolve([from ...], to) 將 to 引數解析為絕對路徑。
resolve: {
alias: {
Js: path.resolve(__dirname + '/src/js'),
Less: path.resolve(__dirname, './src/css'), // 模糊匹配: 引用時,只要匹配到 Less 都會被替換
Css$: path.resolve(__dirname, './src/css/index.css') // 精確匹配:引用時,只能 improt 'Css'
}
},
複製程式碼
這樣,我們在引入 css 和 js 時,只需要在別名下查詢相應檔案就好:
// 設定別名前
import '../css/a.less';
// 設定別名後
import 'Less/a.less';
improt 'Css'
複製程式碼
extensions 自動解析確定的擴充套件
resolve: {
// 預設配置
// extensions: [".js", ".json"]
extensions: ['.js','.jsx','.less','.css']
},
複製程式碼
設定補全副檔名陣列,引用檔案時,可以不加字尾,wepack 會從陣列中自動補全。
resolve 其他配置
resolve: {
// 當目錄下有 package.json 檔案時,預設查詢欄位
// 配置 target === "web" 或者 target === "webworker" 時 mainFields 預設值是:
mainFields: ['browser', 'module', 'main'],
// target 的值為其他時,mainFields 預設值為:
mainFields: ["module", "main"],
// 當目錄下沒有 package.json 檔案時,預設查詢檔案
mainFiles: ["index"],
modules: [
path.resolve(__dirname, 'my_modules'), //告訴 webpack 解析模組時應該搜尋的目錄,可以自定義一些自己的模組路徑。
'node_modules',
]
}
複製程式碼
devtool
devtool: 'source-map',
複製程式碼
此選項控制是否生成,以及如何生成 source map。在開發環境時使用,便於準確定位程式碼位置。
webpack-dev-serve
devServer:{
contentBase: path.resolve(__dirname, "dist"), // 未經 webpack 處理的靜態檔案訪問路徑
port: 9999,
publicPath: '/', // 確定應該從哪裡提供 bundle,並且此選項優先。建議將 devServer.publicPath 和 output.publicPath 的值保持一致。
overlay:{ //當有編譯錯誤或者警告的時候顯示一個全屏 overlay
errors:true,
warnings:true,
},
headers: { // 新增頭部資訊
"X-Custom-Foo": "bar"
},
proxy: { // 請求代理
"/api": {
target: "http://localhost:3000",
}
},
},
複製程式碼
開發環境和生產環境
基於開發環境和生產環境的需求差異,我們一般需要使用兩套不同的配置檔案。
首先,我們來看一下開發環境和生產環境環境需求點的異同:
相同點
1、共同的入口
2、共同的程式碼處理
3、同樣的解析配置
3、共同的出口
複製程式碼
不同點
開發環境
1、模組熱更新 // 提高開發效率
2、介面代理 // 方便介面呼叫
3、devtool // 準確定位程式碼位置
生產環境
1、提取公共程式碼
2、壓縮混淆
3、去除無用程式碼
4、檔案壓縮 // 減小程式碼體積
複製程式碼
我們發現,在 wenpack 4 中,mode 的兩個引數已經能很好地解決我們大部分的需求,但是仍有些個性配置需要我們手動建立。我們需要根據不同的業務需求對配置檔案進行拆分,然後利用 webpack-merge 對不同的配置檔案進行合併。
通常情況下,我們會拆分出三個配置檔案:
webpack.base.conf.js // 公共配置
webpack.prod.conf.js // 生產環境配置
webpack.dev.conf.js // 開發環境配置
複製程式碼
你可以把兩份差異配置引入公共配置,然後判斷傳入引數來決定使用那份配置:
webpack-dev-server --config ./webpack.base.conf.js --mode development/production 或 --env development/production
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin'); // 注意版本號 webpack 4 以上版本請下載 @next 版本
const merge = require('webpack-merge')
const webpack = require('webpack');
const path = require('path');
const prodConf = require('./webpack.prod.conf');
const devConf = require('./webpack.dev.conf');
module.exports = (env, argv) => {
console.log(env,'==================', argv.mode)
const baseConf = {
entry: {
index: path.resolve(__dirname, '../src/index.js'),
},
output: {
path: path.resolve(__dirname, '../dist'),
filename: 'js/[name].min.js'
},
····
}
const config = env === 'dev' ? devConf : prodConf;
return merge(baseConf,devConf)
}
複製程式碼
也可以將共同配置分別引入差異配置中,在啟動命令上指定所使用的配置檔案:
webpack-dev-server --config 配置檔案
複製程式碼
優化
略