You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+24-4Lines changed: 24 additions & 4 deletions
Original file line number
Diff line number
Diff line change
@@ -10,8 +10,10 @@ Tool for building a Node.js [dual package](https://nodejs.org/api/packages.html#
10
10
11
11
- Bidirectional ESM ↔️ CJS dual builds inferred from the package.json `type`.
12
12
- Correctly preserves module systems for `.mts` and `.cts` file extensions.
13
-
- Resolves the [differences between ES modules and CommonJS](https://nodejs.org/api/esm.html#differences-between-es-modules-and-commonjs).
14
-
- Use only one package.json and tsconfig.json.
13
+
- No extra configuration files needed, uses `package.json` and `tsconfig.json` files.
14
+
- Transforms the [differences between ES modules and CommonJS](https://nodejs.org/api/esm.html#differences-between-es-modules-and-commonjs).
15
+
- Works with monorepos.
16
+
15
17
16
18
## Requirements
17
19
@@ -68,12 +70,27 @@ If you prefer to have both builds in directories inside of your defined `outDir`
68
70
69
71
Assuming an `outDir` of `dist`, running the above will create `dist/esm` and `dist/cjs` directories.
70
72
73
+
### Module transforms
74
+
75
+
TypeScript will throw compiler errors when using `import.meta` globals while targeting a CommonJS dual build, but _will not_ throw compiler errors when the inverse is true, i.e. using CommonJS globals (`__filename`, `__dirname`, etc.) while targeting an ES module dual build. There is an [open issue](https://github.com/microsoft/TypeScript/issues/58658) regarding this unexpected behavior. You can use the `--modules` option to have the [differences between ES modules and CommonJS](https://nodejs.org/api/esm.html#differences-between-es-modules-and-commonjs) transformed by `duel` prior to running compilation with `tsc` so that there are no compilation or runtime errors.
76
+
77
+
Note, there is a slight performance penalty since your project needs to be copied first to run the transforms before compiling with `tsc`.
78
+
79
+
```json
80
+
"scripts": {
81
+
"build": "duel --modules"
82
+
}
83
+
```
84
+
85
+
This feature is still a work in progress regarding transforming `exports` when targeting an ES module build (relies on [`@knighted/module`](https://github.com/knightedcodemonkey/module)).
86
+
71
87
## Options
72
88
73
89
The available options are limited, because you should define most of them inside your project's `tsconfig.json` file.
74
90
75
91
-`--project, -p` The path to the project's configuration file. Defaults to `tsconfig.json`.
76
92
-`--pkg-dir, -k` The directory to start looking for a package.json file. Defaults to the cwd.
93
+
-`--modules, -m` Transform module globals for dual build target. Defaults to false.
77
94
-`--dirs, -d` Outputs both builds to directories inside of `outDir`. Defaults to `false`.
78
95
79
96
You can run `duel --help` to get the same info. Below is the output of that:
@@ -84,6 +101,7 @@ Usage: duel [options]
84
101
Options:
85
102
--project, -p [path] Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'.
86
103
--pkg-dir, -k [path] The directory to start looking for a package.json file. Defaults to cwd.
104
+
--modules, -m Transform module globals for dual build target. Defaults to false.
87
105
--dirs, -d Output both builds to directories inside of outDir. [esm, cjs].
88
106
--help, -h Print this message.
89
107
```
@@ -94,12 +112,14 @@ These are definitely edge cases, and would only really come up if your project m
94
112
95
113
- This is going to work best if your CJS-first project uses file extensions in _relative_ specifiers. This is completely acceptable in CJS projects, and [required in ESM projects](https://nodejs.org/api/esm.html#import-specifiers). This package makes no attempt to rewrite bare specifiers, or remap any relative specifiers to a directory index.
96
114
97
-
- 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, there doesn't appear to be a way to convert an arbitrary `.ts` file into another module system, _while also preserving the module system of `.mts` and `.cts` files_, without requiring **multiple** package.json files. In my opinion, the `tsc` compiler is fundamentally broken in this regard, and at best is enforcing usage patterns it shouldn't. This is only mentioned for transparency, `duel`will correct for this and produce files with the module system you would expect based on the file's extension, so that it works with [how Node.js determines module systems](https://nodejs.org/api/packages.html#determining-module-system).
115
+
- Unfortunately, TypeScript doesn't really build [dual packages](https://nodejs.org/api/packages.html#dual-commonjses-module-packages) very well. One instance of unexpected behavior is when the compiler throws errors for ES module globals when running a dual CJS build, but not for the inverse case, despite both causing runtime errors in Node.js. See the [open issue](https://github.com/microsoft/TypeScript/issues/58658). You can circumvent this with `duel`by using the `--modules` option if your project uses module globals such as `import.meta` properties or `__dirname`, `__filename`, etc. in a CommonJS project.
98
116
99
117
- 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`.
100
118
101
119
- If running `duel` with your project's package.json file open in your editor, you may temporarily see the content replaced. This is because `duel` dynamically creates a new package.json using the `type` necessary for the dual build. Your original package.json will be restored after the build completes.
102
120
103
121
## Notes
104
122
105
-
As far as I can tell, `duel` is one (if not the only) way to get a correct dual package build using `tsc` with only **one package.json and tsconfig.json file**, _while also preserving module system by file extension_. Basically, how you expect things to work. The Microsoft backed TypeScript team [keep](https://github.com/microsoft/TypeScript/pull/54546)[talking](https://github.com/microsoft/TypeScript/issues/54593) about dual build support, but they continue to [refuse to rewrite specifiers](https://github.com/microsoft/TypeScript/issues/16577).
123
+
As far as I can tell, `duel` is one (if not the only) way to get a correct dual package build using `tsc` without requiring multiple `tsconfig.json` files or extra configuration. The Microsoft backed TypeScript team [keep](https://github.com/microsoft/TypeScript/pull/54546)[talking](https://github.com/microsoft/TypeScript/issues/54593) about dual build support, but they continue to [refuse to rewrite specifiers](https://github.com/microsoft/TypeScript/issues/16577).
124
+
125
+
Fortunately, Node.js has added `--experimental-require-module` so that you can [`require()` ES modules](https://nodejs.org/api/esm.html#require) if they don't use top level await, which sets the stage for possibly no longer requiring dual builds.
0 commit comments