问题描述
关键字:使用 TypeScript 模块中的类型而不导入,发布仅包含类型的包,告诉 TypeScript 在 NPM 模块中查找类型.
我想发布一个包含全局可访问类型的 NPM 模块,很像 lib.d.ts
.
模块应该具有什么结构,我如何将它包含在另一个项目中?
如果让类型全局可见太难了,用 <reference/>
要求它就足够了,但是当我尝试时这不起作用.
在我想使用类型的项目中,我有一个 src
文件夹,其中包含所有源代码和 bin
文件夹其中包含 tsc
的输出.
包含类型的模块几乎可以有任何结构,只要它有效,我真的不在乎.
到目前为止,我已经尝试了很多很多组合,包括export
ing 类型、declare
ing 类型、export 声明
ing 类型,将它们放入 .ts
或 .d.ts
文件,在 node_modules
中的包文件夹中移动它们,import
ing 它们,ing 它们,将它们放入
rootDirs
......但没有任何效果.缺乏这方面的良好文档也无济于事.
我不得不为我的日志库解决这个问题,winston-jsonl-logger
.它使用一个名为 logger
的全局变量来扩充全局范围.我同意这是 TypeScript 中最难(如果不是最难)的问题之一,尤其是因为缺乏足够的文档.在这个例子中,我创建了一个使用全局可见('script')和模块可见('module')类型的库.澄清官方术语:
在 TypeScript 中,就像在 ECMAScript 2015 中一样,任何包含顶级 import
或 export
的文件都被视为一个模块.相反,没有任何顶级 import
或 export
声明的文件被视为脚本,其内容在全局范围内可用(因此也对模块可用).
目录结构
我的src
文件夹被转译为dist
.test
被转译忽略.
必须将您的输入命名为 index.d.ts
并嵌套在名称与您的项目相同的文件夹中(严格来说可能是 package 中指定的名称).json
).这就是 typeRoots
将要寻找的结构.
'脚本'类型
脚本类型是那些缺少顶级import
或export
的类型.它们将在使用它们的项目中全局可见.
当然,由于它们不能使用顶级import
声明,因此它们的描述性受到限制;你可能经常看到这里使用了很多any
.这是我试图解决的问题 在我自己的问题中.
//typings/index.d.ts声明命名空间 NodeJS {导出接口全局 {记录器?:任何;日志?:任何;日志信息?:任何;}}
如果你在全局范围内使用 logger
,现在它会被输入为 any
.
'模块'类型
模块类型可以使用顶级import
或export
,但只有在模块被导入到项目中时才会看到它们.即它们在整个项目中不全局可见.
//initLoggers.ts从./Logger"导入{Logger};从winston"导入 {LogEntry, Logger as WinstonLogger};//现在我们可以更详细地描述全局类型声明全局{const 记录器:记录器;//LogEntry 的接口:{ level: string, message: string, data?: any }功能日志(条目:LogEntry):WinstonLogger;function logInfo(message: string, data?: any): WinstonLogger;}导出函数 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-project
从 winston-jsonl- 导入此文件记录器
;我在我的应用程序入口点执行此操作.
package.json
我没有使用 typings
或 types
字段(可能指定了 "typings": "typings/winston-jsonl-logger/index.d.ts"
意味着包不必明确声明我的类型的路径;我不知道),但我确实确保分发我的类型文件夹.
{"name": "winston-jsonl-logger","版本": "0.5.3","description": "TypeScript JSONL 记录器.","main": "dist/Logger.js",文件":["区",打字"],开发依赖":{"@types/logform": "1.2.0","@types/node": ">=9.6.21","ts-node": "7.0.1",打字稿":3.1.1"},依赖关系":{"温斯顿": "3.2.0","winston-daily-rotate-file": "3.6.0",温斯顿弹性搜索":0.7.4"}}
省略字段:repository
、keywords
、author
、license
、homepage
、publishConfig
和 scripts
;否则,这就是一切.
tsconfig.json
对于库本身
没什么特别的.只是您的标准 tsc --init
默认值.
对于使用 lib 的项目
只要确保你添加一个 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/注册 --inspect=127.0.0.1:9231 src/index.ts --myCustomArg="hello"
我通常使用 ts-node
,因为我正在使用 Mocha 进行测试.下面是我如何将环境变量从 Mocha 传递给 ts-node
:
//mocha.env.js/* 来自:https://github.com/mochajs/mocha/issues/185#issuecomment-321566188* 通过 mocha.opts,添加 `--require mocha.env` 以便轻松设置测试环境变量.** 理论上可以改成TypeScript文件,但是我试的时候好像没有设置env变量;* 也许它没有遵守 --require 声明的顺序.*/process.env.TS_NODE_FILES = "true";//强制 ts-node 使用 TypeScript 模块解析,以隐式抓取环境 d.ts 文件process.env.TS_NODE_CACHE = "假";//如果模块解析有任何问题,通常是缓存;设置为 false 以进行生产,或在出现任何错误时!
希望这会有所帮助!
Keywords: use types from TypeScript module without importing, publishing a package with types only, tell TypeScript to seek types in an NPM module.
I want to publish an NPM module that contains globally accessible types, much like lib.d.ts
.
What structure should the module have and how do I include it in another project?
If making the types globally visible is just too hard, requiring it with <reference/>
would be enough, but that didn't work when I tried.
In the project where I want to use the types, I've got a src
folder containing all the source code and bin
folder which contains the output of tsc
.
The module containing types can have virtually any structure, I don't really care as long as it works.
So far I've tried many, many combinations including export
ing the types, declare
ing the types, export declare
ing the types, putting them to .ts
or to .d.ts
file, moving them around the package's folder inside node_modules
, import
ing them, <reference/>
ing them, putting them to rootDirs
… But nothing worked. And the lack of good documentation on this also didn't help.
I had to solve this for my logging library, winston-jsonl-logger
. It augments the global scope with a global variable called logger
. I agree that this is one of the hardest (if not the hardest) problem in TypeScript, not least because of lack of sufficient documentation. In this example, I create a library that uses both globally-visible ('script') and module-visible ('module') types. To clarify that official terminology:
Directory structure
My src
folder is transpiled into dist
. test
is ignored from transpilation.
It is imperative that you typings are named index.d.ts
and are nested in a folder whose name is the same as your project (which strictly is probably the name specified in package.json
). That's what structure typeRoots
will be looking for.
.
├── 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
'script' typings
Script typings are those that lack a top-level import
or export
. They will be visible globally across projects that consume them.
Of course, as they can't use top-level import
declarations, they are limited in how descriptive they can be; you may often see a lot of any
used here. This is a problem I'm trying to get solved in my own question.
// typings/index.d.ts
declare namespace NodeJS {
export interface Global {
logger?: any;
log?: any;
logInfo?: any;
}
}
If you use logger
in the global scope, it will be typed as any
now.
'module' typings
Module typings can use top-level import
or export
, but they will only be seen if the module gets imported into the project. i.e. they are not visible globally across the project.
// 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 });
}
}
If you use logger
in the global scope, it will still be typed as any
, but at least global.logger
will have proper typings.
To guarantee that these types are made visible across your project my-project
, make sure that my-project
imports this file from the winston-jsonl-logger
; I do it at my app's entrypoint.
package.json
I didn't use the typings
or types
field (maybe specifying "typings": "typings/winston-jsonl-logger/index.d.ts"
would have meant that packages don't have to explicitly declare the path to my typings; I don't know), but I did make sure to distribute my folder of typings.
{
"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"
}
}
Omitted fields: repository
, keywords
, author
, license
, homepage
, publishConfig
, and scripts
; otherwise, that's everything.
tsconfig.json
For the lib itself
Nothing special. Just your standard tsc --init
defaults.
For projects consuming the lib
Just make sure that you add a typeRoots
looks like this:
{
"compilerOptions": {
// ...All your current fields, but also:
"typeRoots": [
"node_modules/@types",
"node_modules/winston-jsonl-logger/typings/winston-jsonl-logger"
]
}
}
If you're using ts-node
There are further gotchas here. By default, ts-node
ignores script typings and only imports descendents of the entry-level import (the reason for this is speed/efficiency). You can force it to resolve imports just like tsc
does by setting the environment variable: TS_NODE_FILES=true
. Yes, it will run tests slower, but on the other hand, it'll work at all.
If you're using ts-node
via commandline, declare the TS_NODE_FILES
environment variable to be true
. I also had to declare TS_NODE_CACHE
to be false
, because of an inexplicable cache bug in ts-node
(version 7.0.1 – may still be an issue) when it's resolving imports/dependencies.
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"
I'm normally using ts-node
because I'm testing with Mocha. Here's how I pass environment variables to ts-node
from Mocha:
// 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!
Hope this helps!
这篇关于如何制作具有全局可访问类型的 NPM 模块的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!