文章转自:深入理解webpack之resolve - 知乎

相信很多用过vue-cli的朋友对'vue$': 'vue/dist/vue.esm.js',都不会感到陌生,但不知有多少和我一样的朋友只知其行未解其意恰巧之前详细的查阅了相关文档,弄清楚了这个问题,在此做下记录,也希望能对读到的朋友有益。

说一下题外话,真心建议大家一定要细读相关文档,很多不清楚的问题都是因为没有仔细的阅读。

言归正传,webpack中使用resolve字段来配置模块的相关解析策略。本质上是通过对resolve库的使用来解析模块路径,帮助 webpack 找到 bundle 中以require/import引入的模块代码。这种路径的具体解析过程,webpack官网对此进行了清晰的阐述[1]

模块可以是一个文件也可以是一个文件夹,可以是相对路径(以./或../开头),也可以是绝对路径(以/或c:盘符开头),还可以是模块路径(除却以上两种情况如:import "module/lib/file";)。无论是哪一种情况的引入,最终解析得到的都是一个绝对路径的文件,这个文件可能有扩展名,也可能没有扩展名,有扩展名直接打包,如果没有扩展名,resolve也为我们提供了预留选项extensions:

extensions扩展名选项在resolve追踪到的文件如果没有扩展名时,会尝试在其提供的扩展名选项里进行匹配。就如我们常常引入一个js文件时a.js,会直接写import 'a'而不是import 'a.js'。

相对路径和绝对路径的模块引入通常是我们在开发中定义的模块,如引入一个导出一个class的es6 module:import b from './b.js'(b.js里export default b ).这似乎很好理解。那么以模块路径形式引入一个模块如import _ from 'lodash',是如何进行解析的?

首先,你可能会忽略一个重要的常常采用默认配置的选项resolve.modules ,它会告诉 webpack 解析模块时应该搜索的目录。默认配置:modules: ["node_modules"]即我们通常以模块路径引入的模块,是在node_module目录下进行查找的。

其次,你可能还会忽略resolve.descriptionFiles ,这个选项会定义一个用于描述的 JSON 文件,默认为descriptionFiles:['package.json']。这个json里会包括某些描述路径的字段如main、module等,这些字段的值会指定一个具体模块指向的路径。当我们导入的模块是一个目录(通常是一个npm包)如import from 'lodash',此时根据resolve.modules会到node_module目录下去找lodash,发现是一个文件夹,然后去看是否包含我们配置的描述文件,package.json。如果包含,则会去查找package.json里的相应字段,这时我们就需要另外一个选项resolve.mainFields 。

resolve.mainFields:选项将决定在package.json中使用哪个字段导入模块。并依据target的不同默认值不同。但无论哪种target,其都包含module和resolve。mainFields: ["module", "main"]。如上所述,导入lodash模块,到package.json(注意是lodash目录下的package.json)依次查找resolve.mainFields即module或main,如存在module字段即不再向后查找main字段了,如果module字段不存在,再去查看main字段是否存在。

如上图所示,lodash下的package.json只有main字段,其指向lodash.js,所以最终我们得到的是lodash.js文件。当然如果上面的阐述你懂明白了,你应该能够想到如果main指定的路径得到的文件没有扩展名,如main:'lodash',这时其实还是要用到extensions为我们做扩展名补充的。

另外,如果lodash的package.json中不存在resolve.mainFields中定义的字段呢(main和module字段都不存在),这时就解析失败了吗?答案当然是No!因为reaolve还未我们提供了一个字段:resolve.mainFiles ,默认配置mainFiles: ["index"],此选项就是在package.json文件不存在或者package.json文件中的 main 字段没有返回一个有效路径,则按照顺序查找resolve.mainFiles配置选项中指定的文件名,看是否能在 import/require 目录下匹配到一个存在的文件名。

单拿出resolve.mainFiles你可能还有点陌生,但其实绝大多数的朋友应该都用过它的好处。如:我在项目的utils目录下有index.js(导出变量a)和tools.js(导出变量b)。现在想引入它们:

导入a:import { a } from '../utils'

导入b:import { b } from '../utils/tools'

导入a的形式,相信你并不陌生,你也知道其实是import { a } from '../utils/index'的简写。但是现在你应该知道了为什么在导入目录下的index.js时可以省略index.js。原因就是resolve.mainFiles的默认配置去查找index.js。当然你也可以自己配置

这时我的a、b的导入就变成:

导入a:import { a } from '../utils/index'

导入b:import { b } from '../utils'

但是这个选项的默认配置还是非常有道理的,因为很多模块都会有一个index.js,如上述的lodash,如果其package.json里不存在main字段,或main字段指向的不是一个有效的路径,那么就会去查找index.js。所以为了不影响对第三方模块的解析,这个选项还是取默认配置为好。我们这里只是作为理解。

以上说了这么多,现在你是否能理解为什么import _ from 'lodash/index.js'import from 'lodash/lodash.js'以及import _ from 'lodash'三种引入方式都可以呢?

暂且先记录到这里,resolve其实还有一些有意思的配置,只是我还理解的不深,以后有机会再补上。另外找时间再记录一下'vue$': 'vue/dist/vue.esm.js'。

参考

  1. ^webpack4中文网 模块解析(module resolution) | webpack 中文网
Logo

前往低代码交流专区

更多推荐