Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork12
Style guide for adding type definitions to my npm packages
License
sindresorhus/typescript-definition-style-guide
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Style guide for adding type definitions to my npm packages
Open an issue if anything is unclear or if you have ideas for other checklist items.
This style guide assumes your package is native ESM.
- Use tab-indentation and semicolons.
- The definition should target the latest TypeScript version.
- Exported properties/methods should be documented (see below).
- The definition should be tested (see below).
- When you have to use Node.js types, install the
@types/node
package as a dev dependency.Do not add a/// <reference types="node"/>
triple-slash reference to the top of the definition file. - Third-party library types (everything in the
@types/*
namespace) must be installed as direct dependencies, if required. Use imports, not triple-slash references. - Ensure you're not falling for any of thecommon mistakes.
- For packages with a default export, use
export default function foo(…)
syntax. - Do not use
namespace
. - Use the name
"types"
and not"typings"
for the TypeScript definition field in package.json. - Place
"types"
in package.json after all official package properties, but before custom properties, preferably after"dependencies"
and/or"devDependencies"
. - If the entry file in the package is named
index.js
, name the type definition fileindex.d.ts
and put it in root.
You don't need to add atypes
field to package.json as TypeScript will infer it from the name. - Add the type definition file to the
files
field in package.json. - The pull request should have the title
Add TypeScript definition
.(Copy-paste it so you don't get it wrong) - Help reviewother pull requests that adds a type definition.
Check outthis,this, andthis example for how it should be done.
- Types should not have namespaced names;
type Options {}
, nottype FooOptions {}
, unless there are multipleOptions
interfaces. - Use the array shorthand type notation;
number[]
, notArray<number>
. - Use the
readonly number[]
notation; notReadonlyArray<number>
. - Prefer using the
unknown
type instead ofany
whenever possible. - Don't use abbreviation for type/variable/function names;
function foo(options: Options)
, notfunction foo(opts: Opts)
. - When there are more than onegeneric type variable in a method, they should have descriptive names;
type Mapper<Element, NewElement> = …
, nottype Mapper<T, U> = …
. - Don't prefix the name of interfaces with
I
;Options
, notIOptions
. - Imports, destructuring, and object literals shouldnot have spaces around the identifier;
{foo}
, not{ foo }
. - Don't use permissive types like
object
orFunction
. Use specific type-signatures likeRecord<string, number>
or(input: string) => boolean;
. - Use
Record<string, any>
for accepting objects with string index type andRecord<string, unknown>
for returning such objects. The reasonany
is used for assignment is that TypeScript has special behavior for it:The index signature
Record<string, any>
in TypeScript behaves specially: it’s a valid assignment target for any object type. This is a special rule, since types with index signatures don’t normally produce this behavior.
Make something read-only when it's not meant to be modified. This is usually the case for return values and option interfaces. Get familiar with thereadonly
keyword forproperties andarray/tuple types. There's also aReadonly
type to mark all properties asreadonly
.
Before:
typePoint={x:number;y:number;children:Point[];};
After:
typePoint={readonlyx:number;readonlyy:number;readonlychildren:readonlyPoint[];};
Don't use implicit global types except for built-ins or when they can't be imported.
Before:
exportfunctiongetWindow():Electron.BrowserWindow;
After:
import{BrowserWindow}from'electron';exportfunctiongetWindow():BrowserWindow;
Use a readable name when using named imports.
Before:
import{Writable}from'node:stream';
After:
import{WritableasWritableStream}from'node:stream';
Exported definitions should be documented withTSDoc. You can borrow text from the readme.
Example:
exporttypeOptions={/**Allow negative numbers.@default true*/readonlyallowNegative?:boolean;/**Has the ultimate foo.Note: Only use this for good.@default false*/readonlyhasFoo?:boolean;/**Where to save.Default: [User's downloads directory](https://example.com)@example```import add from 'add';add(1, 2, {saveDirectory: '/my/awesome/dir'})```*/readonlysaveDirectory?:string;};/**Add two numbers together.@param x - The first number to add.@param y - The second number to add.@returns The sum of `x` and `y`.*/exportdefaultfunctionadd(x:number,y:number,options?:Options):number;
Note:
- Don't prefix lines with
*
. - Don'thard-wrap.
- Put an empty line between type entries.
- Sentences should start with an uppercase character and end in a dot.
- There's an empty line after the function description.
- Parameters and the return value should be documented.
- There's a dash after the parameter name.
@param
should not include the parameter type.- If the parameter description just repeats the parameter name, leave it out.
- If the parameter is
options
it doesn't need a description. - If the function returns
void
or a wrappedvoid
likePromise<void>
, leave out@returns
. - If you include an
@example
, there should be a newline above it. The example itself should be wrapped with triple backticks (```
). - If the API accepts an options-object, define an
Options
type as seen above. Document default option values using the@default
tag (since type cannot have default values). If the default needs to be a description instead of a basic value, use the formattingDefault: Lorem Ipsum.
. - Use
@returns
, not@return
. - Ambient declarations can't have default parameters, so in the case of a default method parameter, document it in the parameter docs instead, as seen in the above example.
@returns
should not duplicate the type information unless it's impossible to describe it without.@returns A boolean of whether it was enabled.
→@returns Whether it was enabled.
- Include as many code examples as possible. Borrow from the readme.
- Code examples should be fully functional and should include the import statement.
The type definition should be tested withtsd
.Example of how to integrate it.
Example:
import{expectType}from'tsd';importdelayfrom'./index.js';expectType<Promise<void>>(delay(200));expectType<Promise<string>>(delay(200,{value:'🦄'}));expectType<Promise<number>>(delay(200,{value:0}));expectType<Promise<never>>(delay.reject(200,{value:'🦄'}));expectType<Promise<never>>(delay.reject(200,{value:0}));
When it makes sense, also add a negative test usingexpectError()
.
Note:
- The test file should be named
index.test-d.ts
. tsd
supports top-levelawait
.- When testing promise-returning functions, don't use the
await
keyword. Instead, directly assert for aPromise
, like in the example above. When you useawait
, your function can potentially return a bare value without being wrapped in aPromise
, sinceawait
will happily accept non-Promise
values, rendering your test meaningless. - Use
const
assertions when you need to pass literal or readonly typed values to functions in your tests.
About
Style guide for adding type definitions to my npm packages
Topics
Resources
License
Code of conduct
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Releases
Sponsor this project
Uh oh!
There was an error while loading.Please reload this page.
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors4
Uh oh!
There was an error while loading.Please reload this page.