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

Ky typescript, jest and es modules + temp solution #170

Closed
belgattitude opened this issue Aug 27, 2019 · 30 comments
Closed

Ky typescript, jest and es modules + temp solution #170

belgattitude opened this issue Aug 27, 2019 · 30 comments

Comments

@belgattitude
Copy link

belgattitude commented Aug 27, 2019

Update from Ky team: Please see Jest's ESM instructions


Spend few hours trying to make typescript, jest, lerna and ky to play well together. Here's something that might help in case of:

    export default createInstance();
    ^^^^^^
    SyntaxError: Unexpected token export
    > 4 | import ky from "ky";

With Jest you can simply add it to the transformIgnorePatterns, but the way lerna works your node_modules will probably end up in the top level node_modules. So here's something that seems to works in every cases:

    moduleNameMapper: {
        '^ky$': require.resolve('ky').replace('index.js', 'umd.js'),
    }

This way, jest choose the umd build instead of es one.

Warning, this assumes there will always be an umd.js build packaged with ky.

There might be better solutions, feel free to let me know yours

@sindresorhus
Copy link
Owner

I would try discussing this on the Jest support forums instead. Ky is just a normal ESM module. The problem lies in how complex Jest and Lerna are.

@plankter
Copy link

Same problem with Vue CLI 4 configured as Typescript/Jest/ESLint combo.

ky is the only module that is affected.

@sholladay
Copy link
Collaborator

sholladay commented Sep 12, 2019

The most likely reason that ky is the only affected module in your stack is simply because there are not yet many other ES6 modules in the npm registry.

I think what you should do is just use CommonJS-style imports with the UMD build everywhere that you depend on Ky, since it's having trouble with the ESM syntax. I don't see a good reason to re-write the path in your config like that. It would be better to use that path in your app and in your tests directly, in my opinion.

So just do this:

const ky = require('ky/umd');

... Instead of ...

import ky from 'ky';

... or any other combination you may be using.

@belgattitude
Copy link
Author

@sholladay,

don't see a good reason to re-write the path in your config like that. It would be better to use that path in your app and in your tests directly, in my opinion.

Agree... the reason: you'll loose the typings. Would love to have a better option though.

@sholladay
Copy link
Collaborator

We have umd.d.ts with TypeScript types for the UMD build as of v0.7.1. It should work. If not, it might be something we can fix in Ky. It's perfectly reasonable to expect TS support from the UMD build.

@belgattitude
Copy link
Author

@sholladay

We have umd.d.ts with TypeScript types

Well, so import ky from 'ky/umd' will actually work (require is not the way to go in ts). But this way defeats the purpose of using esm.

IMO, Jest problem => should be fixed through jest configuration.

The jest way of fixing this is through transformIgnorePattern or moduleNameMapper till jest supports esm.

The most likely reason that ky is the only affected module in your stack is simply because there are not yet many other ES6 modules in the npm registry.

Yes but some popular ones: lodash-es being a good example. tree-shaking really matters here. They went another way, maintaining two repos: lodash and lodash-es but the same logic applies. The common solutions are either to use transformIgnorePatterns or moduleNameMapper to enable transpilation or map lodash-es to lodash (I prefer the later). So I guess people are used to do it.

Regarding ky.

I would just add a note in the doc or keep this issue as closed, so people would not spend time looking through SO or jest issues.

The trick I posted

moduleNameMapper: {
        '^ky$': require.resolve('ky').replace('index.js', 'umd.js'),
}

