/** 如果您發現錯誤,請一定要告訴窩,拯救一個辣雞(但很帥)的少年就靠您了!*/
本文基於 webpack4
什麼是 webpack
webpack 是一個打包工具。
本質上,webpack 是一個現代 JavaScript 應用程式的靜態模組打包器(module bundler)。當 webpack 處理應用程式時,它會遞迴地構建一個依賴關係圖(dependency graph),其中包含應用程式需要的每個模組,然後將所有這些模組打包成一個或多個 bundle。
什麼是 loader 和 plugins
loader
loader 用於對模組的原始碼進行轉換。loader 可以使你在 import 或"載入"模組時預處理檔案。通過 loader 可以把其他格式的檔案轉為 webpack 可以打包的模組。
loader 有三種配置方式
- 配置檔案:通過
module.rules
欄位配置。 - 內聯:可以在
import
等引入語句中指定 loader。使用!
將資源中的 loader 分開。分開的每個部分都相對於當前目錄解析。 - CLI:通過 CLI 使用 loader
一組鏈式的 loader 將按照相反的順序執行。
常用 loader :babel-loader、style-loader、css-loader、less-loader、postcss-loader、eslint-loader、vue-loader ……
plugins
外掛目的在於解決 loader 無法實現的其他事。
常用 plugins:DefinePlugin(定義全域性常量)、copy-webpack-plugin(複製檔案)、extract-text-webpack-plugin(分離出CSS檔案)IgnorePlugin(忽略檔案)……
webpack 如何解析程式碼模組路徑
webpack 能夠解析三種檔案路徑
-
絕對路徑
-
相對路徑
在
import/require
中給定的相對路徑,會新增此上下文路徑(context path),以產生模組的絕對路徑(absolute path)。 -
模組路徑
模組將在
resolve.modules
中指定的所有目錄(預設是[node_modules]
)內搜尋。
如果路徑指向一個檔案
- 如果路徑具有副檔名,則被直接將檔案打包。
- 否則,將使用
[resolve.extensions]
選項作為副檔名來解析,此選項告訴解析器在解析中能夠接受哪些副檔名(例如.js
,.jsx
)。
如果路徑指向一個資料夾件
- 如果資料夾中包含
package.json
檔案,則按照順序查詢resolve.mainFields
配置選項中指定的欄位。並且package.json
中的第一個這樣的欄位確定檔案路徑。 - 如果
package.json
檔案不存在或者package.json
檔案中的main
欄位沒有返回一個有效路徑,則按照順序查詢resolve.mainFiles
配置選項中指定的檔名,看是否能在import/require
目錄下匹配到一個存在的檔名。 - 副檔名通過
resolve.extensions
選項採用類似的方法進行解析。
hash 和 chunkhash 的區別
hash
:專案中任何一個檔案改動後就會被重新建立,所有的檔名都會使用相同的 hash 指紋。chunkhash
:是根據具體模組檔案的內容計算所得的 hash 值,所以某個檔案的改動只會影響它本身的 hash 指紋,不會影響其他檔案。contenthash
:代表的是文字檔案內容的 hash 值,是指打包後檔案的內容。
webpack-dev-server
啟動 webpack-dev-server
相當於先通過 webpack 打包,然後,啟動本地靜態伺服器執行打包後的程式碼。通過設定 mode
為 development
可以使用一些 webpack 為 development
模式預設的一些配置。
可以通過 devServer
欄位來配置 webpack-dev-server 。
模組熱替換(HMR, hot module replacement)
模組熱替換功能會在應用程式執行過程中,替換、新增或刪除模組,而無需重新載入整個頁面。主要是通過以下幾種方式,來顯著加快開發速度:
- 保留在完全重新載入頁面期間丟失的應用程式狀態。
- 只更新變更內容,以節省寶貴的開發時間。
- 在原始碼中對 CSS/JS 進行修改,會立刻在瀏覽器中進行更新,這幾乎相當於在瀏覽器 devtools 直接更改樣式。
通過設定 devServer.hot=true
可以在 webpack-dev-server 中使用 HMR 。
手寫 webpack 配置
一個簡單的 react 專案配置。使用了常用外掛和 loader ,完整配置可見 github.com/G-lory/fron…
點選檢視程式碼
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CleanWebpackPlugin = require('clean-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
// mode: 'production'
/**
* 配置入口檔案
* 相當於
* entry { main: './src/index.js' }
* 可以配置多個入口
*/
entry: './src/index.js',
/**
* 配置輸出路徑和檔名
*/
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[chunkhash].js'
},
module: {
rules: [
/**
* 配置 babel-loader 來使用 ES6 和 react 語法
* test 和 include/exclude 指定作用的檔案
* use 指定 loader 和相關配置
*/
{
test: /\.js$/,
include: [
path.resolve(__dirname, 'src') // 指定哪些路徑下的檔案需要經過 babel-loader 處理
],
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
}
},
/**
* 將 eslint-loader 設定為前置 loader
* 因為 eslint 檢查的是未經處理的原始碼
* 要保證最先進行處理
* 通過 npx eslint --init 命令初始化一個 eslint 配置檔案
*/
{
enforce: "pre",
test: /\.js$/,
include: [
path.resolve(__dirname, 'src')
],
loader: "eslint-loader"
},
/**
* v4 以後使用 mini-css-extract-plugin 代替 extract-text-webpack-plugin
* style-loader 是把樣式通過 js 生成樣式插入到 html
* MiniCssExtractPlugin 是提取樣式到檔案
* 所以使用 MiniCssExtractPlugin 就不需要 style-loader 了
* css-loader 處理 css 檔案的引用 @import and url() like import/require()
* postcss-loader 配置見檔案 postcss.config.js 自動新增瀏覽器字首
*/
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader"
]
},
/**
* 處理圖片
*/
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]'
}
},
],
}
]
},
plugins: [
/**
* 將打包好的js和css檔案在指定html檔案中自動引入
*/
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'public/index.html'
}),
/**
* 提取css到指定檔案
*/
new MiniCssExtractPlugin({
filename: "[name].[contenthash].css",
// chunkFilename: "[id].css"
}),
/**
* 打包前刪除之前的生成檔案
* 預設刪除 <PROJECT_DIR>/dist/ 下的所有檔案
*/
new CleanWebpackPlugin(),
/**
* 用於建立一些在編譯時可以配置的全域性常量
*/
new webpack.DefinePlugin({
VERSION: JSON.stringify('5fa3b9')
// 如果指定了 mode 會自動新增變數 process.env.NODE_ENV = mode
// "process.env.NODE_ENV": JSON.stringify("production")
}),
/**
* 複製檔案 不經過webpack處理 直接複製到指定目錄
* from 配置來源,to 配置目標路徑
*/
new CopyWebpackPlugin([
{ from: 'src/assets/favicon.ico', to: 'favicon.ico' }
])
],
/**
* 配置程式碼的解析路徑
*/
resolve: {
/**
* 配置路徑別名
*/
alias: {
'@src': path.resolve(__dirname, 'src')
},
/**
* 搜尋模組路徑的目錄
*/
modules: [
"node_modules"
],
// 查詢路徑是自動新增的檔案字尾
// 減少配置以防降低查詢效率
extensions: [".js", ".jsx"]
},
optimization: {
// mode 為 production 時
// 預設開啟壓縮
// minimize: true
// 可以通過提供一個或多個定製過的 TerserPlugin 例項,覆蓋預設壓縮工具(minimizer)。
minimizer: [new UglifyJsPlugin()],
},
}
複製程式碼
webpack 的流程、webpack 的原理、手寫 plugin 和 loader
看了多篇文章之後,確定是我學不起的內容……只找到一篇相對簡單的文章。
乾貨!擼一個webpack外掛(內含tapable詳解+webpack流程)
更深入的以後再學習……