webpack一直是前端工程師的痛點,因為他的復(fù)雜、分散、loader、plugin這些第三方,讓我們的學(xué)習(xí)成本陡然上升,使我們一直對他的配置模棱兩可,今天帶大家徹底明白他如何配置,擺脫困擾我們很久的痛點。本篇主要是webpack基礎(chǔ)配置詳解,關(guān)于webpack的模塊chunk、編譯階段流程、輸出階段流程、loader的編寫和手寫plugin會在后續(xù)文章推出,為了避免錯過可以關(guān)注我或者收藏我的個人博客www.ngaiwe.com
1.webpack是什么?
WebPack可以看做是模塊打包機:它做的事情是,分析你的項目結(jié)構(gòu),找到JavaScript模塊以及其它的一些瀏覽器不能直接運行的拓展語言(Scss,TypeScript等),并將其打包為合適的格式以供瀏覽器使用。并且跟具你的在項目中的各種需求,實現(xiàn)自動化處理,解放我們的生產(chǎn)力
- 代碼轉(zhuǎn)換:TypeScript 編譯成 JavaScript、SCSS 編譯成 CSS 。
- 文件優(yōu)化:壓縮 JavaScript、CSS、HTML 代碼,壓縮合并圖片等。
- 代碼分割:提取多個頁面的公共代碼、提取首屏不需要執(zhí)行部分的代碼讓其異步加載。
- 模塊合并:在采用模塊化的項目里會有很多個模塊和文件,需要構(gòu)建功能把模塊分類合并成一個文件。
- 自動刷新:監(jiān)聽本地源代碼的變化,自動重新構(gòu)建、刷新瀏覽器。
- 代碼校驗:在代碼被提交到倉庫前需要校驗代碼是否符合規(guī)范,以及單元測試是否通過。
- 自動發(fā)布:更新完代碼后,自動構(gòu)建出線上發(fā)布代碼并傳輸給發(fā)布系統(tǒng)。
2.項目初始化
mkdir webpack-start
cd webpack-start
npm init
3.webpack核心概念
- Entry:入口,webpack執(zhí)行構(gòu)建的第一步將從Entry開始,可抽象理解為輸入
- Module:模塊,在webpacl中一切皆為模塊,一個模塊對應(yīng)一個文件,webpack會從配置的Entry開始遞歸找出所有依賴的模塊
- Chunk:代碼塊,一個chunk由多個模塊組合而成,用于將代碼合并和分割
- Loader:模塊轉(zhuǎn)換器,用于把模塊原內(nèi)容按照需求轉(zhuǎn)換為需要的新內(nèi)容
- Plugin:擴展插件,在webpack構(gòu)建流程中的特定時機注入擴展邏輯來改變構(gòu)建結(jié)果和想要做的事情
- Output:輸入結(jié)果,在webpack經(jīng)過一系列處理并得到最終想要的代碼然后輸出結(jié)果
Webpack 啟動后會從
Entry里配置的Module開始遞歸解析 Entry 依賴的所有 Module。 每找到一個 Module, 就會根據(jù)配置的Loader去找出對應(yīng)的轉(zhuǎn)換規(guī)則,對 Module 進行轉(zhuǎn)換后,再解析出當(dāng)前 Module 依賴的 Module。 這些模塊會以 Entry 為單位進行分組,一個 Entry 和其所有依賴的 Module 被分到一個組也就是一個Chunk。最后 Webpack 會把所有 Chunk 轉(zhuǎn)換成文件輸出。 在整個流程中 Webpack 會在恰當(dāng)?shù)臅r機執(zhí)行 Plugin 里定義的邏輯。
1.Entry
context用來解決配置文件和入口文件不再同一層結(jié)構(gòu),列如我們配置文件在config,入口文件在根目錄,則如下配置
module.exports = {
context: path.join(__dirname, '..'), // 找到根目錄
entry: './main.js' //根目錄下的入口文件
}
最簡單的單頁面(SPA)Entry入口,將main.js引入,并根據(jù)main.js中引用和依賴的模塊開始解析
module.exports = {
entry: './main.js'
}
多頁面(MPA)Entry入口,將多個文件引入,當(dāng)然一般是讀取指定文件夾內(nèi)的入口文件,然后引入
entry: {
home: "./home.js",
about: "./about.js",
contact: "./contact.js"
}
如果是單頁面(傳入的是字符串或字符串?dāng)?shù)組),則chunk會被命名為main,如果是多頁面(傳入一個對象),則每個鍵(key)會是chunk的名稱,描述了chunk的入口起點
2.Output
Object 指示webpack如何去輸出,以及在哪里輸出你的bundle、asset 和其他你所打包或使用 webpack 載入的任何內(nèi)容
-
path:輸出目錄對應(yīng)一個絕對路徑
path: path.resolve(__dirname, 'dist') pathinfo:boolean 默認(rèn)false作用是告訴webpack在bundle中引入所包含模塊信息的相關(guān)注釋,不應(yīng)用于生產(chǎn)環(huán)境(production),對開發(fā)環(huán)境(development)極其有用
publicPath:主要作用是針對打包后的文件里面的靜態(tài)文件路徑處理
-
filename:定義每個輸出bundle的名稱,這些bundle將寫入output.path選項指定的目錄下,對于單入口Entry,filename是一個靜態(tài)名稱
filename: "bundle.js"但是在webpack中我們會用到代碼拆分、各種插件plugin或多入口Entry創(chuàng)建多個bundle,這樣我們就應(yīng)該給每個bundle一個唯一的名稱
filename: "[name].bundle.js"使用內(nèi)部chunk id
filename: "[id].bundle.js"唯一hash生成
filename: "[name].[hash].bundle.js"使用基于每個 chunk 內(nèi)容的 hash
filename: "[chunkhash].bundle.js"
3.Module模塊
處理項目中應(yīng)用的不同模塊,主要配置皆在Rules中,匹配到請求的規(guī)則數(shù)組,這些規(guī)則能夠?qū)δK應(yīng)用loader,或者修改解析器parser
-
Module.noParse: 防止webpack解析的時候,將規(guī)則匹配成功的文件進行解析和忽略大型的library來對性能的優(yōu)化,在被忽略的文件中不應(yīng)該含有import、require和define的調(diào)用
module.exports = { module: { rules: [], noParse: function(content) { return /jquery|lodash/.test(content) // 忽略jquery文件解析,直接編譯打包 } } } -
Rules:創(chuàng)建模塊時,匹配請求的規(guī)則數(shù)組
Rule條件:resource(請求文件的絕對路徑)、issuer(被請求資源的模塊文件的絕對路徑,導(dǎo)入時的位置),比如一個文件A導(dǎo)入文件B,resource是/B,issuer是/A是導(dǎo)入文件時的位置,而不是真正的位置,在規(guī)則中,test/include/exclude/resource對resource匹配,而issuer只對issuer匹配
-
Test/include/exclude/resource/issuer的用法和區(qū)別
module.exports = { modules: { rules: [ { test: /\.js?$/, include: [ path.resolve(__dirname, "app") ], exclude: [ path.resolve(__dirname, "app/demo") ], resource:{ test: /\.js?$/, include: path.resolve(__dirname, "app"), exclude: path.resolve(__dirname, "app/demo") }, issuer: { test: /\.js?$/, include: path.resolve(__dirname, "app"), exclude: path.resolve(__dirname, "app/demo") } } ] } }text:一般是提供一個正則表達式或正則表達式的數(shù)組,絕對路徑符合這個正則的則意味著滿足這個條件
include:是一個字符串或者字符串?dāng)?shù)組,指定目錄中的文件需要走這個規(guī)則
exclude:同樣是一個字符串或者字符串?dāng)?shù)組,指定目錄中的文件不需要走這個規(guī)則
resource:就是對text/include/exclude的一個對象包裝,和他們單獨寫沒有區(qū)別
issuer:和resource有異曲同工的作用,不過區(qū)別在于它是將這個rule應(yīng)用于哪個文件以及這個文件所導(dǎo)入的所有依賴文件
resourceQuery:和resource用法一樣,不過針對的是匹配結(jié)果'?'后面的路徑參數(shù),可以調(diào)用resource中的text等
-
oneOf:表示對該資源只應(yīng)用第一個匹配的規(guī)則,一般結(jié)合resourceQuery
{ test: /\.(png|jpe?g|gif|svg)$/, oneOf: [ { resourceQuery: /inline/, loader: 'url-loader' }, { loader: 'file-loader' } ] }- path/to/foo.png?inline: 會匹配url-loader
- path/to/foo.png?other:會匹配file-loader
- path/to/foo.png: 會匹配file-loader
-
useEntry:object包含著每一個loader并且對應(yīng)loader的配置文件
{ loader: "css-loader", options: { modules: true } }options會傳入loader,可以理解為loader的選項
-
use:是對useEntry的集合,并且對每一個入口指定使用一個loader
use: [ 'style-loader', { loader: 'css-loader', options: { importLoaders: 1 } }, { loader: 'less-loader', options: { noIeCompat: true } } ]
4.Resolve解析
主要用來模塊如何被解析,給webpack提供默認(rèn)值
-
alias:object主要用來讓import和require調(diào)用更方便,設(shè)置初始路徑
module.exports = { alias: { Utilities: path.resolve(__dirname, 'src/utilities/'), Templates: path.resolve(__dirname, 'src/templates/') } } // 最開始的import import Utility from '../../utilities/utility'; // 配置完以后 import Utility from 'Utilities/utility'; enforceExtension:Boolean 默認(rèn)false,表示引用不需要擴展名,為true時,import、require中引用必須加擴展名
-
extensions:Array 自動解析不需要擴展名
extensions: [".js", ".json"] // .js、.json引入不需要擴展名 -
modules:Array webpack解析模塊的時候需要搜索的目錄,一般用于優(yōu)先搜索和非node_modules文件中的自定義模塊
modules: [path.resolve(__dirname, "src"), "node_modules"] //優(yōu)先搜索src目錄
5.Loader
通過使用不同的Loader,Webpack可以要把不同的文件都轉(zhuǎn)成JS文件,比如CSS、ES6/7、JSX等,一般用于module的use中
module: {
rules:[
{
test:/\.css$/,
use:['style-loader','css-loader'],
include:path.join(__dirname,'./src'),
exclude:/node_modules/
}
]
}
具體相關(guān)loader需要查看你要引入的loader官方文檔API,手寫Loader會在下一篇文章具體介紹
6.Plugin插件
Array 擴展webpack,在webpack構(gòu)建流程中的特定時機注入擴展邏輯來改變構(gòu)建結(jié)果和想要做的事情,具體使用查看你引入的plugin官方文檔,手寫plugin會在后續(xù)文章中推出
7.webpack-dev-server
開發(fā)中的server,webpack-dev-server可以快速搭建起本地服務(wù),具體使用查看 webpack-dev-server
8.Devtool
此選項控制是否生成,以及如何生成,官方推薦 SourceMapDevToolPlugin 和 source-map-loader 建議看官方文檔 Devtool 主要用來控制打包品質(zhì)和在dev環(huán)境的調(diào)試便捷度和編譯的快慢
9.Watch
webpack 可以監(jiān)聽文件變化,當(dāng)它們修改后會重新編譯和 HotModuleReplacementPlugin 有相似之處,監(jiān)聽文件變動熱啟動
4.配置webpack
webpack安裝命令
npm install webpack webpack-cli -D
Webpack.config.js
具體用到的plugin插件
- clean-webpack-plugin:用于打包前清空輸出目錄 官方API
- html-webpack-plugin:用于自動產(chǎn)出HTML和引用產(chǎn)出的資源 官方API
- copy-webpack-plugin:用于拷貝靜態(tài)資源,包括未被引用的資源 官方API
- uglifyjs-webpack-plugin:用于壓縮JS可以讓輸出的JS文件體積更小、加載更快、流量更省,還有混淆代碼的加密功能 官方API
- extract-text-webpack-plugin:因為CSS的下載和JS可以并行,當(dāng)一個HTML文件很大的時候,我們可以把CSS單獨提取出來加載 官方API
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
// npm i extract-text-webpack-plugin@next // @next可以安裝下一個非正式版本
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin');
let cssExtract = new ExtractTextWebpackPlugin({
filename: 'css/css.css',
allChunks: true
});
let lessExtract = new ExtractTextWebpackPlugin('css/less.css');
let sassExtract = new ExtractTextWebpackPlugin('css/sass.css');
/**
* 有些時候我們希望把頁面中的CSS文件單獨拉出來保存加載
* extract-text-webpack-plugin
*/
//let pages = ['index', 'base'];
// pages = pages.map(page => new HtmlWebpackPlugin({
// template: './src/index.html',//指定產(chǎn)的HTML模板
// filename: `${page}.html`,//產(chǎn)出的HTML文件名
// title: `${page}`,
// chunks: ['common', `${page}`],//在產(chǎn)出的HTML文件里引入哪些代碼塊
// hash: true,// 會在引入的js里加入查詢字符串避免緩存,
// minify: {
// removeAttributeQuotes: true
// }
// }));
module.exports = {
//先找到每個入口(Entry),然后從各個入口分別出發(fā),找到依賴的模塊(Module),
//然后生成一個Chunk(代碼塊),最后會把Chunk寫到文件系統(tǒng)中(Assets)
entry: './src/main.js',
output: {
path: path.join(__dirname, 'dist'),//輸出的文件夾,只能是絕對路徑
//name是entry名字main,hash根據(jù)打包后的文件內(nèi)容計算出來的一個hash值
filename: '[name].[hash].js' //打包后的文件名
},
resolve: {
//引入模塊的時候,可以不用擴展名
extensions: [".js", ".less", ".json"],
alias: {//別名
"bootstrap": "bootstrap/dist/css/bootstrap.css"
}
},
//表示監(jiān)控源文件的變化,當(dāng)源文件發(fā)生改變后,則重新打包
watch: false,
watchOptions: {
ignored: /node_modules/,
poll: 1000,//每秒鐘詢問的次數(shù)
aggregateTimeout: 500//
},
//devtool: 'source-map',//單獨文件,可以定位到哪一列出錯了
// devtool: 'cheap-module-source-map',//單獨文件,體積更小,但只能定位到哪一行出錯
// devtool: 'eval-source-map',//不會生成單獨文件,
// devtool: 'cheap-module-eval-source-map',//不會生成單獨文件 只定位到行,體積更小
/*
loader有三種寫法
use
loader
use+loader
* */
module: {
rules: [
{
test: require.resolve('jquery'),
use: {
loader: 'expose-loader',
options: '$'
}
},
{
test: /\.js/,
use: {
loader: 'babel-loader',
query: {
presets: ["env", "stage-0", "react"]
}
}
},
{
//file-loader是解析圖片地址,把圖片從源位置拷貝到目標(biāo)位置并且修改原引用地址
//可以處理任意的二進制,bootstrap 里字體
//url-loader可以在文件比較小的時候,直接變成base64字符串內(nèi)嵌到頁面中
test: /\.(png|jpg|gif|svg|bmp|eot|woff|woff2|ttf)/,
loader: {
loader: 'url-loader',
options: {
limit: 5 * 1024,
//指定拷貝文件的輸出目錄
outputPath: 'images/'
}
}
},
{
test: /\.css$/,//轉(zhuǎn)換文件的匹配正則
//css-loader用來解析處理CSS文件中的url路徑,要把CSS文件變成一個模塊
//style-loader 可以把CSS文件變成style標(biāo)簽插入head中
//多個loader是有順序要求的,從右往左寫,因為轉(zhuǎn)換的時候是從右往左轉(zhuǎn)換
//此插件先用css-loader處理一下css文件
//如果壓縮
loader: cssExtract.extract({
use: ["css-loader?minimize"]
})
//loader: ["style-loader", "css-loader", "postcss-loader"]
},
{
test: /\.less$/,
loader: lessExtract.extract({
use: ["css-loader?minimize", "less-loader"]
})
//use: ["style-loader", "css-loader", "less-loader"]
},
{
test: /\.scss$/,
loader: sassExtract.extract({
use: ["css-loader?minimize", "sass-loader"]
})
// use: ["style-loader", "css-loader", "sass-loader"]
},
{
test: /\.(html|htm)/,
loader: 'html-withimg-loader'
}
]
},
plugins: [
//用來自動向模塊內(nèi)部注入變量
// new webpack.ProvidePlugin({
// $: 'jquery'
// }),
new UglifyjsWebpackPlugin(),
new CleanWebpackPlugin([path.join(__dirname, 'dist')]),
//此插件可以自動產(chǎn)出html文件
new HtmlWebpackPlugin({
template: './src/index.html',//指定產(chǎn)的HTML模板
filename: `index.html`,//產(chǎn)出的HTML文件名
title: 'index',
hash: true,// 會在引入的js里加入查詢字符串避免緩存,
minify: {
removeAttributeQuotes: true
}
}),
new CopyWebpackPlugin([{
from: path.join(__dirname, 'public'),
to: path.join(__dirname, 'dist', 'public')
}]),
cssExtract,
lessExtract,
sassExtract
],
//配置此靜態(tài)文件服務(wù)器,可以用來預(yù)覽打包后項目
devServer: {
contentBase: './dist',
host: 'localhost',
port: 8000,
compress: true,//服務(wù)器返回給瀏覽器的時候是否啟動gzip壓縮
}
}
5.總結(jié)
本篇偏向基礎(chǔ),能夠搭建起簡單的webpack配置,高級進階會在后續(xù)文章推出,并且希望大家多去看官方API然后自我總結(jié)輸出,只有將知識輸出出來,才能更好的記憶和學(xué)習(xí)
6.博客
有任何問題可留言或者發(fā)送本人郵箱ngaiwe@126.com