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

"TypeError: create is not a function" when using with jest configured for ESM #559

Closed
duarten opened this issue Sep 12, 2021 · 32 comments · Fixed by #1460
Closed

"TypeError: create is not a function" when using with jest configured for ESM #559

duarten opened this issue Sep 12, 2021 · 32 comments · Fixed by #1460
Assignees

Comments

@duarten
Copy link

duarten commented Sep 12, 2021

Consider the following setup:

package.json:

{
  "name": "repro",
  "version": "1.0.0",
  "type": "module",
  "main": "index.js",
  "dependencies": {
    "jest": "27.1.1",
    "react": "17.0.2",
    "zustand": "3.5.10"
  }
}

index.js:

import create from 'zustand'

export const useStore = create(set => ({
    foo: 0,
}))

repro.test.js:

import { useStore } from "."

test("repro", () => {
    console.log(useStore.getState())
});

Then, when running node --experimental-vm-modules node_modules/.bin/jest I get:

    TypeError: create is not a function

      3 | console.log(create)
      4 |
    > 5 | export const useStore = create(set => ({
        |                         ^
      6 |     foo: 0,
      7 | }))
      8 |

      at index.js:5:25
      at TestScheduler.scheduleTests (node_modules/@jest/core/build/TestScheduler.js:333:13)
      at runJest (node_modules/@jest/core/build/runJest.js:387:19)
      at _run10000 (node_modules/@jest/core/build/cli/index.js:408:7)

  console.log
    { default: [Function: create] }

It seems that the import is not picking the esm bundle?

@dai-shi
Copy link
Member

dai-shi commented Sep 13, 2021

Yeah, it seems picking the cjs bundle. Another challenge for us, @barelyhuman .

@barelyhuman
Copy link
Collaborator

Right when I thought it's done 😂 , I'll check it out

@barelyhuman
Copy link
Collaborator

@duarten could you instead try with esm instead?

so

npm i esm -D 

and use that for the execution?

node -r esm <path to jest>

@duarten
Copy link
Author

duarten commented Sep 13, 2021

That way it seems jest doesn't understand the modules it loads:

Jest encountered an unexpected token

    <snip>

    Details:

     repro/repro.test.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import { useStore } from ".";
                                                                                      ^^^^^^

    SyntaxError: Cannot use import statement outside a module

      at new Script (node_modules/esm/esm.js:1:36465)

@barelyhuman
Copy link
Collaborator

Cool, that’s enough info for me to know what’s going on

@barelyhuman barelyhuman self-assigned this Sep 14, 2021
@barelyhuman
Copy link
Collaborator

@duarten
change your package.json to

{
  "name": "repro",
  "version": "1.0.0",
  "type": "module",
  "main": "index.js",
  "scripts": {
    "test": "jest"
  },
  "jest": {
    "transform": {
      "\\.m?jsx?$": "jest-esm-transformer"
    }
  },
  "dependencies": {
    "jest": "27.1.1",
    "react": "17.0.2",
    "zustand": "3.5.10"
  },
  "devDependencies": {
    "jest-esm-transformer": "^1.0.0"
  }
}

the problem is with jest and not zustand.

  1. Asked you to install esm to check if it was node not enabling modules but it was enabled and you don't need to do the whole flag thing and jest would still give you the same import cannot be used error.
  2. Jest's own documentation doesn't specify anything more regarding this implmentation so it's not really something we can dig deeper on without going through their import logic which I'm going through right now and will raise a PR there if it's a quick fix.

the above solution is the simplest one without breaking a lot into your original setup, other options include using buble and buble-jest

Hope that helps

@duarten
Copy link
Author

duarten commented Sep 14, 2021

Thanks, @barelyhuman! Closing as it's not a zustand issue.

@duarten duarten closed this as completed Sep 14, 2021
@mansoor-omrani
Copy link

mansoor-omrani commented Dec 4, 2022

Sorry guys to open this issue again.

I do think it is a zustand issue. It does not have anything to do with jest.

In fact, it returns back to the way zustand is transpiled.

I used rollup to compile my esm module to cjs.

I had used create from zustand/vanilla.

import create from 'zustand/vanilla'

const store = create( ... ) // store details ignored

My compiled module seemed ok.

const create = require('zustand/vanilla');

const store = create( ... )

After tried to run a simple nodejs code to test my store in javascript, I encountered the create is not a function error.

Putting a console.log(create) logged the following result:

{ default: [Function createStore] }

What the module really exports is an object with a default property and it is not hte createStore function.

When I looked down vanilla.js in my node_modules/zustand folder I noticed that createStore function is exporting this way:

....
exports["default"] = createStore;

As far as I know, the correct way to export a default value in a cjs module is as below:

module.exports = createStore;

I think the problem is with the way vanilla.ts is compiled.

Currently, to resolve my issue I did a type checking on create before calling it:

import create from 'zustand/vanilla';

