【问题标题】:How to make an NPM module with globally accessible types如何制作具有全局可访问类型的 NPM 模块
【发布时间】:2019-09-03 16:42:09
【问题描述】:

关键字:使用 TypeScript 模块中的类型而不导入,发布仅包含类型的包,告诉 TypeScript 在 NPM 模块中寻找类型。


我想发布一个包含全局可访问类型的 NPM 模块,很像 lib.d.ts

模块应该有什么结构,我如何将它包含在另一个项目中?

如果使类型全局可见太难了,用<reference/> 要求它就足够了,但是当我尝试时这不起作用。


在我想要使用类型的项目中,我有一个包含所有源代码的src 文件夹和一个包含tsc 输出的bin 文件夹。

包含类型的模块几乎可以有任何结构,只要它有效,我并不关心。


到目前为止,我已经尝试了很多很多组合,包括 exporting 类型、declareing 类型、export declareing 类型,将它们放入 .ts.d.ts 文件,移动它们在node_modules 内的包文件夹周围,importing 他们,<reference/>ing 他们,把他们放到rootDirs... 但没有任何效果。缺乏这方面的良好文档也无济于事。

【问题讨论】:

  • 您是否尝试过将它们添加到您的 tsconfig.json 中的类型根目录中?更具体:tsconfig.json -> compilerOptions -> typeRoots
  • 这给了我error TS2688: Cannot find type definition file for 'src'('src' 是我所有代码所在的文件夹)
  • 您是否尝试过在 package.json 中使用 types 属性?您应该能够将类型声明与您的包捆绑在一起 - 只要它与您的 index.js 位于同一目录中并命名为 index.d.ts 就不会有问题。
  • @Matt 是的,类型包包含 types 条目,它指向存在并包含正确类型的 index.d.ts。但是我仍然无法访问这些类型。

标签: typescript npm


【解决方案1】:

我必须为我的日志库 winston-jsonl-logger 解决这个问题。它使用名为logger 的全局变量来扩充全局范围。我同意这是 TypeScript 中最难(如果不是最难)的问题之一,尤其是因为缺乏足够的文档。在这个例子中,我创建了一个同时使用全局可见('script')和模块可见('module')类型的库。澄清official terminology

在 TypeScript 中,就像在 ECMAScript 2015 中一样,任何包含顶级 importexport 的文件都被视为一个模块。相反,没有任何顶级 importexport 声明的文件被视为其内容在全局范围内可用的脚本(因此也可用于模块)。

目录结构

我的src 文件夹被转换为disttest 从编译中被忽略。

您的输入必须命名为index.d.ts,并嵌套在与您的项目名称相同的文件夹中(严格来说,这可能是package.json 中指定的名称)。这就是typeRoots 将要寻找的结构。

.
├── README.md
├── dist
│   ├── Logger.d.ts
│   ├── Logger.js
│   ├── Logger.js.map
│   ├── initLoggers.d.ts
│   ├── initLoggers.js
│   └── initLoggers.js.map
├── package-lock.json
├── package.json
├── src
│   ├── Logger.ts
│   └── initLoggers.ts
├── test
│   └── index.ts
├── tsconfig.json
└── typings
    └── winston-jsonl-logger
        └── index.d.ts

'脚本'类型

脚本类型是缺少顶级importexport 的类型。它们将在使用它们的项目中全局可见。

当然,由于它们不能使用顶级 import 声明,它们的描述性有限;你可能经常看到这里使用了很多any。这是我正在努力解决的问题in my own question

// typings/index.d.ts
declare namespace NodeJS {
    export interface Global {
        logger?: any;
        log?: any;
        logInfo?: any;
    }
}

如果您在全局范围内使用logger,现在将键入any

“模块”类型

模块类型可以使用顶级importexport,但只有在模块被导入项目时才能看到它们。即它们在整个项目中不可见。

// initLoggers.ts
import {Logger} from "./Logger";
import {LogEntry, Logger as WinstonLogger} from "winston";

// Now we can be more descriptive about the global typings
declare global {
    const logger: Logger;
    // LogEntry's interface: { level: string, message: string, data?: any }
    function log(entry: LogEntry): WinstonLogger;
    function logInfo(message: string, data?: any): WinstonLogger;
}

export function initLoggers(){
    global.logger = new Logger();
    global.log = logger.log.bind(logger);
    global.logInfo = (message: string, data?: any) => {
        return logger.log({ level: "info", message, data });
    }
}

如果您在全局范围内使用logger,它仍将键入为any,但至少global.logger 将具有正确的类型。

