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

Example import syntax in readme doesn't work for ES6 module users #1293

Closed
chase-moskal opened this issue Oct 23, 2017 · 11 comments
Closed

Example import syntax in readme doesn't work for ES6 module users #1293

chase-moskal opened this issue Oct 23, 2017 · 11 comments
Labels

Comments

@chase-moskal
Copy link

Hello, just now I started migrating from enzyme 2 to 3, and ran into a pickle here: I found that the typescript import syntax differs from the official enzyme docs, so I'm posting my troubles and solutions to help others in the same situation.

FAIL

import Enzyme from "enzyme"
import Adapter from "enzyme-adapter-react-16"

Enzyme.configure({adapter: new Adapter()})

//> Enzyme is undefined (no default export)
//> Adapter is undefined (no default export)

FAIL

import {configure} from "enzyme"
import {Adapter} from "enzyme-adapter-react-16"

configure({adapter: new Adapter()})

//> configure is undefined
//> Adapter is undefined

SUCCEED

import * as enzyme from "enzyme"
import * as Adapter from "enzyme-adapter-react-16"

enzyme.configure({adapter: new Adapter()})

//> hooray!

side question

Do I really have to do this configure call in every single *.test.ts file? Jest doesn't seem to give me an entrypoint, so I guess so...

@chase-moskal chase-moskal changed the title Import syntax doesn't work in TypeScript [SOLVED] Import syntax doesn't work in TypeScript [solved?] Oct 23, 2017
@ljharb
Copy link
Member

ljharb commented Oct 24, 2017

  1. the import * as requirement is a bug in TypeScript; that should not be required.
  2. you have to configure it once per test run; since jest runs every file in its own process, you have to do it once per file. However, you don't have to do it in each file - jest has the setupTestFrameworkScriptFile option.

@chase-moskal
Copy link
Author

the import * as requirement is a bug in TypeScript; that should not be required.

hmm.. reading issues like this: microsoft/TypeScript#2719

I find babel quotes like this:

http://babeljs.io/docs/usage/modules/#interop

Interop

In order to encourage the use of CommonJS and ES6 modules, when exporting a default export with no other exports module.exports will be set in addition to exports["default"].

export default test;
exports["default"] = test;
module.exports = exports["default"];
If you don't want this behaviour then you can use the commonStrict module formatter.

I think perhaps when babel said "encourage", they must have meant "confuse"

if I'm reading this right, I'm getting the sense that the import * as requirement of TypeScript is actually the one that's aligned with kosher es6 import standards?

seems like either babel's interop, or typescript generally, has to be on the wrong side of the ES6 module fence here.. and it's not looking like typescript I don't think..

enzyme may be better off using that "commonStrict" module formatter situation that babel mentions?

@ljharb
Copy link
Member

ljharb commented Oct 26, 2017

I'm afraid you're not reading it right; there are no standards about interop, and there's no "wrong side of the module fence" here - there's just "what everyone in JS has been doing for years", and this new weird thing TypeScript is imposing on its users.

@chase-moskal
Copy link
Author

chase-moskal commented Oct 26, 2017

there are no standards about interop, and there's no "wrong side of the module fence" here - there's just "what everyone in JS has been doing for years"

@ljharbthis classic blog post is obviously talking about the ratified standard for es6 modules, the details of which are undoubtedly in the full ecma-262 6th edition language specification 2015:

The default export is actually just a named export with the special name default.

I can see it in the enzyme module source code that there is no "export with the special name default":

module.exports = {
  render,
  shallow,
  mount,
  ShallowWrapper,
  ReactWrapper,
  configure,
  EnzymeAdapter,
};

so enzyme is not providing a standard ES6 default export there, right?

interestingly, some of the enzyme source files use es6 default exports, such as render.js:

export default function render(node, options = {}) {
  const adapter = getAdapter(options);
  const renderer = adapter.createRenderer({ mode: 'string', ...options });
  const html = renderer.render(node, options.context);
  return cheerio.load('')(html);
}

now that right there, is a proper es6 default export — which can be imported with the proper syntax in typescript, which strives at great length to align with the evolving ecmascript standards

why isn't the enzyme module similar to the render module in that way?

@chase-moskal
Copy link
Author

