Webpack打包
webpack是一款目前主流的模塊化打包工具,提供了對前端開發(fā)過程中涉及的所有資源的模塊化打包方案
模塊化打包工具由來
- 解決開發(fā)階段代碼在實際生產(chǎn)運行環(huán)境中的兼容性問題

- 將零散的模塊文件打包到統(tǒng)一的文件中,避免由于模塊文件過多造成頻繁的網(wǎng)絡(luò)請求

- 實現(xiàn)所有前端資源的模塊化,而不僅僅是JS模塊化

快速上手(基本使用)
- 在項目目錄中,執(zhí)行
yarn add webpack webpack-cli --dev安裝webpack依賴 - 執(zhí)行
yarn webpack命令,對項目目錄中的文件進行打包,打包后的文件默認輸出到dist目錄
配置文件及基礎(chǔ)配置
webpack4以上版本支持按照約定零配置直接打包,以
src/index.js為入口文件 -> 以dist/main.js為輸出文件-
使用
webpack.config.js配置文件可以對webpack進行自定義打包配置- entry:指定入口文件
- output:指定輸出文件
- filename:輸出文件名
- path:輸出文件路徑(必須為絕對路徑,可以使用
path.join(__dirname, 'output')獲取絕對路徑) - publicPath:資源目錄,默認值為空字符串
- mode:指定工作模式
- module:
- rules:配置loader加載規(guī)則,一個對象數(shù)組
- test:正則表達式匹配使用loader的文件
- use:指定使用的loader,可以接收loader名字符串(或指定loader文件路徑)、配置對象,或者由多個loader名字符串、配置對象組成的數(shù)組,數(shù)組中配置的多個loader會按照從后往前的順序依次執(zhí)行
- loader:loader名
- options:配置選項,用于指定loader的配置參數(shù)
- rules:配置loader加載規(guī)則,一個對象數(shù)組
- plugins:配置插件,接收一個數(shù)組
// webpack.config.js const path = require('path'); module.exports = { mode: 'none', entry: './src/style.css', output: { filename: 'bundle.js', path: path.join(__dirname, 'output') }, module: { rules: [ { test: /.css$/, use: ['style-loader', 'css-loader'] } ] }, plugins: [ ] }
工作模式
webpakc可以通過命令行參數(shù)
--mode=指定工作模式,默認不指定時執(zhí)行production模式,也可以在配置文件中通過mode配置項進行指定
- production
- 生產(chǎn)模式,會啟用內(nèi)置的插件對打包的代碼進行壓縮優(yōu)化等操作
- development
- 開發(fā)模式,更注重打包效率,不對代碼進行壓縮
- none
- 純打包模式,以最原始的狀態(tài)打包代碼
打包結(jié)果運行原理
以none模式打包代碼,查看打包后的輸出文件,并進行分析
-
入口函數(shù)
-
定義了一個對象來緩存加載的模塊
installedModulesimage-20200921203134994 -
定義了一個函數(shù)
__webpack_require__來加載模塊,返回exportsimage-20200921203154670 -
在定義的
__webpack_require__函數(shù)上掛載了一些數(shù)據(jù)與工具函數(shù)image-20200921203223450 -
在最后調(diào)用
__webpack_require__函數(shù)來加載入口模塊,并返回exportsimage-20200921203237447
-
-
入函數(shù)參數(shù)傳入的是一個由模塊函數(shù)組成的數(shù)組,每個模塊被包裹在一個相同結(jié)構(gòu)的函數(shù)當中
function(module, __webpack_exports__, __webpack_require__){},用來形成模塊私有作用域image-20200921203940524 -
每個模塊函數(shù)中都會執(zhí)行
__webpack_require__.r(__webpack_exports__),用來為exports對象定義一個__esModule標記image-20200921204053586

