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

Not working for typescript #37

Closed
theKashey opened this issue Oct 9, 2018 · 88 comments · Fixed by #95
Closed

Not working for typescript #37

theKashey opened this issue Oct 9, 2018 · 88 comments · Fixed by #95

Comments

@theKashey
Copy link
Contributor

Actually the problem is not with typescript - the problem is with rollup.

https://unpkg.com/memoize-one@4.0.2/dist/memoize-one.cjs.js contains

module.exports = index;

which should be imported as import * as memoizeOne

while https://unpkg.com/memoize-one@4.0.2/dist/memoize-one.esm.js contains

export default index;

which should be imported as import memoizeOne

Result - the code for nodejs (~jest) require one behaviour, while the code for webpack(uses esm bundle) requires another.

It is not a problem for js/flow, as long babel is doing some around magic default imports, but typescript is quite strict about it.

The cjs bundle should match esm bundle.

(and fixing this would introduce a breaking change)

@alexreardon
Copy link
Owner

@TrySound thoughts?

@alexreardon
Copy link
Owner

As you mentioned @theKashey we are using rollup to generate the bundles. It sounds like there is an issue with the cjs bundle. Is there an option we can change for the rollup build...?

@TrySound
Copy link
Contributor

This is the old issue with typescript itself and default exports. My solution is getting rid from default exports in the next major release. They are quite useless.

I'm not alone.
jaredpalmer/formik#973 (comment)

@TrySound
Copy link
Contributor

@theKashey cjs bundle is matched to esm bundle by the way, it's typescript fault.

@MrKWatkins
Copy link

I've added the following function as a workaround which I call at the start of my tests:

export function ensureMemoizeOneInitialized()
{
    if (typeof memoize === "undefined")
    {
        /* tslint:disable:no-var-requires */
        (memoize as any) = require("memoize-one");
    }
}

@alexreardon
Copy link
Owner

I would be keen to find a way forward in our generated code. Ideally it would work with TS out of the box. Any thoughts @TrySound?

@TrySound
Copy link
Contributor

This is the best and not only for typescript
https://basarat.gitbooks.io/typescript/docs/tips/defaultIsBad.html

@TrySound
Copy link
Contributor

Actually there is esModuleInterop flag which fixes this behaviour in typescript similar to "magical" (it's not) babel way.

@TrySound
Copy link
Contributor

It should be default since v3 but "TS has such big ecosystem".

@alexreardon
Copy link
Owner

alexreardon commented Oct 22, 2018 via email

@TrySound
Copy link
Contributor

To typescript? I'm not. Users should enable it and fix all modules in DefinitelyTyped.

@TrySound
Copy link
Contributor

Or do you mean named export?

@theKashey
Copy link
Contributor Author

Please don't use esModuleInterop or allowSyntheticDefaultImports, it will require target consumer to set it, and this is something you shall not ask for.
PS: And also affects imports, not exports.

@TrySound
Copy link
Contributor

@theKashey That's the point. It should be default. Ecosystem is broken now. It's just doesn't work for many packages.

@alexreardon
Copy link
Owner

alexreardon commented Oct 22, 2018

To typescript? I'm not. Users should enable it and fix all modules in DefinitelyTyped.

@TrySound I misunderstood you. I thought we could add a simple rollup plugin and be done with this issue

It sounds like you have some insight into this @theKashey, any thoughts on how to proceed?

@TrySound
Copy link
Contributor

Well another option is named only exports.

@theKashey
Copy link
Contributor Author

The best option I could provide, would be a :badjookeeee: option - rewrite memoize-one to TS, and provide .flow types in a separate, ie "revert" how it works today.
TS could generate "default" exports, understandable both by TS and JS.

@TrySound
Copy link
Contributor

@theKashey What is the point of esModuleInterop if it doesn't work with any typings at all? What for they provided broken configuration?

@TrySound
Copy link
Contributor

@theKashey It's not the best option. It's a hack for hacky ecosystem.

@TrySound
Copy link
Contributor

Flow is not opinionated and that's why it's so beautiful.

@TrySound
Copy link
Contributor

@theKashey Okay, how such export should look like? Like this { default: nowTryToInteropThis }?

@theKashey
Copy link
Contributor Author

Technically -

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

exports = memoizeOne;
exports.default = memoizeOne;

@alexreardon
Copy link
Owner

alexreardon commented Oct 23, 2018 via email

@theKashey
Copy link
Contributor Author

That could be a little problem.

@TrySound
Copy link
Contributor

Babel moved from this hack two major versions ago.

@billiegoose
Copy link

I'm running into this problem as well. Not sure if it's TypeScript per-se or Jest.

But the crux of it is that the CJS module needs to include a named export called "default". Pretty much like @theKashey showed.

I think that would fix it.

@billiegoose
Copy link

Just tried. Can't be done. (Seriously rollup?) Default exports were such a fuckup. :(

I think the only way out is to release a new major version that breaks the way CJS currently works and forces it to use require('memoize').default. I'll make a PR.

@hlolli
Copy link

hlolli commented Nov 16, 2018

This is a problem for me on Typescript, it works

import * as memoizeOne from "memoize-one";

and

import memoizeOne from "memoize-one";

but with server side rendering, one will work on nodejs and one on in the browser, bot not both at the same time.

So if I'd want to do

import * as memoizeOne from 'memoize-one/dist/memoize-one.cjs';

which works both server and browser side, then tslint will complain.

This is the only library of like 50 that behaves this differently between imports.

@TrySound
Copy link
Contributor

esModuleInterop will fix the problem.

@TrySound
Copy link
Contributor

@oonsamyi Why not enable interop on your side?

@oonsamyi
Copy link

When I add esModuleInterop option to ts config several dozen typescript errors appear and part of unit tests fails. Typescript errors are not a problem for me. But the failure tests are a problem. I don't quite understand why tests fail with esModuleInterop option.

Now I turned off esModuleInterop option and added next hack to jest config:

// project/fixDefaultImports.ts
jest.mock('memoize-one', () => ({
  default: jest.requireActual('memoize-one'),
}));
// project/jest.config.js
module.exports = {
  setupFilesAfterEnv: ['<rootDir>/enzyme.config.js', '<rootDir>/fixDefaultImports.ts'],
  ...
}

But it is hack...

Summary, I would like to work with default typescript config.
Is this issue accurate only with typescript? What about jest + pure javascript?
Is it hard to add named export? I think it is easy, memoize-one will be worked out of the box with typescript default config and many people will be grateful :)

