ts-node加载node_modules中的源码

792 阅读3分钟

需求来源

安装了一个自己写的test-package,里面有一些可复用的代码,比如有个index.ts的代码如下:

export const msg = 'msg';

想要在新项目里面import这些代码,

import {msg} from 'test-package/index'

发现无法正常import,提示

image.png

定位问题

解析ts我这边使用的ts-node,对配置也不是太熟悉,根据报错堆栈开始追代码,发现import会调用require,而require会调用module.load, Module._extensions[extension]() 这句代码会根据文件扩展名调用相应的

image.png

顺带看了下json的实现

image.png

原理就是读取字符串,然后JSONParse下,也不是太难

const {JSONParse} = primordials;

ts文件的话,最终就会来到ts-node里面,这里有个非常重要的逻辑:

image.png

image.png

image.png image.png

其中shouleIgnore函数里面,有对node_modules的排除,同时options也是可以干扰这个结果的,其实在ts-node的文档里面也有说明这个参数

那么我们应该如何传递这个参数呢?

解决问题

ts-node的文档上也有写,会读取当前工作目录的tsconfig.json

它会从ts-node中读取需要的参数

tsconfig.json示例

{
    "ts-node":{
        "skipIgnore": true
    }
}

这样就能完美的引入node_modules里面的代码了,至于有其他的什么问题,暂时没有多测试

优化实现

上述的这个解决方案能解决问题,但是我的需求是希望默认开始这个设置,不想要用户自己在tsconfig.json中手动增加这个配置。

我使用的是cli,所以可以通过代码控制这个参数,

require('ts-node')
    .register({
        skipIgnore: true,
    });

但是我这里使用了一个第三方的库:rechoirinterpret,它可以根据不同的文件扩展名准备内置好的环境,对于ts文件,它内置了好几个环境,其中就有这个ts-node,最终也是会调用register函数, 这个库也是通过看vue-cli-server才知道的。

所以接下来,就看下怎么把register的参数传递进去

最终发现interpret的配置是写死的,也没有开放register的参数,看来通过api修改的路堵死了。

image.png

再读ts-node的源码,定义环境变量

发现options的数据有来自process.env

export const env = process.env as ProcessEnv;
export const DEFAULTS: RegisterOptions = {
  cwd: env.TS_NODE_CWD ?? env.TS_NODE_DIR,
  emit: yn(env.TS_NODE_EMIT),
  scope: yn(env.TS_NODE_SCOPE),
  scopeDir: env.TS_NODE_SCOPE_DIR,
  files: yn(env.TS_NODE_FILES),
  pretty: yn(env.TS_NODE_PRETTY),
  compiler: env.TS_NODE_COMPILER,
  compilerOptions: parse(env.TS_NODE_COMPILER_OPTIONS),
  ignore: split(env.TS_NODE_IGNORE),
  project: env.TS_NODE_PROJECT,
  skipProject: yn(env.TS_NODE_SKIP_PROJECT),
  skipIgnore: yn(env.TS_NODE_SKIP_IGNORE),
  preferTsExts: yn(env.TS_NODE_PREFER_TS_EXTS),
  ignoreDiagnostics: split(env.TS_NODE_IGNORE_DIAGNOSTICS),
  transpileOnly: yn(env.TS_NODE_TRANSPILE_ONLY),
  typeCheck: yn(env.TS_NODE_TYPE_CHECK),
  compilerHost: yn(env.TS_NODE_COMPILER_HOST),
  logError: yn(env.TS_NODE_LOG_ERROR),
  experimentalReplAwait: yn(env.TS_NODE_EXPERIMENTAL_REPL_AWAIT) ?? undefined,
};

其中yn模块可以快速的将一些含义非常相似的数据转换为js可识别的值,比如:

  • yn('y')=> true
  • yn(true)=> true

TS_NODE_SKIP_IGNORE环境变量就是我们要找的目标。

只要我们在process.env上定义这个变量也可以达到同样的效果,通过dotenvdotenv-expand来操作环境变量也可以达到预期的效果。

总结

一路看下来,还是学到了不少的新知识,遇到问题从源码入手解决问题,是有点浪费时间,但是能让你对细节更加的了解。