Webpack源码分析 - 目录解析及优化

Webpack中目录解析及优化

在Webpack中咱们能够任意使用多种模块化方式组织代码:node

  • ES6: import xxx from 'xxx'
  • CommonJS: require('./xxx')
  • AMD...

咱们可使用它导入各类第三方库及项目文件,也可使用别名来简化路径。咱们知道,虽然CommonJS是node原生支持的依赖管理方法,可是咱们打包项目时不是node来加载要打包的文件,并且查找规则也和node的不同,因此Webpack实现了本身的一套文件查找方法。webpack

依赖这套加载器,Webpack能够实现各类模块化规则解析,同时支持别名,目录查找等定制化的需求。下面咱们来看看它的实现方式:git

初始化及使用Resolver

Webpack中使用enhanced-resolve包装的ResolverFactory提供实例化方法,并根据参数添加特殊功能的插件,共建立了三种类型的resolver:github

  • normalResolver:提供文件路径解析功能,用于普通文件导入
  • contextResolver:提供目录路径解析功能,用于动态文件导入
  • loaderResolver:提供文件路径解析功能,用于loader文件导入

在建立模块时,就会利用Resolver预先判断路径是否存在,并获取路径的完整地址供后续加载文件使用。web

ResolverFactory

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

WebpackOptionsApply中初始化配置时,会在hook.resolveOptions上添加额外配置缓存

// normalResolver, loaderResolver
{ fileSystem: compiler.inputFileSystem }
// contextResolver
{ fileSystem: compiler.inputFileSystem, resolveToContext: true }
复制代码

NodeSourcePlugin

用于对Node.js核心库polyfill,当Webpack配置targetweb,webworker时启用。例如os是node的核心模块,在浏览器里是没有的,可是用webpack打包后会加上polyfill,打包后能在浏览器中运行:服务器

const os = require('os')
console.log(os)
复制代码

在初始化时,会向hooks.resolver添加AliasPlugin插件,将须要polyfill的库名添加到别名中。babel

AMDPlugin

用于处理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.modulesresolveLoader.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
复制代码

参考资料

enhanced-resolve

node-libs-browser

相关文章
相关标签/搜索