might be and unsexy overkill but does not affect your bundle and works with lerna too (that's where I spent time).

But I would not document use of require, neither suggest to use import ky from 'ky/umd', it's not about ky, rather about lack of esm support in Jest.

@ericnkatz
Copy link

ericnkatz commented Jan 9, 2020

Any way we can solve this issue related:
Circular definition of import alias 'default'. ?

Screen Shot 2020-01-09 at 4 55 11 PM

@nfour
Copy link

nfour commented Mar 24, 2020

I found to get this working with typescript and ky I had to do this:

I want to write this code:

import ky from 'ky';

So I configured jest with:

  moduleNameMapper: {
    '^ky$': '<rootDir>/.jest/ky.js',
  }

As the umd bundle doesnt export a default, at least in Jest, (yet the TS type says it does), I created ./.jest/ky.js

exports.default = require('ky/umd');

Lot of effort but at least its inline with how the webpack bundle operates.

@riccardo-forina
Copy link

"moduleNameMapper": {
  "ky": "ky/umd"
}

this seems to work for me

@targumon
Copy link

@riccardo-forina Thanks for this simple solution!

however...

@sindresorhus v0.26.0 broke it (because #315 - drop UMD).
So for now I'll stay at 0.25, but would really appreciate guidance on how to re-solve this.

@targumon
Copy link

targumon commented Jan 10, 2021

Never mind, solved it with:

  transformIgnorePatterns: [
    '/node_modules/(?!(ky))'
  ]

(Explanation to whoever stumbles upon this thread like me: By default jest ignores node_modules. This configuration tells it: 'yeah, ignore anything in there, EXCEPT for ky'. If you have more dependencies like this, just replace (ky) with (ky|other-deps|needing-transpilation) source: jestjs.io)

Thanks for a great package!

@paulrostorp
Copy link

Using transformIgnorePatterns didn't work for me

@sholladay
Copy link
Collaborator

Don't forget to remove moduleNameMapper and any other workaround you were using previously.

For anyone who still can't get this to work, I suggest linking to a GitHub repo or CodeSandbox with a minimal reproducible example where we can all take a look and collaborate on the solution.

@robcaldecott
Copy link

robcaldecott commented Jan 15, 2021

This is not working for me either. I removed moduleNameMapper and added transformIgnorePatterns. I'm lucky enough to be using react-app-rewired so have full control over the jest config but it looks like the default CRA config is causing the issue. If I log what jest is configured to use by default it looks like this:

transformIgnorePatterns: [
    '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$',
    '^.+\\.module\\.(css|sass|scss)$'
  ],

And If I modify this to:

transformIgnorePatterns: [
    '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$',
    '^.+\\.module\\.(css|sass|scss)$',
    '/node_modules/(?!(ky))'
  ],

Then it won't work, no matter what order the lines appear. However, if I replace it with this:

transformIgnorePatterns: [
  "^.+\\.module\\.(css|sass|scss)$",
  '/node_modules/(?!(ky))'
]

Then it works. I'll have to try it but I don't think replacing the entire config like this is an option for most CRA users.

@sholladay
Copy link
Collaborator

I'm happy to take a deeper look if someone posts a link to a minimal reproducible example. :)

@robcaldecott
Copy link

It does work with a create-react-app project 👯 but just note that using transformIgnorePatterns will completely overwrite the CRA defaults which might cause issues if you're using CSS modules because this gets removed:

'^.+\\.module\\.(css|sass|scss)$'

Here's a link to a sample repo.

https://github.com/robcaldecott/ky-jest

Run npm test ky to see it working.

@bugzpodder
Copy link

bugzpodder commented Jan 26, 2021

ReferenceError: globalThis is not defined

>  9 | import ky from "ky";

when I use transformIgnorePatterns approach in CRA.  still trying to work out a solution

@sholladay
Copy link
Collaborator

@bugzpodder what environment is that error from?

@bugzpodder
Copy link

bugzpodder commented Jan 26, 2021

@sholladay I'm using latest CRA 4.0.1 + React 17 + yarn 2 (berry) + pnp + ky 0.36.

EDIT: hmm i just created a fresh repo using the versions above and everything seem to work fine. weird.
EDIT2: it seems that my original repo all of a sudden works locally, but still dies on CircleCI with the error I saw before 👎

@bugzpodder
Copy link

ok i figured it out, i am using node 10 and ky requires node >= 12, the globalThis error is from node 10.

@sholladay
Copy link
Collaborator

sholladay commented Jan 26, 2021

@bugzpodder I'm glad you got it working! Is your setup different than the repo that @robcaldecott posted in #170 (comment)? If so, would you be willing to put up an example repo we can link to for people who are having trouble?

@bugzpodder
Copy link

It should be the same. Use node 12 and the globalThis error should be gone.

@angeloashmore
Copy link

angeloashmore commented Feb 14, 2021

For anyone else coming across this when using Jest + TypeScript via ts-jest, this Jest config is working for me.

Note the inclusion of ts-jest/presets/js-with-ts rather than just ts-jest. This tells ts-jest to transform .js files in addition to .ts.

// jest.config.js

module.exports = {
  preset: 'ts-jest/presets/js-with-ts',
  transformIgnorePatterns: ['/node_modules/(?!(ky))'],
}

You will need to polyfill fetch in your tests if you don't already have it available globally:

// my-test-file.test.ts

import 'cross-fetch/polyfill'

test('my test', () => {
  // ...
})

@sholladay
Copy link
Collaborator

@angeloashmore we have ky-universal to take care of polyfilling for you. I'd strongly recommend using that instead of polyfilling yourself, because there's more than just fetch that needs to be polyfilled in Node.

@angeloashmore
Copy link

angeloashmore commented Feb 14, 2021

@sholladay Thanks, I did try ky-universal but using it is not straightforward, at least with the combination of Jest, TS, and the version of Node (v14.15.3) I am using.

ky-universal shouldn't be transpiled, as recommended by you in another thread (sorry, not exactly sure where I read that earlier today). By transpiling using ts-jest, a top-level await is introduced, which throws the following error:

    /[redacted]/node_modules/ky-universal/index.js:25
            globalThis.ReadableStream = await Promise.resolve().then(() => tslib_1.__importStar(require('web-streams-polyfill/ponyfill/es2018')));
                                        ^^^^^

    SyntaxError: await is only valid in async function

      3 | import * as TE from 'fp-ts/TaskEither'
      4 | import { pipe } from 'fp-ts/function'
    > 5 | import ky from 'ky-universal'
        | ^
      6 |
      7 | import {
      8 |   BuildTypePathsStoreFilenameEnv,

      at Runtime.createScriptFromCode (../../node_modules/jest-runtime/build/index.js:1350:14)

Not transpiling leaves in imports, which also cannot be run:

    Jest encountered an unexpected token

    This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.

    By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".

    Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/en/ecmascript-modules for how to enable it.
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/en/configuration.html

    Details:

    /[redacted]/node_modules/ky-universal/index.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import fetch, {Headers, Request, Response} from 'node-fetch';
                                                                                             ^^^^^^

    SyntaxError: Cannot use import statement outside a module

      3 | import * as TE from 'fp-ts/TaskEither'
      4 | import { pipe } from 'fp-ts/function'
    > 5 | import ky from 'ky-universal'
        | ^
      6 |
      7 | import {
      8 |   BuildTypePathsStoreFilenameEnv,

      at Runtime.createScriptFromCode (../../node_modules/jest-runtime/build/index.js:1350:14)

The only difference between the two runs is singling out ky from ky-universal in the transformIgnorePatterns option:

  module.exports = {
    preset: 'ts-jest/presets/js-with-ts',
-  transformIgnorePatterns: ['/node_modules/(?!(ky))'],
+  transformIgnorePatterns: ['/node_modules/(?!(ky/))'],
  }

I don't doubt there's some combination of config that allows this, but I kind of gave up. Happy to provide more details if you're able to guide a correct setup.

@sholladay
Copy link
Collaborator

@angeloashmore your transformIgnorePatterns regex looks wrong to me. If you're using ky-universal, then that regex probably needs to exclude both ky and ky-universal. Your regex has ky/, which I believe will correctly exclude the path for ky but not ky-universal. I expect that something like '/node_modules/(?!(ky|ky-unversal))' should work.

You seemed to get a bit closer with the second error where it said "Jest encountered an unexpected token". That error links to docs on using ESM: https://jestjs.io/docs/en/ecmascript-modules

Based on those docs, it sounds like they want your app to also be in ESM mode ("type": "module" in package.json) in order to properly use an ESM dependency like Ky, because they're relying on the Node.js ESM loader to make those import statements work. If you already have that in your package.json, then I'm not sure what's wrong, but hopefully that helps. I also noticed that those docs suggest a slightly different way of disabling transforms than what people in this thread have been doing. Might be worth giving that a shot, too.

@jcace
Copy link

jcace commented Apr 20, 2021

I managed to get it working by pasting the following into my package.json

  "jest": {
    "transformIgnorePatterns": [
      "/node_modules/(?!(ky))"
    ],
    "moduleNameMapper": {
      "\\.(css|less)$": "identity-obj-proxy"
    }
  },

Although I'm not sure if this will break other tests down the road.. definitely a temporary solution for me

axelhunn added a commit to folio-org/stripes-acq-components that referenced this issue May 13, 2021
* add ["@babel/plugin-proposal-private-methods", \{ "loose": true }] to Babel plugins (due to Babel's warning)
* adjust mocking Ky (see sindresorhus/ky#170 and sindresorhus/ky#340)
* add TS transformation
@alessandrojcm
Copy link

alessandrojcm commented Dec 30, 2021

Hi there, I've tried the solutions on this issue but I can't seem to get it working. I'm stuck on the top-level await issue as @angeloashmore commented. My jest.config.js:

const { pathsToModuleNameMapper } = require('ts-jest');
const { compilerOptions } = require('./tsconfig');

module.exports = {
    roots: ['<rootDir>/src'],
    transform: {
        '^.+\\.(ts|tsx|js|jsx)$': 'babel-jest',
        '^.+\\.svg$': '<rootDir>/svgTransform.js',
    },
    globals: {
        'ts-jest': {
            tsconfig: 'tsconfig.test.json',
            diagnostics: false,
            isolatedModules: true,
        },
    },
    testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
    testEnvironment: 'jest-environment-jsdom',
    setupFilesAfterEnv: ['<rootDir>/setupTests.ts'],
    collectCoverage: true,
    moduleFileExtensions: ['ts', 'tsx', 'js', 'json', 'jsx'],
    moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/src/' }),
    testPathIgnorePatterns: ['<rootDir>[/\\\\](node_modules|.next)[/\\\\]'],
    transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(ts|tsx)$', '/node_modules/(?!(@fullcalendar|ky|ky-universal)/)'],
};

As you can see, I already had a transformIgnorePatterns option set for the @fullcalendar package (also ESM), it should work by just adding ky and ky-universal; although it does not. I've tried adding type: module to my package.json but this just causes all other sorts of things to break; I've also got my babel.config.js file with @babel/preset-env. Any ideas?

EDIT: I've got the tests running with this, I just changed the moduleNameMapper pattern to ^(ky|ky-universal)$. But now every test that uses ky is failing, I need to dig deeper to see why tho.

@sholladay
Copy link
Collaborator

@alessandrojcm my understanding is that if you follow these instructions, then moduleNameMapper (and probably a lot of that other stuff) becomes unnecessary.

@EmaSuriano
Copy link

For the people using CRA, the option of transformIgnorePatterns is not available when extending through the jest config. Therefore you are force to pass it as inline argument to the test script. Change your test command to:
"test": "react-scripts test --transformIgnorePatterns \"node_modules/(?!ky)/\"",

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests