前端(vue)入門到精通課程:進入學習
Apipost = Postman + Swagger + Mock + Jmeter 超好用的API偵錯工具:
Webpack是一款模組打包工具。它為不同的依賴建立模組,將其整體打包成可管理的輸出檔案。這一點對於單頁面應用(如今Web應用的事實標準)來說特別有用。
假設我們有一個可以執行兩個簡單數學任務(加法和乘法)的應用程式,為了方便維護,我們決定切分這些函數到不同的檔案中去。
index.html
<html>
<head>
<script src="src/sum.js"></script>
<script src="src/multiply.js"></script>
<script src="src/index.js"></script>
</head>
</html>
登入後複製
index.js
var totalMultiply = multiply(5, 3);
var totalSum = sum(5, 3);
console.log('Product of 5 and 3 = ' + totalMultiply);
console.log('Sum of 5 and 3 = ' + totalSum);
登入後複製
multiply.js
var multiply = function (a, b) {
var total = 0;
for (var i = 0; i < b; i++) {
total = sum(a, total);
}
return total;
};
登入後複製
sum.js
var sum = function (a, b) {
return a + b;
};
登入後複製
這個應用程式的輸出應該是:
Product of 5 and 3 = 15
Sum of 5 and 3 = 8
登入後複製
我們不能僅僅只是使用工具,而不知道這些工具能幫助我們做什麼。那麼,Webpack幫我們做了什麼呢?
用模組來拯救依賴
在上面的程式碼中,我們可以看到,multiply.js與index.js均依賴於sum.js。因此,如果index.html匯入依賴時使用了錯誤的順序,那麼我們的應用就無法運作。舉個例子,如果index.js最先被匯入,或者sum.js在multiply.js之後被匯入,都會得到錯誤。
基於上面的例子,讓我們想象一下,一個真實的Web應用往往可能會包含多達幾十個依賴項,這些依賴項之間還可能存在依賴關係,維護這些依賴項之間的順序想想就讓人窒息。這裡還可能存在變數被其它依賴覆蓋的風險,而這將會導致難以發現的BUG。
為了解決這個痛點,Webpack會將依賴轉換為作用域更小的模組,從而避免變數被覆蓋。依賴轉換為模組帶來的額外好處是,Webpack可以為我們管理這些依賴。具體做法是,Webpack會在需要時,把依賴模組引入進來,並匹配對應的作用域。
通過打包來減少HTTP請求次數
我們還是回看一下index.html,這個檔案中我們需要下載三個獨立的檔案。當然這裡檔案比較少還能夠應付,但還是之前提到的問題,真實的Web應用中,依賴項可能會很多,而這將會導致使用者不得不等待所有依賴項挨個下載完成後,主應用才能執行。
而這就引出了Webpack的另一特性——打包。Webpack可以將所有的依賴打包成一個檔案,而這就意味著,使用者只需要下載一個依賴項,主應用就可以執行。
綜上所述,Webpack的主要特性就是打包和模組化。通過使用外掛和載入器,我們可以擴充套件Webpack的這兩大特性。
我們將使用CommonJS模組語法,作為初始設定。當然,這裡也有諸如AMD,ES2015等其它選擇,但這裡我們將先使用CommonJS,稍後遷移到ES2015。
CommonJS將模組匯出,使得其它程式碼可以使用匯出模組中的函數或變數。我們可以通過require
將匯出模組中的值讀出來。
index.html
<html>
<head>
<script src="./dist/bundle.js""></script>
</head>
</html>
登入後複製
index.js
var multiply = require('./multiply');
var sum = require('./sum');
var totalMultiply = multiply(5, 3);
var totalSum = sum(5, 3);
console.log('Product of 5 and 3 = ' + totalMultiply);
console.log('Sum of 5 and 3 = ' + totalSum);
登入後複製
multiply.js
var sum = require('./sum');
var multiply = function (a, b) {
var total = 0;
for (var i = 0; i < b; i++) {
total = sum(a, total);
}
return total;
};
module.exports = multiply;
登入後複製
sum.js
var sum = function (a, b) {
return a + b;
};
module.exports = sum;
登入後複製
觀察上面的程式碼,不難發現,為了讓函數sum
與函數multiply
能夠被使用,我們在sum.js與multiply.js指令碼中,匯出了這兩個函數。這裡有個細節,不知道大家是否注意到了,在index.html中我們現在僅需要匯入一個bundle.js檔案。
這可幫大忙了!我們現在不再需要關注依賴的順序,可以暴露我們想暴露的內容,並使得其它內容仍然保持私有。同時,我們現在僅需要匯入一個檔案,而不是三個,這有助於提高應用的載入速度。
Webpack的初始設定
為了實現我們上面要達到的效果,我們需要對Webpack做一些初始的設定。
var path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, './dist/'),
filename: 'bundle.js'
}
}
登入後複製
這裡我們實現了一個最簡單的設定。我們至少需要告訴Webpack,我們應用的入口在哪,輸出結果應該是什麼。我們來詳細看看每個屬性所代表的含義。
entry
: 這個屬性表示應用的入口。入口就意味著,這是我們載入程式和程式邏輯的起點。Webpack將從這個入口開始,遍歷整棵依賴樹。根據遍歷結果建立一個依賴間關係圖,並建立需要的模組。output.path
: 這個屬性表示存放打包結果的絕對路徑。這裡為了方便使用,我們採用了Node.js自帶的函數path
,這個函數能夠根據我們程式所處的位置,動態的建立絕對路徑。其中,__dirname
是Node.js的一個工具變數,它表示當前檔案的目錄名。output.filename
: 這個屬性表示打包結果的檔名。它的名字可以是任意的,只不過我們習慣叫它bundle.js
。來看看bundle.js
閱讀生成的bundle.js
程式碼,可以給我們帶來一些啟發。
// the webpack bootstrap
(function (modules) {
// The module cache
var installedModules = {};
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
// Create a new module (and put it into the cache)
// Execute the module function
// Flag the module as loaded
// Return the exports of the module
}
// expose the modules object (__webpack_modules__)
// expose the module cache
// Load entry module and return exports
return __webpack_require__(0);
})
/************************************************************************/
([
// index.js - our application logic
/* 0 */
function (module, exports, __webpack_require__) {
var multiply = __webpack_require__(1);
var sum = __webpack_require__(2);
var totalMultiply = multiply(5, 3);
var totalSum = sum(5, 3);
console.log('Product of 5 and 3 = ' + totalMultiply);
console.log('Sum of 5 and 3 = ' + totalSum);
},
// multiply.js
/* 1 */
function (module, exports, __webpack_require__) {
var sum = __webpack_require__(2);
var multiply = function (a, b) {
var total = 0;
for (var i = 0; i < b; i++) {
total = sum(a, total);
}
return total;
};
module.exports = multiply;
},
// sum.js
/* 2 */
function (module, exports) {
var sum = function (a, b) {
return a + b;
};
module.exports = sum;
}
]);
登入後複製
從上面的程式碼可以看出,Webpack把每一個js指令碼都封裝到一個模組中,並把這些模組放進陣列中。模組陣列被傳入Webpack的載入程式中,載入程式會把這些模組加入Webpack並執行,使得模組可用。
這裡bundle.js
返回的是__webpack_require__(0)
,而這剛好對應了模組陣列中的index.js
部分。基於此我們同樣可以得到正確的執行結果,而不需要處理依賴管理,下載依賴的次數也僅需要一次。
Loader讓Webpack更好用
Webpack僅能理解最基本的JavaScript ES5程式碼,它自身僅支援建立模組並打包JavaScript ES5。如果我們不僅僅侷限於JavaScript ES5,例如我們想使用ES2015,這就需要告訴Webpack如何處理ES2015。這裡我們的處理方式往往是,我們需要將其它語言(如TypeScript)或其它版本(如ES2015)預處理成JavaScript ES5,再讓Webpack做打包。這裡就需要使用Babel來做轉換,把ES2015轉換為ES5(當然Babel能做的事情遠不止如此)。
為了說明這個過程,我們使用ES2015重寫之前的功能。
index.js
import multiply from './multiply';
import sum from './sum';
const totalMultiply = multiply(5, 3);
const totalSum = sum(5, 3);
console.log(`Product of 5 and 3 = ${totalMultiply}`);
console.log(`Sum of 5 and 3 = ${totalSum}`);
登入後複製
multiply.js
import sum from './sum';
const multiply = (a, b) => {
let total = 0;
for(let i=0;i<b;i++) {
total = sum(a, total);
}
return total;
};
export default multiply;
登入後複製
sum.js
const sum = (a, b) => a + b;
export default sum;
登入後複製
這裡我們使用了很多ES2015的新特性,例如箭頭函數、const
關鍵字、模板字串和ES2015的匯入匯出。ES2015的程式碼Webpack無法處理,所以我們需要Babel來進行轉換。想要讓Babel配合Webpack完成工作,我們就需要用到Babel Loader。事實上,Loader就是Webpack處理JavaScript ES5以外內容的方式。有了載入器,我們就可以讓Webpack處理各式各樣的檔案。
想要在Webpack中使用Babel Loader,我們還需要三個Babel依賴:
babel-loader
: 提供Babel與Webpack之間的介面;
babel-core
: 提供讀取和解析程式碼的功能,並生成對應的輸出;
babel-preset-es2015
: 提供將ES2015轉換為ES5的Babel規則;
在Webpack中設定Babel Loader的程式碼,差不多是下面這樣子:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, './dist/'),
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015']
}
}
]
}
};
登入後複製
這段程式碼你可以在webpack.config.js
中找到。值得注意的是,Webpack中是支援同時存在多個Loader的,所以提供的值是一個陣列。接著,還是讓我們來看看每個屬性代表的含義。
test
: 我們只希望Loader處理JavaScript檔案,這裡通過一個正規表示式匹配.js檔案;loader
: 要使用的Loader,這裡使用了babel-loader
;exclude
: 哪些檔案不需要被處理,這裡我們不希望Babel處理node_modules下的任何檔案;query.presets
: 我們需要使用哪個規則,這裡我們使用Babel轉換ES2015的規則;設定好這些內容後,再次檢視打包生成的bundle.js
,其中的內容看起來就像下面這樣:
/* 2 */
function(module, exports) {
var sum = function sum(a, b) {
return a + b;
};
module.exports = sum;
}
登入後複製
可以看到,Babel Loader已經把ES2015的程式碼變成了ES5的程式碼。
接下來,讓我們拓展上面的例子,輸出計算結果。我們將在頁面上建立一個body
,然後把乘積與加和的結果新增到span
中。
import multiply from './multiply';
import sum from './sum';
const totalMultiply = multiply(5, 3);
const totalSum = sum(5, 3);
// create the body
const body = document.createElement("body");
document.documentElement.appendChild(body);
// calculate the product and add it to a span
const multiplyResultsSpan = document.createElement('span');
multiplyResultsSpan.appendChild(document.createTextNode(`Product of 5 and 3 = ${totalMultiply}`));
// calculate the sum and add it to a span
const sumResultSpan = document.createElement('span');
sumResultSpan.appendChild(document.createTextNode(`Sum of 5 and 3 = ${totalSum}`));
// add the results to the page
document.body.appendChild(multiplyResultsSpan);
document.body.appendChild(sumResultSpan);
登入後複製
這段程式碼的輸出結果,應該與之前是一致的,區別僅在於顯示在頁面上。
Product of 5 and 3 = 15 Sum of 5 and 3 = 8
登入後複製
我們可以使用CSS來美化這個結果,比如,我們可以讓每個結果都獨佔一行,並且給每個結果都加上邊框。為了實現這一點,我們可以使用如下的CSS程式碼。
span {
border: 5px solid brown;
display: block;
}
登入後複製
我們需要將這個CSS也匯入應用中。這裡最簡單的解決方案是,在我們的html中新增一個link
標籤。但有了Webpack提供的強大功能,我們可以先匯入它,再用Webpack來處理這個樣式。
在程式碼中匯入CSS帶來的另一個好處是,開發者可以清晰的看到CSS與其使用之間的關聯。這裡需要注意的是,CSS的作用域並不侷限於它所匯入的模組本身,其作用域仍然是全域性的,但從開發者的角度看,這樣使用更加清晰。
import multiply from './multiply';
import sum from './sum';
// import the CSS we want to use here
import './math_output.css';
const totalMultiply = multiply(5, 3);
const totalSum = sum(5, 3);
// create the body
const body = document.createElement("body");
document.documentElement.appendChild(body);
// calculate the product and add it to a span
const multiplyResultsSpan = document.createElement('span');
multiplyResultsSpan.appendChild(document.createTextNode(`Product of 5 and 3 = ${totalMultiply}`));
// calculate the sum and add it to a span
const sumResultSpan = document.createElement('span');
sumResultSpan.appendChild(document.createTextNode(`Sum of 5 and 3 = ${totalSum}`));
// add the results to the page
document.body.appendChild(multiplyResultsSpan);
document.body.appendChild(sumResultSpan);
登入後複製
這段程式碼中,與前面程式碼的唯一區別在於,我們匯入了CSS。我們需要兩個Loader來處理我們的CSS:
css-loader
: 用於處理CSS匯入,具體來說,獲取匯入的CSS並載入CSS檔案內容;
style-loader
: 獲取CSS資料,並新增它們到HTML檔案中;
現在我們在webpack.config.js
中的Webpack設定看起來像這樣:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, './dist/'),
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015']
}
},
{
test: /.css$/,
loaders: ['style-loader', 'css-loader']
}
]
}
};
登入後複製
我們還是來看看新增的CSS設定屬性所表示的內容。
test
: 我們需要告訴Loader,我們只需要它處理CSS檔案。這裡的正規表示式僅匹配CSS檔案。loaders
: 這裡與前面不同的是,我們使用了多個Loader。還有一個需要注意的細節是,Webpack從右向左處理loader,因此css-loader
處理的結果(讀出CSS檔案內容)會被傳遞給style-loader
,最終得到的是style-loader
的處理結果(將樣式新增到HTML檔案中)。假如我們現在需要提取CSS,並輸出到一個檔案中,再匯入該檔案。為了實現這一點,我們就要用到Plugin。Loader的作用在於,在資料被打包輸出前進行預處理。而Plugin則可以阻止預處理的內容直接出現在我們的打包結果中。
我們的Webpack設定現在變成了這樣:
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, './dist/'),
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015']
}
},
{
test: /.css$/,
loader: ExtractTextPlugin.extract('css-loader')
}
]
},
plugins: [
new ExtractTextPlugin('style.css')
]
};
登入後複製
在這段程式碼的頂部,我們匯入了ExtractTextPlugin
,並使用這個外掛改造了之前的CSS Loader。這裡的作用是,css-loader
的處理結果不再直接返回給Webpack,而是傳遞給ExtractTextPlugin
。在底部我們設定了這個Plugin。
這裡設定的作用是,對於傳遞給ExtractTextPlugin
的CSS樣式資料,將會被儲存在名為style.css
的檔案中。這樣做的好處與之前處理JavaScript時一樣,我們可以將多個獨立的CSS檔案合併為一個檔案,從而減少載入樣式時的下載次數。
最終我們可以直接使用我們合併好的CSS,實現和之前一致的效果。
<html>
<head>
<link rel="stylesheet" href="dist/style.css"/>
<script src="./dist/bundle.js""></script>
</head>
</html>
登入後複製
現在我們開始嘗試嚮應用中新增圖片,並讓Webpack來協助我們處理這些圖片。這裡我們新增了兩張圖片,一個用於求和,一個用於求積。為了讓Webpack有能力處理這些圖片,我們使用這兩個Loader:
image-webpack-loader
: 嘗試幫助我們自動壓縮圖片體積;
url-loader
: 如果image-webpack-loader
的輸出圖片體積小,就內聯使用這些圖片,如果image-webpack-loader
的輸出圖片體積大,就將影象包含在輸出目錄中;
我們準備了兩張圖片,用於求積的圖片(multiply.png)相對較大,用於求和的圖片(sum.png)相對較小。首先,我們新增一個圖片工具方法,這個方法會為我們建立圖片,並將圖片新增到檔案中。
image_util.js
const addImageToPage = (imageSrc) => {
const image = document.createElement('img');
image.src = imageSrc;
image.style.height = '100px';
image.style.width = '100px';
document.body.appendChild(image);
};
export default addImageToPage;
登入後複製
接著,讓我們匯入這個圖片工具方法,以及我們想要新增到index.js中的圖片。
import multiply from './multiply';
import sum from './sum';
// import our image utility
import addImageToPage from './image_util';
// import the images we want to use
import multiplyImg from '../images/multiply.png';
import sumImg from '../images/sum.png';
// import the CSS we want to use here
import './math_output.css';
const totalMultiply = multiply(5, 3);
const totalSum = sum(5, 3);
// create the body
const body = document.createElement("body");
document.documentElement.appendChild(body);
// calculate the product and add it to a span
const multiplyResultsSpan = document.createElement('span');
multiplyResultsSpan.appendChild(document.createTextNode(`Product of 5 and 3 = ${totalMultiply}`));
// calculate the sum and add it to a span
const sumResultSpan = document.createElement('span');
sumResultSpan.appendChild(document.createTextNode(`Sum of 5 and 3 = ${totalSum}`));
// add the results to the page
addImageToPage(multiplyImg);
document.body.appendChild(multiplyResultsSpan);
addImageToPage(sumImg);
document.body.appendChild(sumResultSpan);
登入後複製
最後,我們還是來修改webpack.config.js
,設定兩個新的Loader來處理這些圖片。
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, './dist/'),
filename: 'bundle.js',
publicPath: 'dist/'
},
module: {
loaders: [
{
test: /.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015']
}
},
{
test: /.css$/,
loader: ExtractTextPlugin.extract('css-loader')
},
{
test: /.png$/,
loaders: [
'url-loader?limit=5000',
'image-webpack-loader'
]
}
]
},
plugins: [
new ExtractTextPlugin('style.css')
]
};
登入後複製
還是老規矩,我們來看看新增的引數都表示什麼含義。
output.publicPath
: 讓url-loader
知道,儲存到磁碟的檔案需要新增指定的字首。例如,我們需要儲存一個output_file.png
,那麼最終儲存的路徑應該是dist/output_file.png
;test
: 還是和之前一樣,通過正規表示式匹配影象檔案,非影象檔案不處理;loaders
: 這裡還是要再強調一下,Webpack從右向左處理loader,因此image-webpack-loader
的處理結果將會被傳遞給url-loader
繼續處理。現在我們執行Webpack打包,會得到下面三個東西。
38ba485a2e2306d9ad96d479e36d2e7b.png
bundle.js
style.css
登入後複製
這裡的38ba485a2e2306d9ad96d479e36d2e7b.png
實際上就是我們的大圖片multiply.png
,較小的圖片sum.png
會被內聯到bundle.js
中,就像下面這樣。
module.exports = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAMAAAACDyzWAAAC6FBMVEUAuv8AgL...."
登入後複製
這其實相當於
img.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAMAAAACDyzWAAAC6FBMVEUAuv8AgL..."
登入後複製
更多程式設計相關知識,請存取:!!
以上就是Webpack是什麼?詳解它是如何工作的?的詳細內容,更多請關注TW511.COM其它相關文章!