P.S. React resolves this problem next way:
https://github.com/facebook/react/blob/master/packages/react/index.js#L16
It is cool because no matter what is used in the environment of the project: CommonJS or ES6 modules, javascript or typescript, webpack or rollup. It works for all the ones.

@TrySound
Copy link
Contributor

Summary, I would like to work with default typescript config.

This is not an option with typescript. You loose safety without strictNullChecks.

Is it hard to add named export?

This is inconsistent design. There should be either default or named export.

React resolves this problem next way:

React does not distribute esm and does not export function as default/module.exports.

But it is hack...

This is too, but this is how ts behaves by default.

import * as memoizeOne from 'memoize-one';

I'd say babel as compiler will solve all your issues.

@theKashey
Copy link
Contributor Author

Why not enable interop on your side?

then classnames would be broken :)

@alexreardon
Copy link
Owner

I'm happy to add a named export if it is easier for people

@oonsamyi
Copy link

@alexreardon Can I make a pull request?

@mits87
Copy link

mits87 commented Apr 3, 2020

Any news?

@filipef101
Copy link

Let's go

@theKashey
Copy link
Contributor Author

Wasn't it solved in a new version?

@mits87
Copy link

mits87 commented Apr 6, 2020

It looks like still there is a problem.
I have installed: "memoize-one": "^5.1.1"

When I'm trying to use it like that:

import memoizeOne from 'memoize-one';

I've got an error:

{
  "errorType":"TypeError",
  "errorMessage":"memoize_one_1.default is not a function",
  "stack":[
    "TypeError: memoize_one_1.default is not a function", "......"]
}```

@TrySound
Copy link
Contributor

TrySound commented Apr 6, 2020

@mits87 Try to enable esModuleInterop in TS config

@mits87
Copy link

mits87 commented Apr 7, 2020

Actually I can't use the esModuleInterop option because I have a lot of libraries like:

import * as SQS from 'aws-sdk/clients/sqs';

and after enabled esModuleInterop flag I've got errors like:

Misnamed import. Import should be named 'sqs' but found 'SQS'

or

Type originates at this import. A namespace-style import cannot be called or constructed, and will cause a failure at runtime. Consider using a default import or import require here instead.

TrySound added a commit to TrySound/memoize-one that referenced this issue Apr 7, 2020
References
alexreardon#37
https://github.com/rollup/rollup/blob/master/build-plugins/emit-module-package-file.js
https://unpkg.com/nanoevents@5.1.5/package.json

Node supports unflagged esm since v13. In this diff I changed default
import to memoizeOne named one.

```
import { memoizeOne } from 'memoize-one';
const { memoizeOne } = require('memoize-one');
const { memoizeOne } = window.memoizeOne;
```

The main reason is TS support without fighting with default commonjs/esm
interop which forces to import a function as a namespace.

```
import * as memoizeOne from 'memoize-one';
```

This is incorrect. Module namespace is an object due spec.

Libraries like nanoevents and nanoid ended with named export aswell.
See details here ai/nanoid#128
@TrySound
Copy link
Contributor

TrySound commented Apr 7, 2020

Let's kill default import.
#86

@tamazlykar
Copy link

Any news?

I have a strange problem. I was using memoize-one without esModuleInterop and it works great with import like this:
import * as memoizeOne from 'memoize-one'
but when i set esModuleInterop to true and change import to import memoizeOne from 'memoize-one' app still works but jest start to fails with TypeError: memoize_one_1.default is not a function.
If I set a breakpoint around the place where memoize-one is used I see that memoize_one_1 looks like this:

memoize_one_1 : {
  default: {
    {
      default: actual_function 
    }
  }
}

App compiles with webpack and ts-loader, jest with ts-jest, typescript version is 3.7.5.
If this problem not relates to this library, sorry.
Any ideas why this happens will be appreciated.

@TrySound
Copy link
Contributor

@tamazlykar I faced this problem with webpack when some commonjs module imported esm version of library. Make sure you do not transpile modules by babel-loader.

@alexreardon
Copy link
Owner

alexreardon commented Aug 13, 2020

i'm totally open to adding a minor release which allows people to use memoize-one as a named import

export const memoizeOne = memoizeOne;
export const memoize = memoizeOne;
import { memoizeOne } from 'memoize-one';
// OR
import { memoize } from 'memoize-one';

@alexreardon
Copy link
Owner

Just to make peoples lives easier ^

@theKashey
Copy link
Contributor Author

👍 import { memoizeOne } from 'memoize-one'; - it will be better autoimportable.

Plus every time you import memoizeOne from 'memoize-one' eslint will complain about the named export and propose a change (actually that could break builds, but 😅 it is a good to have feature)

@cwood-panopto
Copy link

@alexreardon With the revert in v5.2.1 is this an issue again? I am still having the error in jest with ts: TypeError: memoize_one_1.default is not a function

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.