为保证这些类型在您的项目my-project 中可见,请确保my-projectwinston-jsonl-logger 导入此文件;我在我的应用程序的入口点执行此操作。

package.json

我没有使用 typingstypes 字段(也许指定 "typings": "typings/winston-jsonl-logger/index.d.ts" 意味着包不必显式声明我的类型的路径;我不知道),但我确实确保分发我的打字文件夹。

{
  "name": "winston-jsonl-logger",
  "version": "0.5.3",
  "description": "TypeScript JSONL logger.",
  "main": "dist/Logger.js",
  "files": [
    "dist",
    "typings"
  ],
  "devDependencies": {
    "@types/logform": "1.2.0",
    "@types/node": ">=9.6.21",
    "ts-node": "7.0.1",
    "typescript": "3.1.1"
  },
  "dependencies": {
    "winston": "3.2.0",
    "winston-daily-rotate-file": "3.6.0",
    "winston-elasticsearch": "0.7.4"
  }
}

省略的字段:repositorykeywordsauthorlicensehomepagepublishConfigscripts;否则,仅此而已。

tsconfig.json

对于库本身

没什么特别的。只是您的标准 tsc --init 默认值。

对于使用 lib 的项目

只需确保添加 typeRoots 如下所示:

{
  "compilerOptions": {
    // ...All your current fields, but also:
    "typeRoots": [
      "node_modules/@types",
      "node_modules/winston-jsonl-logger/typings/winston-jsonl-logger"
    ]
  }
}

如果您使用的是ts-node

这里还有更多的陷阱。默认情况下,ts-node 忽略脚本类型,只导入入门级导入的后代(原因是速度/效率)。您可以通过设置环境变量:TS_NODE_FILES=true 来强制它解析导入,就像 tsc 所做的那样。是的,它会更慢地运行测试,但另一方面,它完全可以工作。

如果您通过命令行使用ts-node,请将TS_NODE_FILES 环境变量声明为true。我还必须将TS_NODE_CACHE 声明为false,因为在解析导入/依赖项时ts-node(版本7.0.1 - 可能仍然是一个问题)中有一个无法解释的缓存错误。

TS_NODE_FILES="true" TS_NODE_CACHE="false" TS_NODE_PROJECT="./tsconfigs/base.json" /usr/bin/nodejs --require ts-node/register --inspect=127.0.0.1:9231 src/index.ts --myCustomArg="hello"

我通常使用ts-node,因为我正在使用 Mocha 进行测试。以下是我将环境变量从 Mocha 传递给 ts-node 的方法:

// mocha.env.js

/* From: https://github.com/mochajs/mocha/issues/185#issuecomment-321566188
 * Via mocha.opts, add `--require mocha.env` in order to easily set up environment variables for tests.
 *
 * This can theoretically be made into a TypeScript file instead, but it seemed to not set the env variable when I tried;
 * perhaps it failed to respect the order of the --require declarations. */
process.env.TS_NODE_FILES = "true"; // Force ts-node to use TypeScript module resolution in order to implictly crawl ambient d.ts files
process.env.TS_NODE_CACHE = "false"; // If anything ever goes wrong with module resolution, it's usually the cache; set to false for production, or upon any errors!

希望这会有所帮助!

【讨论】:

  • 太棒了!今天晚些时候我要试试!我只是好奇为什么您的“typings”文件夹包含另一个与项目名称相同的文件夹?
  • 来自tsconfig.json docs:“types 包 是一个文件夹,其中包含一个名为index.d.ts 的文件,或者一个包含package.json 且具有types 字段的文件夹...例如,如果您使用 import "foo" 语句,TypeScript 可能仍会查看 node_modulesnode_modules/@types 文件夹以找到 foo 包。”它们在这里可能更明确一点,但它们的约定是将类型包表示为<package name>/index.d.ts。声明here
  • 我发现import一个模块文件<reference/>s 全局类型的脚本就足够了,不需要修改typeRoots。但是我现在正在努力解决相反的问题,这可能是当前 TS 无法解决的问题?? 请参阅我的 new question
  • 我会写一个详细的答案,很快就会针对这个问题调查 TS Node。
  • @m93a (╯ರ ~ ರ)╯︵ ┻━┻ 只是在这里的每一步都对编译器进行第二次猜测。真的希望 TypeScript 团队能提供更充足的文档,最好是一些示例 repos。也许值得以这种速度提交问题...
【解决方案2】:

花几天时间弄清楚。我找到了两种方法:

A- 发布一个@types/your-module 包

就像一个魅力,这里不会详细说明。

B-bundle 声明文件到你的模块中

