luckyyyyy / blog

William Chan's Blog
https://williamchan.me/
175 stars 28 forks source link

Webpack Module Resolution && nodejs require #35

Open luckyyyyy opened 5 years ago

luckyyyyy commented 5 years ago

在一个月前遇到了一个棘手的问题,起因大概是这样的,由于我自己希望了解全部的生态,所以我一直都是使用自己的脚手架,而不是Vue或者React官方的提供的脚手架,虽然诸如umi,vue-cli,create-react-app他们做的都很优秀,但是我还是想从中知道每一个plugin,每一个loader都做了什么事情。

在最近的一次升级中,我使用了最新的babel7,并且使用了core-js@3版本,但在过程中遇到了一些问题。

问题

先来看一下我遇到的问题,我使用了ant-design-vue这个UI库,他依赖了babel-runtime@6.x,从编译后的代码可以看到存在着诸如许多import _extends from 'babel-runtime/helpers/extends';这样的代码。

然后我们再来看一下babel-runtime@6.x这个库,他依赖了core-js@2的版本,主要是提供运行时的新 API 转换,例如 babel 不能转换的一些新的 API,他会提供一些 polyfill 级别的解决方案,当然与babel-polyfill是有本质差异的,babel-polyfill更多是偏向业务层面的东西,他所支持的语法更多,毕竟babel-runtime只支持到 static 语法,并不能对[1, 2, 3].includes(1)进行处理,这里不展开,后面会有篇幅重点介绍。

那么现在依赖关系就是我依赖了ant-design-vuecore-js@3,但是ant-design-vue依赖了core-js@2,看了 core-js 2和3版本,发现差异还是非常大的,连目录结构都不同了,我仔细查看了依赖结构,实际上其实是不应该有问题的,我把依赖结构写在下面,为了清晰表达省略一些内容。

test
├─┬ babel-runtime@6.26.0
│ ├── core-js@2.6.9 extraneous
│ └── regenerator-runtime@0.11.1
└── core-js@3.1.3

但当我执行编译时,webpack 却提示找不到core-js@2中的模块。

Uncaught Error: Cannot find module 'core-js/library/fn/object/define-property'

定位问题

当发现这个问题后,首先想到的肯定是查看 node_modules 中是否存在这个模块,但是查询后发现由于依赖关系的原因,core-js@2被放到了babel-runtime中的 node_modules,然后我就去查阅了 nodejs 的 require 规则,发现他是从 module.paths 的目录中依次顺序查找的,那么根据规则,实际上在babel-runtime中的调用并不会去找我项目中的 node_modules 而是去寻找 babel-runtime 中的 node_modules。

那么从nodejs的行为上来说,这是正确的,并不会造成两个库依赖的版本不同。找不到模块的情况。

然后我尝试写了一个简单的 demo 来验证这个结论,直接执行 node src/main.js,注释掉对vue文件的引用,因为没有经过 vue-loader 处理的 vue 文件是无法被 nodejs 直接解析的,发现可以顺利通过。

随后使用 webpack 执行,发现报告了找不到模块的错误,这时候第一反应就是 webpack 的配置是有问题的。

解决问题

查阅文档后,最终发现,在 webpack 中其实是可以定义模块解析规则的,除了省略扩展名,定义 alias 外,还可以指定路径,这一项在默认情况下其实是正确的,但是如果你定义了绝对路径,那么他只会根据你定义的路径进行解析,这一部分的 webpack 源码晚些我会发布在下面。

webpack 的具体配置如下

// 文档中的描述

// Tell webpack what directories should be searched when resolving modules.

// Absolute and relative paths can both be used, but be aware that they will behave a bit differently.

// A relative path will be scanned similarly to how Node scans for node_modules, by looking through the current directory as well as its ancestors (i.e. ./node_modules, ../node_modules, and on).

// With an absolute path, it will only search in the given directory.

resolve: {
  modules: ['node_modules'],  // 这部分非常重要,之前错误的定义为决定路径,实则如果不定义,那么它就会和 nodejs 的行为相近
},

实际上这个问题如果说的复杂,还需要牵扯到许多,我会分几期来说这些问题。