什么是 jiti
Runtime Typescript and ESM support for Node.js 一个直接用来运行 TS 和 ESM 的 node 运行时
jiti vs esno
在此之前,学习过 esno & tsx 的 TS 运行原理,esno & tsx 原理初探,大致原理是拓展了 import 和 require 的能力,ESM 通过 node 官方的 lodaer 实现的解析和加载 TS,CJS 通过扩展 Module 的语法,通过修改原型链的方法来扩展。而 jiti 使用了另一种方式来实现类似的逻辑。
NodeJS 中的 commonJS 本质是啥
Note
从文章可知,commonjs 的本质就是一个函数
_10(function(exports, require, module, **filename, **dirname) {_10// Module code actually lives in here_10});
我们用到的 exports, require ,module, __filename , __dirname 其实都是 node 在运行的时候帮帮我们注入进来的。
runInThisContext
node VM 模块提供的方法,可以用来直接执行 js 代码,但是作用域就是当前的全局环境,本地的环境是访问不到的,然后将返回的结果返回回来
_10const vm = require('vm');_10let localVar = 'GfG';_10const vmresult = vm.runInThisContext('localVar = "Geeks";');_10console.log(`vmresult: '${vmresult}', localVar: '${localVar}'`);
vm.runInThisContext() 方法无法访问本地范围,因此 localVar 此处未更改。所以在这里我们知道了 runInThisContext 可以隔离作用域,防止修改 node 原本的运行环境
jiti 的基本原理
Note以下的内容是基于 jiti 理解的简单实现,只列举了很少的一部分功能,需要学习全部的功能实现请移步到 GitHub 源码观看
jiti 如何使用
Note仓库给出的代码实例如下
_10const jiti = require("jiti")(\_\_filename);_10jiti("./path/to/file.ts");
直接给出最小实现,然后解释代码的大体执行顺序
- 在第 8 行代码中我们创建了一个新的 require 然后直接将 jiti 运行的代码返回回去
- 在第 16 行的时候, 我们通过运行时候传入的文件后缀名判断出来了是 JSON 的文件,直接返回回去就可以了
- 在 21 行的时候,我们默认所有的文件都需要编译,去走下面的 evalModule 方法
- 在 35 行的时候,我们使用了 babel 的方法将所有的文件都编译成 commonjs 的内容
- 然后根据当前文件的信息新建一个 mod,更新 mod 的 parent 和 parent 的 children 信息
- 在第 52 行的时候 我们用 Module.wrap 来包装一下我们的代码,在上次的 commonjs 转换中,我们将原来的代码都编译成了 commonjs 的格式,里面存在 require 和 exports,我们通过 Module.wrap 来包装原来的代码,将自己实现的 require 穿入进去
- 在 52 行的时候,我们可以大致认为我们的 compiled 就是下面的样子
_10(function (exports, require, module, __filename, __dirname) {_10 // Module code actually lives in here_10});
然后将我们自定义的 exports, require ,module, __filename , __dirname 穿入进去
_10compiled(mod.exports, mod.require, mod, mod.filename, dirname(mod.filename));
可以发现,最简代码可以运行。
缺点
jiti 是基于 commonjs 来实现的,所以存在 commonjs 的限制 例如直接 引入 HTTP 的模块
_10import presetUno from 'https://esm.sh/@unocss/[email protected]';
https://github.com/unjs/jiti/issues/161