In the past, I have written about making NPM packages with just the typscript compiler (https://cmdcolin.github.io/posts/2022-05-27-youmaynotneedabundler). There, I focused on "CJS" build because it was arguably simpler
Now, I am addressing the "ESM" case
TLDR: here is the minimal github repo for this post https://github.com/cmdcolin/minimal_pureesm_package/
As my previous article mentions, I like taking a 'bundler-less' approach to
library distribution. That means that multiple files might end up in the dist
folder which reference each other. However, using "pure ESM" requires these
files that reference each other to import the actual path, with the file
extensions
Which was awkward before...but now
The recent addition of the tsc
settings
allowImportingTsExtensions
and
rewriteRelativeImportExtensions
have now allow us to import from the .ts file extension in the src folder, and
it automatically rewrites to use the .js file extension in the dist folder
For example we can have
then running tsc over these files will produce
❯❯❯ ll dist
total 24K
-rw-rw-r-- 1 cdiesh cdiesh 37 Jan 14 06:07 bar.d.ts
-rw-rw-r-- 1 cdiesh cdiesh 91 Jan 14 06:07 bar.js
-rw-rw-r-- 1 cdiesh cdiesh 43 Jan 14 06:07 index.d.ts
-rw-rw-r-- 1 cdiesh cdiesh 116 Jan 14 06:07 index.js
where dist/index.js now contains an import with the .js extension
and dist/bar.js says
Previously you had to write import {foo} from './bar.js'
in the src folder to
have this behavior, but now you can reference the actual file, bar.ts
You can see from the above, I did not need to specify "exports" or "types".
Other random things you can observe
I use rimraf to clear the dist folder before building. I can use "yarn build --watch" for a tsc watcher
You can run yarn publish
to publish to NPM, and it will automatically run
the clean and build via the preversion script, and then will automatically
push the updated version and tag to github once it is finished via the
postversion script
This article proposes a bundler-less approach to distributing typescript
packages on NPM. It was possible before, but I think the addition of the
allowImportingTsExtensions
and rewriteRelativeImportExtensions
made it more
sane. Previously you had to use "import {thing} from './localFile.js'" even when
you were writing .ts which was awkward
If you want to publish ESM/CJS you can update your package.json to use the "exports" field, and run the tsc compiler twice over the code, once using --module cjs
Note that postbuild:cjs (which is automatically run after any invocation of build:cjs) outputs a "one line" extra package.json to the cjs folder that says type:cjs specifically in the cjs distribution (credit: https://evertpot.com/universal-cjs-esm-typescript-packages/).
You can alternatively name the all the files in the cjs folder with the .cjs file extension, which node will recognize as being commonjs and not ESM module files, if you do not want to use the one-line-package.json-with-type:cjs trick, but the tsc compiler does not currently have an option to output the .cjs file extension natively