有了ts-node和swc-node,我还要写个ts_hook

754 阅读4分钟

在使用 ts 写 Node 程序时,为了调试方便,我们平时一般会开启 tsc 的 --watch 功能,或者选择直接在 Node 中运行 ts 文件 而在 Node 中直接运行 ts 文件,可以借助 Node 提供的 --loader 和 -r 选项,在导入前将文件编译后再执行,这类工具有 ts-node 和 @swc-node/register

那为什么我还要造这个轮子,又写了一个@asnc/ts_hook? 想必用过这两个工具的应该有体会。总之就是在两个模块(ES Module 和 Commonjs)的兼容上很繁琐,还有就是路径别名的问题

先上链接: github.com/asnow-c/ts-…

与 ts-node 和 @swc-node/register 对比

@asnc/ts_hook@swc-node/registerts-node
依赖typescript 或@swc/coretypescript 和 @swc/coretypescript
类型检查不支持不支持支持
源映射支持支持支持
跨包导入 ts 项目支持不支持不支持
混合 ES Module 和 CommonJs支持不支持支持
路径别名支持某些情况下支持不支持

跨包导入

在 monorepo 项目中,我们会同时用 Typescript 开发多个包,他们之间可能会互相引用 在这种场景下使用 Node debugger 某个包时,我们必须将这个包和它所依赖的 项目中的包进行编译 而 而如果使用 ts-node@swc-node/register 则只需要编译它所依赖的包,这是因为他们的钩子不会对外部依赖进行编译 而这是非常不方便的 所以 @asnc/ts_hook 解决这个问题。@asnc/ts_hook 会跟踪所有 .ts .mts .cts 文件的导入

混合 ES Module 和 CommonJs

有如下目录:

work
- index.mts
- esm.mts
- cjs.tsc

index.mts:

import "./cjs.cjs";
import "./esm.mjs";
console.log(import.meta);

@swc-node/register 直接无法解析这种全路径的导入

路径别名

很多情况下 我们会在 tsconfig.json 设置 paths 字段 @swc-node/register 在运行时,只查找工作目录下的 tsconfig.json 文件的 paths 字段,如果在运行时工作目录不在项目中,这就意味着 @swc-node/register 无法读取别名配置,除非显示的设置别名

@asnc/ts_hooks 支持项目级的 tsconfig.json 中的别名配置(需要与 package.json 同目录)

总结

是的,这两个太多东西不支持了,我只想直接 debugger,但是他们很多场景都无法解决,所以我干脆直接写一个算了

ts_hook

要求 node 版本>=16

全局安装: npm install @asnc/ts_hook -g
项目安装: npm install @asnc/ts_hook -D

用法: node --loader MODULE_PATH/@asnc/ts_hook/hook.mjs xx.ts
用法: node --loader MODULE_PATH/@asnc/ts_hook/hook.mjs xx.mts
用法: node --loader MODULE_PATH/@asnc/ts_hook/hook.mjs xx.cts
用法: node --loader @asnc/ts_hook/hook.mjs xx.cts (项目级使用)\

注意: MODULE_PATH是你安装@asnc/ts_hook的所在目录的绝对或相对路径, 使用绝对路径时, Windows 系统下必须以/开头 如 /C:/npm_global/@asnc/ts_hook.mjs, 使用相对路径时, 相对于 node 的启动目录, 且必须以./开头

如果使用全局安装ts_hook, 你需要全局安装 typescript 或 @swc/core 其中的一个依赖, 如果是项目安装ts_hook, 你需要在项目中安装 typescript 或 @swc/core, ts_hook 依赖他们, ts_hook 优先尝试导入 @swc/core, 如果导入失败, 才会尝试导入 typescript, 你可以在通过配置进行选择

需要注意的是:

不论你的 tsconfig.json 的 module 配置成什么,最终编译都是用 NodeNext

这是因为,既然是 Node 的 loader,那就是你的 ts 编译后,必定是要在 NodeJs 中运行的,那肯定要遵循 Node 的导入策略

ts-hook 功能

  • 不提供类型检查
  • 可选 tsc 和 swc,编译器, 优先尝试导入 swc 编译器
  • 支持 package.json 的 imports、 exports、main 字段
    • 如果通过这几个字段解析出的 文件 id 是以.js .mjs .cjs 扩展名结尾,并且这个文件不存在,则会长沙将扩展名替换成 .ts .mts .cts 后再尝试导入
  • ts 的 module 编译选项固定为 NodeNext
  • 支持源映射
  • ES Module 可以使用与 commonjs 一致的解析策略

配置

配置需要从环境变量注入

对于 boolean 类型: 设置""false 其他值均为 true

SAME_PARSER: ES Module 是否使用与 Commonjs 相同的解析策略

类型 boolean 默认 false 开启后 ES Module 将使用与 Commonjs 相同的模块解析策略

TS_COMPILER_OPTIONS: 全局的 ts 文件编译配置

类型 string

注意: 有些配置是会被覆盖的

例子: vscode debugger 配置

{
    "configurations": [
        {
            "type": "node",
            "env": {    //注入环境变量
                "TS_COMPILER_OPTIONS": "{\"target\":\"esnext\"}",
            },
            ...
        }
    ]
}
TS_CONFIG_PATH: 全局 ts 文件编译的配置文件

类型 string

注意: 有些配置是会被覆盖的

配置案例

vscode 调试配置, 可以注入环境变量

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "ts debugger",
            "runtimeArgs": ["--loader", "./node_modules/@asnc/ts_hook/hook.mjs"], //使用loader
            //注入环境变量
            "env": {
                "SAME_PARSER": "" //设置"" 即false
            },
            "sourceMaps": true,
            "program": "${file}"
        }
    ]
}