if (typeof create == 'function') {
    store = create( .... );
} else {
    store = create.default( ... );
}

Sorry for my long post.

I hope my feedback helps to correct zustand compilation issue.

@Pagebakers
Copy link

Pagebakers commented Dec 4, 2022

Running into the same issue with Next.js. I need to access create.default instead of create when cjs is used.

Somehow it stopped working correctly after updating ts-up in my lib.

This seems odd:

export { createStore as default };

@barelyhuman
Copy link
Collaborator

barelyhuman commented Dec 4, 2022

Let me check if the export target was changed in the previous releases, can one of you @Pagebakers @mansoor-omrani let me know the zustand version you are working with?

The transpilation should take care of adding an additional node module resolver compatible export with the esm default export standard as well.

example:

module.exports = create 
exports["default"] = create

both should be a part of the output code, which I'm sure was working before
either way,

a little information about the version would help

@dai-shi
Copy link
Member

dai-shi commented Dec 4, 2022

While this isn't ideal, I think it's intended.
It's known as babel transpiled ESM, not true CJS.
https://unpkg.com/browse/zustand@4.1.4/index.js

Object.defineProperty(exports, '__esModule', { value: true });

So, in Node CJS, it's correct to read it as cosnt { default: create } = require('zustand').

If we were to support const create = require('zustand') in Node,
we'd need to something like this: https://github.com/dai-shi/proxy-memoize/blob/05c5b873c65d079d953ef9f08833702e29fd2978/package.json#L21

But, I start to wonder if we should export as named, in addition to default, instead of overcoming CJS/ESM compatibility problem.

@dai-shi
Copy link
Member

dai-shi commented Dec 4, 2022

example:

module.exports = create 
exports["default"] = create

That's actually nice, if we can configure rollup to do it.

@barelyhuman
Copy link
Collaborator

That's actually nice, if we can configure rollup to do it

A few of my older packages has this with rollup, let me look it up

@Pagebakers
Copy link

https://nodejs.org/api/packages.html#writing-dual-packages-while-avoiding-or-minimizing-hazards

The recommendation is to use named exports only for dual bundle packages.

4. The package provides named exports, e.g. import { name } from 'pkg' rather than import pkg from 'pkg'; pkg.name.

@barelyhuman
Copy link
Collaborator

The recommendation came long after we already had the exposed API solidified 😅

@dai-shi
Copy link
Member

dai-shi commented Dec 4, 2022

Precisely.

@Pagebakers
Copy link

Pagebakers commented Dec 4, 2022

Yeah fair enough 😆 Just stating it, since I suspect it will cause issues with projects other than jest/nextjs as well.

@Pagebakers
Copy link

So this is why my build broke after upgrading ts-up.

Previously my cjs bundle was transpiled like this.

var import_react = __toESM(require("react"));
var import_zustand = __toESM(require("zustand"));
var import_vanilla = __toESM(require("zustand/vanilla"));
var import_context = __toESM(require("zustand/context"));

Now just plain cjs

var react = require("react");
var zustand = require("zustand");
var vanilla = require("zustand/vanilla");
var context = require("zustand/context");

@barelyhuman
Copy link
Collaborator

barelyhuman commented Dec 4, 2022

I appreciate the tooling trying to handle the cases for us but there's way too many openings that it's bound to fail at some or the other corner.

We'll obviously try to patch it up but just a fact when working with ESM and CJS.

At this point the simplest way to write libraries would be to just write in esm and let the user's bundler handle how the code is compiled.

This solution has it own set of issues but is the one with the least friction in terms of overall usability.
That's a talk for some other day.

As for now,
@dai-shi the setup/solution I found for this was the following as the export addition.

module.exports = Object.assign({}, module.exports, exports.default)

the change in rollup for cjs mode exports would be like so

// `config` being the rollup config object 
config.output.outro =
      'module.exports = Object.assign({}, module.exports, exports.default)'

The problem is that, this would add it to every file we have and that might not be ideal, I could raise a dummy PR to test it out

@dai-shi
Copy link
Member

dai-shi commented Dec 4, 2022

I could raise a dummy PR to test it out

Yes, please try it.

@mansoor-omrani
Copy link

Let me check if the export target was changed in the previous releases, can one of you @Pagebakers @mansoor-omrani let me know the zustand version you are working with?

The transpilation should take care of adding an additional node module resolver compatible export with the esm default export standard as well.

example:

module.exports = create 
exports["default"] = create

both should be a part of the output code, which I'm sure was working before either way,

a little information about the version would help

Hi @barelyhuman, I'm using zustand 4.1.1

@barelyhuman
Copy link
Collaborator

@mansoor-omrani Thanks,
Could you try the instructions on this comment?
#1460 (comment)

@mansoor-omrani
Copy link

@barelyhuman

That's working. Thank you.

@mkhib
Copy link

mkhib commented Jan 23, 2023

