1. Webpack 模块与解析原理
在讲webpack模块解析之前,我们先了解下webpack模块的概念,以及简单探究下webpack的具体实现。
① webpack 模块
① 何为 webpack 模块
能在 webpack 工程化环境里成功导入的模块,都可以视作webpack模块。 与 Node.js 模块相比,webpack 模块 能以各种方式表达它们的依赖关系。下面是一些示例:
- ES2015 import 语句
- CommonJS require() 语句
- AMD define 和 require 语句
- css/sass/less 文件中的 @import 语句
- stylesheet url(…) 或者 HTML
文件中的图片链接
② 支持的模块类型 Webpack 天生支持如下模块类型:
- ECMAScript 模块
- CommonJS 模块
- AMD 模块
- Assets
- WebAssembly 模块
而我们早就发现——通过 loader 可以使 webpack 支持多种语言和预处理器语法编写的模块。loader 向 webpack 描述了如何处理非原生模块,并将相关依赖引入到你的 bundles 中。包括且不限于:
- TypeScript
- Sass
- Less
- JSON
- YAML
总的来讲,这些都可以被认为是 webpack 模块。
② compiler与Resolvers
在我们运行webpack的时候(就是我们执行webpack命令进行打包时),其实就是相当于执行了下面的代码:
const webpack = require('webpack');const compiler = webpack({// ...这是我们配置的webpackconfig对象})
webpack的执行会返回一个描述webpack打包编译整个流程的对象,我们将其称之为 compiler 。compiler对象描述整个 webpack 打包流程———它内置了一个打包状态,随着打包过程的进行,状态也会实时变更,同时触发对应的 webpack 生命周期钩子。 (简单点讲,我们可以将其类比为一个Promise对象,状态从打包前,打包中到打包完成或者打包失败。) 每一次 webpack 打包,就是创建一个 compiler 对象,走完整个生命周期的过程。
而 webpack 中所有关于模块的解析,都是 compiler 对象里的内置模块解析器去工作的————简单点讲,你可以理解为这个对象上的一个属性,我们称之为 Resolvers。 webpack 的 Resolvers 解析器的主体功能就是模块解析,它是基于 enhanced-resolve 这个包实现的。换句话讲,在webpack中,无论你使用怎样的模块引入语句,本质其实都是在调用这个包的 api 进行模块路径解析。

2. 模块解析
webpack 通过Resolvers实现了模块之间的依赖和引用。举个例子:
// 引入第三方库import _ from 'lodash';// 引入本地模块const add = require('./utils/add');
所引用的模块可以是来自应用程序的代码,也可以是第三方库。 resolver帮助 webpack 从每个 require/import 语句中,找到需要引入到 bundle 中的模块代码。 当打包模块时,webpack 使用enhanced-resolve来解析文件路径。
① webpack中的模块路径解析规则
通过内置的enhanced-resolve,webpack 能解析三种文件路径:
绝对路径
import '/home/me/file';import 'C:\\Users\\me\\file';
由于已经获得文件的绝对路径,因此不需要再做进一步解析。
相对路径
import '../utils/reqFetch';import './styles.css';
这种情况下,使用 import 或 require 的资源文件所处的目录,被认为是上下文目录。 在
import/require中给定的相对路径,enhanced-resolve会拼接此上下文路径,来生成模块的绝对路径 (path.resolve(__dirname, RelativePath) 。 这也是我们在写代码时最常用的方式之一模块路径
import _ from 'lodash'import 'module/lib/file';
也就是在
resolve.modules中指定的所有目录检索模块(node_modules里的模块已经被默认配置了)。 你可以通过配置别名的方式来替换初始模块路径, 具体请参照下面resolve.alias配置选项。
② resolve
- alias
通过配置resolve.alias为路径起别名:
const path = require('path');module.exports = {//...resolve: {alias: {"@": path.resolve(__dirname, './')},},};
// 原来引入路径import './src/utils/add.js'// 现在通过别名引入import '@/utils/add.js'
- extentions
当所引入的文件在原来的文件夹中没有同名文件时,我们可以省略文件后缀:
import './src/utils/add'
但当所引入的文件在原来的文件夹中有同名文件时(如:utils 文件夹下有两个 add 文件,一个是 js 文件,一个是 json 文件),它会按照 webpack 默认配置的优先级,优先引入优先级高的文件格式的文件(add.js),那么如何自定义引入文件格式的优先级呢?可以通过配置resolve.extentions来实现
module.exports = {//...resolve: {extensions: ['.js', '.json', '.vue'],},};
webpack 会按照数组顺序去解析这些后缀名,对于同名的文件,webpack 总是会先解析列在数组首位的后缀名的文件。
3. 外部扩展
有时候我们为了减小 bundle 的体积,从而把一些不变的第三方库用cdn的形式引入进来,比如:jQuery。在我们 webpack 项目中不安装 jQuery 的情况下,通过 cdn 的方式使用 jQuery ,则可以通过 webpack 的内置属性externals来配置外部扩展模块。
index.js
import $ from 'jquery'console.log($)
module.exports = {//...externals: {// key值(jquery)与引入的包名要相同// 第一个参数表示cdn链接// 第二个参数表示script标签在浏览器上暴露的对象jquery: ['https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js', 'jQuery']},// 指定cdn链接是以什么标签插入index.html中的externalsType: 'script'};
然后我们就实现了以 cdn 的方式使用 jQuery 了