- 模塊中使用
__webpack_require__加載其他依賴的模塊,并執(zhí)行模塊中相應(yīng)的代碼
資源模塊加載
webpack使用loader實現(xiàn)對模塊的加載,默認loader只對js文件進行加載與解析,對于css等其他資源模塊的加載,則可以通過配置額外的loader來實現(xiàn)
loader是webpack實現(xiàn)前端模塊的核心,通過不同的loader可以實現(xiàn)加載任何類型的資源

在webpack.confg.js中,通過module屬性,配置loader加載規(guī)則
-
module
- rules:配置loader加載規(guī)則,一個對象數(shù)組
- test:正則表達式匹配使用loader的文件
- use:指定使用的loader,可以接收loader名字符串,或者由多個loader名字符串組成的數(shù)組,數(shù)組中配置的多個loader會按照從后往前的順序依次執(zhí)行
module: { rules: [ { test: /.css$/, use: ['style-loader', 'css-loader'] } ] } - rules:配置loader加載規(guī)則,一個對象數(shù)組
導(dǎo)入資源模塊
通常情況下,應(yīng)當使用js文件作為模塊打包的入口,其他資源文件通過import的方式引入
webpack建議應(yīng)當在對應(yīng)的代碼模塊中引入當前模塊所需要的資源(按需加載),而不是在全局入口引入

