Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update TypeScript recipe for ESM support #2593

Closed
novemberborn opened this issue Oct 3, 2020 · 27 comments · Fixed by #3192
Closed

Update TypeScript recipe for ESM support #2593

novemberborn opened this issue Oct 3, 2020 · 27 comments · Fixed by #3192

Comments

@novemberborn
Copy link
Member

@FallingSnow shared how to (experimentally) configure AVA and Node.js so that AVA can load TypeScript-based ESM files:

"ava": {
    "extensions": {
      "ts": "module"
    },
    "nonSemVerExperiments": {
      "configurableModuleFormat": true
    },
    "nodeArguments": [
      "--loader=ts-node/esm",
      "--experimental-specifier-resolution=node"
    ],
    "files": [
      "test/**/*.spec.ts"
    ]
}

It'd be great to add this to our TypeScript recipe.

@thasmo
Copy link

thasmo commented Feb 5, 2021

Update Somehow I missed to also declare --experimental-specifier-resolution=node which was the issue after all. Thanks!


Trying to run TypeScript tests with ESM support, but failed to do so with the configuration above, adapted to my setup.

export default {
    nonSemVerExperiments: {
        nextGenConfig: true,
        configurableModuleFormat: true,
    },
    nodeArguments: [
        '--loader=ts-node/esm',
        '--experimental-specifier-resolution=node',
    ],
    extensions: {
        ts: 'module',
    },
    require: [
        'ts-node/register/transpile-only',
    ],
    files: [
        'packages/*/test/**/*.ts',
    ],
}

I'm running it with Node 14 and TypeScript 4, which results in this errors:

➜ npm test

> test
> ava

⚠ Experiments are enabled. These are unsupported and may change or be removed at any time.

(node:13990) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:13996) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:13997) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:14010) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:14004) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:14025) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:14018) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:14031) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:14038) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)

  ✖ No tests found in packages/pack-cache/test/pack.ts
  ✖ No tests found in packages/pack-environment/test/pack.ts
  ✖ No tests found in packages/pack-watch/test/pack.ts
  ✖ No tests found in packages/pack-less/test/pack.ts
  ✖ No tests found in packages/core/test/packs/optimization.ts
  ✖ No tests found in packages/core/test/packmule.ts
  ✖ No tests found in packages/pack-log/test/pack.ts
  ✖ No tests found in packages/core/test/packs/base.ts
  ✖ No tests found in packages/core/test/packs/minification.ts

  ─

Uncaught exception in packages/pack-cache/test/pack.ts

Error: ERR_UNSUPPORTED_DIR_IMPORT /mnt/c/Users/thasmo/Projects/packmule.packmule/packages/pack-cache/src/ /mnt/c/Users/thasmo/Projects/packmule.packmule/packages/pack-cache/test/pack.ts

  › finalizeResolution (node_modules/ts-node/dist-raw/node-esm-resolve-implementation.js:370:17)
  › moduleResolve (node_modules/ts-node/dist-raw/node-esm-resolve-implementation.js:809:10)
  › Object.defaultResolve (node_modules/ts-node/dist-raw/node-esm-resolve-implementation.js:920:11)
  › node_modules/ts-node/src/esm.ts:55:38
  › Generator.next (<anonymous>)
  › node_modules/ts-node/dist/esm.js:8:71
  › __awaiter (node_modules/ts-node/dist/esm.js:4:12)
  › resolve (node_modules/ts-node/dist/esm.js:31:16)

@fregante

This comment has been minimized.

@fregante

This comment was marked as outdated.

@mikob
Copy link

mikob commented Jun 9, 2021

This doesn't work with default exports for me.

e.g.

import Visitor from "@swc/core/Visitor.js";
console.log('Visitor', Visitor);

prints:
Visitor { default: [class Visitor] }

@swc/core/Visitor looks like this:

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
class Visitor { ... }
exports.default = Visitor;

@novemberborn
Copy link
Member Author

@mikob that's a typical interoperability pattern to make CJS behave like ESM. However now that you're using actual ESM, it does not behave the way you expect. See https://nodejs.org/api/esm.html#esm_import_statements:

