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

Unable to get Typescript working with many ES modules #16

Open
therealpaulgg opened this issue Aug 25, 2020 · 31 comments
Open

Unable to get Typescript working with many ES modules #16

therealpaulgg opened this issue Aug 25, 2020 · 31 comments
Labels
invalid This doesn't seem right question Further information is requested

Comments

@therealpaulgg
Copy link

therealpaulgg commented Aug 25, 2020

I have a typescript Svelte component that makes use of moment.JS, importing like so:

import moment from "moment

It works with my rollup compilation and development server flawlessly. However, when using svelte-jester and using preprocessing, it says that 'moment is not a function'.

Here is my jest configuration:

module.exports = {
    testEnvironment: "jsdom",
    transform: {
        "^.+\\.svelte$": [
            "svelte-jester",
            {
                preprocess: true
            }
        ],
        "^.+\\.js$": "babel-jest",
        "^.+\\.ts$": "ts-jest"
    },
    moduleFileExtensions: ["js", "ts", "svelte"],
    setupFilesAfterEnv: ["./jestSetup.ts"],
    transformIgnorePatterns: ["node_modules/(?!(svelte-typewriter|svelte-flatpickr)/)"],
    moduleNameMapper: {
        "\\.(css|less|scss)$": "identity-obj-proxy"
    },
    testPathIgnorePatterns: ["/lib/", "/node_modules/"]
}

jestSetup.ts:

import "@testing-library/jest-dom";

tsconfig.json (have tried many variants):

{
    "extends": "@tsconfig/svelte/tsconfig.json",
    "include": ["src/**/*", "src/types/*"],
    "exclude": ["node_modules/*", "__sapper__/*", "public/*"],
    "compilerOptions": {
        "types": ["jest", "node"],
        "allowSyntheticDefaultImports": true,
        "target": "ES2019"
    }
}

