美文网首页vue 好吧
Webpack 如何解析模块路径

Webpack 如何解析模块路径

作者: 越前君 | 来源:发表于2020-10-29 01:16 被阅读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)将检查路径是否指向文件目录

    • 指向文件

      1. 如果路径具有文件扩展名,则被直接打包。
      2. 否则,将使用 resolve.extensions 选项作为文件扩展名来解析。
    • 指向目录

      按以下步骤找到具有正确扩展名的文件:

      1. 如果文件夹中包含 package.json 文件,则按顺序查找 resolve.mainFields 配置选项中指定的字段,并且 package.json 中的第一个这样的字段确定文件路径。
      2. 如果 package.json 文件不存在或者 package.json 文件中 main 字段没有返回一个有效路径,则按顺序查找 resolve.mainFields 配置选项中指定的文件名,看是否能在 import/require 目录下匹配到一个存在的文件名。
      3. 文件扩展名通过 resolve.extensions 选项采用类似的方法进行解析。

    若使用 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 查看。

    参考

    相关文章

      网友评论

        本文标题:Webpack 如何解析模块路径

        本文链接:https://www.haomeiwen.com/subject/krpqvktx.html