Skip to content

Commit

Permalink
test: add more build assertions. (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
knightedcodemonkey authored Jul 29, 2023
1 parent b16bb2e commit bfd59a9
Show file tree
Hide file tree
Showing 5 changed files with 26 additions and 15 deletions.
21 changes: 12 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@ Early stages of development. Inspired by https://github.com/microsoft/TypeScript

* Node >= 16.19.0.
* TypeScript, `npm i typescript`.
* A `tsconfig.json` with `outDir` defined.

## Example

First, install the package to create the `duel` executable inside your `node_modules/.bin` directory.
First, install this package to create the `duel` executable inside your `node_modules/.bin` directory.

```console
user@comp ~ $ npm i @knighted/duel
```

Then, given a `package.json` that defines `"type": "module"` and a `tsconfig.json` file that looks like the following:
Then, given a `package.json` that defines `"type": "module"` and a `tsconfig.json` file that looks something like the following:

```json
{
Expand Down Expand Up @@ -51,9 +52,9 @@ And then running it:
user@comp ~ $ npm run build
```

If everything worked, you should have an ESM build inside of `dist` and a CJS build inside of `dist/cjs`. Now you can update your `exports` in package.json to match the build output.
If everything worked, you should have an ESM build inside of `dist` and a CJS build inside of `dist/cjs`. Now you can update your [`exports`](https://nodejs.org/api/packages.html#exports) to match the build output.

It should work similarly for a CJS first project. Except, your `tsconfig.json` would define `--module` and `--moduleResolution` differently, and you'd want to pass `-x .mjs`.
It should work similarly for a CJS first project. Except, your `tsconfig.json` would define `--module` and `--moduleResolution` differently, and you'd want to pass `--target-extension .mjs`.

See the available [options](#options).

Expand All @@ -71,13 +72,15 @@ You can run `duel --help` to get more info. Below is the output of that:
Usage: duel [options]

Options:
--project, -p Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'.
--target-extension, -x Sets the file extension for the dual build. [.cjs,.mjs]
--help, -h Print this message.
--project, -p Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'.
--target-extension, -x Sets the file extension for the dual build. [.cjs,.mjs]
--help, -h Print this message.
```

## Gotchas

* Unfortunately, TypeScript doesn't really understand dual packages very well. For instance, it will **always** create CJS exports when `--module commonjs` is used, even on files with an `.mts` extension. One reference issue is https://github.com/microsoft/TypeScript/issues/54573. If you use `.mts` extensions to enforce an ESM module system, this might break in the corresponding dual CJS build.
* If targeting a dual CJS build, and you are using [top level `await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#top_level_await), you will most likely encounter the compilation error `error TS1378: Top-level 'await' expressions are only allowed when the 'module' option is set to 'es2022', 'esnext', 'system', 'node16', or 'nodenext', and the 'target' option is set to 'es2017' or higher.` during the CJS build. This is because `duel` creates a temporary `tsconfig.json` from your original and overwrites the `--module` and `--moduleResolution` based on the provided `--target-ext`.
* Unfortunately, TypeScript doesn't really build [dual packages](https://nodejs.org/api/packages.html#dual-commonjses-module-packages) very well in regards to preserving module system by file extension. For instance, it will **always** create CJS exports when `--module commonjs` is used, _even on files with an `.mts` extension_, which is contrary to [how Node determines module systems](https://nodejs.org/api/packages.html#determining-module-system). The `tsc` compiler is fundamentally broken in this regard. One reference issue is https://github.com/microsoft/TypeScript/issues/54573. If you use `.mts` extensions to enforce an ESM module system, this will break in the corresponding dual CJS build. There is no way to fix this until TypeScript fixes their compiler.

* If targeting a dual CJS build, and you are using [top level `await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#top_level_await), you will most likely encounter the compilation error `error TS1378: Top-level 'await' expressions are only allowed when the 'module' option is set to 'es2022', 'esnext', 'system', 'node16', or 'nodenext', and the 'target' option is set to 'es2017' or higher.` during the CJS build. This is because `duel` creates a temporary `tsconfig.json` from your original and necessarily overwrites the `--module` and `--moduleResolution` based on the provided `--target-ext`. There is no workaround other than to **not** use top level await if you want a dual build.

* If doing an `import type` across module systems, i.e. from `.mts` into `.cts`, or vice versa, you might encounter the compilation error ``error TS1452: 'resolution-mode' assertions are only supported when `moduleResolution` is `node16` or `nodenext`.``. This is a [known issue](https://github.com/microsoft/TypeScript/issues/49055) and TypeScript currently suggests installing the nightly build, i.e. `npm i typescript@next`.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@knighted/duel",
"version": "1.0.0-alpha.2",
"version": "1.0.0-alpha.3",
"description": "TypeScript dual packages.",
"type": "module",
"main": "dist",
Expand Down
6 changes: 3 additions & 3 deletions src/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ const init = async args => {
log('Usage: duel [options]\n')
log('Options:')
log(
"--project, -p \t\t\t Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'.",
"--project, -p \t\t Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'.",
)
log(
'--target-extension, -x \t\t Sets the file extension for the dual build. [.cjs,.mjs]',
'--target-extension, -x \t Sets the file extension for the dual build. [.cjs,.mjs]',
)
log('--help, -h \t\t\t Print this message.')
log('--help, -h \t\t Print this message.')
} else {
const { project, 'target-extension': targetExt } = parsed
let configPath = resolve(project)
Expand Down
8 changes: 8 additions & 0 deletions test/integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import assert from 'node:assert/strict'
import { fileURLToPath } from 'node:url'
import { dirname, resolve } from 'node:path'
import { rm } from 'node:fs/promises'
import { existsSync } from 'node:fs'

import { duel } from '../src/duel.js'

Expand Down Expand Up @@ -87,6 +88,13 @@ describe('duel', () => {
assert.ok(
spy.mock.calls[2].arguments[0].startsWith('Successfully created a dual CJS build'),
)
// Check that the expected files and extensions are there
assert.ok(existsSync(resolve(dist, 'index.js')))
assert.ok(existsSync(resolve(dist, 'index.d.ts')))
assert.ok(existsSync(resolve(dist, 'cjs.cjs')))
assert.ok(existsSync(resolve(dist, 'cjs/index.cjs')))
assert.ok(existsSync(resolve(dist, 'cjs/index.d.cts')))
assert.ok(existsSync(resolve(dist, 'cjs/esm.mjs')))
})

it('reports compilation errors during a build', async t => {
Expand Down

0 comments on commit bfd59a9

Please sign in to comment.