When importing CommonJS modules, the module.exports object is provided as the default export. Named exports may be available, provided by static analysis as a convenience for better ecosystem compatibility.

You might be able to import { default as Visitor } from … but default is a special member so that probably won't work.

@mikob
Copy link

mikob commented Jun 12, 2021

You might be able to import { default as Visitor } from … but default is a special member so that probably won't work.

Tried that as well without success. In the end I reverted to using "esm" (https://github.com/standard-things/esm) in the "requires" property for ava.

@novemberborn
Copy link
Member Author

That might paper over the cracks for now, yes. AVA 4 removes the special treatment for that package though.

@fregante
Copy link

Node 16.10 or later will break the ESM loader, not sure if this affects this recipe.

@Akiyamka
Copy link

At this moment ts-node/esm can't handle some edge-cases .
More stable alternative is esbuild-node-loader.
Es-build work in transpile only mode, but work much faster.

"ava": {
  "extensions": {
    "ts": "module"
  },
  "nodeArguments": [
    "--loader=esbuild-node-loader",
    "--experimental-specifier-resolution=node"
  ],
  "nonSemVerExperiments": {
    "configurableModuleFormat": true
  }
}

@Cobertos
Copy link

Cobertos commented Dec 8, 2021

The above config is much better.

The default installation using ts-node/esm was very slow. I had to increase the timeout for my tests.

I was also getting very odd failures. I narrowed down a couple problem tests and found that ES6 features that were working previously (in js before migrating to TypeScript) were behaving differently. Awaiting an object that wasn't a Promise was giving unexpected values. for...of loops over a custom Iterable would always received undefined, but if I called .next() on them myself, they seemed to work just fine.

After switching to the above (esbuild-node-loader) all my tests passed and it was much faster

@rrichardson
Copy link
Contributor

I created a small PR to update the typescript recipe per the above (I independently found the solution.. searching the issues here would have saved me some time :) )

#2910

@mesqueeb
Copy link
Contributor

I'm getting:

✖ nonSemVerExperiments.configurableModuleFormat from undefined is not a supported experiment

@novemberborn
Copy link
Member Author

@mesqueeb see #2945.

@Grawl
Copy link

Grawl commented Jul 8, 2022

just started to work with AVA, and I want to replace Jest with it on my regular Webpack project (with Quasar Framework under the hood).

I have a lot of .ts files written with import/export and a few tests using some of them.

No one guide helped me with setup because I have Typescript files written in ESM. I tried a few configs from issues here and from guides of AVA and @ava/typescript and each of them led me to a problem like "AVA cannot run ESM" or "cannot run ESM outside of a module" or "unknown extension: typescript".

But I do not want to compile tests before running, and I cannot add "type": "module" to my package.json because Webpack and other tools goes crazy with it.

Only one working solution it using esbuild like @Akiyamka offers. Here's my working config:

module.exports = {
	'extensions': {
		'ts': 'module',
	},
	'nodeArguments': [
		'--loader=esbuild-node-loader',
		'--experimental-specifier-resolution=node',
	],
}

Also I have to yarn add -D esbuild-node-loader but it's fast and safe I think.

@fregante

This comment was marked as outdated.

@plantain-00
Copy link

