{"id":15693531,"url":"https://github.com/qnighy/ts-type-module-example","last_synced_at":"2025-05-08T04:20:59.506Z","repository":{"id":66648237,"uuid":"340676023","full_name":"qnighy/ts-type-module-example","owner":"qnighy","description":"Turning your TypeScript package into \"type\": \"module\"","archived":false,"fork":false,"pushed_at":"2021-02-23T13:40:43.000Z","size":169,"stargazers_count":8,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-31T17:07:12.583Z","etag":null,"topics":["babel","esmodules","jest","nodejs","typescript","webpack"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/qnighy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-02-20T14:38:02.000Z","updated_at":"2023-12-19T05:43:40.000Z","dependencies_parsed_at":"2023-02-22T17:30:18.730Z","dependency_job_id":null,"html_url":"https://github.com/qnighy/ts-type-module-example","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qnighy%2Fts-type-module-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qnighy%2Fts-type-module-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qnighy%2Fts-type-module-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qnighy%2Fts-type-module-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/qnighy","download_url":"https://codeload.github.com/qnighy/ts-type-module-example/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252997480,"owners_count":21837824,"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":["babel","esmodules","jest","nodejs","typescript","webpack"],"created_at":"2024-10-03T18:45:08.126Z","updated_at":"2025-05-08T04:20:59.481Z","avatar_url":"https://github.com/qnighy.png","language":"TypeScript","readme":"# Turning your TypeScript package into `\"type\": \"module\"`\n\nThis repository contains an example TypeScript package with full ES Module support.\n\n## Import specifier\n\nUnlike CommonJS Modules, file extensions (`.js`) are mandatory for ES Modules in Node.js.\n\n| | Node.js | Webpack |\n|---|---|---|\n| CommonJS Modules | `require(\"foo\")` or \u003cbr\u003e `require(\"foo.js\")` | `require(\"foo\")` or \u003cbr\u003e `require(\"foo.js\")` |\n| ES Modules | `import \"foo.js\";` only | `import \"foo\";` or \u003cbr\u003e `import \"foo.js\";` |\n\nThis is more problematic in TypeScript; source extensions and output extensions differ in the language.\n\n```typescript\n// Which declaration should we use to import foo.ts?\nimport { foo } from \"foo\";\nimport { foo } from \"foo.js\";\nimport { foo } from \"foo.ts\";\n```\n\nIn this repository, **we use `foo.js` to import `foo.ts`** because both TypeScript and Node.js supports it.\n\n\u003cdetails\u003e\u003csummary\u003eError example\u003c/summary\u003e\n\n```\n# TypeScript's error for an attempt to import .ts files\nsrc/index.ts:1:21 - error TS2691: An import path cannot end with a '.ts' extension. Consider importing './square' instead.\n\n1 import { square } from \"./square.ts\";\n                         ~~~~~~~~~~~~~\n\n\nFound 1 error.\n```\n\n\u003c/details\u003e\n\n## TypeScript\n\nAs stated above, TypeScript can resolve `*.js` imports as `*.ts` or `*.tsx` files.\n\n```typescript\n// index.ts\nimport { square } from \"./square.js\"; // resolves to square.ts\n```\n\nWhen transpiled, `square.ts` turns into `square.js` so Node.js straightforwardly resolves the import.\n\n### Problems with react-jsx\n\nWhen `\"jsx\": \"react-jsx\"` is specified, tsc produces the following snippet:\n\n```typescript\nimport { jsx as _jsx } from \"react/jsx-runtime\";\n```\n\nThe import is expected to be resolved as `node_modules/react/jsx-runtime.js`. As of the current version (React v17.0.1), it fails due to extension mismatch.\n\n\u003cdetails\u003e\u003csummary\u003eError example\u003c/summary\u003e\n\n```\ninternal/process/esm_loader.js:74\n    internalBinding('errors').triggerUncaughtException(\n                              ^\n\nError [ERR_MODULE_NOT_FOUND]: Cannot find module '$CWD/node_modules/react/jsx-runtime' imported from $CWD/dist-ts/App.js\nDid you mean to import react/jsx-runtime.js?\n    at finalizeResolution (internal/modules/esm/resolve.js:276:11)\n    at moduleResolve (internal/modules/esm/resolve.js:699:10)\n    at Loader.defaultResolve [as _resolve] (internal/modules/esm/resolve.js:810:11)\n    at Loader.resolve (internal/modules/esm/loader.js:86:40)\n    at Loader.getModuleJob (internal/modules/esm/loader.js:230:28)\n    at ModuleWrap.\u003canonymous\u003e (internal/modules/esm/module_job.js:56:40)\n    at link (internal/modules/esm/module_job.js:55:36) {\n  code: 'ERR_MODULE_NOT_FOUND'\n}\n```\n\n\u003c/details\u003e\n\nThe fix https://github.com/facebook/react/pull/20304 will land in the next version of React.\n\n## Webpack\n\nDue to Webpack's pipeline structure, imports must be resolved to the source extension (`.ts`), not the output extension (`.js`).\n\n[Webpack has the ability to automatically add extensions](https://webpack.js.org/configuration/resolve/#resolveextensions), but unfortunately, there's no removal counterpart.\n\n\u003cdetails\u003e\u003csummary\u003eError example\u003c/summary\u003e\n\n```\nasset index.js 2.42 KiB [emitted] (name: index)\nruntime modules 274 bytes 1 module\n./index.ts 194 bytes [built] [code generated]\n\nERROR in ./index.ts 1:0-31\nModule not found: Error: Can't resolve './square.js' in '$CWD/src'\nresolve './square.js' in '$CWD/src'\n  using description file: $CWD/package.json (relative path: ./src)\n    using description file: $CWD/package.json (relative path: ./src/square.js)\n      no extension\n        $CWD/src/square.js doesn't exist\n      .wasm\n        $CWD/src/square.js.wasm doesn't exist\n      .mjs\n        $CWD/src/square.js.mjs doesn't exist\n      .js\n        $CWD/src/square.js.js doesn't exist\n      .jsx\n        $CWD/src/square.js.jsx doesn't exist\n      .ts\n        $CWD/src/square.js.ts doesn't exist\n      .tsx\n        $CWD/src/square.js.tsx doesn't exist\n      .json\n        $CWD/src/square.js.json doesn't exist\n      as directory\n        $CWD/src/square.js doesn't exist\n```\n\n\u003c/details\u003e\n\nIn this repository, we remove the `.js` extension using [a babel plugin](https://www.npmjs.com/package/babel-plugin-replace-import-extension). We should enable this plugin only for Webpack.\n\n## ts-node\n\nAs of ts-node 9.1.1, you cannot use the `ts-node` command in an ESM package:\n\n\u003cdetails\u003e\u003csummary\u003eError example\u003c/summary\u003e\n\n```\nTypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension \".ts\" for $CWD/src/index.ts\n    at Loader.defaultGetFormat [as _getFormat] (internal/modules/esm/get_format.js:71:15)\n    at Loader.getFormat (internal/modules/esm/loader.js:102:42)\n    at Loader.getModuleJob (internal/modules/esm/loader.js:231:31)\n    at Loader.import (internal/modules/esm/loader.js:165:17)\n    at Object.loadESM (internal/process/esm_loader.js:68:5)\n```\n\n\u003c/details\u003e\n\nYou need a different command to enable [the experimental ESM support](https://github.com/TypeStrong/ts-node/issues/1007):\n\n```\n$ node --loader ts-node/esm src/index.js\n```\n\n## Jest (with babel-jest)\n\nYou need to [pass `--experimental-vm-modules` to Node.js](https://jestjs.io/docs/en/ecmascript-modules), but that's not enough.\n\n\u003cdetails\u003e\u003csummary\u003eError example\u003c/summary\u003e\n\n```\n$ node --experimental-vm-modules node_modules/.bin/jest --config=jest-babel.config.ts\n(node:27619) ExperimentalWarning: VM Modules is an experimental feature. This feature could change at any time\n(Use `node --trace-warnings ...` to show where the warning was created)\n(node:27660) ExperimentalWarning: VM Modules is an experimental feature. This feature could change at any time\n(Use `node --trace-warnings ...` to show where the warning was created)\n(node:27654) ExperimentalWarning: VM Modules is an experimental feature. This feature could change at any time\n(Use `node --trace-warnings ...` to show where the warning was created)\n FAIL  src/square.test.ts\n  ● Test suite failed to run\n\n    Jest encountered an unexpected token\n\n    This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.\n\n    By default, if Jest sees a Babel config, it will use that to transform your files, ignoring \"node_modules\".\n\n    Here's what you can do:\n     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/en/ecmascript-modules for how to enable it.\n     • To have some of your \"node_modules\" files transformed, you can specify a custom \"transformIgnorePatterns\" in your config.\n     • If you need a custom transformation specify a \"transform\" option in your config.\n     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the \"moduleNameMapper\" config option.\n\n    You'll find more details and examples of these config options in the docs:\n    https://jestjs.io/docs/en/configuration.html\n\n    Details:\n\n    $CWD/src/square.test.ts:1\n    ({\"Object.\u003canonymous\u003e\":function(module,exports,require,__dirname,__filename,global,jest){import { square } from \"./square\";\n                                                                                             ^^^^^^\n\n    SyntaxError: Cannot use import statement outside a module\n\n      at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1350:14)\n\nTest Suites: 1 failed, 1 total\nTests:       0 total\nSnapshots:   0 total\nTime:        0.803 s\nRan all test suites.\nerror Command failed with exit code 1.\n```\n\n\u003c/details\u003e\n\nYou need to [set `extensionsToTreatAsEsm` to `[\".ts\"]`](https://github.com/facebook/jest/pull/10823), but the option is only available in the coming Jest 27.\n\n### Translating extensions\n\nBy default, Jest only tries `foo.js`, `foo.js.js` or `foo.js.ts` for `foo.js` request.\n\n\u003cdetails\u003e\u003csummary\u003eError example\u003c/summary\u003e\n\n```\n(node:939) ExperimentalWarning: VM Modules is an experimental feature. This feature could change at any time\n(Use `node --trace-warnings ...` to show where the warning was created)\n PASS  src/square.test.ts\n FAIL  src/square42.test.ts\n  ● Test suite failed to run\n\n    Error [ERR_VM_MODULE_LINKING_ERRORED]: Linking has already failed for the provided module\n\n          at async Promise.all (index 0)\n\nTest Suites: 1 failed, 1 passed, 2 total\nTests:       1 passed, 1 total\nSnapshots:   0 total\nTime:        0.286 s, estimated 1 s\nRan all test suites.\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eError example\u003c/summary\u003e\n\n```\n(node:1052) ExperimentalWarning: VM Modules is an experimental feature. This feature could change at any time\n(Use `node --trace-warnings ...` to show where the warning was created)\n FAIL  src/square.test.ts\n  ● Test suite failed to run\n\n    Cannot find module './square.js' from 'square.test.ts'\n\n      at Resolver.resolveModule (../node_modules/jest-resolve/build/index.js:311:11)\n\n FAIL  src/square42.test.ts\n  ● Test suite failed to run\n\n    Cannot find module './square42.js' from 'square42.test.ts'\n\n      at Resolver.resolveModule (../node_modules/jest-resolve/build/index.js:311:11)\n\nTest Suites: 2 failed, 2 total\nTests:       0 total\nSnapshots:   0 total\nTime:        0.288 s, estimated 1 s\nRan all test suites.\nerror Command failed with exit code 1.\n```\n\n\u003c/details\u003e\n\nTo resolve `foo.js` as `foo.ts`, you can [use `moduleNameMapper` to strip the `.js` extension](https://github.com/facebook/jest/issues/9430#issuecomment-782835408).\n\n```javascript\nexport default {\n  // ...\n  moduleNameMapper: {\n    \"^(.*)\\\\.js$\": \"$1\",\n  },\n  // ...\n};\n```\n\n## Jest (with ts-jest)\n\nFor whatever reason, it seems ts-jest just works with Jest 26.\n\nHowever, this is no longer the case with Jest 27, which comes with the proper support for ES Modules. See the previous section below.\n\nNote that, you'll need the following [additional configuration](https://kulshekhar.github.io/ts-jest/docs/next/guides/esm-support):\n\n```javascript\n  globals: {\n    'ts-jest': {\n      useESM: true,\n    },\n  },\n```\n\nOtherwise you may (conditionally) see errors like below:\n\n\u003cdetails\u003e\u003csummary\u003eError example\u003c/summary\u003e\n\n```\n(node:25435) ExperimentalWarning: VM Modules is an experimental feature. This feature could change at any time\n(Use `node --trace-warnings ...` to show where the warning was created)\n(node:25476) ExperimentalWarning: VM Modules is an experimental feature. This feature could change at any time\n(Use `node --trace-warnings ...` to show where the warning was created)\n(node:25470) ExperimentalWarning: VM Modules is an experimental feature. This feature could change at any time\n(Use `node --trace-warnings ...` to show where the warning was created)\n FAIL  src/square42.test.ts\n  ● Test suite failed to run\n\n    ReferenceError: exports is not defined\n\n      1 | import { square42 } from \"./square42.js\";\n    \u003e 2 |\n        | ^\n      3 | describe(\"square42\", () =\u003e {\n      4 |   it(\"returns 1764\", () =\u003e {\n      5 |     expect(square42()).toBe(1764);\n\n      at square42.test.ts:2:23\n\n FAIL  src/square.test.ts\n  ● Test suite failed to run\n\n    ReferenceError: exports is not defined\n\n      1 | import { square } from \"./square.js\";\n    \u003e 2 |\n        | ^\n      3 | describe(\"square\", () =\u003e {\n      4 |   it(\"squares the number\", () =\u003e {\n      5 |     expect(square(42)).toBe(1764);\n\n      at square.test.ts:2:23\n\nTest Suites: 2 failed, 2 total\nTests:       0 total\nSnapshots:   0 total\nTime:        4.113 s\nRan all test suites.\n```\n\n\u003c/details\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqnighy%2Fts-type-module-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fqnighy%2Fts-type-module-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqnighy%2Fts-type-module-example/lists"}