- Getting Started
- Github Action
- Installing package from a git url
- Adding types with JSDoc
- Manage dependencies types
- Rules for optimal type declarations and documentation
- Must read references
- Resources
Add a tsconfig.json
to your repo that extends the default aegir ts config:
{
"extends": "aegir/src/config/tsconfig.aegir.json",
"compilerOptions": {
"outDir": "dist",
"emitDeclarationOnly": true
},
"include": [
"src",
"test"
]
}
Add types configuration to your package.json:
"types": "dist/src/index.d.ts",
types
will tell tsc
where to look for the entry point type declarations.
When a packages needs to allow type imports other than the entry point, you can use this workaround:
"typesVersions": {
"*": {
"src/*": [
"dist/src/*",
"dist/src/*/index"
],
"src/": [
"dist/src/index"
]
}
}
typeVersions
will tell tsc
where to look for every other files inside the src
folder. Note: This might get smaller when this issue is resolved or a proper way is introduced.
Use this hack only if you really need it, this might change from the TS side at any time and break type checks.
To run the typechecker in the CI you can use this action https://github.com/Gozala/typescript-error-reporter-action and you will get the errors reported inline with the code.
When installing a dependency from a git url (ie. PRs depending on other PRs) the types won't be packed in. To fix this you need to add a npm script called prepare
to run aegir build
.
"scripts": {
"prepare": "aegir build --no-bundle"
},
yarn
needs a .npmignore file to properly install dependencies withprepare
scripts that create extra files that need to be packed in.
Typescript can infere lots of the types without any help, but you can improve your code types by using just JSDoc for that follow the official TS documentation https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html.
When dependencies don't publish types you have two options.
1. NPM from DefinitelyTyped
npm install @types/tape
Create a types
folder at the root of your project to keep all your vendored types. Inside create one folder per dependency using the dependency name as the folder name and inside a create index.d.ts
file with the types.
Tell TS where to look for types when a package doesn't publish them.
"compilerOptions": {
"baseUrl": "./",
"paths": {
"*": ["./types/*"]
}
}
"include": [
...
"types"
]
Scoped packages folder name need to use
__
instead of/
, ie. the folder for@pre-bundle/tape
would bepre-bundle__tape
.
Aegir will copy the types folder to the dist
folder (ie. dist/types
) when you build or run the types typescript preset. This way all your types, vendored and from source, will be published without broken imports.
Reference: voxpelli/types-in-js#7 (comment)
This list is a WIP, more rules will be added as we identify them.
When using commonjs
modules, only use default exports when exporting a single class
.
// GOOD
class IPFS {}
module.exports = IPFS
// GOOD
IPFS.hash = ()=>{}
module.exports = IPFS
// BAD
function hash() {}
module.exports = hash
// REALLY BAD
function hash() {}
function hash2() {}
module.exports = hash
exports.hash2 = hash2
When using commonjs
modules, always use named exports if you want to export multiple references.
// GOOD
function hash() {}
function hash2() {}
class IPFS {}
module.exports = {
IPFS
hash,
hash2,
...
}
// BAD
exports.hash2 = hash2() {}
exports.hash = hash() {}
exports.IPFS = IPFS
When writing types JSDoc can sometimes be cumbersome, impossible, it can output weird type declarations or even broken documentation. Most of these problems can be solved by defining some complex types in typescript in a types.d.ts
file.
// types.d.ts
export type IntersectionType = Type1 & Type2
// index.js
/** @type { import('./types').IntersectionType } */
const list
You can also organise your source types in the same way as vendored types.
Create a folder inside the types
folder called self
or the package name. Then you can import like you would a third party type.
/**
* @typedef {import('self').CustomOptions} CustomOptions
* /
Some TS tooling may have problems parsing comments if they are not very well divided.
// BAD - the base typedef can be parsed as a comment for Square
/**
*
* @typedef {import('./index') Base} Base
*/
class Square {}
// GOOD
/** @typedef {import('./index') Base} Base */
/**
* Cool Square class
* @class
*/
class Square {}
Keep in mind rule nº 4 above
Check ipfs/community#474
const { Adapter, utils } = require('interface-datastore')
const fs = require('fs')
/**
* @typedef {import('interface-datastore/src/types').Datastore} Datastore
* @typedef {import("interface-datastore/src/types").Options} Options
* @typedef {import("interface-datastore/src/types").Batch} Batch
* @typedef {import('interface-datastore/src/key')} Key
* @typedef {import('interface-datastore/src/adapter').Query} Query
* @typedef {import('./types').KeyTransform} KeyTransform
*/
10 Insights from Adopting TypeScript at Scale Typescript official performance notes TypeScript: Don’t Export const enums TypeScript: Prefer Interfaces Typescript Narrowing
TS with JSDoc Discussions Tackling Typescript Effective Typescript