{"id":15408907,"url":"https://github.com/antongolub/tsc-esm-fix","last_synced_at":"2025-04-12T23:29:52.711Z","repository":{"id":38366494,"uuid":"364542561","full_name":"antongolub/tsc-esm-fix","owner":"antongolub","description":"Make Typescript projects compatible with esm/mjs requirements","archived":false,"fork":false,"pushed_at":"2025-03-15T21:44:25.000Z","size":3118,"stargazers_count":75,"open_issues_count":6,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-04T01:58:49.184Z","etag":null,"topics":["esm","mjs","tsc","typescript","util"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/antongolub.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-05-05T10:41:16.000Z","updated_at":"2025-03-15T21:43:56.000Z","dependencies_parsed_at":"2023-02-17T05:01:13.721Z","dependency_job_id":"75e2451a-c4d6-449d-b32c-1906e334def9","html_url":"https://github.com/antongolub/tsc-esm-fix","commit_stats":{"total_commits":355,"total_committers":11,"mean_commits":32.27272727272727,"dds":0.5267605633802817,"last_synced_commit":"baa4024830583ec36b01380d68e3dc6df4c4f21d"},"previous_names":["antongolub/tsc-es2020-fix"],"tags_count":88,"template":false,"template_full_name":"qiwi/blank-ts-repo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antongolub%2Ftsc-esm-fix","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antongolub%2Ftsc-esm-fix/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antongolub%2Ftsc-esm-fix/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antongolub%2Ftsc-esm-fix/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/antongolub","download_url":"https://codeload.github.com/antongolub/tsc-esm-fix/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248647094,"owners_count":21139080,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["esm","mjs","tsc","typescript","util"],"created_at":"2024-10-01T16:35:47.602Z","updated_at":"2025-04-12T23:29:52.687Z","avatar_url":"https://github.com/antongolub.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# tsc-esm-fix\n\u003e Make TS projects compatible with [esm/mjs requirements](https://nodejs.org/api/esm.html#esm_packages)\n\n[![CI](https://github.com/antongolub/tsc-esm-fix/workflows/CI/badge.svg)](https://github.com/antongolub/tsc-esm-fix/actions)\n[![Maintainability](https://api.codeclimate.com/v1/badges/1ca2196057a3184d63d0/maintainability)](https://codeclimate.com/github/antongolub/tsc-esm-fix/maintainability)\n[![Test Coverage](https://api.codeclimate.com/v1/badges/1ca2196057a3184d63d0/test_coverage)](https://codeclimate.com/github/antongolub/tsc-esm-fix/test_coverage)\n[![npm (tag)](https://img.shields.io/npm/v/tsc-esm-fix)](https://www.npmjs.com/package/tsc-esm-fix)\n\n- [Problem](#problem)\n- [Solutions](#solutions)\n- [Features](#features)\n- [Getting started](#getting-started)\n  - [Requirements](#requirements)\n  - [Install](#install)\n  - [Usage examples](#usage-examples)\n  - [CLI](#cli)\n  - [JS/TS API](#jsts-api)\n- [Alternatives](#alternatives)\n- [Contributing](#contributing)\n- [References](#references)\n- [License](#license)\n\n### Problem\nThis workaround is aimed to bypass a bunch of **tsc**, **ts-jest** and **esbuild** issues _right here and right now_. \n* [TS/13422](https://github.com/microsoft/TypeScript/issues/13422) / [TS/16577](https://github.com/microsoft/TypeScript/issues/16577): **tsc** should add `.js` extensions for relative module paths if compiled as [`es2020/esnext`](https://www.typescriptlang.org/tsconfig/#module).\n* [ts-jest/1174](https://github.com/kulshekhar/ts-jest/issues/1174): `import.meta` is not allowed.\n* [esbuild/1043](https://github.com/evanw/esbuild/issues/1043): empty output for interface files that breaks reimport.\n* [TS/4433](https://github.com/microsoft/TypeScript/issues/4433): extensions in module declarations force tsconfig changes for dependent projects. See [tsc-dts-fix](https://github.com/antongolub/misc/tree/master/packages/dep/tsc-dts-fix) for details.\n\n#### moduleResolution: nodenext\n[Nightly build TypeScript 4.7](https://www.typescriptlang.org/docs/handbook/nightly-builds.html) provides [experimental esm support](https://www.typescriptlang.org/docs/handbook/esm-node.html). But it still forces to add extensions by hand (tested on [4.7.0-dev.20220408](https://www.npmjs.com/package/typescript/v/4.7.0-dev.20220408)).\n```shell\nsrc/main/ts/q/u/x/index.ts:1:21 - error TS2835: Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node12' or 'nodenext'. Did you mean '../../../foo.js'?\n\n1 import { foo } from '../../../foo'\n```\nMoreover, if understand [TS/49271](https://github.com/microsoft/TypeScript/issues/49271) correctly, `nodenext` + pkg.json `type: module` requires `.js` extension to be added to all `.d.ts` files of **external** ESM packages too. Well, good luck with that.  \n\n### Solutions\n1. Post-process tsc-compiled outputs each time after build.\n2. Patch project sources once as Sindre recommends in [the ESM migration guide](https://github.com/sindresorhus/meta/discussions/15)\n3. Use [ttypescript](https://github.com/cevek/ttypescript) with [transformer-append-js-ext plugin](https://github.com/Zoltu/typescript-transformer-append-js-extension/)\n\nThis lib covers options 1 and 2.\n\n### Features\n* Injects extensions to imports/re-exports statements.\n  * `import {foo} from './foo'` → `import {foo} from './foo.js'`\n  * `import {baz} from 'external/baz'` → `import {baz} from 'external/baz.js'`\n  * Note, [including the file extension is only necessary for packages without an \"exports\" field](https://nodejs.org/api/esm.html#esm_packages). So in this case all the external refs remain as are.\n  * Pays attention to index files: `import {bar} from './bar'` → `import {bar} from './bar/index.js'`\n  * Handles `.` and `..` shortcuts\n    * `export * from '.'` → `export * from './index.js'`\n    * `export * from '..'` → `export * from '../index.js'`\n  * Injects `.js` extensions into  `.d.ts` libdef files\n  * Does not affect string literals and comments: [depseek](https://github.com/antongolub/misc/tree/master/packages/dep/depseek)\n* Handles conditional exports (https://nodejs.org/api/packages.html#conditional-exports)\n* Follows `outDir` found in **tsconfig.json**.  \n* Searches and replaces `__dirname` and `__filename` refs with `import.meta`.\n* Fills blank files with `export {}` ([esbuild issue 1043](https://github.com/evanw/esbuild/issues/1043))\n* Patches source map files to point to the updated files.\n* Patches `require` statements with new file refs if ext changes ([hybrid/dual pkg](https://2ality.com/2019/10/hybrid-npm-packages.html))\n* Changes file extensions (applied to local deps only).\n* Supports Windows-based runtimes.\n\n## Getting started\n### Requirements\nNode.js `\u003e=16.0.0`\n\n### Install\n```shell\nnpm i -dev tsc-esm-fix\nyarn add -D tsc-esm-fix\n\n# or w/o saving to package.json\nnpx tsc-esm-fix [options]\n```\n\n### Usage examples\n```shell\ntsc-esm-fix [options]\n\n# to post-process outputs each time\ntsc-esm-fix --target='target/es6'\n\n# to patch ts sources once\ntsc-esm-fix --src='src/main/ts' --ext='.js'\n```\n\n```typescript\nimport { fix } from 'tsc-esm-fix'\nawait fix({\n  dirnameVar: true,\n  filenameVar: true,\n  ext: true\n})\n```\n\n**Input**  \n[code ref](https://github.com/antongolub/tsc-esm-fix/blob/master/src/test/fixtures/ts-project/src/main/ts/index.ts)\n```js\nimport { foo } from './foo';\nimport './bar';\n\n// external cjs module\nimport * as e1def from 'e1/a/b/c';\nimport * as e1root from 'e1';\nconst { e1 } = e1def;\nconst { e1: e1x } = e1root;\nexport { e1, e1x };\n\n// external esm module with `main` in pkg.json\nexport { m1 } from 'm1';\nexport { m1 as m1x } from 'm1/index';\n\n// external esm module with `exports` in pkg.json\nexport { e2 } from 'e2';\nexport { e2 as es3 } from 'e2/index';\nexport { e2 as es4 } from 'e2/alias';\nexport { e2foo } from 'e2/foo';\nexport { e2bar } from 'e2/bar-bundle';\n\nexport * from './foo';\nexport * from './baz';\nexport * from './q/u/x';\nexport const foobaz = foo + 'baz';\nexport { foo as foo1 } from './foo.js';\n\n// Dir with index.js file inside: ./qux.js/index.js\nexport { qux } from './qux.js';\n\nexport const dirname = __dirname;\nexport const filename = __filename;\n\nconsole.log(foobaz);\n```\n\n**Output**\n```js\nimport { foo } from './foo.js';\nimport './bar.js';\n\nimport * as e1def from 'e1/a/b/c/index.js';\nimport * as e1root from 'e1';\nconst { e1 } = e1def;\nconst { e1: e1x } = e1root;\nexport { e1, e1x };\n\nexport { m1 } from 'm1';\nexport { m1 as m1x } from 'm1/index.js';\n\nexport { e2 } from 'e2';\nexport { e2 as es3 } from 'e2/index';\nexport { e2 as es4 } from 'e2/alias';\nexport { e2foo } from 'e2/foo';\nexport { e2bar } from 'e2/bar-bundle';\n\nexport * from './foo.js';\nexport * from './baz/index.js';\nexport * from './q/u/x/index.js';\nexport const foobaz = foo + 'baz';\nexport { foo as foo1 } from './foo.js';\n\nexport { qux } from './qux.js/index.js';\n\nexport const dirname = /file:\\\\\\\\/\\\\\\\\/(.+)\\\\\\\\/[^/]/.exec(import.meta.url)[1];\nexport const filename = /file:\\\\\\\\/\\\\\\\\/(.+)/.exec(import.meta.url)[1];\n```\n\n### CLI\n```shell\ntsc-esm-fix [opts]\n```\n| Option                 | Description                                                                                    | Default                                                                  |\n|------------------------|------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|\n| `--tsconfig`           | Path to project's ts-config(s)                                                                 | `tsconfig.json`                                                          |\n| `--src`                | Entry points where the ts-source files are placed. If defined `src` option suppresses `target` |                                                                          |\n| `--target`             | tsc-compiled output directory                                                                  | If not specified inherited from tsconfig.json **compilerOptions.outDir** |\n| `--dirnameVar`         | Replace `__dirname` usages with `import.meta`                                                  | true                                                                     |\n| `--filenameVar`        | Replace `__filename` var references with `import.meta` statements                              | true                                                                     |\n| `--ext`                | Append extension to relative imports/re-exports                                                | `.js`                                                                    |\n| `--ts-ext`             | Known TS extensions                                                                            | `.ts,.tsx,.mts,.mtsx,.cts,.ctsx`                                         |\n| `--js-ext`             | Known JS extensions                                                                            | `.js,.jsx,.mjs,.mjsx,.cjs,.cjsx`                                         |\n| `--unlink`             | Remove original files if ext changes                                                           | true                                                                     |\n| `--fillBlank`          | Fill blank files with `export {}`                                                              | false                                                                    |\n| `--forceDefaultExport` | Injects `export default undefined` if not present                                              | false                                                                    |\n| `--sourceMap`          | Patch source map files to point to the updated files.                                          | false                                                                    |\n| `--cwd`                | cwd                                                                                            | `process.cwd()`                                                          |\n| `--out`                | Output dir. Defaults to `cwd`, so files would be overwritten                                   | `process.cwd()`                                                          |\n| `--debug`              | Prints debug notes                                                                             |                                                                          |\n\n#### --target vs --src\nWhen `--src` option is used, the util just modifies file contents in place.\n`--target` also renames files to change their extension.\nYou may prevent deletion original of files by using `--no-unlink`.\n\n#### glob patterns\nBy default, the util looks for `ts/tsx` files in `src` directory and `js/d.ts` files in `target`. But you can specify custom patterns via corresponding options. For example: `--src='src/main/ts/**/*.ts'`.\n```js\nconst patterns =\n  sources.length \u003e 0\n    ? sources.map((src) =\u003e src.includes('*') ? src : `${src}/**/*.{ts,tsx}`)\n    : targets.map((target) =\u003e target.includes('*') ? target : `${target}/**/*.{js,d.ts}`)\n```\n\n### JS/TS API\n```ts\nimport { fix, IFixOptions } from 'tsc-esm-fix'\n\nconst fixOptions: IFixOptions = {\n  tsconfig: 'tsconfig.build.json',\n  dirnameVar: true,\n  filenameVar: true,\n  ext: true\n}\n\nawait fix(fixOptions)\n```\n```typescript\nexport interface IFixOptions {\n  cwd: string\n  src?: string | string[]\n  target?: string | string[]\n  out?: string\n  tsconfig?: string | string[]\n  dirnameVar: boolean\n  filenameVar: boolean\n  fillBlank?: boolean\n  forceDefaultExport?: boolean\n  sourceMap?: boolean\n  ext: boolean | string\n  tsExt: string | string[]\n  jsExt: string | string[]\n  unlink?: boolean,\n  debug?: boolean | IFunction\n}\n```\n\n## Alternatives\n* https://github.com/mothepro/tsc-esm\n* https://github.com/digital-loukoum/tsc-esm\n* https://github.com/beenotung/fix-esm-import-path\n\n## Contributing\nFeel free to open any issues: bug reports, feature requests or questions.\nYou're always welcome to suggest a PR. Just fork this repo, write some code, add some tests and push your changes.\nAny feedback is appreciated.\n\n## References\n* [TypeScript/issues/13422: TypeScript and script `type=\"module\"`](https://github.com/microsoft/TypeScript/issues/13422)\n* [TypeScript/issues/28288: Feature: disable extensionless imports](https://github.com/microsoft/TypeScript/issues/28288)\n* [ts-jest/issues/1174: import.meta not allowed](https://github.com/kulshekhar/ts-jest/issues/1174)\n* [esbuild/issues/1043: Empty file bundles as `{ default: {} }`](https://github.com/evanw/esbuild/issues/1043)\n* [stackoverflow.com/how-to-use-import-meta-when-testing-with-jest](https://stackoverflow.com/questions/64961387/how-to-use-import-meta-when-testing-with-jest)\n* [Pure ESM package](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c)\n* [stackoverflow.com/alternative-for-dirname-in-node-when-using-the-experimental-modules-flag](https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-when-using-the-experimental-modules-flag)\n* [ecma262/#sec-imports](https://tc39.es/ecma262/#sec-imports)\n* [ERR_REQUIRE_ESM](https://dev.to/antongolub/errrequireesm-4j0h)\n* [Publishing Node modules with TypeScript and ES modules](https://blog.logrocket.com/publishing-node-modules-typescript-es-modules/)\n\n## License\n[MIT](./LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantongolub%2Ftsc-esm-fix","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fantongolub%2Ftsc-esm-fix","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantongolub%2Ftsc-esm-fix/lists"}