chase-moskal commented Oct 26, 2017

this article did a great job clearing confusion for me: http://www.thedreaming.org/2017/04/28/es6-imports-babel/

  • there are common js modules, which export any js value
  • there are es6 modules, with named exports including a possible default
  • enzyme's module is common js, because it does not have an es6 default export
  • enzyme's render.js is an es6 module, because it has an es6 default export

please update the readme usage example with something like the following:

Finally, you need to configure enzyme to use the adapter you want it to use. To do this, you can use the top level configure(...) API.

import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });

Enzyme is provided in Common JS format, so developers using es6 modules will need to use the import * as syntax instead:

import * as Enzyme from 'enzyme';
import * as Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });

let me know if you'd rather me post a PR for it

@chase-moskal
Copy link
Author

chase-moskal commented Oct 26, 2017

I noticed something, in the source file enzyme/mount.js:

module.exports = require('./build/mount').default;

we can see that enzyme is actually taking the internal ES6 modules, and exposing them as Common JS modules instead

this is very interesting

it's almost like internally, enzyme is using the newer ES6 modules — however when the functionality is presented for the consumer to use, it is transformed into the older Common JS format

@chase-moskal chase-moskal changed the title Import syntax doesn't work in TypeScript [solved?] Readme usage example: Import syntax doesn't work for ES6 module users Oct 26, 2017
@chase-moskal chase-moskal changed the title Readme usage example: Import syntax doesn't work for ES6 module users Readme example import syntax doesn't work for ES6 module users Oct 26, 2017
@chase-moskal chase-moskal changed the title Readme example import syntax doesn't work for ES6 module users Example import syntax in readme doesn't work for ES6 module users Oct 26, 2017
@ljharb
Copy link
Member

ljharb commented Oct 26, 2017

@ChaseMoskal yes, this is correct for all npm packages - node doesn't support ES Modules natively yet, so everything is transpiled to commonJS. However, babel's interop means that when you use import with it, it will properly have the semantics of ESM.

"Enzyme is provided in Common JS format, so developers using es6 modules will need to use the import * as syntax instead:" is only true for TypeScript. Developers using ES6 Modules with every other tool don't need to do that extra step, because every other tool has chosen an interop method that avoids inflicting burden on users.

@chase-moskal
Copy link
Author

I found a relevant typescript compiler option:

--allowSyntheticDefaultImports — Allow default imports from modules with no default export. This does not affect code emit, just typechecking.

I suspect this may allow the readme usage example to work for TypeScript users, I think this is something like the equivalent of Babel's interop

I think it would be useful to add a quip in the readme's usage example:

TypeScript users might need to either use the import * as Enzyme from 'enzyme' syntax, or consider using the --allowSyntheticDefaultImports compiler option.

@ljharb
Copy link
Member

ljharb commented Oct 26, 2017

That one sentence seems much more palatable to me (with the order of the "either"s reversed). Want to make a PR?

@mbenadda
Copy link

Hi.

I just ran into this issue and ended up solving it using the import * as Adapter syntax (did not want to add a lax compiler flag just for one testing utility). However, I agree with you that this syntax does break the spec (as Adapter should effectively be a namespace, not a function).

The real compatible mode would be to simply not use a default export. This way it would work cleanly out of the box for everyone (and only users on very old-school codebases that can use neither ES6 modules nor object destructuring would have a slightly clumsier import).

import { Adapter } from 'enzyme-adapter-react-16'; // babel/TS

const { Adapter } = require('enzyme-adapter-react-16'); // Node 6+ no transpilation

var Adapter = require('enzyme-adapter-react-16').Adapter; // Node 5 or older no transpilation
// A bit clumsy but not broken like the TS "fix"

If I may say so, I think in 2018 supporting Typescript ought to be higher priority than offering a slightly lighter syntax to pre-2016 versions of Node.js. Maybe starting with the next version of React.

@ljharb
Copy link
Member

ljharb commented Feb 23, 2018

I believe TypeScript has fixed their module issues in v2.8; import * as should no longer be needed for this case.

Regardless, “not JavaScript” will never be a higher priority than “JavaScript” :-)

@ljharb ljharb closed this as completed Feb 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants