針對技術而言,越學越感受不會的太多。多是沒到達必定的境界。在網上衝浪學習的時候,看到有些大佬文筆從容,思路清晰。對某個技術點的深度刨析。真產生了一種發自心裏的尊重和佩服和尊重和佩服。有些時候,一段文字就能點醒你,原來是這樣!!!有時候感受到這東西被嚼碎了往你嘴裏塞你都嚼不動的感受(無奈)。本人對於webpack的學習也有一段時間了。想借這篇博文梳理webpack相關的知識體系。也是對於本身學習的一段總結。css
# 像解析Tapable事件流和實現,分析webpack-cli源碼,解析構建流程,實現xx帶有難度的loader或plugin均都不在本文章之列(都不會)
複製代碼
::經過webpack將零散的模塊代碼打包到同一個JavaScript文件中,對於代碼中有環境兼容的問題,經過模塊加載器(loader)將其轉化,webpack還能夠進行代碼拆分。對應用中的代碼根據須要打包(按需打包),實現了漸進式加載。這樣就解決了文件太碎或太大的問題。webpack會以模塊化的方式去載入任意類型的文件(import './index.css')。html
webpack解決了前端總體的模塊化,而不是單指JavaScript的模塊化。全部的資源均可以看作一個模塊。前端
import j1 from './index.js'
import c1 from './index.css'
import h1 from './index.html'
import p1 from './index.png'
....
複製代碼
下面來看一下vue
webpack是如何知道這是一個模塊,我要對他進行打包的呢?java
webpack並不會對入口文件中全部的數據進行無腦打包,而是須要觸發方式。react
衆所周知,ESModule 的 import 語法會被webpack看成一個模塊進行打包。那麼還有啥方式?jquery
javaScript代碼:Commonjs規範的require語法、AMD的require語法webpack
非javaScript代碼:衆所周知,非javaScript代碼是須要經過loader處理的web
css: 在loader處理css的過程當中,像樣式代碼中的@import/url 也會觸發打包機制。vue-cli
處理css文件時,咱們使用css-loader,css-loader若是發現了@import語法或者url語法會將其引入的路徑做爲一個新的模塊進行打包。好比:background-image: url(background.png); webpack發現了.png文件,會將該文件交給url-loader進行處理。好比@import url(reset.css); webpack發現了.css文件,會出發資源加載,而後將文件交給css-loader處理。
html: 在loader處理html的過程當中,像src也會觸發打包機制。若是須要更多的觸發機制,須要看loader有沒有暴露接口,若是提供,須要本身配置。
只討論一點:關於對import/require語法的支持。
瀏覽器是不支持import/require語法的,那麼這些語法是怎麼被轉換了呢?由於webpack提供了基礎代碼"替換了"import這些語法。 而gulp就是一個純粹的自動化構建工具流。沒有提供這些基礎代碼讓用戶輕鬆的使用模塊化語法。
下面來看一下
基礎代碼或引導代碼webpack是如何實現的? 或者說webpack是對import/require等語法的實現?
ps: 如下是在mode: none的時候的打包方式。在mode: development的時候有所不一樣。
/******/ (function (modules) { // webpackBootstrap
.....
})
([
/* 0 - 入口文件*/
/***/ (function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _testA_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); // 也就是import testA from './testA.js'
/* harmony import */ var _testB_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2); // 也就是import testB from './testB.js'
console.log(_testA_js__WEBPACK_IMPORTED_MODULE_0__["default"], _testB_js__WEBPACK_IMPORTED_MODULE_1__["default"]);
}),
/* 1 第一個引入的模塊testA*/
/***/ (function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
var t1 = 'hello1';
/* harmony default export */ __webpack_exports__["default"] = (t1);
}),
/* 2 第二個引入的模塊testB*/
/***/ (function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
var t2 = 'hello2';
/* harmony default export */ __webpack_exports__["default"] = (t2);
})
]);
複製代碼
從上面能夠看到webpack是經過__webpack_require__方法實現了 import x from './x.xx' 的語法。default的意思是默認導出。這個匿名函數的參數是一個數組,每個模塊都做爲了這個數組中的成員。模塊被解析成了一個個函數,從而產生了獨立的做用域, 模塊與模塊之間不會產生變量衝突的問題。 在mode: development的時候,會有一些差別,但大致上是相同的。webpack打包事後提供的引導代碼讓模塊與模塊之間的關係清晰獨立,並且更容易進行拆分和組合。
總結:
把全部的模塊放到同一個文件中,提供基礎代碼讓模塊與模塊之間的關係保持原有的關係。 將全部的模塊都做爲巨大匿名函數的數組參數中的成員,數組中的每一個成員都是一個函數,也就是說webpack將每個模塊都做爲一個函數。以維持模塊的私有化。從第一個入口參數開始執行, 只會先執行下標爲0的函數。 每個模塊都對應一個下標,webpack將模塊與模塊之間的關係在編譯階段就作好了處理。好比a.js import b.js。 b.js的下標爲3, 那麼就會在a.js這個函數中,webpack.require(3) , 這樣編譯好,webpack就是這樣維護模塊與模塊之間的關係的。
webpack能夠零配置,默認 是src/index.js --> dist/main.js
webpack會將遇到的全部模塊都看成JavaScript去處理
1. 對於同一個資源能夠依次使用多個loader
2. 在模塊加載的時候工做
複製代碼
Loader 專一實現資源模塊加載,去實現總體模塊的打包
webpack 加強了webpack自動化能力
e g. plugin 能夠清除打包的目錄、能夠拷貝一些資源文件、壓縮輸出的代碼等等等的能力。
webpack的插件機制是由鉤子機制實現的。相似於web中的事件,插件的工做過程當中會有不少的環節,爲了便於插件的擴展,webpack給每個環節掛載鉤子,這樣插件的運行和開發就是在鉤子中擴展能力。
plugin必須是一個函數或者是一個包含apply方法的對象。
實現plugin是經過在生命週期的鉤子中關在函數實現擴展,達到插件的目的。
線上文件引入jquery.min.js, 若是須要須要調試jquery.js的話, 則須要在引入的jquery.min.js最後一行加上
//# sourceMappingURL=jquery-3.4.1.min.map
告訴此文件去尋找jquery-3.4.1.min.map。該文件記錄了轉換以後的代碼和轉換以前的代碼的映射關係。
目前,webpack對sourcemap的風格支持 有 12種實現方式,每種方式的效率和效果各不相同。
eval模式
eval('console.log(123)') //VM122:1 運行在一個臨時的虛擬機上。
eval('console.log(123) //# sourceURL=./foo/bar.js') // ./foo/bar.js:1 運行環境就是./foo/bar.js
// 經過sourceURL 就能夠指定該運行環境名稱或者說是路徑。
複製代碼
devtools: eval , 設置成eval模式,能夠看到打包後的模塊化代碼,在打包後的bundle.js中,每一個模塊的執行都使用eval包裹執行,在eval的最後能夠看到//# sourceURL=webpack:///./src/main.js?
這樣的信息來標註文件路徑。表明該模塊只想源文件的路徑。構建的速度最快,可是效果也很通常。只能定位到是哪一個文件有問題。不會生成source map
devtools: eval-source-map, 一樣也是使用eval函數執行模塊代碼,除了能夠幫咱們定位到出現問題的文件,還能夠肯定行列信息。這種相比較eval,在生成的js內部去生成了以dataURL的形式引入生成的source map。這個sourcemap是通過babel轉換的,而不是最原始的。
devtools: cheap-eval-source-map , 在上一個eval-source-map的基礎上加了一個cheap,就是廉價的,便宜的,用計算機術語來講就是閹割版。相比較上一個只能定位到行,可是不能定位到列的信息。可是構建的速度加快了。source map原理同上
cheap-module-eval-source-map, 相比較cheap-eval-source-map, source map映射的真正的源代碼,而不是編譯後的。會產生 xxx.js.map文件。其餘的痛cheap-eval-source-map
inline-source-map, 和 source map 是效果是相同的,可是的.js.map文件是以dataURL的形式放到編譯後文件的最後一行。用#sourceMappingURL引入。普通的就是生成 .map.js文件
hidden-source-map, 構建過程當中生成了source map文件, 在代碼中沒有使用註釋的方式引入sourcemap,通常咱們在開發第三方包的時候會使用這個sourcemap風格。
nosources-source-map, 能看到錯誤出現的位置,可是看不到源代碼,只提供錯誤出現的位置,可是不給你顯示,這是在生產環境中防止暴露源代碼的一種方案。
選擇最佳實踐的sourcemap
開發環境下:cheap-module-eval-source-map。
生產環境下:none
生產環境下: nosources-source-map
webpack打包的模塊會默認放到output的目錄中。
import icon from './icon.png'
// 若是沒有publicPath,則icon爲 './[hash].png'
// 若是有publicPath,則icon爲 publicPath+ './[hahs].png'
複製代碼
publicPath
爲基準,使用它來決定在哪一個目錄下啓用服務,來訪問 webpack 輸出的文件。html頁面中資源路徑,被自動注入了output中filename的值。
output中filename的值。/backend/js/app.11c8b942e5dd4b3a51b5.js?2c61c09c3e5bff69e658, 自動注入到index.html中做爲src/href,可是打包的位置是由path和filename一塊兒決定的,也就是須要爲filename的結果設置爲服務器上做爲此項目的根路徑。由於只有根路徑才能夠找到。
若是設置了publicPath,那麼全部的靜態資源的路徑前面都會加上公共路徑 publicPath的值。(不會產生目錄,只是在引入的路徑前加上publicPath的值)
// 設置publicPath: '/abc'
<script src=/abc/backend/js/app.57e28a966ab9582ff286.js?1dcc397cc95f5934ef81></script>
index.html的路徑是由filename決定的
也就是說實際在磁盤上產生的路徑是由path+filename決定的,可是 代碼中的資源地址的路徑是filename+publicPath決定的。
對於loader而言,有些loader有本身的publiPath,可是也能夠經過設置filename來替換掉publicPath,一勞永逸
複製代碼
webpack中最強大的功能之一
模塊熱替換
應用運行過程當中實時替換某個模塊,應用的運行狀態不受影響。熱替換隻將修改的模塊實時替換至應用中,沒必要去徹底刷新應用。
開啓HMR
HMR已經集成到了webpack-dev-Serve了,使用 webpack-dev-Serve --hot 開啓熱更新。
那麼怎樣爲每一個js單獨提供hmr呢? 使用 webpack提供的module.hot.accept(文件路徑, () => { // 熱替換邏輯 }) , 下面簡單的展現下圖片的HMR
if (module.hot) {
// 圖片的hmr
module.hot.accept('./test.png', () => {
img.src = background
console.log(background)
})
}
複製代碼
hotOnly: true // 只使用 HMR,不會 fallback 到 live reloading.
new webpack.DefinePlugin({
// 值要求的是一個代碼片斷
API_BASE_URL: JSON.stringify('https://api.example.com')
})
複製代碼
搖樹 , 就是搖掉代碼中未被引用的部分(dead-code)。mode:production。會自動開啓搖樹優化。
tree-shaking的實現:
optimization: {
usedExports: true, // 標記枯樹葉
minimize: true // 負責搖掉標記的枯樹葉
}
複製代碼
let foo = () => {
let x = 1
if (false) {
console.log('never readched')
}
let a = 3
return a
}
let baz = () => {
var x = 'baz is running'
console.log(x)
function unused () {
return 5
}
return x
let c = x + 3
return c
}
module.exports = { // commonjs模塊規範導出
baz
}
export { // ESM 模塊規範導出
baz
}
複製代碼
esModule 規範 打包後的模塊
([
function (e, n, r) {
"use strict";
r.r(n);
var t;
t = "baz is running",
console.log(t),
console.log("main.js running")
}
]);
commonjs 規範 打包後的模塊
([
function (e, n, t) {
(0, t(1).baz)(), console.log("main.js running")
},
function (e, n, t) {
e.exports = {
baz: function () {
var e = "baz is running";
return console.log(e),
e
}
}
}
]);
複製代碼
1. ES6的模塊引入是靜態分析的,故而能夠在編譯時正確判斷到底加載了什麼代碼。
2. 分析程序流,判斷哪些變量未被使用、引用,進而刪除此代碼。
複製代碼
合併模塊函數
optimization: {
usedExports: true, // 標記枯樹葉
minimize: true, // 負責搖掉標記的枯樹葉
concatenateModules: true. // 開啓 Scope Hoisting
}
複製代碼
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { modules: 'commonjs' }] // 開啓esModule的轉換爲commonjs, 這樣將不會進行commonjs轉換。
]
}
複製代碼
反作用
容許咱們經過配置的方式去標識咱們的代碼是否有反作用,從而爲tree-shaking提供更大的壓縮空間。
反作用:模塊執行時除了導出成員以外所作的事情。好比給xxx原型上添加了原型方法,別人引入的時候會污染到xxx原型。
通常用於npm包標記是否有反作用
如何使用:
使用以前請確保你的代碼沒有反作用。不然會誤刪掉反作用代碼。
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'none',
entry: {
index: './src/index.js',
album: './src/album.js'
},
output: {
filename: '[name].bundle.js'
},
optimization: {
splitChunks: {
// 自動提取全部公共模塊到單獨 bundle
chunks: 'all'
}
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Multi Entry',
template: './src/index.html',
filename: 'index.html',
chunks: ['index']
}),
new HtmlWebpackPlugin({
title: 'Multi Entry',
template: './src/album.html',
filename: 'album.html',
chunks: ['album']
})
]
}
複製代碼
全部動態導入的模塊會被自動分包
經過代碼的邏輯控制何時須要動態導入,或者說何時加載這個模塊。
魔法註釋:能夠對動態打包出來的文件進行從新命名, 並且能夠對文件進行靈活組合。
import('路徑').then(data => {}) import() 是ESModule規範的語法,而這個方法返回的是一個promise。
按需加載webpack是如何打包的呢?這是webpack爲import() 語法提供的引導代碼。
__webpack_require__.e = function requireEnsure(chunkId) {
var promises = [];
var installedChunkData = installedChunks[chunkId];
if(installedChunkData !== 0) { // 0 means "already installed".
// a Promise means "currently loading".
if(installedChunkData) {
promises.push(installedChunkData[2]);
} else {
// setup Promise in chunk cache
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
promises.push(installedChunkData[2] = promise);
// start chunk loading
var script = document.createElement('script'); // 建立一個script標籤
var onScriptComplete;
script.charset = 'utf-8';
script.timeout = 120; // 設置script的超時時間
script.src = jsonpScriptSrc(chunkId); // 設置src
// create error before stack unwound to get useful stacktrace later
var timeout = setTimeout(function(){
onScriptComplete({ type: 'timeout', target: script }); // 完成後的邏輯
}, 120000);
script.onerror = script.onload = onScriptComplete;
document.head.appendChild(script); // 插入到頁面上
}
}
return Promise.all(promises); // 返回一個promise
};
複製代碼
能夠看到,動態引入就是建立script,而後獲得到script標籤的src,將建立好的script標籤插入到head裏面。
const render = () => {
const hash = window.location.hash || '#posts'
const mainElement = document.querySelector('.main')
mainElement.innerHTML = ''
if (hash === '#posts') {
// mainElement.appendChild(posts())
import(/* webpackChunkName: 'components' */'./posts/posts').then(({ default: posts }) => {
mainElement.appendChild(posts())
})
} else if (hash === '#album') {
// mainElement.appendChild(album())
import(/* webpackChunkName: 'components' */'./album/album').then(({ default: album }) => {
mainElement.appendChild(album())
})
}
}
render()
window.addEventListener('hashchange', render)
複製代碼
文件級別的hash,根據文件生成的hash,文件發生修改,hash就會改變。
webpack是大前端發展到如今不能否認的居功至偉的功臣,如今框架開發通常狀況都會使用高度開箱即用的腳手架工具,可是對於webpack的瞭解也是很必要的。理解咱們的程序是如何一步步的從一隻滿身雞毛的雞變成一隻香噴噴的奧爾良口味的乾坤烤雞(drf烤雞名)