esbuild-node-loader is deprecated: https://github.com/antfu/esbuild-node-loader
I use tsx(https://github.com/esbuild-kit/tsx) instead and it works fine for me:

{
  "extensions": {
    "ts": "module"
  },
  "nodeArguments": [
    "--loader=tsx",
  ]
}

@fregante
Copy link

fregante commented Feb 3, 2023

@novemberborn can you update the first post with @plantain-00’s and hide the outdated comments? Thankfully now the amount of config is reduced (tested on Node 18.13)

Or maybe this issue should be closed since https://github.com/avajs/ava/blob/main/docs/recipes/typescript.md seems to be up to date (although it uses ts-node)

@novemberborn
Copy link
Member Author

Or maybe this issue should be closed since main/docs/recipes/typescript.md seems to be up to date (although it uses ts-node)

That works for me, though you're suggesting it's not ideal?

@Sparticuz
Copy link

Sparticuz commented Feb 8, 2023

ts-node seems to be unmaintained, @plantain-00's solution should probably be what should be on https://github.com/avajs/ava/blob/main/docs/recipes/typescript.md

Maybe including a link to this page would be helpful to determine which loader the user would want to use https://github.com/privatenumber/ts-runtime-comparison

edit: I was looking at the original repo, not typestrongs

@MartynasZilinskas
Copy link
Contributor

MartynasZilinskas commented Mar 13, 2023

@Sparticuz for the performance reasons tsx looks like a good option. But has no support for emitDecoratorMetadata. I think ts-node has only drawback, that is performance.

I agree that we should have a link for other possible loaders, so you could choose for a specific case.

@ondreian
Copy link
Contributor

After several hours of head scratching and hair pulling trying to setup ava on a serverless project, the tsx example provided by @plantain-00 is the only loader that works correctly with Typescript setups that cannot use "type": "module".

This should probably be added to the documentation as the current boilerplate does not work at all and always seems to error with:

  Uncaught exception in test/api.spec.ts

  <project>/test/api.spec.ts:1
  import anyTest, {TestFn} from "ava"
  ^^^^^^

  SyntaxError: Cannot use import statement outside a module

@novemberborn
Copy link
Member Author

This should probably be added to the documentation as the current boilerplate does not work at all

A PR would be welcome!

Personally I use https://github.com/avajs/typescript in my projects and compile TypeScript separately, which removes the dependency on various loaders and complications with the module systems.

@Sparticuz
Copy link

@novemberborn Does running ava on your compiled files affect coverage? I guess when I was compiling to cjs there were extra code paths that made it difficult to cover, but I'm not sure I've tried since switching to esm.

@novemberborn
Copy link
Member Author

@Sparticuz https://github.com/bcoe/c8 does the trick.

@LangLangBart
Copy link

esbuild-node-loader is deprecated: https://github.com/antfu/esbuild-node-loader I use tsx(https://github.com/esbuild-kit/tsx) instead and it works fine for me:

{
  "extensions": {
    "ts": "module"
  },
  "nodeArguments": [
    "--loader=tsx",
  ]
}

The solution above worked for me up to nodejs/node version v19.9.0.


Starting with node v20.0.0 (18/Apr/23) there are a lot of errors.

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts"

The workaround was to tweak the package.json as follows and start ava with npm run ava:run.

{
  "scripts": {
    "ava:run": "NODE_OPTIONS='--loader=tsx --no-warnings' ava"
  },
  "ava": {
    "extensions": {
      "ts": "module"
    }
  }
}

@punkpeye
Copy link

punkpeye commented May 7, 2023

Note that in the above, setting nodeArguments: ['--loader=tsx'], does not work as a replacement for NODE_OPTIONS. You must start ava using NODE_OPTIONS just how @LangLangBart demonstrated.

MaybeJustJames added a commit to vibbits/phylio that referenced this issue May 8, 2023
tommy-mitchell added a commit to tommy-mitchell/listr-cli that referenced this issue Jul 17, 2023
ivan added a commit to ludios/cookied that referenced this issue Nov 22, 2023
See avajs/ava#2593 (comment)

This fixes:

```
./node_modules/.bin/ava

(node:605416) ExperimentalWarning: Custom ESM Loaders is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
  Uncaught exception in tests/session_test.ts

  TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for code/websites/cards/tests/session_test.ts

  ✘ tests/session_test.ts exited with a non-zero exit code: 1
  ─

  1 uncaught exception
```
Angelelz added a commit to Angelelz/drizzle-orm that referenced this issue Dec 11, 2023
@firxworx
Copy link

I stumbled on this while searching about an issue with node + esm loading nothing to do with ava...

Above there is a good suggestion for workaround to set the environment: NODE_OPTIONS='--loader=tsx --no-warnings' (make sure you have tsx installed as a dev dependency!) -- #2593 (comment)

I wanted to add for the sake of anyone else who ends up here that --loader only works for earlier versions of Node 20 and for later versions like the current LTS you would need to use --import in its place: NODE_OPTIONS='--import=tsx --no-warnings'

@avajs avajs locked as resolved and limited conversation to collaborators Mar 31, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.