tshy
Advanced tools
Comparing version 1.0.0-3 to 1.0.0
import './tsconfig.js'; | ||
declare const _default: () => void; | ||
export default _default; | ||
//# sourceMappingURL=build.d.ts.map |
import chalk from 'chalk'; | ||
import { spawnSync } from 'node:child_process'; | ||
import { renameSync } from 'node:fs'; | ||
import { relative, resolve } from 'node:path/posix'; | ||
import { rimrafSync } from 'rimraf'; | ||
import { syncContentSync } from 'sync-content'; | ||
import bins from './bins.js'; | ||
import { buildCommonJS } from './build-commonjs.js'; | ||
import { buildESM } from './build-esm.js'; | ||
import * as console from './console.js'; | ||
import dialects from './dialects.js'; | ||
import { fail } from './fail.js'; | ||
import polyfills from './polyfills.js'; | ||
import setFolderDialect from './set-folder-dialect.js'; | ||
import './tsconfig.js'; | ||
import writePackage from './write-package.js'; | ||
const buildFail = (res) => { | ||
setFolderDialect('src'); | ||
fail('build failed'); | ||
console.error(res); | ||
process.exit(1); | ||
export default () => { | ||
rimrafSync('.tshy-build-tmp'); | ||
if (dialects.includes('esm')) | ||
buildESM(); | ||
if (dialects.includes('commonjs')) | ||
buildCommonJS(); | ||
console.debug(chalk.cyan.dim('moving to ./dist')); | ||
syncContentSync('.tshy-build-tmp', 'dist'); | ||
console.debug(chalk.cyan.dim('removing build temp dir')); | ||
rimrafSync('.tshy-build-tmp'); | ||
console.debug(chalk.cyan.dim('chmod bins')); | ||
bins(); | ||
console.debug(chalk.cyan.dim('write package.json')); | ||
writePackage(); | ||
}; | ||
rimrafSync('.tshy-build-tmp'); | ||
if (dialects.includes('esm')) { | ||
setFolderDialect('src', 'esm'); | ||
console.debug(chalk.cyan.dim('building esm')); | ||
const res = spawnSync('tsc -p .tshy/esm.json', { | ||
shell: true, | ||
stdio: 'inherit', | ||
}); | ||
setFolderDialect('src'); | ||
if (res.status || res.signal) | ||
buildFail(res); | ||
setFolderDialect('.tshy-build-tmp/esm', 'esm'); | ||
console.error(chalk.cyan.bold('built esm')); | ||
} | ||
if (dialects.includes('commonjs')) { | ||
setFolderDialect('src', 'commonjs'); | ||
console.debug(chalk.cyan.dim('building commonjs')); | ||
const res = spawnSync('tsc -p .tshy/commonjs.json', { | ||
shell: true, | ||
stdio: 'inherit', | ||
}); | ||
setFolderDialect('src'); | ||
if (res.status || res.signal) | ||
buildFail(res); | ||
setFolderDialect('.tshy-build-tmp/commonjs', 'commonjs'); | ||
console.error(chalk.cyan.bold('built commonjs')); | ||
// apply polyfills | ||
for (const [f, t] of polyfills.entries()) { | ||
const stemFrom = resolve('.tshy-build-tmp/commonjs', relative(resolve('src'), resolve(f))).replace(/\.cts$/, ''); | ||
const stemTo = resolve('.tshy-build-tmp/commonjs', relative(resolve('src'), resolve(t))).replace(/\.tsx?$/, ''); | ||
renameSync(`${stemFrom}.cjs`, `${stemTo}.js`); | ||
renameSync(`${stemFrom}.d.cts`, `${stemTo}.d.ts`); | ||
} | ||
} | ||
console.debug(chalk.cyan.dim('moving to ./dist')); | ||
syncContentSync('.tshy-build-tmp', 'dist'); | ||
console.debug(chalk.cyan.dim('removing build temp dir')); | ||
rimrafSync('.tshy-build-tmp'); | ||
console.debug(chalk.cyan.dim('chmod bins')); | ||
bins(); | ||
console.debug(chalk.cyan.dim('write package.json')); | ||
writePackage(); | ||
//# sourceMappingURL=build.js.map |
// get the config and package and stuff | ||
import { fail } from './fail.js'; | ||
import pkg from './package.js'; | ||
import sources from './sources.js'; | ||
import validDialects from './valid-dialects.js'; | ||
import validExports from './valid-exports.js'; | ||
const validConfig = (e) => !!e && | ||
@@ -7,58 +10,2 @@ typeof e === 'object' && | ||
(e.dialects === undefined || validDialects(e['dialects'])); | ||
const isDialect = (d) => d === 'commonjs' || d === 'esm'; | ||
const validDialects = (d) => !!d && Array.isArray(d) && !d.some(d => !isDialect(d)); | ||
const validExternalExport = (exp) => { | ||
const i = resolveExport(exp, 'import'); | ||
const r = resolveExport(exp, 'require'); | ||
if (!i && !r) | ||
return false; | ||
if (i && join(i).startsWith('src/')) | ||
return false; | ||
if (r && join(r).startsWith('src/')) | ||
return false; | ||
return true; | ||
}; | ||
const validExports = (e) => { | ||
if (!e) | ||
return false; | ||
if (typeof e !== 'object') | ||
return false; | ||
for (const [sub, exp] of Object.entries(e)) { | ||
if (sub !== '.' && !sub.startsWith('./')) { | ||
fail(`tshy.exports key must be "." or start with "./", got: ${sub}`); | ||
process.exit(1); | ||
} | ||
// just a module. either a built export, or a simple unbuilt export | ||
if (typeof exp === 'string') { | ||
e[sub] = addDot(exp); | ||
continue; | ||
} | ||
if (typeof exp !== 'object' || !exp || Array.isArray(exp)) { | ||
fail(`tshy.exports ${sub} value must be string or import/require object, ` + | ||
`got: ${JSON.stringify(exp)}`); | ||
process.exit(1); | ||
} | ||
// can be: | ||
// "./sub": "./unbuilt.js" | ||
// "./sub": { require: "./unbuilt.js", types: "./unbuilt.d.ts" } | ||
// "./sub": {require:"./u.cjs",import:"./u.cjs",types:"./u.dts"} | ||
// "./sub": {import:{types:"u.d.ts",default:"u.js"},require:{types:"u.d.cts", default:"u.cjs"}} | ||
// Just verify that import and require resolutions are not in src | ||
if (!validExternalExport(exp)) { | ||
fail(`tshy.exports ${sub} unbuilt exports must not be in ./src, ` + | ||
`and exports in src must be string values. ` + | ||
`got: ${JSON.stringify(exp)}`); | ||
process.exit(1); | ||
} | ||
e[sub] = exp; | ||
} | ||
if (e.dialects) { | ||
if (!validDialects(e.dialects)) { | ||
fail(`tshy.dialects must be array containing 'esm' and/or 'commonjs', ` + | ||
`got: ${JSON.stringify(e.dialects)}`); | ||
} | ||
} | ||
return true; | ||
}; | ||
const addDot = (s) => `./${join(s)}`; | ||
const getConfig = (pkg, sources) => { | ||
@@ -81,8 +28,4 @@ const tshy = validConfig(pkg.tshy) ? pkg.tshy : {}; | ||
}; | ||
import { join } from 'path/posix'; | ||
import pkg from './package.js'; | ||
import sources from './sources.js'; | ||
import { resolveExport } from './resolve-export.js'; | ||
const config = getConfig(pkg, sources); | ||
export default config; | ||
//# sourceMappingURL=config.js.map |
@@ -1,4 +0,7 @@ | ||
import { Export } from './types.js'; | ||
import { Export, TshyConfig, TshyExport } from './types.js'; | ||
export declare const getImpTarget: (s: string | TshyExport | undefined | null) => string | undefined | null; | ||
export declare const getReqTarget: (s: string | TshyExport | undefined | null, polyfills: Map<string, string>) => string | null | undefined; | ||
export declare const getExports: (c: TshyConfig, polyfills: Map<string, string>) => Record<string, Export>; | ||
declare const _default: Record<string, Export>; | ||
export default _default; | ||
//# sourceMappingURL=exports.d.ts.map |
import { relative, resolve } from 'node:path/posix'; | ||
import config from './config.js'; | ||
import dialects from './dialects.js'; | ||
import { fail } from './fail.js'; | ||
import fail from './fail.js'; | ||
import pkg from './package.js'; | ||
import polyfills from './polyfills.js'; | ||
import { resolveExport } from './resolve-export.js'; | ||
const getImpTarget = (s) => { | ||
export const getImpTarget = (s) => { | ||
if (s === undefined) | ||
@@ -19,7 +19,5 @@ return undefined; | ||
} | ||
if (s && typeof s === 'object') { | ||
return resolveExport(s, 'import'); | ||
} | ||
return resolveExport(s, 'import'); | ||
}; | ||
const getReqTarget = (s, polyfills) => { | ||
export const getReqTarget = (s, polyfills) => { | ||
if (s === undefined) | ||
@@ -35,7 +33,7 @@ return undefined; | ||
} | ||
if (s && typeof s === 'object') { | ||
return getReqTarget(resolveExport(s, 'require'), polyfills); | ||
} | ||
return getReqTarget(resolveExport(s, 'require'), polyfills); | ||
}; | ||
const getExports = (c, polyfills) => { | ||
export const getExports = (c, polyfills) => { | ||
// by this time it always exports, will get the default if missing | ||
/* c8 ignore start */ | ||
if (!c.exports) { | ||
@@ -45,6 +43,5 @@ fail('no exports on tshy config (is there code in ./src?)'); | ||
} | ||
/* c8 ignore stop */ | ||
const e = {}; | ||
for (const [sub, s] of Object.entries(c.exports)) { | ||
const impTarget = getImpTarget(s); | ||
const reqTarget = getReqTarget(s, polyfills); | ||
// external export, not built by us | ||
@@ -56,4 +53,9 @@ if (typeof s !== 'string' || !s.startsWith('./src/')) { | ||
} | ||
const impTarget = getImpTarget(s); | ||
const reqTarget = getReqTarget(s, polyfills); | ||
// should be impossible | ||
/* c8 ignore start */ | ||
if (!impTarget && !reqTarget) | ||
continue; | ||
/* c8 ignore stop */ | ||
const exp = (e[sub] = {}); | ||
@@ -60,0 +62,0 @@ if (impTarget) { |
@@ -1,2 +0,3 @@ | ||
export declare const fail: (message: string, er?: Error) => void; | ||
declare const _default: (message: string, er?: Error) => void; | ||
export default _default; | ||
//# sourceMappingURL=fail.d.ts.map |
import chalk from 'chalk'; | ||
import * as console from './console.js'; | ||
export const fail = (message, er) => { | ||
export default (message, er) => { | ||
console.error(chalk.red.bold(message)); | ||
@@ -5,0 +5,0 @@ if (er) |
@@ -6,2 +6,3 @@ #!/usr/bin/env node | ||
import pkg from './package.js'; | ||
import build from './build.js'; | ||
const { exports: exp, tshy } = pkg; | ||
@@ -11,5 +12,4 @@ console.debug(chalk.yellow.bold('building'), process.cwd()); | ||
console.debug(chalk.cyan.dim('exports'), exp); | ||
// have our config, time to build | ||
await import('./build.js'); | ||
build(); | ||
console.log(chalk.bold.green('success!')); | ||
//# sourceMappingURL=index.js.map |
import { Package } from './types.js'; | ||
declare const pkg: Package; | ||
export default pkg; | ||
declare const _default: Package; | ||
export default _default; | ||
//# sourceMappingURL=package.d.ts.map |
// get the package.json data for the cwd | ||
import { readFileSync } from 'fs'; | ||
import { fail } from './fail.js'; | ||
import fail from './fail.js'; | ||
const readPkg = () => { | ||
try { | ||
return JSON.parse(readFileSync('package.json', 'utf8')); | ||
return Object.assign(JSON.parse(readFileSync('package.json', 'utf8')), { type: 'module' }); | ||
} | ||
@@ -13,5 +13,3 @@ catch (er) { | ||
}; | ||
const pkg = readPkg(); | ||
pkg.type = 'module'; | ||
export default pkg; | ||
export default readPkg(); | ||
//# sourceMappingURL=package.js.map |
@@ -1,2 +0,2 @@ | ||
export declare const resolveExport: (exp: any, t: 'import' | 'require') => string | undefined; | ||
export declare const resolveExport: (exp: any, m: 'import' | 'require') => string | undefined | null; | ||
//# sourceMappingURL=resolve-export.d.ts.map |
@@ -1,10 +0,12 @@ | ||
export const resolveExport = (exp, t) => { | ||
export const resolveExport = (exp, m) => { | ||
if (typeof exp === 'string') | ||
return exp; | ||
if (!exp || typeof exp !== 'object') | ||
if (typeof exp !== 'object') | ||
return undefined; | ||
if (exp === null) | ||
return exp; | ||
if (Array.isArray(exp)) { | ||
for (const e of exp) { | ||
const u = resolveExport(e, t); | ||
if (u) | ||
const u = resolveExport(e, m); | ||
if (u || u === null) | ||
return u; | ||
@@ -14,9 +16,9 @@ } | ||
} | ||
if (exp[t]) | ||
return resolveExport(exp[t], t); | ||
if (exp[m]) | ||
return resolveExport(exp[m], m); | ||
if (exp.node) | ||
return resolveExport(exp.node, t); | ||
return resolveExport(exp.node, m); | ||
if (exp.default) | ||
return resolveExport(exp.default, t); | ||
return resolveExport(exp.default, m); | ||
}; | ||
//# sourceMappingURL=resolve-export.js.map |
@@ -32,3 +32,3 @@ export type TshyConfig = { | ||
bin?: string | Record<string, string>; | ||
exports: Record<string, Export>; | ||
exports?: Record<string, Export>; | ||
tshy?: TshyConfig; | ||
@@ -35,0 +35,0 @@ [k: string]: any; |
{ | ||
"name": "tshy", | ||
"version": "1.0.0-3", | ||
"version": "1.0.0", | ||
"description": "TypeScript HYbridizer - Hybrid (CommonJS/ESM) TypeScript node package builder", | ||
@@ -27,4 +27,9 @@ "author": "Isaac Z. Schlueter <i@izs.me> (https://izs.me)", | ||
"format": "prettier --write . --ignore-path ./.prettierignore --cache", | ||
"typedoc": "typedoc" | ||
"typedoc": "typedoc", | ||
"test": "tap", | ||
"snap": "tap" | ||
}, | ||
"tap": { | ||
"coverage-map": "map.js" | ||
}, | ||
"engines": { | ||
@@ -45,2 +50,3 @@ "node": "16 >=16.17 || 18 >=18.16.0 || >=20.6.1" | ||
"prettier": "^2.8.8", | ||
"tap": "^18.0.0-26", | ||
"typedoc": "^0.25.1" | ||
@@ -47,0 +53,0 @@ }, |
# tshy - TypeScript HYbridizer | ||
Hybrid (CommonJS/ESM) TypeScript node package builder. | ||
Hybrid (CommonJS/ESM) TypeScript node package builder. Write | ||
modules that Just Work in ESM and CommonJS, in easy mode. | ||
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. | ||
builds your TypeScript program using `tsc` 5.2, emitting both ESM | ||
and CommonJS variants, [providing the full strength of | ||
TypeScript’s checking for both output | ||
formats](https://twitter.com/atcb/status/1702069237710479608). | ||
@@ -38,7 +41,7 @@ ## USAGE | ||
Mostly, this is opinionated convention, and so there is very | ||
little to configure. | ||
Mostly, this just uses 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. | ||
Source must be in `./src`. Builds are in `./dist/commonjs` for | ||
CommonJS and `./dist/esm` for ESM. | ||
@@ -73,8 +76,8 @@ There is very little configuration for this. The only thing to | ||
"import": { | ||
"types": "./dist/mjs/foo.d.ts", | ||
"default": "./dist/mjs/foo.js" | ||
"types": "./dist/esm/foo.d.ts", | ||
"default": "./dist/esm/foo.js" | ||
}, | ||
"require": { | ||
"types": "./dist/cjs/foo.d.ts", | ||
"default": "./dist/cjs/foo.js" | ||
"types": "./dist/commonjs/foo.d.ts", | ||
"default": "./dist/commonjs/foo.js" | ||
} | ||
@@ -87,12 +90,23 @@ } | ||
Any exports that are not within `./src` will not be built, and | ||
can be either a string, or a `{ import, require, types }` object: | ||
can be anything supported by package.json `exports`, as they will | ||
just be passed through as-is. | ||
```json | ||
{ | ||
"exports": { | ||
"./package.json": "./package.json" | ||
"./thing": { | ||
"import": "./lib/thing.mjs", | ||
"require": "./lib/thing.cjs", | ||
"types": "./lib/thing.d.ts" | ||
"tshy": { | ||
"exports": { | ||
".": "./src/my-built-module.ts", | ||
"./package.json": "./package.json" | ||
"./thing": { | ||
"import": "./lib/thing.mjs", | ||
"require": "./lib/thing.cjs", | ||
"types": "./lib/thing.d.ts" | ||
}, | ||
"./arraystyle": [ | ||
{ "import": "./no-op.js" }, | ||
{ "browser": "./browser-thing.js" }, | ||
{ "require": [{ "types": "./using-require.d.ts" }, "./using-require.js"], | ||
{ "types": "./blah.d.ts" }, | ||
"./etc.js" | ||
] | ||
} | ||
@@ -143,3 +157,3 @@ } | ||
// ^^^^^^^^^^--------- matching name | ||
// ^^^^----- "-cts" tag | ||
// ^^^^----- "-cjs" tag | ||
// ^^^^- ".cts" filename suffix | ||
@@ -156,5 +170,10 @@ // this one has a -cjs.cts suffix, so it will override the | ||
You will generally have to `//@ts-ignore` a bunch of stuff to get | ||
the CommonJS build to ignore it, so it's best to keep the | ||
polyfill surface as small as possible. | ||
```js | ||
// src/source-dir.ts | ||
// This is the ESM version of the module | ||
//@ts-ignore | ||
export const sourceDir = new URL('.', import.meta.url) | ||
@@ -164,5 +183,5 @@ ``` | ||
Then in your code, you can just `import { sourceDir } from | ||
'./source-dir.js'` and it'll work in both dialects. | ||
'./source-dir.js'` and it'll work in both builds. | ||
## `.cts` and `.mts` files | ||
## Excluding from a build using `.cts` and `.mts` files | ||
@@ -173,4 +192,4 @@ Files named `*.mts` will be excluded from the CommonJS 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 | ||
If you need to do something one way for CommonJS 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 | ||
@@ -210,3 +229,3 @@ `src/<whatever>-cjs.cts`. | ||
get local imports, and the package.json files placed in | ||
`dist/{cjs,mjs}` can't have local imports outside of their | ||
`dist/{commonjs,esm}` can't have local imports outside of their | ||
folders. | ||
@@ -236,3 +255,3 @@ | ||
Then the `tsconfig.json` file will be used as the default project | ||
for code hints in VSCode/nvim, your tests, etc. | ||
for code hints in VSCode, neovim, tests, etc. | ||
@@ -242,3 +261,3 @@ ## `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 | ||
cjs, and also import packages using node-style `"exports"`-aware | ||
module resolution, is to set the `type` field in the | ||
@@ -245,0 +264,0 @@ `package.json` file closest to the TypeScript source code. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
73765
95
566
1
260
4