vue 好吧

Webpack 如何解析模块路径

2020-10-29  本文已影响0人  越前君

你一定见过这些导入方式,无论是 ESM 还是 CommonJS 模块,或是其他模块规范。

import react from 'react'
import button from './components/button'
const path = require('path')

那么 webpack 是如何去解析查找它们的呢?

模块解析

resolver 是一个库(library),用于帮助找到模块的绝对路径。一个模块可以作为另一个模块的依赖模块,然后被后者引用。例如:

import foo from 'path/to/module'

所依赖的模块可以是来自应用程序或者第三方库(library)。resolver 帮助 webpack 找到 bundle 中需要引入的模块代码,这些代码在每个 import/require 语句中。

webpack 使用 enhanced-resolve 来解析文件路径。

解析规则

使用 enhanced-resolve 解析模块,支持三种形式:绝对路径相对路径模块路径

1. 绝对路径

不建议使用。

由于已经取得文件的绝对路径,因此不需要进一步再做解析了。

在实际项目中,除了设置别名 resolve.alias 时采用绝对路径的方式,其他的我几乎没见过使用绝对路径的。(也可能我读的项目太少了)

import button from '/Users/frankie/component/button'
2. 相对路径

在这种情况下,使用 import/require 的资源文件(resource file)所在的目录被认为是上下文目录(context directory)。在 import/require 中给定的相对路径,会添加此上下文路径(context path),以产生模块的绝对路径(absolute path)。

import button from './component/button'
3. 模块路径

上面两种方式,应该没有太多理解难度,而模块名才是我们要重点理解的。

直接引入模块名,首先查找当前文件目录,若查找不到,会继续往父级目录一个一个地查找,直至到项目根目录下的 node_modules 目录(默认)。若再查找不到,则会抛出错误。

import 'react'
import 'module/lib/file'

注意:

  • 默认的 node_modules 可以通过 resolve.modules 进行更改。
  • 查找中会根据 resolve.extensions 自动补全扩展名,默认是 ['.wasm', '.mjs', '.js', '.json']
  • 查找中会根据 resolve.alias 替换掉别名。

模块将在 resolve.modules 中指定的目录内搜索。可以通过 resolve.alias 配置创建一个别名来替换初始模块路径。

一旦上述规则解析路径之后,解析器(resolver)将检查路径是否指向文件目录

若使用 webpack-dev-server 3.x 版本,建议不要随意修改 resolve.mainFields 配置项,它会报错。已确认是 webpack-dev-server 的 bug,将在不久要发布的 4.x 版本修复。详请 #2801

解析与缓存

Loader 解析遵循与文件解析器指定的规则相同的规则。resolveLoader 配置选项可以用来为 Loader 提供独立的解析规则。

每个文件系统访问都被缓存,以便更快触发对同一文件的多个并行或者串行请求。在观察模式下,只有修改过的文件会从缓存中摘出。如果关闭观察模式,在每次编译前清理缓存。

Resolve 配置

该选项用于配置模块如何解析。例如,当在 ES6 中调用 import 'lodash'resolve 选项能够对 webpack 查找 lodash 的方式去做修改。

1. resolve.alias

创建 import 或 require 的别名,来确保模块引入变得更简单。

例如,一些位于 src/ 文件夹下的常用模块:

// webpack.config.js
const path = require('path')

module.exports = {
  //...
  resolve: {
    alias: {
      // 可以是绝对路径,或者是相对路径。
      // 据我不完全观察,结合 path 模块和 __dirname 拼接成“绝对路径”的方案更多。
      // 以下为模糊匹配
      Utilities: path.resolve(__dirname, 'src/utilities/'),
      Templates: path.resolve(__dirname, 'src/templates/')
    }
  }
}

现在,你可以这样使用别名了:

import Utility from '../../utilities/utility'

// 别名
import Utility from 'Utilities/utility'

也可以在给定的对象的键后的末尾添加 $,以表示精准匹配。这里不展开赘述,详细请看这里

注意,采用别名引入模块时,先替换后解析。先将模块路径中匹配 alias 中的 key 替换成对应的 value,再做查找。

2. resolve.extensions

自动解析确定的扩展。

// webpack.config.js
module.exports = {
  //...
  resolve: {
    // 使用此选项,会覆盖默认数组,默认值:['.wasm', '.mjs', '.js', '.json']。
    // 注意不要少了符号(.),有些人配置不成功,就是因为少了它。
    // 从左到右(从上到下)先后匹配扩展名,选项中没有的后缀,是不会自动补全的。
    extensions: ['.js', '.json']
  }
}
3. resolve.modules

告诉 webpack 解析模块时应该搜索的目录。可以是绝对路径或者相对路径,但是它们之间有一点差异。

通过查看当前目录以及祖先路径(即 ./node_modules../node_modules 等等),相对路径将类似于 Node 查找 node_modules 的方式进行查找。

当使用绝对路径,将只在给定目录中搜索。

// webpack.config.js
const path = require('path')

module.exports = {
  //...
  resolve: {
    // 默认值
    modules: ['node_modules']
    // 添加一个目录到模块搜索目录,此目录优先于 node_modules 搜索。
    modules: [path.resolve(__dirname, 'src'), 'node_modules']
  }
}

一般地,不要去更改该选项。

4. resolve.mainFields

当从 npm 包中导入模块时(例如,import * as D3 from 'd3'),此选项将决定在 package.json 中使用哪个字段导入模块。根据 webpack 配置中指定的 target 不同,默认值也会有所不同。

// webpack.config.js
module.exports = {
  //...
  resolve: {
    // 不建议修改

    // target 为 webworker、web 或没有指定时,默认值为:
    mainFields: ['browser', 'module', 'main'],

    // 除去上述几个 target,对于其他任意 target(包括 node),默认值为:
    mainFields: ['browser', 'module', 'main']
  }
}

通常情况下,模块的 package.json 都不会声明 browsermodule 字段,所以便是使用 main 了。(该选项同样不建议更改)

5. resolve.mainFiles

解析目录时要使用的文件名。

当目录中没有 package.json 时,结合 resolve.extensions 来指明使用该目录中哪个文件。

// webpack.config.js
module.exports = {
  //...
  resolve: {
    // 默认值
    // 可添加多个,但不建议修改。
    mainFiles: ['index']
  }
}

尽可能地,不要去修改该选项。因为它同样会影响第三方依赖包解析,可能会导致部分第三方包解析错误。例如,我在验证该配置时,就发现 webpack-dev-server v3 的一个 bug,开发者表示将在 v4 版本中修复。

所以,不建议随意修改的配置包括 modulesmainFieldsmainFiles

6. 更多

它还有其他一些配置项,但比较少用,所以不展开赘述。更多请看这里

ResolveLoader 配置

从 webpack 2 开始,在配置 loader 时强烈建议使用全名。例如 example-loader,以尽可能地清晰。

然而,如果你确实想省略 -loader,也就是说只使用 example,则可以使用 resolveLoader.moduleExtensions 此选项来实现:

// webpack.config.js
module.exports = {
  //...
  resolve: {
    // ...
  }
  resolveLoader: {
    moduleExtensions: ['-loader']
  }
}

我使用 webpack 4 在不配置该选项时,假如将 css-loader 省略为 css,会报错提示找不到 loader。为什么我会单独拿出来介绍一下,因为网上很多文章表示在配置 module.rules 时可以省略 -loader,但我是省略了就不行。所以这里补充一下原因。

小技巧

关于 webpack 默认配置可以从 node_modules/webpack/lib/WebpackOptionsDefaulter.js 查看。

参考

上一篇下一篇

猜你喜欢

热点阅读