在Webpack中咱們能夠任意使用多種模塊化方式組織代碼:node
import xxx from 'xxx'
require('./xxx')
咱們可使用它導入各類第三方庫及項目文件,也可使用別名來簡化路徑。咱們知道,雖然CommonJS是node原生支持的依賴管理方法,可是咱們打包項目時不是node來加載要打包的文件,並且查找規則也和node的不同,因此Webpack實現了本身的一套文件查找方法。webpack
依賴這套加載器,Webpack能夠實現各類模塊化規則解析,同時支持別名,目錄查找等定製化的需求。下面咱們來看看它的實現方式:git
Webpack中使用enhanced-resolve
包裝的ResolverFactory
提供實例化方法,並根據參數添加特殊功能的插件,共建立了三種類型的resolver:github
在建立模塊時,就會利用Resolver預先判斷路徑是否存在,並獲取路徑的完整地址供後續加載文件使用。web
ResolverFactory
繼承了Tapable
,共提供兩類鉤子,兩個都是個是在建立resolver
時調用:json
class ResolverFactory extends Tapable {
constructor() {
super();
this.hooks = {
resolveOptions: new HookMap(() => new SyncWaterfallHook(["resolveOptions"])),
resolver: new HookMap(() => new SyncHook(["resolver", "resolveOptions"]))
};
}
get(type, resolveOptions) {
// 調用配置鉤子
resolveOptions = this.hooks.resolveOptions.for(type).call(resolveOptions);
const resolver = Factory.createResolver(resolveOptions);
// 調用新建鉤子
this.hooks.resolver.for(type).call(resolver, resolveOptions);
return resolver;
}
複製代碼
在未修改配置狀況下,Webpack給Resolver設定的默認值以下:瀏覽器
normalResolver
aliasFields: ["browser"]
cacheWithContext: false
extensions: [".wasm", ".mjs", ".js", ".json"]
fileSystem: CachedInputFileSystem
mainFields: ["browser", "module", "main"]
mainFiles: ["index"]
modules: ["node_modules"]
unsafeCache:true
複製代碼
contextResolver
aliasFields: ["browser"]
cacheWithContext: false
extensions: [".wasm", ".mjs", ".js", ".json"]
fileSystem: CachedInputFileSystem
mainFields: ["browser", "module", "main"]
mainFiles: ["index"]
modules: ["node_modules"]
unsafeCache:true
resolveToContext:true
複製代碼
loaderResolver
cacheWithContext: false
extensions: [".js", ".json"]
fileSystem: CachedInputFileSystem
mainFields: ["loader", "main"]
mainFiles: ["index"]
unsafeCache: true
複製代碼
在WebpackOptionsApply
中初始化配置時,會在hook.resolveOptions
上添加額外配置緩存
// normalResolver, loaderResolver
{ fileSystem: compiler.inputFileSystem }
// contextResolver
{ fileSystem: compiler.inputFileSystem, resolveToContext: true }
複製代碼
用於對Node.js核心庫polyfill,當Webpack配置target
是web,webworker
時啓用。例如os
是node的核心模塊,在瀏覽器裏是沒有的,可是用webpack打包後會加上polyfill,打包後能在瀏覽器中運行:服務器
const os = require('os')
console.log(os)
複製代碼
在初始化時,會向hooks.resolver
添加AliasPlugin
插件,將須要polyfill的庫名添加到別名中。babel
用於處理AMD
模塊化相關代碼,其中會向hooks.resolver
添加AliasPlugin
插件。在啓用AMD模塊化時,define
做爲AMD
關鍵字不容許被間接使用,例以下面代碼在打包過程當中,define
會轉換爲webpack amd define
當成一個模塊被引入,插件在遇到這個模塊時會返回一個異常的函數:
const df = define
console.log(df)
複製代碼
打包後生成的代碼:
"./buildin/amd-define.js": (function(module, exports) {
module.exports = function() {
throw new Error("define cannot be used indirect");
};
}),
"./src/example.js": (function(module, exports, __webpack_require__) {
const df = __webpack_require__("./buildin/amd-define.js")
console.warn(__webpack_require__("./buildin/amd-define.js"))
})
複製代碼
NormalModuleFactory
用於建立普通模塊對象,在create
方法中會調用建立好的normalResolver
檢查將要建立的模塊是否存在,另外會使用loaderResolver
檢查使用的loader是否存在。
ContextModuleFactory
用於建立普通模塊對象,在create
方法中會調用建立好的contextResolver
檢查將要建立的目錄是否存在。
若是對
enhanced-resolve
原理不瞭解,能夠先看這篇文章
首先準備兩個文件,並添加babel-loader:
// example.js
const inc = require('./increment');
// increment.js
console.log('1+1');
// webpack.config.js
module.exports = {
mode: 'development',
entry: "./src/example.js",
module: {
rules: [{
test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: { presets: ['@babel/preset-env'] }
}
}]
}
}
複製代碼
運行打包後,能夠看到resolver相關的輸出日誌:
// resolve './src/example.js' in '/Users/kukiiu/Desktop/study/webpack'
// using description file: /Users/kukiiu/Desktop/study/webpack/package.json (relative path: .)
// Field 'browser' doesn't contain a valid alias configuration
// using description file: /Users/kukiiu/Desktop/study/webpack/package.json (relative path: ./src/example.js)
// no extension
// Field 'browser' doesn't contain a valid alias configuration
// existing file: /Users/kukiiu/Desktop/study/webpack/src/example.js
// reporting result /Users/kukiiu/Desktop/study/webpack/src/example.js
// resolve 'babel-loader' in '/Users/kukiiu/Desktop/study/webpack'
// Parsed request is a module
// using description file: /Users/kukiiu/Desktop/study/webpack/package.json (relative path: .)
// resolve as module
// /Users/kukiiu/Desktop/study/node_modules doesn't exist or is not a directory
// /Users/kukiiu/Desktop/node_modules doesn't exist or is not a directory
// /Users/node_modules doesn't exist or is not a directory
// /node_modules doesn't exist or is not a directory
// looking for modules in /Users/kukiiu/node_modules
// using description file: /Users/kukiiu/package.json (relative path: ./node_modules)
// using description file: /Users/kukiiu/package.json (relative path: ./node_modules/babel-loader)
// no extension
// /Users/kukiiu/node_modules/babel-loader doesn't exist
// looking for modules in /Users/kukiiu/Desktop/study/webpack/node_modules
// using description file: /Users/kukiiu/Desktop/study/webpack/package.json (relative path: ./node_modules)
// using description file: /Users/kukiiu/Desktop/study/webpack/node_modules/babel-loader/package.json (relative path: .)
// no extension
// /Users/kukiiu/Desktop/study/webpack/node_modules/babel-loader is not a file
// .js
// /Users/kukiiu/node_modules/babel-loader.js doesn't exist
// .js
// /Users/kukiiu/Desktop/study/webpack/node_modules/babel-loader.js doesn't exist
// .json
// /Users/kukiiu/node_modules/babel-loader.json doesn't exist
// as directory
// /Users/kukiiu/node_modules/babel-loader doesn't exist
// .json
// /Users/kukiiu/Desktop/study/webpack/node_modules/babel-loader.json doesn't exist
// as directory
// existing directory
// use ./lib/index.js from main in package.json
// using description file: /Users/kukiiu/Desktop/study/webpack/node_modules/babel-loader/package.json (relative path: .)
// using description file: /Users/kukiiu/Desktop/study/webpack/node_modules/babel-loader/package.json (relative path: ./lib/index.js)
// no extension
// existing file: /Users/kukiiu/Desktop/study/webpack/node_modules/babel-loader/lib/index.js
// reporting result /Users/kukiiu/Desktop/study/webpack/node_modules/babel-loader/lib/index.js
// resolve './increment' in '/Users/kukiiu/Desktop/study/webpack/src'
// using description file: /Users/kukiiu/Desktop/study/webpack/package.json (relative path: ./src)
// Field 'browser' doesn't contain a valid alias configuration
// using description file: /Users/kukiiu/Desktop/study/webpack/package.json (relative path: ./src/increment)
// no extension
// Field 'browser' doesn't contain a valid alias configuration
// /Users/kukiiu/Desktop/study/webpack/src/increment doesn't exist
// .wasm
// Field 'browser' doesn't contain a valid alias configuration
// /Users/kukiiu/Desktop/study/webpack/src/increment.wasm doesn't exist
// .mjs
// Field 'browser' doesn't contain a valid alias configuration
// /Users/kukiiu/Desktop/study/webpack/src/increment.mjs doesn't exist
// .js
// Field 'browser' doesn't contain a valid alias configuration
// existing file: /Users/kukiiu/Desktop/study/webpack/src/increment.js
// reporting result /Users/kukiiu/Desktop/study/webpack/src/increment.js
複製代碼
能夠看到,resovler作了不少重複或沒用的查詢,主要分爲3個文件的查詢example.js, babel-loader, ./increment
,下面咱們看看經常使用的幾個優化方法是怎麼提高查找速度的
由於有了緩存機制,別名能夠在不一樣文件使引入路徑不變,發揮緩存做用。可是單獨使用時由於多了個別名查找,會多一層替換步驟反而慢一點。
resolve: {
alias: {
ROOT: path.resolve('src/'),
},
},
複製代碼
引入文件時使用擴展名能夠最快速度找到文件,不須要經過遍歷擴展名來判段文件是否存在。
resolve.extensions
能夠控制默認須要搜索的擴展,默認是[".wasm", ".mjs", ".js", ".json"]
,能夠根據項目狀況來設置,最經常使用的放第一位,一般只設置['.js']
就好。
resolve.modules
和resolveLoader.modules
的默認值爲[node_modules]
,指在查找第三方模塊目錄或項目根目錄,而模塊是否找到是以目錄下是否有描述文件爲斷定,若是在當前文件目錄下查找不到,就往上一級查找。通常來講咱們都會將node_modules
放在項目根目錄下,因此只要將其指定爲絕對路徑就能減小查找次數。
resolve: {
modules: [path.resolve("node_modules")],
},
resolveLoader: {
modules: [path.resolve("node_modules")],
},
複製代碼
在使用第三方庫時,將庫名設置別名,能夠省去Webpack查找的過程,由於緩存的存在使其意義不大:
resolveLoader: {
alias: {
'babel-loader': path.resolve('node_modules/babel-loader/lib/index.js'),
},
},
複製代碼
有些庫爲了同時輸出web端代碼和服務器代碼,會在package.json
描述哪一個文件是用於web端,哪一個用於服務端。一般來講,導出的文件通常以main
字段來描述,可是若是main
描述的文件被服務端文件佔用,就會使用browser
來描述。在使用下面優化時要考慮第三方庫的兼容,不然打包出的代碼有可能不能在web端運行。
mainFields
是在查找第三方模塊的入口文件使用,咱們可使用[main]
來縮小搜索範圍。
aliasFields
配置是爲了替換模塊裏的某些文件使用,默認配置了[browser]
,咱們能夠配置爲[]
不進行查找
resolve: {
aliasFields: [],
mainFields: ['main'],
}
複製代碼
通過一頓操做後,再次打包,輸出日誌以下,明顯少了不少步驟。掌握了原理咱們就能夠根據項目狀況來選擇優化方案。
// resolve './src/example.js' in '/Users/kukiiu/Desktop/study/webpack'
// using description file: /Users/kukiiu/Desktop/study/webpack/package.json (relative path: .)
// using description file: /Users/kukiiu/Desktop/study/webpack/package.json (relative path: ./src/example.js)
// no extension
// existing file: /Users/kukiiu/Desktop/study/webpack/src/example.js
// reporting result /Users/kukiiu/Desktop/study/webpack/src/example.js
// resolve 'babel-loader' in '/Users/kukiiu/Desktop/study/webpack'
// Parsed request is a module
// using description file: /Users/kukiiu/Desktop/study/webpack/package.json (relative path: .)
// aliased with mapping 'babel-loader': '/Users/kukiiu/Desktop/study/webpack/node_modules/babel-loader/lib/index.js' to '/Users/kukiiu/Desktop/study/webpack/
// node_modules/babel-loader/lib/index.js'
// using description file: /Users/kukiiu/Desktop/study/webpack/package.json (relative path: .)
// using description file: /Users/kukiiu/Desktop/study/webpack/node_modules/babel-loader/package.json (relative path: ./lib/index.js)
// no extension
// existing file: /Users/kukiiu/Desktop/study/webpack/node_modules/babel-loader/lib/index.js
// reporting result /Users/kukiiu/Desktop/study/webpack/node_modules/babel-loader/lib/index.js
// resolve './increment.js' in '/Users/kukiiu/Desktop/study/webpack/src'
// using description file: /Users/kukiiu/Desktop/study/webpack/package.json (relative path: ./src)
// using description file: /Users/kukiiu/Desktop/study/webpack/package.json (relative path: ./src/increment.js)
// no extension
// existing file: /Users/kukiiu/Desktop/study/webpack/src/increment.js
// reporting result /Users/kukiiu/Desktop/study/webpack/src/increment.js
複製代碼