This is a working test repo with i18next, React, Typescript, Jest/Enzyme, Webpack
esModuleInterop: false
import * as i18next from 'i18next'
This repo is a full explanation with a working example of esModuleInterop: false
(NOT RECOMMENDED):
https://github.com/rosskevin/test-i18next/tree/non-esmoduleinterop-ts-loader
and a companion branch of esModuleInterop: true
(RECOMMENDED).
https://github.com/rosskevin/test-i18next/tree/esmoduleinterop
I want to be clear: this is not a typescript problem, it is one that typescript exposes. Webpack insists on specific loader semantics, nodejs is stuck in commonjs loading semantics, libraries offer various bundles for consumer optimization (commonjs, esm), and test tools have a different runtime than web browsers.
esModuleInterop: false
says:
I know the difference between commonjs and esm modules, I care about the semantics of how they are imported and exported, I care about how each of my runtimes are going to load those modules, and I want to break if there is a difference.
This has implications that most people don't care, or want to know about.
This IS NOT what a user typically wants. If you are not an expert user that wants to master the module types differences in imports and manipulation of module loading for jest and webpack, then DO NOT use this setting. If you want it to just work, typescript is quite awesome, it covers all of these differences noted above with a simple esModuleInterop: true
.
This stemmed from a new user sample: i18next/react-i18next-gitbook#63 (comment)
This issue shows up in i18next repo often for new users I think for a few reasons:
- i18next is very popular and one of the first libraries added to new efforts
- i18next uses
export default
in addition to other named exports from the index and offerscommonjs
as well asesm
builds - typescript defaults
esModuleInterop: false
instead of the easier path oftrue
i18next is certainly not alone, this is a problem will show up in many dependencies, but it just hits i18next very early because it is very early in any runtime setup.
A combination of things make this difficult to understand, it is actually an entire set of circumstances that makes different syntaxes and modules incompatible:
- node uses
commonjs
dependencies (e.g. jest) - webpack prioritizes
esm
module
dependencies overmain
commonjs
import * as
is valid ecmascript- typescript transpilation often targets
es2015
, allowing babel to deal with the rest. esModuleInterop: false
withimport * as i18n from 'i18next'
generatesvar i18n = require("i18next")
which does not resolve themodule.default
. This is NOT a valid import for anesm
module, but it is for acommonjs
module.esModuleInterop: true
withimport i18n from 'ii18next'
generatesconst i18next_1 = __importDefault(require("i18next"));
. Critically, this generated__importDefault
helper is triggered to resolve thedefault
module and works for BOTHcommonjs
andesm
modules.
Many libraries export default
(strongly discouraged in my opinion) as well as offer commonjs
, esm
, umd
(quite nice in my opinion).
If you use esModuleInterop: false
then you have to know and manipulate for:
- Is this dependency
esm
?commonjs
? - Does it use
export default
?export {named}
?module.exports =
?module.exports = {named}
- Am I running it with node (
commonjs
) or webpack (default priority isesm
thencommonjs
)
Do you care about this? really? If not, use esModuleInterop: true
and allowSyntheticDefaults: true
and be done with all of this.
I assume you then REALLY want to make esModuleInterop: false
work, I have found one reliable way to do so for all of the various module types and loaders, both jest/node and webpack.
Namely:
- Use ts-loader before babel and FORCE
target: es5
production viatsconfig.json
- Alter webpack
mainFields
to FORCEmain
resolution of commonjs modules beforemodule
(esm
)
If you do this, it will work for jest and webpack. Do I recommend this? ABSOLUTELY NOT. I DO NOT but it is your prerogative.
These scripts are setup to expediently test all stages of this process. THIS IS NOT a starter project, but an example to verify working status.
cd <PROJECTDIR>
npm install
npm run start
Then navigate to http://localhost:3000
- Use
esModuleInterop: true
, it is much simpler and JUST WORKS. https://github.com/rosskevin/test-i18next/tree/esmoduleinterop - Libraries can prevent this sort of thing entirely by using named exports only - no default exports. There is no incompatibility in resolution past/present.
- I repeat, do not use
export default
and this entire problem disappears, both in js and typescript land. - This is not a "typescript" problem, but it is one that typescript exposes. One such example is from a non-typescript commonjs library loading issue in webpack webpack/webpack#5756.