I still get this error TypeError: (0 , _zustand.create) is not a function
I tried adding "transform": { "\\.m?jsx?$": "jest-esm-transformer" }, to my jest configuration
I am using zustand version ^4.3.2 with expo version ~47.0.12 and jest version 26.6.3
Also I've import create like this: import { create } from "zustand";

@barelyhuman
Copy link
Collaborator

@mkhib
Could you help me with minimal reproduction for this?

Before you do that, can you try import * as zustand from "zustand" and then const {create} = zustand and see if that works, it's not a fix, I just want to know if that's working for you.

If neither works, a reproducible example would help :)

@mkhib
Copy link

mkhib commented Jan 23, 2023

@mkhib Could you help me with minimal reproduction for this?

Before you do that, can you try import * as zustand from "zustand" and then const {create} = zustand and see if that works, it's not a fix, I just want to know if that's working for you.

If neither works, a reproducible example would help :)

Unfortunately, it didn't work.
I made a minimal repo to reproduce the issue. Here is the link
I created a new expo app installed zustand, async storage and immer, created a simple component and wrote a test.

@barelyhuman
Copy link
Collaborator

Oh okay, I see the issue, it's picking the CJS even with TS, if it's blocking your work you can replace the import with zustand/esm and it should work.

In the mock though, where you are using jest.requireActual use actualCreate.create since that resolves the package correctly.

I'll see if can get jest to resolve the cjs package properly from TS, but that's a workaround for now
@mkhib

@barelyhuman
Copy link
Collaborator

@mkhib ignore the above, I failed to see that there was a __mocks__ folder.

Your mocks version of zustand is to export {create} and not export default create

That should solve your issue

image

@mkhib
Copy link

mkhib commented Jan 24, 2023

@barelyhuman Thanks a lot! It worked like a charm. Thanks for your time and attention.

adimit added a commit to adimit/zustand that referenced this issue Feb 16, 2023
Default exports and imports are deprecated. The default import of `zustand` generates a deprecation warning on the console. The default export that was suggested previously would actually fail to compile. See the comments at the very bottom of pmndrs#559 for further discussion.

This commit fixes the example in the documentation by introducing named exports & imports instead of default exports & imports.
adimit added a commit to adimit/zustand that referenced this issue Feb 16, 2023
Default exports and imports are deprecated. The default import of `zustand` generates a deprecation warning on the console. The default export that was suggested previously would actually fail to compile. See the comments at the very bottom of pmndrs#559 for further discussion.

This commit fixes the example in the documentation by introducing named exports & imports instead of default exports & imports.
adimit added a commit to adimit/zustand that referenced this issue Feb 16, 2023
Default exports and imports are deprecated. The default import of `zustand` generates a deprecation warning on the console. The default export that was suggested previously would actually fail to compile. See the comments at the very bottom of pmndrs#559 for further discussion.

This commit fixes the example in the documentation by introducing named exports & imports instead of default exports & imports.
adimit added a commit to adimit/zustand that referenced this issue Feb 16, 2023
Default exports and imports are deprecated. The default import of `zustand` generates a deprecation warning on the console. The default export that was suggested previously would actually fail to compile. See the comments at the very bottom of pmndrs#559 for further discussion.

This commit fixes the example in the documentation by introducing named exports & imports instead of default exports & imports.
dai-shi pushed a commit that referenced this issue Feb 27, 2023
* docs: Fix Jest example's usage of default imports

Default exports and imports are deprecated. The default import of `zustand` generates a deprecation warning on the console. The default export that was suggested previously would actually fail to compile. See the comments at the very bottom of #559 for further discussion.

This commit fixes the example in the documentation by introducing named exports & imports instead of default exports & imports.

* docs: Suggest using plain import for Jest

`jest.requireActual` is not necessary when we're not mocking any other
part of `zustand`.

* docs: Fix TypeScript example

The `create` function was curried where it should not have been.
The example is now written in plain TypeScript, as the code that it
contains does not require JSX.

* Prettier suggestions

* Use `jest.requireActual` to avoid circular dependencies
@andosteinmetz
Copy link

I'm still running into this problem, following the instructions provided at https://github.com/pmndrs/zustand/blob/main/docs/guides/testing.mdx#typescript-usage.

Is it possible that it's because I'm using the slices pattern with TypeScript?

I'd also note that the docs at https://docs.pmnd.rs/zustand/guides/testing#typescript-usage don't reflect the changes described here.

@barelyhuman
Copy link
Collaborator

The above solution was very specific to the repro mentioned by the above developer. If there's a specific error that you are getting a reproduction would help us help you.

@raphaelpinel
Copy link

raphaelpinel commented May 23, 2023

I am not using ESM but I had a similar error while running tests with jest: Test suite failed to run TypeError: (0 , _zustand.create) is not a function
and what worked for me was to remove the zustand.ts file in __mocks__

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

Successfully merging a pull request may close this issue.

8 participants