常用資源加載器-loader
-
file-loader:文件資源加載器、
image-20200921220222982 -
url-loader:類似于file-loader,區(qū)別是當文件小于指定大?。赏ㄟ^options下的limit選項配置)時,可以直接返回DataURL,將圖片等轉(zhuǎn)換成base64編碼直接存放在js文件中,將小文件通過這種方式處理,可以減少頁面加載時,瀏覽器請求資源文件的次數(shù),提高加載效率
url-loader依賴于file-loader,無法單獨使用
-
配置樣例
{ test: /.png$/, use: { loader: 'url-loader', options: { limit: 10 * 1024 // 10KB } } }
-
css-loader:編譯css文件到j(luò)s中,以style形式掛載到html中
image-20200921220207106 -
eslint-loader:語法格式校驗
image-20200921220316275 babel-loader:ES語法轉(zhuǎn)換
html-loader:加載處理html文件
資源加載方式
- 遵循ES Modules標準的import聲明
- 遵循CommonJS標準的require聲明
- 遵循AMD標準的define函數(shù)和require函數(shù)
- css文件中的@import與css屬性中使用的url(例如background-image)
- html中的src、a標簽的href等
核心工作原理
- 以指定js文件為入口,查找資源文件依賴,并生成依賴樹
- 遍歷依賴樹,將依賴的資源模塊交給相應(yīng)的loader進行處理
- 將最終處理的結(jié)果匯總到輸出的bundle.js文件中

開發(fā)一個loader
嘗試開發(fā)一個markdown文件加載器markdown-loader
創(chuàng)建markdown-loader.js文件,使用module.exports導(dǎo)出一個處理函數(shù),接收加載的模塊內(nèi)容作為參數(shù)
-
loader返回的是經(jīng)過處理過后的結(jié)果,最后執(zhí)行的loader,必須返回js腳本
image-20200921225002907 -
直接返回js腳本方式
// markdown-loader.js const marked = require('marked'); module.exports = source => { const html = marked(source) return `module.exports = ${JSON.stringify(html)}` } -
使用html-loader來進行后續(xù)處理方式
// markdown-loader.js const marked = require('marked'); module.exports = source => { const html = marked(source) // return `module.exports = ${JSON.stringify(html)}` return html }{ test: /.md$/, use: [ 'html-loader', './markdown-loader.js' ] }
插件及常用插件
loader用來處理資源打包,plugin則用來處理除了打包以外的其他自動化工作,例如:清除指定目錄、拷貝資源文件、壓縮輸出代碼等
通過webpack.config.js中配置plugins屬性來配置plugin
webpack loader + plugin實現(xiàn)了前端工程化大部分工作
-
clean-webpack-plugin:清除指定目錄,默認清理輸出目錄
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); module.exports = { ... plugins: [ new CleanWebpackPlugin() ] } -
html-webpack-plugin:自動生成html文件
filename:指定生成html文件名,默認index.html
title:html的title
meta:html的meta
template:指定生成html的模板文件,模板文件中的動態(tài)參數(shù)可以使用
<%= htmlWebpackPlugin.options.title %>方式指定-
配置多個htmlWebpackPlugin實例,可以生成多個html文件
const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { ... plugins: [ new HtmlWebpackPlugin({ title: 'hello world', meta: { viewport: '' }, template: 'src/index.html' }), new HtmlWebpackPlugin({ filename: 'about.html' }) ] }
-
copy-webpack-plugin:拷貝指定文件到輸出目錄,接收一個路徑數(shù)組
const CopyWebpackPlugin = require('copy-webpack-plugin'); module.exports = { ... plugins: [ // 開發(fā)階段不建議使用,頻繁的拷貝讀寫磁盤沒有意義,同時會降低打包效率 new CopyWebpackPlugin({ patterns: [ { from: 'public' } ] }), ] }
開發(fā)一個插件
webpack的插件通過hook機制實現(xiàn)
webpack規(guī)定一個插件必須是一個函數(shù),或者一個包含apply方法的對象,apply方法接收一個compiler對象參數(shù)
-
嘗試實現(xiàn)一個去除js文件注釋的插件
class MyPlugin { apply(compiler) { compiler.hooks.emit.tap('MyPlugin', compilation => { for (const name in compilation.assets) { if(name.endsWith('.js')) { const contents = compilation.assets[name].source() const cleanContents = contents.replace(/\/\*\*+\*\//g, '') compilation.assets[name] = { source: () => cleanContents, size: ()=> cleanContents.length, } } } }) } } module.exports = { ... plugins: [ new MyPlugin(), ] }
使用webpack提升開發(fā)體驗
-
自動編譯
- 使用
--watch參數(shù)
- 使用
-
自動刷新瀏覽器
- 可以使用browser-sync工具來實現(xiàn)瀏覽器自動刷新,缺點是對于webpack來說需要額外的工具來實現(xiàn),且需要webpack在監(jiān)聽到文件改動后頻繁的寫入磁盤,更好的方式是使用webpack-dev-server插件
-
Dev Server
webpack-dev-server提供了一個本地開發(fā)服務(wù)器,同時集成了自動編譯、自動刷新瀏覽器等功能
打包編譯的結(jié)果存放在內(nèi)存中,由集成的http server直接讀取,避免了頻繁的磁盤讀寫,提高了編譯效率
-
通過配置文件中的devServer屬性,可以對webpack-dev-server進行相應(yīng)的參數(shù)配置
contentBase:指定額外靜態(tài)資源目錄
-
proxy:代理配置
devServer: { contentBase: 'public', proxy: { '/api': { // http://localhost:8080/api/users -> https://api.github.com/api/users target: 'https://api.github.com', // http://localhost:8080/api/users -> https://api.github.com/users pathRewrite: { '^/api': '', // 正則匹配替換路徑 }, // 以實際代理請求的主機名訪問,localhost對于外部服務(wù)器無法識別 changeOrigin: true, } } }
-
-
Source Map
Source Map用來幫助開發(fā)者調(diào)試壓縮后的js、css文件,便于進行問題定位
在壓縮后的代碼中,通過在最后添加指定格式的注釋,來開啟Souce Map的引用
例如
//# sourceMappingURL=xxxx.map,xxxx.map指代對應(yīng)的Source Map文件名通過配置文件中的devtool屬性
devtool: 'source-map'來配置在webpack中開啟Source Map-
webpack中提供了多種不同模式的Source Map,不同的模式在打包速度、打包質(zhì)量上存在一定的差異
image-20200923231232477- eval模式:將模塊函數(shù)放到eval中執(zhí)行,不生成source map文件,只能定位到代碼來自哪個文件,構(gòu)建速度較快
- eval-source-map模式:將模塊還數(shù)放到eval中執(zhí)行,生成source map文件,可以定位原始代碼位置,構(gòu)建速度最慢
- cheap-eval-source-map模式:定位到的代碼文件經(jīng)過loader轉(zhuǎn)換,只能定位到行
- cheap-module-eval-source-map模式:定位到的代碼文件是轉(zhuǎn)換前的源碼,只能定位到行,構(gòu)建速度慢
- hidden-source-map模式:生成source map,但不在代碼中引入,通常適合工具庫開發(fā)使用
- nosources-source-map模式:可以定位到代碼位置,但是無法查看源碼,在生產(chǎn)模式下使用,保護源碼
-
模式組合
- eval:是否使用eval執(zhí)行代碼模塊
- cheap:source map是否包含行信息
- module:是否能得到loader處理前的源碼
生產(chǎn)模式下,不建議生成source map,容易暴露源碼
-
自動刷新與HMR
自動刷新功能當監(jiān)視到代碼資源文件發(fā)生改動,會自動重新編譯打包,并刷新瀏覽器,此時會導(dǎo)致瀏覽器上正在編輯的內(nèi)容以及操作狀態(tài)會丟失,這樣在實際開發(fā)過程中的體驗并不是很好,更理想的方式是在瀏覽器不刷新的情況下實現(xiàn)代碼資源改動的實時替換,即HMR(Hot Module Replacement)的功能,HMR極大的提升了開發(fā)效率
- webpack-dev-server集成了HMR功能,有兩種開啟方式
- 運行參數(shù)增加
--hot - 配置屬性devServer下增加
hot: true,同時配置啟用webpack.HotModuleReplacementPlugin插件
- 運行參數(shù)增加
- 開啟webpack的HMR后,css樣式就實現(xiàn)了自動熱更新,而js、圖片資源等需要開發(fā)者使用HMR API
module.hot.accept()去手動處理熱更新,通常使用一些前端框架或者腳手架工具創(chuàng)建的前端項目,都已經(jīng)集成了成熟完整的HRM處理方案 - 如果HMR實現(xiàn)上存在異常,webpack會退而使用自動刷新頁面來完成更新,通過使用
hotOnly: true配置,可以只使用HMR而不會使用自動刷新功能
- webpack-dev-server集成了HMR功能,有兩種開啟方式
-
生產(chǎn)環(huán)境優(yōu)化
生產(chǎn)環(huán)境打包與開發(fā)環(huán)境打包通常存在較大的差異,webpack4為不同的模式提供了預(yù)設(shè)的配置,也推薦開發(fā)者為不同模式提供不同的配置文件,例如將通用配置放入webpack.common.js中,為開發(fā)環(huán)境提供webpack.dev.js,為生產(chǎn)環(huán)境提供webpack.prod.js,在對應(yīng)模式的配置文件中,使用webpack-merge插件引入并合并配置,并在執(zhí)行打包命令時使用
--config參數(shù)指定要使用的配置文件,例如webpack --config webpack.prod.jsDefinePlugin:用于為代碼注入全局成員,例如
process.env.NODE_ENV,這是webpack內(nèi)置的插件,可以在配置文件plugins中進行配置-
Tree Shaking:去除未引用代碼(dead-code)、去除冗余代碼,Tree-Shaking并不是一項單獨的配置,是一系列優(yōu)化功能組合使用的結(jié)果,production生產(chǎn)模式下自動開啟
- 在配置文件中使用optimization屬性集中配置優(yōu)化選項
-
usedExports: true只導(dǎo)出外部使用的成員 -
minimize: true壓縮代碼 -
concatenateModules: true盡可能將模塊代碼合并到一個函數(shù)中輸出(默認一個模塊對應(yīng)一個函數(shù)),這樣既提升了代碼執(zhí)行效率,又減少了代碼的體積,這種方式又被稱為Scope Hoisting(作用域提升,webpack3中添加的特性) -
sideEffects: true:一般用于npm包標記副作用,在package.json中配置sideEffects屬性來指定哪些代碼包含副作用
Tree Shaking的實現(xiàn)基于ES Module,當使用了Babel插件或者babel-loader來處理轉(zhuǎn)換es代碼時,可能會將ES Module轉(zhuǎn)換為CommonJS模塊,此時Tree Shaking將不生效,最新的babel-loader中自動禁用了ES Module的轉(zhuǎn)換,Tree Shaking可以生效
-
Code Splitting - 代碼分割
隨著前端工程越來越復(fù)雜,打包形成的bundle.js文件會變的越來越龐大,而并不是所有模塊都需要在應(yīng)用啟動時使用,如果將所有模塊打包到一個文件中,會使得網(wǎng)絡(luò)加載js時間較久。降低應(yīng)用加載啟動的速度(首頁白屏?xí)r間)
-
多入口打包:適合多頁應(yīng)用,每個頁面作為一個打包入口,公共部分單獨提取
// 多入口配置樣例 const path = require('path'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { mode: 'none', entry: { //使用對象而不是數(shù)組,每個屬性代表一個入口 index: './src/index.js', hello: './src/hello.js', }, output: { filename: '[name].bundle.js', path: path.join(__dirname, 'dist'), }, module: { rules: [ { test: /.css$/, use: ['style-loader', 'css-loader'] }, ] }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ filename: 'index.html', template: 'src/index.html', chunks: ['index'], }), new HtmlWebpackPlugin({ filename: 'hello.html', template: 'src/hello.html', chunks: ['hello'], }), ], optimization: { splitChunks: { chunks: 'all' //提取公共js、css到一個單獨的bundle } } } -
動態(tài)導(dǎo)入:按需加載,使用ES Module中的import()函數(shù)來實現(xiàn);動態(tài)導(dǎo)入的模塊會被自動分包,無需額外配置,適合單頁應(yīng)用對路由組件的按需加載
- 在import()函數(shù)中使用指定注釋可以用來指定打包的bundle文件名稱,例如
import(/*webpackChunkName: utils*/ './utils') - MiniCssExtractPlugin:用來實現(xiàn)css文件的按需加載,使用這個插件的時候,css會被提取到一個單獨的文件,可以直接通過link標簽引入,而不是style標簽,因此不需要再使用style-loader,而轉(zhuǎn)為使用MiniCssExtractPlugin.loader;當css文件的大小并不是很大的時候,意義不是很大
- OptimizeCssAssetsWebpackPlugin:壓縮css樣式文件,單獨的css文件不會默認被webpack壓縮,需要借助額外的插件
- 壓縮的插件默認不應(yīng)該放在plugins中,因為開發(fā)階段并不需要對js與css進行壓縮,應(yīng)當將相應(yīng)的插件放入optimization屬性的minimizer配置中,這樣當以production生產(chǎn)模式進行打包時,就會壓縮相應(yīng)的資源,而開發(fā)模式不會進行壓縮
- 在import()函數(shù)中使用指定注釋可以用來指定打包的bundle文件名稱,例如
-
-
Hash文件名:輸出文件名使用占位符實現(xiàn)輸出帶hash值的文件名,有三種hash模式
- [hash]:全局hash,每次打包hash值都會發(fā)生變化
- [chunkhash]:相同chunk使用相同的hash值,每次修改,只有對應(yīng)發(fā)生修改的chunk的hash會發(fā)生變化
- [contenthash]:不同的文件會有獨立的hash值,每次修改,只有對應(yīng)文件發(fā)生修改的hash會發(fā)生變化
在占位符中使用冒號+數(shù)值,可以指定hash位數(shù),使用[contenthash: 8] 8位contenthahs來控制緩存在實際應(yīng)用中較為合適










