大綱:
1、webpack 是什麼,為什麼需要webpack
2、子產品化
3、webpack的基本配置
webpack是什麼
我們先引入一段官網的解釋
本質上,webpack 是一個現代 JavaScript 應用程式的靜态子產品打包器(module bundler)。當 webpack 處理應用程式時,它會遞歸地建構一個依賴關系圖(dependency graph),其中包含應用程式需要的每個子產品,然後将所有這些子產品打包成一個或多個 bundle。
了解一下,那就是 webpack 是一個打包子產品化 JavaScript 程式的工具。webpack 處理應用程式時,會從配置的入口檔案開始,識别出子產品化導入語句,然後遞歸找出入口檔案的所有子產品依賴,将這些依賴統統打包進一個或多個獨立檔案中(即bundle檔案)。
為什麼需要子產品化打包工具
那麼我們為什麼要使用它,子產品化究竟有什麼好處呢?試想一下,在沒有子產品化之前,代碼是怎麼樣的呢?
- 變量和方法容易造成全局污染,不容易維護
- 通過script标簽從上自下加載資源
- 大型項目資源難以維護,多人協作容易造成混亂
我們都知道,在HTML中,可以通過script标簽引入js資源。script标簽自上而下的加載執行,當然,如果設定了defer 和 async 屬性的話,那就另當别論了。但不難看出,js的執行順序至關重要,資源加載的先後順序決定了你的項目是否能夠成功運作下去。當項目越來越大,邏輯越來越複雜時,就會讓維護變得困難和混亂起來,全局污染的可能性就大的多了。
形成獨立作用域
在早期,我們通過命名空間的方法,盡量減少全局變量的産生:
// 使用對象字面量的形式形成命名空間
var Lily = {
name: 'Lily',
age: 20,
hello: function() {
console.log('My name is ' + this.name);
}
};
var Tom = {
name: 'Tom',
age: 20,
hello: function() {
console.log('My name is ' + this.name);
}
};
雖然命名空間能夠在一定程度上減少全局變量的産生,但它存在另外一個問題。我們很容易就能夠對 Lily 和 Tom 的姓名和年齡進行更改,但實際我們隻希望暴露 hello 方法,而不希望基礎屬性被修改。那麼我們怎麼進行屬性私有化呢?
那就是IIFE 立即執行函數,它會形成自己的獨立作用域。我們可以在立即執行函數中建立屬性和方法,return出希望暴露的方法。
var helloLily = (function() {
var _Lily = {
name: 'Lily',
age: 20,
};
var hello = function() {
console.log('My name is ' + _Lily.name);
}
return hello;
})();
var helloTom = (function() {
var _Tom = {
name: 'Tom',
age: 20,
};
var hello = function() {
console.log('My name is ' + _Tom.name);
}
return hello;
})();
實際上立即執行函數就是子產品化的基石。我們使用 webpack 打包生成的 bundle 檔案就是一個立即執行函數。
随之,社群制定了 COMMONJS/AMD/CMD 子產品化管理方案,讓大程式拆分成互相依賴的小檔案成為可能。
ES6 子產品化
ES6 在語言标準的層面上,實作了子產品功能。
ES6 子產品的設計思想是盡量的靜态化,使得編譯時就能确定子產品的依賴關系,以及輸入和輸出的變量。CommonJS 和 AMD 子產品,都隻能在運作時确定這些東西。比如,CommonJS 子產品就是對象,輸入時必須查找對象屬性。
子產品化的優點:
- 編譯時加載的特性,讓靜态分析成為可能
- 團隊協作、代碼組織及管理起來更加友善
- 減少代碼的耦合性,一個子產品處理一件事情
- 将來浏覽器的新 API 就能用子產品格式提供,不再必須做成全局變量或者navigator對象的屬性。
- 不再需要對象作為命名空間(比如Math對象),未來這些功能可以通過子產品提供。
現在我們大緻了解了子產品化的一個演變過程,接下來我們來談談webpack的子產品。
webpack 子產品
在webpack子產品中,我們可以使用:
- ES2015 import 語句 CommonJS require() 語句
- AMD define 和 require 語句
- css/sass/less 檔案中的 @import 語句。
- 樣式(url(…))
- HTML 檔案(<img src=…>)中的圖檔連結(image url)
webpack本身隻能處理 json 和 JavaScript 語言。但是它提供的 loader,讓其他語言的編寫子產品變成了可能。loader 描述了 webpack 如何處理非 JavaScript 子產品。
webpack 基礎概念
入口 entry
在webpack進行建構的時候,首先會找到入口檔案,通過入口檔案的依賴遞歸查找到所有依賴。
單頁應用入口配置:
// 執行建構的入口檔案
entry: './src/index.js'
多頁應用入口配置:
// 多頁應用入口
entry: {
index: './src/index.js',
list: './src/list.js',
about: './src/about.js'
}
出口 output
output 配置項,用于告訴 webpack 将打包生成的 bundle 檔案放在哪個目錄下。
多個入口時必須配置多出口,否則會造成建構出錯。
output: {
path: path.resolve(__dirname, "./dist"),
// filename: 'index.js', // 可指定導出檔案命名
/*
可自動根據name生成導出檔案名,多個時會與入口配置的屬性一一對應
*/
filename: "[name].js",
}
loader
webpack 自身隻了解 JavaScript 檔案,而 loader 為 webpack 提供了處理其他語言的能力,它可以将所有類型的檔案轉換為 webpack 能夠處理的有效子產品。
-
loader 支援鍊式傳遞,loader将按照從右往左的順序,鍊式的進行調用。
在第一個 loader,傳入的參數是資源檔案(resource file)的内容。
在最終一個 loader 中,處理結果應該是 String 或者 Buffer(被轉換為一個 string),代表了子產品的 JavaScript 源碼。
- loader 可以同步、可以異步
-
loader 子產品需要傳回一個函數
配置loader:
module: {
rules: [
{
// 正規表達式,辨別應該被處理的檔案
test: /\.css$/,
// 使用什麼 loader 去處理比對到的檔案
use: ['style-loader', 'css-loader'],
}
]
},
插件 plugins
Loader 被用于轉換某些類型的子產品,而插件則可以用于執行範圍更廣的任務。插件的範圍包括,從打包優化和壓縮,一直到重新定義環境中的變量。插件接口功能極其強大,可以用來處理各種各樣的任務。
在使用插件時,我們隻需要通過 require 将插件引入,new 一個執行個體出來就可以。
const htmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
plugins: [
new htmlWebpackPlugin({
template: "./src/index.html",
filename: "index.html",
}),
]
}
模式 mode
用于告訴 webpack 啟用哪種模式的内置優化
module.exports = {
mode: "development"
}
選項 | 描述 |
---|---|
development | 會将 process.env.NODE_ENV 的值設為 development。啟用 NamedChunksPlugin 和 NamedModulesPlugin。 |
production | 會将 process.env.NODE_ENV 的值設為 production。啟用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin. |
bundle
bundle 即是 webpack 打包建構之後生成的檔案。通常情況下,一個入口會對應一個 bundle 檔案。
bundle 檔案是由 chunk 片段組成的,每一個子產品,都會生成一個 chunk 片段。例如入口檔案和入口檔案引用a.js打包後的chunk檔案:
defer 和 async 拓展
當浏覽器解析到js腳本時:
情況一:立即加載執行
<script src="index.js"></script>
情況二:異步解析,解析完成之後立即執行。
可能在DOMContentLoaded事件執行之前或之後執行,一定會在loaded事件前執行。
多個async腳本,誰先解析完成誰先執行
<script src="index.js" async></script>
情況三:異步解析,解析完成之後,需要等待文檔解析完成之後,DOMContentLoaded 事件觸發之前執行。
有defer的腳本會阻止DOMContentLoaded事件,直到腳本解析完成。
defer腳本會按照js順序執行。
<script src="index.js" defer></script>
情況四:既設定了 async,又設定了 defer 的腳本,如果浏覽器兩者都支援,則會按照 async 來處理腳本的解析和執行
<script src="index.js" async defer></script>
情況五:浏覽器在加載 es6 子產品時,需要加入type = ‘module’ 屬性,浏覽器在識别到該屬性時,會對腳本進行異步加載,表現形式上等同于 defer。
如果此時設定了 async,則會解析完成後立即執行,遵循 async 的執行規則。
<script src="index.js" type="module"></script>
參考
webpack官方文檔
阮一峰-es6入門子產品化章節
async、defer與DOMContentLoaded的執行先後關系
代碼位址
webpack基礎
寫在後面:初次釋出于個人部落格網站 http://agangxuezhang.cn/single-minded