如何制作具有全局可访问类型的NPM模块

时间:2019-04-12 21:33:21

标签: typescript npm

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


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

该模块应具有什么结构,以及如何将其包含在另一个项目中?

如果要使这些类型在全局范围内可见实在太难了,则只需使用<reference/>就可以了,但这在我尝试时不起作用。


在我要使用类型的项目中,我有一个src文件夹,其中包含所有源代码,还有一个bin文件夹,其中包含{ {1}}。

包含类型的模块实际上可以具有任何结构,只要它可以工作,我就不在乎。


到目前为止,我已经尝试了很多组合,包括tsc设置类型,export设置类型,declare设置类型,将其放入export declare或到.ts文件中,将它们在.d.ts内的软件包文件夹中移动,node_modules对其进行打包,import对其进行打包,然后将其放入<reference/>……但是没有任何效果。而且缺少好的文档也无济于事。

3 个答案:

答案 0 :(得分:2)

我必须为我的日志记录库winston-jsonl-logger解决此问题。它使用称为logger的全局变量来扩展全局范围。我同意这是TypeScript中最难解决的问题(如果不是最难解决的话),尤其是因为缺少足够的文档。在此示例中,我创建了一个使用全局可见(“脚本”)和模块可见(“模块”)类型的库。为了澄清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,它将 still 键入为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;否则就这些了。<​​/ p>

tsconfig.json

对于lib本身

没什么特别的。只是您的标准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会忽略脚本类型,仅导入入门级导入的后代(其原因是速度/效率)。您可以通过设置环境变量tsc来强制其解析导入,就像TS_NODE_FILES=true一样。是的,它运行测试的速度会变慢,但另一方面,它将完全起作用。

如果通过命令行使用ts-node,则将TS_NODE_FILES环境变量声明为true。我还必须声明TS_NODE_CACHEfalse,因为在解决导入/依赖项时ts-node中存在莫名其妙的缓存错误(版本7.0.1 –可能仍然是一个问题)。 / p>

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!

希望这会有所帮助!

答案 1 :(得分:1)

花几天时间弄清楚。我发现了两种方法可以做到这一点:

A-发布@ types /您的模块包

作品就像一个饰物,这里不再详述。

将B-声明文件打包到您的模块中

对于解决方案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中轻易地错过它。

enter image description here

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

答案 2 :(得分:1)

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

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


我的目标与 OP 的目标略有不同:我想发布全局接口类型,并且让我的库的下游用户在编写类型时随时可用,但我不想增加 windowglobal,只需像 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. 发布 lib 时,请告诉您的用户在其项目文件夹的根目录下创建一个 .d.ts 文件,其中包含对您的 lib 的三斜杠引用。许多 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" />
// ???
<块引用>

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