昨天写了一篇文章,是关于 Vite
的,基于 Vite 从 0 到 1 启动一个 Vue2 项目,文章后面留下了几个问题是关于模块寻址问题,举个 Vue
项目里比较常用的 Vue
模块导入例子
import Vue from "vue";
为什么不用写相对地址和绝对地址就能够导出 Vue
呢?似乎也没有配置路径?也没有配置映射,那么究竟 from "vue";
对应的究竟是那个路径呢?
先提出两个可能的方案
VS Code/WebStorm
这些IDE
自己编撰的映射,并缓存了Webpack/Vite
这些开发工具自带的映射
关于上面这两个方案其实很容易验证,基于终端/命令行去运行项目并且不使用 Webpack
和 Vite
这两个库
- 第一步,执行以下语句
npm init
# shelljs 是一个可以使用 node 执行 shell 脚本的库
# 例子中使用 shelljs 测试输出
npm install shelljs
- 第二步,在
package.json
同目录下创建index.js
,内容如下
const shelljs = require("shelljs");
shelljs.exec("echo Hello World");
- 第三步,测试运行
node index.js
,输出如下
即证与 IDE
、Webpack/Vite
无关
那究竟是怎么找到的呢?
node 模块
仔细观察 node index.js
是通过 node
执行的,所以有没有可能是通过 node
去找到的模块呢?
那么 node
执行 .js
文件时是按照什么规则来寻找的呢?
假设在 Y
路径执行 index.js
// index.js
const module = require(X)
X 以 或 './' 或 '../' 开头
判断 Y + X
是文件还是目录
Y + X 是文件
- 如果
X
是一个文件,加载X
作为JavaScript
文本。结束 - 如果
X.js
是一个文件,加载X.js
作为JavaScript
文本。结束 - 如果
X.json
是一个文件,解析X.json
成一个JavaScript
对象。结束 - 如果
X.node
是一个文件,加载X.node
作为二进制插件。结束
例子
// index.js
const shelljs = require("./X");
shelljs.exec("echo Hello World");
// X.js
module.exports = {
exec: () => {
console.log("Hello World");
}
}
├── index.js
└── X.js
注意,有文档称,require(/X)
这种方式也可以找到对于的模块,但在 node@16.15.1
的版本中已经不行,是无法找到的
Y + X 是目录
判断是否有名称为 index
的文件
Y + X 目录下有名为 index 的文件
- 如果 X/index.js 是一个文件,加载 X/index.js 作为 JavaScript 文本。结束
- 如果 X/index.json 是一个文件,解析 X/index.json 成一个 JavaScript 对象。结束
- 如果 X/index.node 是一个文件,加载 X/index.node 作为二进制插件。结束
Y + X 目录下没有名为 index 的文件
根据 Y + X
目录下的 package.json
的 "main"
字段找到对应的文件
没有 package.json
怎么办?当然是报错咯
X 是模块名称
- 先去系统模块寻找
系统模块没有找到就去执行环境的同目录下找
node_modules
,查看有无同名的.js
文件没有同名的
.js
文件就去找同名目录- 如果找到了,就查看文件夹里有没有
index.js
- 没有找到就查看
package.json
的"main"
属性来确定
- 如果找到了,就查看文件夹里有没有
- 没有同名目录,抛出错误
- 同目录找不到
node_modules
就去父目录找,父目录找不到就找父目录的父目录,直至找到全局node_modules
关于 node
更加详细的‘寻模块’规则,可以去查阅这个文档,里面有一个伪代码,非常详细和清晰
ES Module
上面讲了 require
的寻找规则,但是没讲 ES Module
的寻找规则(即 import
),其实它们的寻址方式是一致的,不过 ES Module
需要在 package.json
设置 "type"
字段为 module
才可以开启,而且找的是package.json
中 "module"
字段对应的文件
// package.json
{
"type": "module"
}
那么回到文章一开始提到的 import Vue from "vue";
,它最终找到的模块是什么呢?因为 Vue
本身提供多种打包文件(Commen.js
和 ES Module
)
如上图所示 import Vue from "vue";
最终找的是 vue.runtime.esm.js
,这也就解释了为什么导出来的 Vue
是不带编译时只有运行时的了