在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
复制代码