Security News
JSR Working Group Kicks Off with Ambitious Roadmap and Plans for Open Governance
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
Hybrid (CommonJS/ESM) TypeScript node package builder.
This tool manages the exports
in your package.json file, and
builds your TypeScript program using tsc
5.2 in both ESM and
CJS modes.
Install tshy:
npm i -D tshy
Put this in your package.json to use it with the default configs:
{
"files": ["dist"],
"scripts": {
"prepare": "tshy"
}
}
Put your source code in ./src
.
The built files will end up in ./dist/esm
(ESM) and
./dist/commonjs
(CommonJS).
Your exports
will be edited to reflect the correct module entry
points.
Mostly, this is opinionated convention, and so there is very little to configure.
Source must be in ./src
. Builds are in ./dist/cjs
for
CommonJS and ./dist/mjs
for ESM.
There is very little configuration for this. The only thing to
decide is the exported paths. If you have a ./index.ts
file,
then that will be listed as the main "."
export by default.
You can set other entry points by putting this in your
package.json
file:
{
"tshy": {
"exports": {
"./foo": "./src/foo.ts",
"./bar": "./src/bar.ts",
".": "./src/something-other-than-index.ts",
"./package.json": "./package.json"
}
}
}
Any exports pointing to files in ./src
will be updated to their
appropriate build target locations, like:
{
"exports": {
"./foo": {
"import": {
"types": "./dist/mjs/foo.d.ts",
"default": "./dist/mjs/foo.js"
},
"require": {
"types": "./dist/cjs/foo.d.ts",
"default": "./dist/cjs/foo.js"
}
}
}
}
Any exports that are not within ./src
will not be built, and
can be either a string, or a { import, require, types }
object:
{
"exports": {
"./package.json": "./package.json"
"./thing": {
"import": "./lib/thing.mjs",
"require": "./lib/thing.cjs",
"types": "./lib/thing.d.ts"
}
}
}
You can tell tshy which dialect you're building for by setting
the dialects
config to an array of strings:
{
"tshy": {
"dialects": ["esm", "commonjs"]
}
}
The default is ["esm", "commonjs"]
(ie, both of them). If you
set it to just one, then only that dialect will be built and
exported.
Sometimes you have to do something in different ways depending on
the JS dialect in use. For example, maybe you have to use
import.meta.url
in ESM, but polyfill with
pathToFileURL(__filename)
in CommonJS.
To do this, create a polyfill file with the CommonJS code in
<name>-cjs.cts
. (The cts
extension matters.)
// src/source-dir-cjs.cts
// ^^^^^^^^^^--------- matching name
// ^^^^----- "-cts" tag
// ^^^^- ".cts" filename suffix
// this one has a -cjs.cts suffix, so it will override the
// module at src/source-dir.ts in the CJS build,
// and be excluded from the esm build.
import { pathToFileURL } from 'node:url'
//@ts-ignore - Have to ignore because TSC thinks this is ESM
export const sourceDir = pathToFileURL(__dirname)
Then put the "real" ESM code in <name>.ts
(not .mts
!)
// src/source-dir.ts
// This is the ESM version of the module
export const sourceDir = new URL('.', import.meta.url)
Then in your code, you can just import { sourceDir } from './source-dir.js'
and it'll work in both dialects.
.cts
and .mts
filesFiles named *.mts
will be excluded from the CommonJS build.
Files named *.cts
will be excluded from the ESM build.
If you need to do something one way for CJS and another way for
ESM, use the "Dialect Switching" trick, with the ESM code living
in src/<whatever>.ts
and the CommonJS polyfill living in
src/<whatever>-cjs.cts
.
Code is built in ./.tshy-build-tmp
and then copied over only if
the build succeeds. This makes it work in monorepo cases where
you may have packages that depend on one another and are all
being built in parallel (as long as they've been built one time,
of course).
The exports
field in your package.json file will be updated
based on the tshy.exports
configuration, as described above.
If you don't provide that config, then the default is:
{
"tshy": {
"exports": {
".": "./src/index.ts",
"./package.json": "./package.json"
}
}
}
#imports
Using the imports
field in package.json
is not currently
supported, because this looks at the nearest package.json
to
get local imports, and the package.json files placed in
dist/{cjs,mjs}
can't have local imports outside of their
folders.
There's a way it could theoretically be done, but it's a bit complicated. A future version may support this.
Put whatever configuration you want in tsconfig.json
, with the
following caveats:
include
- will be overridden based on build, best omittedexclude
- will be overridden based on build, best omittedoutDir
- will be overridden based on build, best omittedrootDir
- will be set to ./src
in the build, can only
cause annoying errors otherwise.target
- will be set to es2022
module
- will be set to NodeNext
moduleResolution
- will be set to NodeNext
If you don't have a tsconfig.json
file, then one will be
provided for you.
Then the tsconfig.json
file will be used as the default project
for code hints in VSCode/nvim, your tests, etc.
src/package.json
As of TypeScript 5.2, the only way to emit JavaScript to ESM or
CJS, and also import packages using node-style "exports"
-aware
module resolution, is to set the type
field in the
package.json
file closest to the TypeScript source code.
During the build, tshy
will create a file at src/package.json
for this purpose, and then delete it afterwards. If that file
exists and wasn't put there by tshy
, then it will be
destroyed.
FAQs
TypeScript HYbridizer - Hybrid (CommonJS/ESM) TypeScript node package builder
The npm package tshy receives a total of 176,843 weekly downloads. As such, tshy popularity was classified as popular.
We found that tshy demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
Security News
Research
An advanced npm supply chain attack is leveraging Ethereum smart contracts for decentralized, persistent malware control, evading traditional defenses.
Security News
Research
Attackers are impersonating Sindre Sorhus on npm with a fake 'chalk-node' package containing a malicious backdoor to compromise developers' projects.