@tsmodule/tsmodule
v44.7.0
Published
Create standardized TypeScript ESM packages for Node or browser.
Downloads
43
Maintainers
Readme
Create standardized TypeScript ESM packages for Node or browser. Includes
--bundle
and --binary
build options, powered by
esbuild.
Table of contents
Installation
Requirements (global)
- Node v14+ (for ESM support)
- PNPM
Existing projects
To convert an existing project (and install standardized package.json settings, dependencies, and config files), run this in your project directory:
tsmodule convert
You will need to move all TS source files to src/
if they are not there
already. Ensure you read the module
configuration notes regarding "index exports" as it
relates to importing downstream.
New projects
Create a new package with:
tsmodule create [--react] project-name
Purpose
Create ESM packages using TypeScript
tsmodule create
exists to bootstrap a Node or React project in as little time
as possible. The created packages are modular, and tsmodule create --react
will create a modular Next.js project with importable components and styles.
Ready out of the box:
- package.json scripts
- TypeScript, ESLint configs (Tailwind, PostCSS for React)
- CI/CD with GitHub Actions
Develop projects in real-time
Build in dev mode and watch for changes:
tsmodule dev
Build to optimized ES modules
Production builds are minified ESM, with support for my-package/a/b/c
path
resolution (see Module configuration below).
tsmodule build [--bundle]
All projects:
- Emit pure ESM, no polyfilling to CJS
- Emit ESNext by default, no polyfilling to older feature sets
React projects created with create --react
:
- Bundle CSS by default
- Use Tailwind by default
Optimize NPM dependencies with build --bundle
With -b, --bundle
mode, all entry points are compiled "in-place" and runtime NPM dependencies will generally not be needed as they will be inlined. If you build in bundle mode, you can move your dependencies to devDependencies, as the only thing that will be needed to run any/all compiled-in-place entry point(s) in your module are the bundles themselves.
TSModule itself builds with -b, --bundle
flag, and requires only three runtime NPM dependencies:
esbuild
, which does the heavy lifting for the build process, does not allow itself to be bundled;typescript
, so TSModule can use the builttsc
binary to generate.d.ts
type declarations during builds; andpkg
, for building binaries withbuild --binary
(a specific standalone bundle mode).
Note: Bundling every entry point in place may not be what you want, i.e. if you
only have a single entrypoint. In these cases, tsmodule build -b src/index.ts
is more appropriate.
Build executable binaries with build --binary
Uses Vercel's pkg
to build binaries from
src/bin.ts
.
IMPORTANT: This requires coercing to CJS, which is not possible if your program uses top-level await. For now, replace with async closures and monitor this issue for updates on full ESM support.
Run TypeScript directly
tsmodule file.ts
- Uses Node module loader to resolve TS at runtime
- Executable TypeScript files with
#!/usr/bin/env tsmodule
Use Cases
Below are some example use cases of TS modules in practice.
Generic TypeScript library
The most common type of library will be a TS module with generic TypeScript
exports in src/**/index.ts
, e.g.
universal-shell
, a Promise wrapper
around child_process.spawn
that's used in tsmodule itself.
This library contains only one export, at src/index.ts
(a function called
shell
), but you could import e.g. import { test } from "my-package/path/to/export"
by exporting that identifier at src/path/to/export/index.ts
.
React component library (using Next.js)
tsmodule create --react
creates a TS module which is also a Next app; pages are in src/pages
, and components are in src/components
. Tailwind, PostCSS, and postcss-import
are all supported by default.
CSS will be bundled from src/components/index.css
and exported at my-package/styles
, which the package.json style
field also points to (for postcss-import
support), so that components packages are modular.
{
"style": "./dist/components/index.css",
"exports": {
".": "./dist/index.js",
"./*": "./dist/components/*/index.js",
"./styles": "./dist/components/index.css",
"./styles/*": "./dist/components/*/index.css",
"./package.json": "./package.json"
},
}
To use a component downstream, import the styles into the page, e.g.:
// src/pages/_app.tsx
import "my-package/styles";
Or in CSS (resolved by postcss-import
using "style"
field in package.json):
@import "my-package";
And render your component:
// src/pages/test.tsx
import { MyComponent } from "my-package";
export default function TestPage() {
return (
<MyComponent />
);
}
Footnotes
Module configuration
All packages built with tsmodule build
are ES modules. { "type": "module" }
is forced to minimize ambiguity.
tsmodule build
also forces the following tsconfig.json values during the
type-check and declaration emit:
{
"rootDir": "src/",
"outDir": "dist/",
}
Index exports
Conditional exports in package.json are configured such that "index exports" are available at a subpath. For example:
src/a/b/c/index.ts
Will be available downstream via:
import { c } from "your-package/a/b/c"
Notes:
Index exports are the only entry points available for import, are ones located at
src/**/index.ts(x?)
.1The default package entry point for
your-package
issrc/index.ts
.
1 This has no restriction on internal imports between files, which can import from each other freely, including at runtime. However, consumers of your package will only be able to import from index exports as shown.
Package.json export
For information on why the "./package.json": "./package.json"
export is
specified, see #1.
License
MIT © C. Lewis