对于解决方案 B:

  • 编译器将声明导出到my-module/<DIST>/index.d.ts
  • 在模块根文件夹中手动编写另一个声明文件:my-module/globalTypes/index.d.ts,它将公开全局命名空间:
// access from window.MyModule
interface Window {
    MyModule: import('my-module/DIST_FOLDER').MyModule
}

// or directly MyModule
declare const MyModule: import('my-module/DIST_FOLDER').MyModule

也许你发现了它。但是,是的,您必须将全局声明文件放在模块的子文件夹中。 为什么 ?因为typesRoot 指令会抓取您指向的文件夹的子项。 这意味着,在您的主项目中,当您设置:

{
    "typeRoots": ["./node_modules/@types", "./node_modules/my-module"]
}

TSC 会找到 ./node_modules/my-module/globalType/index.d.ts,但不会找到 ./node_modules/my-module/index.d.ts

事实上,这是合乎逻辑的,但你可以(也)很容易在doc 中错过它。

默认情况下,TSC 使用值:"typeRoots": ["./node_modules/@types"]。 @types 文件夹中没有声明。 因此,它对您指定的所有路径都具有相同的作用。

【讨论】:

    【解决方案3】:

    与这里的其他响应者类似,我也花了相当多的时间试图解决这个问题 ?。我的用例略有不同,我根据我看到的其他库的做法找到了另一种方法。

    发布以防这对其他人有用。


    我的目标与 OP 的目标略有不同:我想发布全局接口类型,并让我的 lib 的下游用户在编写类型时随时可用,但我不想增加 window 或 @987654322 @,只需像 React 那样发布全局环境类型(例如,您不必在编写类型时使用 import React 来使用 React.ComponentType)。

    我是这样做的:

    1. 在某些环境类型文件中创建要发布的全局类型。我打电话给我的ambient.d.ts,我把它放在我的项目文件夹的根目录下。此文件应与dist 文件夹一起发布。
    2. 创建一个新的类型入口点 (/index.d.ts),它重新导出已编译的入口点 (./dist/index.d.ts),并在其中包含对环境类型文件的三斜杠引用 (/// <reference types="./ambient" />)。还要确保您的tsconfig.jsoninclude 选项具有ambient.d.ts
    3. 当您发布您的库时,告诉您的用户在他们的项目文件夹的根目录中创建一个.d.ts 文件,其中包含对您的库的三斜杠引用。许多create-*-app 初学者已经创建了这个文件。例如,create-next-app 创建一个已包含三斜杠引用的 next-env.d.ts 文件。你可以告诉你的用户来增强它。

    目录结构:

    .
    ├── README.md
    ├── package-lock.json
    ├── package.json
    ├── src
    │   ├── index.ts
    │   └── initLoggers.ts
    ├── dist
    │   ├── index.d.ts   <-- your compiled index.d.ts file
    │   ├── index.js
    │   ├── index.js.map
    │   ├── foo.d.ts
    │   ├── foo.js
    ├── ambient.d.ts     <-- write global types here
    ├── index.d.ts       <-- new types entry point
    ├── tsconfig.json
    

    您将发布 package.jsonambient.d.tsindex.d.ts 以及 dist 中的所有内容。

    /package.json:

    {
      // ...
      "types": "./index.d.ts", // specify the new entry types entry-point
      // ...
    }
    

    /index.d.tstsconfig.json(第 2 步):

    // this is the new entry-point for your types
    // use the triple-slash reference to bring in your ambient types
    /// <reference types="./ambient" />
    
    // re-export your compiled types
    export * from './dist';
    
    {
      "compilerOptions": { /* ... */ },
      "include": ["./src", "./ambient.d.ts"]
    }
    

    对于您的下游用户(第 3 步):

    告诉他们创建一个blah.d.ts 文件并向您的库添加一个三斜杠引用。如上所述,next.js 已经有了这个文件,它被称为next-env.d.ts。您可以告诉您的用户增加它或创建一个新的*.d.ts 文件。

    /// <reference types="next" />
    /// <reference types="next/types/global" />
    // ???
    /// <reference types="your-published-lib-name" />
    // ???
    

    ? 或者,正如其他答案所建议的那样,您可以告诉您的用户将您的 lib 添加到 tsconfig.json 中的 typeRoots 编译器选项,但我更喜欢三斜杠引用,因为它不会更改默认编译器选项和这是我见过的其他库(例如 next.js)所做的。

    【讨论】:

      猜你喜欢
      • 2014-09-23
      • 1970-01-01
      • 1970-01-01
      • 2017-03-30
      • 2018-06-13
      • 1970-01-01
      • 2012-01-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多