test output:


    TypeError: moment is not a function

      35 |                 dv[0] = flatpickr.formatDate(dv[0], "Y-m-d")
      36 |             }
    > 37 |             if (
         |             ^
      38 |                 typeof dv[1] != "string" &&
      39 |                 String(new Date(dv[1])) !== "Invalid Date"
      40 |             ) {

      at updateDate (src/components/Options.svelte:37:13)
      at Object.$$self.$$.update (src/components/Options.svelte:26:4)
      at init (node_modules/svelte/internal/index.js:1450:8)
      at new Options (src/components/Options.svelte:1815:3)
      at Array.create_default_slot_5 (src/App.svelte:25:12)
      at create_slot (node_modules/svelte/internal/index.js:65:29)
      at create_fragment (src/components/Sidebar.svelte:19:23)
      at init (node_modules/svelte/internal/index.js:1454:37)
      at new Sidebar (src/components/Sidebar.svelte:122:3)
      at create_fragment (src/App.svelte:377:12)
      at init (node_modules/svelte/internal/index.js:1454:37)
      at new App (src/App.svelte:510:3)
      at Object.render (node_modules/@testing-library/svelte/dist/pure.js:71:21)
      at Object.<anonymous> (tests/integration/Home.spec.ts:5:40)

This isn't the first time I have had trouble with transpiling. There have been issues with getting other Svelte components to work, problems with flatpickr, etc.

Please let me know if there is something I am doing wrong.

@therealpaulgg
Copy link
Author

I was able to get it to work by doing import * as moment from "moment" in Jest, but that makes my application not work. So clearly something is going on with the transpilation.

@mihar-22
Copy link
Collaborator

Hey @therealpaulgg these types of issues come up a lot but they're always related to Jest, generally with how Jest resolves modules and transforms them. Jest only uses svelte-jester when it finds a file it doesn't understand such as .svelte and it then usessvelte-jester to transform that code to JS, but Jest is still responsible for resolving modules. By default Jest only resolves CommonJS by looking at the main key in package.json, and it's why import * as moment from "moment" works, and it's also why it fails resolving modules fails a lot.

Anyway ... that was to help you potentially debug in the future but in your case you're using TypeScript to resolve modules so ... the first thing I'd say is you can remove babel-jest and let ts-jest resolve modules:

transform: {
  // ...
  "^.+\\.(js|ts)$": "ts-jest"
}

The second is you should configure TypeScript to use the node module resolution strategy, there's a great section in the handbook about it.

"compilerOptions": {
  // ...
 "moduleResolution": "node"
}

Try these changes, if it doesn't still work let me know if you're receiving any new errors or the same.

@therealpaulgg
Copy link
Author

therealpaulgg commented Aug 26, 2020

Still having the same issue. I've changed my config (removed the extends svelte tsconfig portion, wasnt sure if that was messing with compilerOptions):

tsconfig.json:

{
    "include": ["src/**/*", "src/types/*"],
    "exclude": ["node_modules/*", "__sapper__/*", "public/*"],
    "compilerOptions": {
        "types": ["jest", "svelte"],
        "allowSyntheticDefaultImports": true,
        "target": "ES2019",
        "moduleResolution": "node",
        "allowJs": true,
        "importsNotUsedAsValues": "error",
        "isolatedModules": true,
        "sourceMap": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true
    }
}

jest.config.js:

module.exports = {
    preset: "ts-jest",
    testEnvironment: "jsdom",
    transform: {
        "^.+\\.svelte$": [
            "svelte-jester",
            {
                preprocess: true
            }
        ],
        "^.+\\.(js|ts)$": "ts-jest"
    },
    moduleFileExtensions: ["js", "ts", "svelte"],
    setupFilesAfterEnv: ["./jestSetup.ts"],
    transformIgnorePatterns: [
        "node_modules/(?!(svelte-typewriter|svelte-flatpickr)/)"
    ],
    moduleNameMapper: {
        "\\.(css|less|scss)$": "identity-obj-proxy"
    },
    globals: {
        "ts-jest": {
            isolatedModules: true
        }
    },
    testPathIgnorePatterns: ["/lib/", "/node_modules/"]
}

Also worth noting I guess, since it's weird, I am using flatpickr as well for something using the format import flatpickr from "flatpickr". Tried doing import * as moment from "moment", so that stage of jest clears. and then I get (when doing flatpickr.parseDate(...)):

TypeError: Cannot read property 'parseDate' of undefined

@mihar-22
Copy link
Collaborator

mihar-22 commented Aug 26, 2020

Oh there's few more steps to configure ts-jest, add allowJs: "true" to your tsconfig compilerOptions and then set the preset in jest.config.js to the following ts-jest/presets/js-with-ts. Let me know if it's still the same after that.

@mihar-22
Copy link
Collaborator

If you also want the original Svelte files when using Jest then it's a good idea to use jest-svelte-resolver.

@therealpaulgg
Copy link
Author

Same deal 😕

also not totally how to use jest-svelte-resolver but it gave me an error basically immediately when parsing file:

export { default as Router } from "./Router.svelte";
    ^^^^^^

    SyntaxError: Unexpected token 'export

@mihar-22
Copy link
Collaborator

That really sucks @therealpaulgg, so we don't go back and forth too much, when I have some time today I'll set up a project just like yours, and I'll see if I can get it working.

@mihar-22
Copy link
Collaborator

mihar-22 commented Aug 28, 2020

Hey @therealpaulgg I tried to recreate the issue but I can't. Everything works on my end. Can you give me an example of some file where you're trying the things that are failing? Some demo Svelte component with whatever you're importing and using would be awesome.

@therealpaulgg
Copy link
Author

Yes in fact I can send you the entire project setup I'm using...

My test in test/integration is rather 'dumb' so to speak and doesn't really do a whole lot. It loads the entire App.svelte obviously to make sure everything works. I just added the DatePicker.svelte component which imports moment and flatpickr and things like that. There shouldn't be any import errors, but in its current state I doubt the test will pass, just kind of threw the code in there. As long as I can get to a point where the error isn't related to bad imports, that's great.

https://github.com/therealpaulgg/svelte-rollup-template

@mihar-22
Copy link
Collaborator

Okay so first thing we now know is that moment has issues with Jest resolution, not sure why ... There is this hack to make it work. I'd like to recommend this library as an alternative: https://github.com/iamkun/dayjs. Same API but only 2KB.

Now need to figure out why Typewriter.svelte is not getting transformed.

@mihar-22
Copy link
Collaborator

So Jest always resolves based on the main key in the package.json and that's why svelte-flatpickr is resolved as a CJS module and it only works if we do import * as Flatpickr from 'svelte-flatpickr. You can always open up node_modules/{package}/package.json and have a look at the exports to see what's being resolved. If you look inside node_modules/svelte-flatpickr/package.json you'll see there is both a main and svelte export. Jest is defaulting to main. If we want the svelte file then we have to use a resolver like the one I linked earlier. Simply install it and add this line "resolver": "jest-svelte-resolver" in jest.config.js.

@mihar-22
Copy link
Collaborator

Finally, Typewriter was failing because you added node_modules to testPathIgnorePatterns in jest.config.js. This means Jest won't transform the file and ignores it, then at runtime it will fail because it doesn't understand .svelte files.

@mihar-22
Copy link
Collaborator

mihar-22 commented Aug 28, 2020

Overall here's what you can do:

  1. Run npm install @tsconfig/svelte and change your tsconfig to:
{
  "extends": "@tsconfig/svelte/tsconfig.json",
  "include": ["src/**"],
  "exclude": ["node_modules/**"],
  "compilerOptions": {
      "types": ["jest"],
      "allowJs": true,
      "allowSyntheticDefaultImports": true,
  }
}
  1. Change your jest.config.js to:
module.exports = {
    preset: "ts-jest/presets/js-with-ts",
    globals: {
      'ts-jest': {
         // Let's avoid type checking during tests (performance boost).
        diagnostics: false
      }
    },
    transform: {
        "^.+\\.svelte": [
            "svelte-jester",
            {
                preprocess: true
            }
        ],
        "^.+\\.(js|ts)": "ts-jest"
    },
    moduleFileExtensions: ["svelte", "js", "ts"],
    setupFilesAfterEnv: ["./jestSetup.ts"],
    transformIgnorePatterns: [
        "node_modules/(?!svelte-typewriter|svelte-flatpickr)/",
    ],
    moduleNameMapper: {
        "\\.(css|less|scss)$": "identity-obj-proxy"
    }
}
  1. Swap moment for dayjs.

  2. Do import * as Flatpickr from 'svelte-flatpickr or setup jest-resolver and do import Flatpickr from 'svelte-flatpickr.

@therealpaulgg
Copy link
Author

Ok, thanks! tried all that. I am now getting dayjs is not a function. dunno if you were able to test that or not, but its somehow the exact same problem as moment.

@mihar-22
Copy link
Collaborator

Ah it might be, I didn't check it and I cleaned up the project. Does import * as dayjs from 'dayjs' do the trick or same issue as moment where you lose the ability to call a function?

@therealpaulgg
Copy link
Author

same problem as moment, my whole project complains when i do any import like that

@mihar-22
Copy link
Collaborator

That hack I linked earlier might do the trick then if moment and dayjs are tricky:

import * as dayjs from "dayjs";
const dayjs = require("dayjs").default || require("dayjs");

@ngavinsir
Copy link

Hey guys, is there any update on this? I'm also experiencing the same issue when I try to use the clsx module. The error is kinda similar: clsx is not a function. Have you @therealpaulgg solve your dayjs is not a function error?

@therealpaulgg
Copy link
Author

@ngavinsir unfortunately I don't recall this ever being solved for me. I have not worked on my project that required svelte in a while. Not sure if it is indicative of a problem with svelte-jester or something else.

@ngavinsir
Copy link

Ah, okay, thanks for your response 👍

@miaoz2001
Copy link

Hi @mihar-22, I have the same issue as @therealpaulgg (except I use svelte-calendar, not svelte-flatpickr).
following the instructions above, I got the svelte-calendar issue fixed by adding the resolver.

but, the solution about dayjs does not work:
the trick const dayjs = require("dayjs").default || require("dayjs"); does fix the issue with jest, but the same code will cause a problem in browser with an error require is not defined, because require() does not exist in the browser/client-side JavaScript.

Do you have any workaround for dayjs? thanks

@wgrabias
Copy link

A nasty workaround for clsx:

  1. In your package.json:
  "jest": {
    ...
    "moduleNameMapper": {
      "^clsx$": [
        "<rootDir>/src/clsx.jest.js"
      ]
    }
  }
  1. Create a new file /src/clsx.jest.js
const clsx = require('clsx/dist/clsx.js')
module.exports.default = clsx

@sebastianrothe
Copy link
Collaborator

Hey guys, is there any update on this? I'm also experiencing the same issue when I try to use the clsx module. The error is kinda similar: clsx is not a function. Have you @therealpaulgg solve your dayjs is not a function error?

Can you please retry with the latest version and jest 27+?

@sebastianrothe sebastianrothe added invalid This doesn't seem right question Further information is requested labels Sep 15, 2021
@bibizio
Copy link

bibizio commented Nov 29, 2021

Hi! I had the same problem, the hack I came up with is:

In my test setup script ( setupFilesAfterEnv: ["./jestSetup.ts"] in jest.config.js)

jest.mock('moment', () => ({ _esModule: true, default: jest.requireActual('moment'), }));

Hope it helps

@simeonTsonev
Copy link

A nasty workaround for clsx:

  1. In your package.json:
  "jest": {
    ...
    "moduleNameMapper": {
      "^clsx$": [
        "<rootDir>/src/clsx.jest.js"
      ]
    }
  }
  1. Create a new file /src/clsx.jest.js
const clsx = require('clsx/dist/clsx.js')
module.exports.default = clsx

That's the only solution which works for me. I use it for my troubles with Flatpickr:
In your package.json:

  moduleNameMapper: {
    "^flatpickr$": ["<rootDir>/src/flatpickr.jest.js"]
    }

and then you make a new file ''/src/flatpickr.jest.js:

const flatpickr = require('flatpickr/dist/flatpickr.js')
module.exports.default = flatpickr

Thank you @wgrabias

@illright
Copy link

It seems like TypeScript isn't the real culprit here. @sebastianrothe I've created a reproduction repository with the latest versions of Jest and svelte-jester to demonstrate that the issue persists.

What's confusing about this issue is that if one uses svelte-add-jest to add Jest to a SvelteKit application, such issues won't come up because this adder generates code that runs Jest in experimental ESM mode. It's all good, but then the user-event part of the Testing Library doesn't work, so it's a no-go zone for proper testing.

@sebastianrothe
Copy link
Collaborator

We have some tests with click-events, also. What is not working on your side?

@illright
Copy link

@sebastianrothe never mind, turns out it's an issue with @testing-library/user-event (testing-library/user-event#757 (comment)). I've created a reproduction of it in the esm branch of the same repository in case you'd like to take a look.

However, I would personally prefer not using the experimental ES module support. Is there any chance this could be fixed inside of svelte-jester to enable both modes?

@sebastianrothe
Copy link
Collaborator

We do ship both versions (CJS and ESM). You may have to manually override the transformer in your jest.config:

transformer: "svelte-jester/dist/transformer.cjs",

@illright
Copy link

You may have to manually override the transformer in your jest.config:

This configuration didn't resolve the issue in the reproduction repository:

export default {
  testEnvironment: "jsdom",
  transform: {
    "^.+\\.js$": "babel-jest",
    "^.+\\.svelte$": "svelte-jester/dist/transformer.cjs"
  },
  moduleFileExtensions: ["js", "svelte"]
};

Did I understand you correctly?

@sebastianrothe
Copy link
Collaborator

sebastianrothe commented Feb 18, 2022

Yes, thats correct. But, I saw that your project is ESM (https://github.com/illright/jest-clsx-repro/blob/main/package.json#L21).

Jest is the culprit, sorry. Jest needs the experimental flag to work in ESM mode(https://jestjs.io/docs/ecmascript-modules). Node understands ESM natively.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
invalid This doesn't seem right question Further information is requested
Projects
None yet
Development

No branches or pull requests

9 participants