在Webpack中,一切資源都被看做模塊
,也就是說不論是js
、css
仍是圖片文件,Webpack都將他們都抽象成一個個模塊,模塊記錄了資源的位置和內容,編譯過程就是從入口模塊開始遞歸找到全部須要用的模塊,最終將全部模塊打包輸出。javascript
模塊說白了就是文件在Webpack中的表示對象,道理很簡單,但不知你們心中有沒有許多問號,資源是怎樣轉換成爲模塊?Webpack怎樣識別不一樣的資源?怎樣從收集到全部的模塊?模塊又是怎樣輸出結果?css
前面的入口Entry
章節咱們已經知道,一個單入口配置通過一頓操做後會生成NormalModule
來表示入口文件模塊,從第一個起始模塊開始,就能夠將其餘require
或import
導入的資源通通轉成模塊供後續輸出流程使用。這節開始,咱們就來揭曉Webpack中模塊
是怎樣構建起來:java
js文件是咱們打包的主要文件類型,因此固然先從一個js文件開始,咱們這裏選擇打包一個沒有require
或import
其餘文件的js,由於若是有依賴其餘文件分析起來太複雜,咱們先從簡單的js開始分析:webpack
// .src/empty.js
console.log('hello webpack')
複製代碼
// webpack.config.js
{ entry: './src/empty.js' }
複製代碼
Webpack默認只能解析js
、json
及wasm
類型的文件,因此會有專門處理這三種類型的解析器Parser
,其餘類型的文件須要經過loader轉換成js
來兼容。在配置解析過程當中,會註冊js模塊處理插件JavascriptModulesPlugin
,它的做用就是註冊js解析器Parser
及js輸出模版生成器JavascriptGenerator
:es6
解析js的Parser就叫
Parser
,解析json的Parser叫JsonParser
web
// JavascriptModulesPlugin.js
(compilation, { normalModuleFactory }) => {
// 對 javascript/auto 類型的文件使用 Parser 解析
normalModuleFactory.hooks.createParser.for("javascript/auto")
.tap("JavascriptModulesPlugin", options => {
return new Parser(options, "auto");
});
// ...
}
複製代碼
生成入口依賴的過程咱們在前面的入口Entry
章節有分析過,因爲咱們這裏使用了單入口配置./src/empty.js
,因此會生成單入口依賴SingleEntryDependency
。這個地方比較有意思的是Webpack並無直接生成一個模塊,而是先生成了一個依賴
,接着再使用NormalModuleFactory
解析依賴,並生成模塊NormalModule
:json
// NormalModuleFactory.js
class NormalModuleFactory {
create(context, dependencie) {
// 資源路徑,若是是入口依賴則這個就是入口文件路徑
const request = dependencie.request
// 資源類型
const type = this.getType(request)
return new NormalModule({
// 建立解析該模塊須要的loader
loaders: this.createLoaders(request)
// 獲取模塊解析器
parser: this.getParser(type)
// 獲取模版生成器
generator: this.getGenerator(type)
});
}
}
複製代碼
這裏有個比較重要的變量type
,它用來標識這個模塊用什麼樣的方式解析,上面說了Webpack默認只能解析js
、json
及wasm
類型的文件,因此type
也只有這三種,分別對應三種不一樣的Parser
,默認值爲javascript/auto
即默認使用處理js的Parser
。Webpack內部添加了默認規則,這個規則會和外部配置的rules
合併。ide
// WebpackOptionsDefaulter.js
class WebpackOptionsDefaulter {
constructor() {
this.set("module.defaultRules", "make", options => [
// 默認文件使用類型javascript/auto"
{ type: "javascript/auto", resolve: {} },
// 若是遇到.json文件,用json類型
{ test: /\.json$/i, type: "json" },
]);
}
}
複製代碼
那難道解析圖片文件也用javascript/auto
類型解析器嗎?沒錯,待會下面咱們會說到。這裏解析器Parser
和loader
可不是同一個東西,你們須要區分開,解析器是用來分析文件依賴,如分析require
等;loader是用來作內容轉換,如es6
轉es5
等。通過這一步後,入口文件就轉換成一個模塊了。函數
模塊建立完後,此時只是獲取了模塊的基本信息,如相對路徑,文件類型,須要用哪些loader處理這個文件等。接下來就要調用模塊的構建函數,用於真正加載並處理文件。post
// Compilation.js
buildModule(module) {
module.build()
}
複製代碼
首先會使用loader加載文件並轉換內容,這裏會經過使用不一樣loader來轉換資源文件,js文件內容能夠直接傳給下一步,非js文件通過loader後通常會輸出一段js字符串內容,後面會舉例演示。本例通過loader處理後獲得的結果內容就是原文件內容console.log('hello webpack')
。
接着使用parser解析上面的結果,解析js的parser作的事情主要是將內容轉換成ast
,並解析出文件全部的依賴,因爲咱們解析的js很是簡單,在構建時並不會有什麼依賴輸出,因此咱們先暫且忽略這裏。
// NormalLoader.js
class NormalLoader {
build() {
// 執行loader
const result = runLoaders(this.resource, this.loaders)
this.source = result
// 解析輸出內容
this.parser.parse(this.source)
}
}
複製代碼
通過模塊構建階段後,NormalModule
就已經獲取到完整的內容,而且獲取到了它的全部依賴。接下來就是要處理這些依賴。在這裏會過濾掉不須要遞歸解析的依賴,因爲Webpack中依賴的表明的內容比較多,一般來講只有使用require
或import
的模塊依賴
纔會須要被遞歸解析,即遞歸生成相應的資源模塊,其餘依賴不須要生成新的模塊就不會在這裏處理。因爲本例子沒有生成依賴,因此這裏也不會執行:
// Compilation.js
// 用於獲取須要被遞歸解析的依賴
processModuleDependencies(module) {
const dependencies = []
module.dependencies.map(dep => {
// 有resourceIdent的纔是模塊依賴,須要生成新的模塊
const resourceIdent = dep.getResourceIdentifier();
if (resourceIdent) {
dependencies.push(dep);
}
})
this.addModuleDependencies(module, dependencies);
}
// 遞歸解析模塊依賴
addModuleDependencies(module, dependencies) {
dependencies.map(dep => {
// 建立模塊 -> 構建模塊 ...
const factory = dep.factory
const dependentModule = factory.create()
this.buildModule(dependentModule)
})
}
複製代碼
在全部模塊解析完成後,模塊構建階段make
就完成了,接下來進入seal
階段,即把全部模塊構建出輸出內容,主要負責生成輸出內容的函數是Compilation.createChunkAssets
,可是不是這裏的重點,咱們在輸出文件章節中再詳細分析。最終咱們會看到Webpack給咱們生成了下面的js:
(function(modules){
// 啓動函數...
})({ "./src/empty.js":
/*!******************************!*\ !*** (webpack)/src/empty.js ***! \******************************/
/*! no static exports found */
(function(module, exports) {
console.log('hello webpack')
})
})
複製代碼
Webpack除了解析js
文件外,同時內置支持解析json
文件,咱們一樣能夠解析一個json
看看它的工做流程。
// a.json
{
"say hello": "webpack",
"foo": ["bar"]
}
複製代碼
// webpack.config.js
entry: './src/a.json'
複製代碼
JsonModulesPlugin
註冊json解析器JsonParser
及json輸出模版生成器JsonGenerator
,webpack能直接解析json文件就是這個解析器的功勞:// JsonModulesPlugin.js
(compilation, { normalModuleFactory }) => {
normalModuleFactory.hooks.createParser.for("json")
.tap("JsonModulesPlugin", () => new JsonParser() });
normalModuleFactory.hooks.createGenerator.for("json")
.tap("JsonModulesPlugin", () => new JsonGenerator() });
}
複製代碼
SingleEntryDependency
,經過NormalModuleFactory
解析後,生成NormalModule
,這一步跟解析js文件同樣。module.build
,經過loader獲取到json文件內容。JsonParser
解析內容,將json文件轉爲對象,給module
添加依賴JsonExportsDependency
依賴,用於在輸出文件時標記導出內容,做用不大能夠先忽略。class JsonParser {
parse(source, state) {
// 解析json爲對象
const data = parseJson(source);
state.module.buildInfo.jsonData = data;
state.module.buildMeta.exportsType = "named";
// 給json添加依賴,可是這個不是模塊依賴,只是給輸出作個標記,註釋掉也沒問題
if (typeof data === "object" && data) {
state.module.addDependency(new JsonExportsDependency(Object.keys(data)));
}
state.module.addDependency(new JsonExportsDependency(["default"]));
return state;
}
}
複製代碼
processModuleDependencies
,因爲JsonExportsDependency
繼承於NullDependency
,不屬於模塊依賴,因此這裏不須要繼續處理。JsonGenerator
生成json輸出數據。執行Webpack事後,咱們能夠看到輸出了下面的js文件:
(function(modules){
// 啓動函數...
})({
"./src/a.json":
/*!****************************!*\ !*** (webpack)/src/a.json ***! \****************************/
/*! exports provided: say hello, foo, default */
(function(module) {
module.exports = JSON.parse("{\"say hello\":\"webpack\",\"foo\":[\"bar\"]}");
})
})
複製代碼
能夠看到Webpack對json
的解析和對js
的解析過程大同小異。因爲解析json
所用的JsonParse
很是簡單,能夠說是js的簡化版本,之因此這麼簡單是由於通常json只是做爲描述內容,並不會依賴其餘文件,並且咱們代碼中引用json文件也只是獲取它的值,若是看js的Parser
那就複雜多了。
以上在解析js
和json
都是Webpack默認支持的,如今咱們看看默認不支持的文件類型是怎麼打包的。因爲webpack默認不識別圖片類型文件,因此咱們要加個能處理圖片的loader來處理。
file-loader
a.png
// webpack.config.js
{
entry: './src/a.png',
module: {
rules: [{
test: /\.png$/,
use: [{ loader: 'file-loader', options: { outputPath: '/', } }]
}]
}
}
複製代碼
NormalModule
過程和上面同樣;emitFile
將文件輸出,而通過loader
處理後返回的結果則是一段js字符串,因爲是js字符串因此固然能夠給解析js的Parser
處理結果:export default __webpack_public_path__ + "/2abc8e354a818e3316cc7c9dc97f881e.png";
複製代碼
Parser
解析上面的結果,因爲這段js還比較複雜,通過處理後一共添加了下面幾個依賴HarmonyCompatibilityDependency
、HarmonyInitDependency
、HarmonyExportHeaderDependency
、HarmonyExportExpressionDependency
、ConstDependency
可是好在這些都不是模塊依賴,不須要遞歸進行處理,他們主要在輸出內容時用到,咱們能夠暫時忽略;執行Webpack事後,會輸出一張圖片和下面的js文件:
(function(modules){
// 啓動函數...
})({ "./src/a.png":
/*!***************************!*\ !*** (webpack)/src/a.png ***! \***************************/
/*! exports provided: default */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = (__webpack_require__.p + "/2abc8e354a818e3316cc7c9dc97f881e.png");
})
})
複製代碼
以上咱們分析的三個文件都是很是簡單的內容,基本流程就是如下幾點:
固然Webpack對模塊的處理確定沒那麼簡單,文章反覆提到的模塊依賴
還沒正式出場,解析js文件用的Parser
也只是簡單提起。這裏咱們只須要對解析模塊流程有個大概印象,瞭解模塊在Webpack充當的角色就行,後面咱們將詳細分析其中的具體內容。