webpack一直是前端工程師的痛點(diǎn),因?yàn)樗膹?fù)雜箍邮、分散、loader叨叙、plugin這些第三方锭弊,讓我們的學(xué)習(xí)成本陡然上升,使我們一直對(duì)他的配置模棱兩可擂错,今天帶大家徹底明白他如何配置味滞,擺脫困擾我們很久的痛點(diǎn)。本篇主要是webpack基礎(chǔ)配置詳解,關(guān)于webpack的模塊chunk剑鞍、編譯階段流程昨凡、輸出階段流程、loader的編寫(xiě)和手寫(xiě)plugin會(huì)在后續(xù)文章推出蚁署,為了避免錯(cuò)過(guò)可以關(guān)注我或者收藏我的個(gè)人博客www.ngaiwe.com
1.webpack是什么便脊?
WebPack可以看做是模塊打包機(jī):它做的事情是,分析你的項(xiàng)目結(jié)構(gòu)光戈,找到JavaScript模塊以及其它的一些瀏覽器不能直接運(yùn)行的拓展語(yǔ)言(Scss哪痰,TypeScript等),并將其打包為合適的格式以供瀏覽器使用久妆。并且跟具你的在項(xiàng)目中的各種需求晌杰,實(shí)現(xiàn)自動(dòng)化處理,解放我們的生產(chǎn)力
- 代碼轉(zhuǎn)換:TypeScript 編譯成 JavaScript镇饺、SCSS 編譯成 CSS 乎莉。
- 文件優(yōu)化:壓縮 JavaScript、CSS奸笤、HTML 代碼惋啃,壓縮合并圖片等。
- 代碼分割:提取多個(gè)頁(yè)面的公共代碼监右、提取首屏不需要執(zhí)行部分的代碼讓其異步加載边灭。
- 模塊合并:在采用模塊化的項(xiàng)目里會(huì)有很多個(gè)模塊和文件,需要構(gòu)建功能把模塊分類(lèi)合并成一個(gè)文件健盒。
- 自動(dòng)刷新:監(jiān)聽(tīng)本地源代碼的變化绒瘦,自動(dòng)重新構(gòu)建、刷新瀏覽器扣癣。
- 代碼校驗(yàn):在代碼被提交到倉(cāng)庫(kù)前需要校驗(yàn)代碼是否符合規(guī)范惰帽,以及單元測(cè)試是否通過(guò)。
- 自動(dòng)發(fā)布:更新完代碼后父虑,自動(dòng)構(gòu)建出線上發(fā)布代碼并傳輸給發(fā)布系統(tǒng)该酗。
2.項(xiàng)目初始化
mkdir webpack-start
cd webpack-start
npm init
3.webpack核心概念
- Entry:入口,webpack執(zhí)行構(gòu)建的第一步將從Entry開(kāi)始士嚎,可抽象理解為輸入
- Module:模塊呜魄,在webpacl中一切皆為模塊,一個(gè)模塊對(duì)應(yīng)一個(gè)文件莱衩,webpack會(huì)從配置的Entry開(kāi)始遞歸找出所有依賴(lài)的模塊
- Chunk:代碼塊爵嗅,一個(gè)chunk由多個(gè)模塊組合而成,用于將代碼合并和分割
- Loader:模塊轉(zhuǎn)換器笨蚁,用于把模塊原內(nèi)容按照需求轉(zhuǎn)換為需要的新內(nèi)容
- Plugin:擴(kuò)展插件睹晒,在webpack構(gòu)建流程中的特定時(shí)機(jī)注入擴(kuò)展邏輯來(lái)改變構(gòu)建結(jié)果和想要做的事情
- Output:輸入結(jié)果趟庄,在webpack經(jīng)過(guò)一系列處理并得到最終想要的代碼然后輸出結(jié)果
Webpack 啟動(dòng)后會(huì)從
Entry
里配置的Module
開(kāi)始遞歸解析 Entry 依賴(lài)的所有 Module。 每找到一個(gè) Module册招, 就會(huì)根據(jù)配置的Loader
去找出對(duì)應(yīng)的轉(zhuǎn)換規(guī)則岔激,對(duì) Module 進(jìn)行轉(zhuǎn)換后,再解析出當(dāng)前 Module 依賴(lài)的 Module是掰。 這些模塊會(huì)以 Entry 為單位進(jìn)行分組虑鼎,一個(gè) Entry 和其所有依賴(lài)的 Module 被分到一個(gè)組也就是一個(gè)Chunk
。最后 Webpack 會(huì)把所有 Chunk 轉(zhuǎn)換成文件輸出键痛。 在整個(gè)流程中 Webpack 會(huì)在恰當(dāng)?shù)臅r(shí)機(jī)執(zhí)行 Plugin 里定義的邏輯炫彩。
1.Entry
context用來(lái)解決配置文件和入口文件不再同一層結(jié)構(gòu),列如我們配置文件在config絮短,入口文件在根目錄江兢,則如下配置
module.exports = {
context: path.join(__dirname, '..'), // 找到根目錄
entry: './main.js' //根目錄下的入口文件
}
最簡(jiǎn)單的單頁(yè)面(SPA)Entry入口,將main.js引入丁频,并根據(jù)main.js中引用和依賴(lài)的模塊開(kāi)始解析
module.exports = {
entry: './main.js'
}
多頁(yè)面(MPA)Entry入口杉允,將多個(gè)文件引入,當(dāng)然一般是讀取指定文件夾內(nèi)的入口文件席里,然后引入
entry: {
home: "./home.js",
about: "./about.js",
contact: "./contact.js"
}
如果是單頁(yè)面(傳入的是字符串或字符串?dāng)?shù)組)叔磷,則chunk會(huì)被命名為main,如果是多頁(yè)面(傳入一個(gè)對(duì)象)奖磁,則每個(gè)鍵(key)會(huì)是chunk的名稱(chēng)改基,描述了chunk的入口起點(diǎn)
2.Output
Object 指示webpack如何去輸出,以及在哪里輸出你的bundle咖为、asset 和其他你所打包或使用 webpack 載入的任何內(nèi)容
-
path:輸出目錄對(duì)應(yīng)一個(gè)絕對(duì)路徑
path: path.resolve(__dirname, 'dist')
pathinfo:boolean 默認(rèn)false作用是告訴webpack在bundle中引入所包含模塊信息的相關(guān)注釋?zhuān)粦?yīng)用于生產(chǎn)環(huán)境(production)秕狰,對(duì)開(kāi)發(fā)環(huán)境(development)極其有用
publicPath:主要作用是針對(duì)打包后的文件里面的靜態(tài)文件路徑處理
-
filename:定義每個(gè)輸出bundle的名稱(chēng),這些bundle將寫(xiě)入output.path選項(xiàng)指定的目錄下躁染,對(duì)于單入口Entry鸣哀,filename是一個(gè)靜態(tài)名稱(chēng)
filename: "bundle.js"
但是在webpack中我們會(huì)用到代碼拆分、各種插件plugin或多入口Entry創(chuàng)建多個(gè)bundle吞彤,這樣我們就應(yīng)該給每個(gè)bundle一個(gè)唯一的名稱(chēng)
filename: "[name].bundle.js"
使用內(nèi)部chunk id
filename: "[id].bundle.js"
唯一hash生成
filename: "[name].[hash].bundle.js"
使用基于每個(gè) chunk 內(nèi)容的 hash
filename: "[chunkhash].bundle.js"
3.Module模塊
處理項(xiàng)目中應(yīng)用的不同模塊我衬,主要配置皆在Rules中,匹配到請(qǐng)求的規(guī)則數(shù)組备畦,這些規(guī)則能夠?qū)δK應(yīng)用loader低飒,或者修改解析器parser
-
Module.noParse: 防止webpack解析的時(shí)候许昨,將規(guī)則匹配成功的文件進(jìn)行解析和忽略大型的library來(lái)對(duì)性能的優(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)建模塊時(shí)莉恼,匹配請(qǐng)求的規(guī)則數(shù)組
Rule條件:resource(請(qǐng)求文件的絕對(duì)路徑)拌喉、issuer(被請(qǐng)求資源的模塊文件的絕對(duì)路徑,導(dǎo)入時(shí)的位置)俐银,比如一個(gè)文件A導(dǎo)入文件B尿背,resource是/B,issuer是/A是導(dǎo)入文件時(shí)的位置捶惜,而不是真正的位置田藐,在規(guī)則中,test/include/exclude/resource對(duì)resource匹配吱七,而issuer只對(duì)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:一般是提供一個(gè)正則表達(dá)式或正則表達(dá)式的數(shù)組汽久,絕對(duì)路徑符合這個(gè)正則的則意味著滿足這個(gè)條件
include:是一個(gè)字符串或者字符串?dāng)?shù)組,指定目錄中的文件需要走這個(gè)規(guī)則
exclude:同樣是一個(gè)字符串或者字符串?dāng)?shù)組踊餐,指定目錄中的文件不需要走這個(gè)規(guī)則
resource:就是對(duì)text/include/exclude的一個(gè)對(duì)象包裝景醇,和他們單獨(dú)寫(xiě)沒(méi)有區(qū)別
issuer:和resource有異曲同工的作用,不過(guò)區(qū)別在于它是將這個(gè)rule應(yīng)用于哪個(gè)文件以及這個(gè)文件所導(dǎo)入的所有依賴(lài)文件
resourceQuery:和resource用法一樣吝岭,不過(guò)針對(duì)的是匹配結(jié)果'?'后面的路徑參數(shù)三痰,可以調(diào)用resource中的text等
-
oneOf:表示對(duì)該資源只應(yīng)用第一個(gè)匹配的規(guī)則,一般結(jié)合resourceQuery
{ test: /\.(png|jpe?g|gif|svg)$/, oneOf: [ { resourceQuery: /inline/, loader: 'url-loader' }, { loader: 'file-loader' } ] }
- path/to/foo.png?inline: 會(huì)匹配url-loader
- path/to/foo.png?other:會(huì)匹配file-loader
- path/to/foo.png: 會(huì)匹配file-loader
-
useEntry:object包含著每一個(gè)loader并且對(duì)應(yīng)loader的配置文件
{ loader: "css-loader", options: { modules: true } }
options會(huì)傳入loader窜管,可以理解為loader的選項(xiàng)
-
use:是對(duì)useEntry的集合散劫,并且對(duì)每一個(gè)入口指定使用一個(gè)loader
use: [ 'style-loader', { loader: 'css-loader', options: { importLoaders: 1 } }, { loader: 'less-loader', options: { noIeCompat: true } } ]
4.Resolve解析
主要用來(lái)模塊如何被解析,給webpack提供默認(rèn)值
-
alias:object主要用來(lái)讓import和require調(diào)用更方便微峰,設(shè)置初始路徑
module.exports = { alias: { Utilities: path.resolve(__dirname, 'src/utilities/'), Templates: path.resolve(__dirname, 'src/templates/') } } // 最開(kāi)始的import import Utility from '../../utilities/utility'; // 配置完以后 import Utility from 'Utilities/utility';
enforceExtension:Boolean 默認(rèn)false舷丹,表示引用不需要擴(kuò)展名,為true時(shí)蜓肆,import颜凯、require中引用必須加擴(kuò)展名
-
extensions:Array 自動(dòng)解析不需要擴(kuò)展名
extensions: [".js", ".json"] // .js、.json引入不需要擴(kuò)展名
-
modules:Array webpack解析模塊的時(shí)候需要搜索的目錄仗扬,一般用于優(yōu)先搜索和非node_modules文件中的自定義模塊
modules: [path.resolve(__dirname, "src"), "node_modules"] //優(yōu)先搜索src目錄
5.Loader
通過(guò)使用不同的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,手寫(xiě)Loader會(huì)在下一篇文章具體介紹
6.Plugin插件
Array 擴(kuò)展webpack退个,在webpack構(gòu)建流程中的特定時(shí)機(jī)注入擴(kuò)展邏輯來(lái)改變構(gòu)建結(jié)果和想要做的事情募壕,具體使用查看你引入的plugin官方文檔,手寫(xiě)plugin會(huì)在后續(xù)文章中推出
7.webpack-dev-server
開(kāi)發(fā)中的server语盈,webpack-dev-server可以快速搭建起本地服務(wù)舱馅,具體使用查看 webpack-dev-server
8.Devtool
此選項(xiàng)控制是否生成,以及如何生成刀荒,官方推薦 SourceMapDevToolPlugin 和 source-map-loader 建議看官方文檔 Devtool 主要用來(lái)控制打包品質(zhì)和在dev環(huán)境的調(diào)試便捷度和編譯的快慢
9.Watch
webpack 可以監(jiān)聽(tīng)文件變化代嗤,當(dāng)它們修改后會(huì)重新編譯和 HotModuleReplacementPlugin 有相似之處棘钞,監(jiān)聽(tīng)文件變動(dòng)熱啟動(dòng)
4.配置webpack
webpack安裝命令
npm install webpack webpack-cli -D
Webpack.config.js
具體用到的plugin插件
- clean-webpack-plugin:用于打包前清空輸出目錄 官方API
- html-webpack-plugin:用于自動(dòng)產(chǎn)出HTML和引用產(chǎn)出的資源 官方API
- copy-webpack-plugin:用于拷貝靜態(tài)資源,包括未被引用的資源 官方API
- uglifyjs-webpack-plugin:用于壓縮JS可以讓輸出的JS文件體積更小干毅、加載更快宜猜、流量更省,還有混淆代碼的加密功能 官方API
- extract-text-webpack-plugin:因?yàn)镃SS的下載和JS可以并行,當(dāng)一個(gè)HTML文件很大的時(shí)候硝逢,我們可以把CSS單獨(dú)提取出來(lái)加載 官方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可以安裝下一個(gè)非正式版本
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');
/**
* 有些時(shí)候我們希望把頁(yè)面中的CSS文件單獨(dú)拉出來(lái)保存加載
* 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,// 會(huì)在引入的js里加入查詢(xún)字符串避免緩存,
// minify: {
// removeAttributeQuotes: true
// }
// }));
module.exports = {
//先找到每個(gè)入口(Entry)姨拥,然后從各個(gè)入口分別出發(fā),找到依賴(lài)的模塊(Module)渠鸽,
//然后生成一個(gè)Chunk(代碼塊),最后會(huì)把Chunk寫(xiě)到文件系統(tǒng)中(Assets)
entry: './src/main.js',
output: {
path: path.join(__dirname, 'dist'),//輸出的文件夾垫毙,只能是絕對(duì)路徑
//name是entry名字main,hash根據(jù)打包后的文件內(nèi)容計(jì)算出來(lái)的一個(gè)hash值
filename: '[name].[hash].js' //打包后的文件名
},
resolve: {
//引入模塊的時(shí)候,可以不用擴(kuò)展名
extensions: [".js", ".less", ".json"],
alias: {//別名
"bootstrap": "bootstrap/dist/css/bootstrap.css"
}
},
//表示監(jiān)控源文件的變化拱绑,當(dāng)源文件發(fā)生改變后综芥,則重新打包
watch: false,
watchOptions: {
ignored: /node_modules/,
poll: 1000,//每秒鐘詢(xún)問(wèn)的次數(shù)
aggregateTimeout: 500//
},
//devtool: 'source-map',//單獨(dú)文件,可以定位到哪一列出錯(cuò)了
// devtool: 'cheap-module-source-map',//單獨(dú)文件猎拨,體積更小膀藐,但只能定位到哪一行出錯(cuò)
// devtool: 'eval-source-map',//不會(huì)生成單獨(dú)文件,
// devtool: 'cheap-module-eval-source-map',//不會(huì)生成單獨(dú)文件 只定位到行红省,體積更小
/*
loader有三種寫(xiě)法
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)位置并且修改原引用地址
//可以處理任意的二進(jìn)制,bootstrap 里字體
//url-loader可以在文件比較小的時(shí)候吧恃,直接變成base64字符串內(nèi)嵌到頁(yè)面中
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用來(lái)解析處理CSS文件中的url路徑,要把CSS文件變成一個(gè)模塊
//style-loader 可以把CSS文件變成style標(biāo)簽插入head中
//多個(gè)loader是有順序要求的虾啦,從右往左寫(xiě),因?yàn)檗D(zhuǎn)換的時(shí)候是從右往左轉(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: [
//用來(lái)自動(dòng)向模塊內(nèi)部注入變量
// new webpack.ProvidePlugin({
// $: 'jquery'
// }),
new UglifyjsWebpackPlugin(),
new CleanWebpackPlugin([path.join(__dirname, 'dist')]),
//此插件可以自動(dòng)產(chǎn)出html文件
new HtmlWebpackPlugin({
template: './src/index.html',//指定產(chǎn)的HTML模板
filename: `index.html`,//產(chǎn)出的HTML文件名
title: 'index',
hash: true,// 會(huì)在引入的js里加入查詢(xún)字符串避免緩存,
minify: {
removeAttributeQuotes: true
}
}),
new CopyWebpackPlugin([{
from: path.join(__dirname, 'public'),
to: path.join(__dirname, 'dist', 'public')
}]),
cssExtract,
lessExtract,
sassExtract
],
//配置此靜態(tài)文件服務(wù)器痕寓,可以用來(lái)預(yù)覽打包后項(xiàng)目
devServer: {
contentBase: './dist',
host: 'localhost',
port: 8000,
compress: true,//服務(wù)器返回給瀏覽器的時(shí)候是否啟動(dòng)gzip壓縮
}
}
5.總結(jié)
本篇偏向基礎(chǔ)傲醉,能夠搭建起簡(jiǎn)單的webpack配置,高級(jí)進(jìn)階會(huì)在后續(xù)文章推出呻率,并且希望大家多去看官方API然后自我總結(jié)輸出硬毕,只有將知識(shí)輸出出來(lái),才能更好的記憶和學(xué)習(xí)
6.博客
有任何問(wèn)題可留言或者發(fā)送本人郵箱ngaiwe@126.com