Provides a modern battle tested near zero configuration tool for ESM / ES Module / Javascript developers to generate
bundled Typescript declarations from ESM source code utilizing typed JSDoc
. This tooling can be employed to build
types for a primary export and one or more sub-path exports creating
independent ESM oriented / module based declarations utilizing import / export semantics. This tooling can be
employed by any project, but is particularly useful for library authors as there are many additional options covering
advanced use cases that library authors may encounter. Some of these optional advanced features include support for
re-exporting / re-bundling packages w/ TS declarations and thorough support for utilizing
imports / import conditions in a variety of flexible ways.
It is recommended to install esm-d-ts
as a developer dependency in package.json
as follows:
{
"devDependencies": {
"@typhonjs-build-test/esm-d-ts": "^0.2.0"
}
}
Presently the CLI and esm-d-ts
can not be installed or used globally; this will be addressed in a future update.
- Added a TS AST transformer to support import types in
@implements
JSDoc tags. This allows you to reference an interface from a class and have it properly converted toimplements <INTERFACE>
in the declarations generated. - Added
transformer
"meta-transformer" to reduce the boilerplate of creating custom TS AST transformers.
- Added a new internal AST transformer that corrects the output of the TS compiler for setter accessor parameter names.
The TS compiler for ESM will rename setter accessor parameter names to
arg
regardless of the value set in the source file. If there is a JSDoc comment associated with a setter the first@param
tag name will be set to the AST node param name. Downstream tooling such as TypeDoc0.25.7+
validates comment /@param
name against the type declaration name; this change fixes that mismatch.
-
Optional postprocessing
- The first built-in postprocessing function is support for
@inheritDoc
. This is an unsupported JSDoc tag for Typescript and when types are generated any methods or constructor functions that use@inheritDoc
have parameters that are typed asany
. It is also possible to create custom postprocessing functions. For more details on postprocessing and AST transformation please see the wiki.
- The first built-in postprocessing function is support for
-
Support for the JSDoc
@module
/@packageDocumentation
comment pass-through to the generated DTS bundle. This is helpful when generating docs from the DTS bundle. This is only supported for the main entry point source file. -
All dependencies updated along with peer dependency requirements of
Rollup 3.3 - 4.x
andTypescript 5.1+
.
There is a lot to unpack regarding how to set up a modern ESM Node package for efficient distribution that includes
TS declarations. At this time I'll point to the Typescript JSDoc informational resources
and the handbook description
on how to set up package.json
exports
with the types
condition. In time, I will expand the documentation and
resources available about esm-d-ts
covering new patterns unlocked from modern use cases combining JSDoc / TS
capabilities. If you have questions please open a discussion in the issue tracker. You may also stop by
the wiki and the TyphonJS Discord server for
discussion & support.
A design goal behind esm-d-ts
is to provide flexibility and near-zero configuration, so that you may adapt and use
esm-d-ts
for a variety of build and usage scenarios. There are four main ways to configure esm-d-ts
:
- CLI immediate mode.
- CLI w/ configuration file.
- As a rollup plugin (100% zero configuration)
- Programmatically.
The Rollup plugin can be used w/ 100% zero configuration, but the other ways to set up esm-d-ts
require at minimum
an input
source file that should be the entry point for the given main or sub-path export. By default, when only
providing the input
entry point the bundled declaration file will be generated next to the input
source file with
the same name and .d.ts
extension. To generate the bundled declaration file at a specific location provide an
output
file path w/ extension. All the ways to configure esm-d-ts
accept the same configuration object. Except for
the Rollup plugin every way to configure esm-d-ts
accepts a list of configuration objects allowing you to completely
build all sub-path exports in one invocation of esm-d-ts
.
The following examples demonstrate essential usage patterns. Each example will take into consideration a hypothetical
package that has a primary export and one sub-path export. The resulting package.json
exports field looks like this:
{
"exports": {
".": {
"types": "./src/main/index.d.ts",
"import": "./src/main/index.js"
},
"./sub": {
"types": "./src/sub/index.d.ts",
"import": "./src/sub/index.js"
}
}
}
Note: Typescript requires the types
condition to always be the first entry in a conditional block in exports
.
You may use the CLI via the command line or define a NPM script that invokes it. The CLI has two commands check
and
generate
. generate
has two aliases gen
& g
. The generate
command creates bundled declaration files. The
check
command is a convenient way to log diagnostics from the Typescript compiler checkJs
output that by default is
filtered to only display messages limited to the scope of the source files referenced from the entry point specified.
To receive help about the CLI use esm-d-ts --help
. Please use it to learn about additional CLI options available.
All examples will demonstrate NPM script usage.
There are two ways to use the CLI. The first is "immediate mode" where you directly supply an input / entry point. Presently, only one source file may be specified in "immediate mode".
{
"scripts": {
"types": "esm-d-ts gen src/main/index.js && esm-d-ts gen src/sub/index.js"
}
}
A more convenient way to define a project is through defining a configuration file. You may specify the --config
or
alias -c
to load a default config defined as ./esm-d-ts.config.js
or ./esm-d-ts.config.mjs
. You may also provide
a specific file path to a config after the --config
option.
{
"scripts": {
"types": "esm-d-ts gen --config"
}
}
The config file should be in ESM format and have a default export that provides one or a list of GenerateConfig
objects.
/**
* @type {import('@typhonjs-build-test/esm-d-ts').GenerateConfig[]}
*/
const config = [
{ input: './src/main/index.js' },
{ input: './src/sub/index.js' },
];
export default config;
You may directly import checkDTS
or generateDTS
. These are asynchronous functions that can be invoked with top
level await.
import { checkDTS, generateDTS } from '@typhonjs-build-test/esm-d-ts';
// Generates TS declaration bundles.
await generateDTS([
{ input: './src/main/index.js' },
{ input: './src/sub/index.js' },
]);
// Log `checkJs` diagnostics.
await checkDTS([
{ input: './src/main/index.js' },
{ input: './src/sub/index.js' },
]);
A Rollup plugin is accessible via generateDTS.plugin()
and takes the same configuration object as generateDTS
. When
using Rollup you don't have to specify the input
or output
parameters as it will use the Rollup options for input
and file
option for output
. An example use case in a Rollup configuration object follows:
import { generateDTS } from '@typhonjs-build-test/esm-d-ts';
// Rollup configuration object which will generate the `dist/index.d.ts` declaration file.
export default [
{
input: 'src/main/index.js',
plugins: [generateDTS.plugin()],
output: {
format: 'es',
file: 'dist/main/index.js'
}
},
{
input: 'src/sub/index.js',
plugins: [generateDTS.plugin()],
output: {
format: 'es',
file: 'dist/sub/index.js'
}
}
]
esm-d-ts
will generate respective bundled declarations next to the output file
:
dist/main/index.d.ts
dist/sub/index.d.ts
Presently esm-d-ts
only handles a single input entry point. A future update may expand this to handle multiple entry
points. If you need this functionality please open an issue.
There is no checkDTS
Rollup plugin.
There are several more advanced configuration options and usage scenarios that are not discussed in this README
. You
may view a description of all options available in the documentation for
GenerateConfig /
GeneratePluginConfig
esm-d-ts
allows some rather advanced usage scenarios for library authors as well from handling imports
in
package.json
to further modification of the TS declarations generated through processing the intermediate AST /
Abstract Syntax Tree data.
One thing that is super useful is that you can use Typescript .ts
files and export named types (aliases / interfaces)
and anything that is too cumbersome to manage with JSDoc. Any .ts
files that are located at the entry point and
subdirectories are included in compilation of the declarations. You may use import types
to reference them just like
other symbols across your project. With the new support for @implements
you can now properly represent classes in ESM
that implement an interface. Note: .d.ts
files are not included in the declaration generation. However, it is useful
to use .d.ts
files exporting types that are considered package private.
There is not a well-defined resource that pulls together all the concepts employed or available for using JSDoc to
generate Typescript declarations. esm-d-ts
has been in development since November 2021. It is completely working and
used in production for TyphonJS
packages and releases. A good recent article to review that covers JSDoc + Typescript
is Boost Your JavaScript with JSDoc Typing.
That being said presently esm-d-ts
does require a very particular way of linking all types in JSDoc across a project
requiring explicit use of import types
for all symbols linked. Even symbols from the local project. This likely is a foreign concept to most ESM / JS
developers used to IDE tooling that analyzes a project and allows local symbols to be referenced directly in @param
JSDoc tags. This will be solved by adding an analysis stage to esm-d-ts
in the future allowing local symbols to be
used without import types
.
The background on the current need for import types
is that with Typescript you must explicitly import all symbols
referenced in documentation or source code. Typescript performs "import / export elision" when transpiling TS to JS
source code removing imports only used in documentation. JSDoc when used in IDEs for ESM / JS development handles any
project analysis and documentation generation tooling also analyzes a project for local symbols.
An additional caveat to be aware of is that presently esm-d-ts
during the generation process creates intermediate TS
declaration files and by default they are located in the ./.dts
folder. It is recommended to add an exclusion rule
in a .gitignore
file for /.dts
. This also is on the roadmap to provide a completely in-memory generation process.
Providing type declarations for your package is a great way to make your package easier to use and consume with modern
tooling. What about automatically generating API documentation from the generated types?
@typhonjs-typedoc/typedoc-pkg provides a zero
configuration CLI frontend to generate API documentation with TypeDoc from a well configured package.json
with
Typescript declarations.
-
Create an initial processing stage where
esm-d-ts
analyzes all exported symbols of the local code base allowing local symbols to be used withoutimport types
. -
Provide a way to manage the generation process entirely in memory. Presently the intermediate individual TS declarations created in execution are stored in the
./.dts
folder. Add this folder to your.gitignore
. This is a limitation ofrollup-plugin-dts
& the TS compiler API utilized that uses the file system for bundling. I will be looking into submitting a PR torollup-plugin-dts
to handle virtual bundling. -
Generate source maps for the bundled TS declarations allowing IDEs to not just jump to the declarations, but also open linked source code.
I would like to bring awareness to the awesome underlying packages that make esm-d-ts
possible: