Open mishani0x0ef opened 4 months ago
Just to add, for those who faced the same issue and are looking for a solution.
If you can use esm
instead of cjs
- just use it. It's better, believe me 😄
If you cannot avoid cjs
- as a workaround for this issue use cjs
everywhere (assuming you cannot tune your bundler to workaround it in other way).
I know it's painful, but this is what currently available.
const { toast } = require('react-toastify');
Depending on your project configuration, you may also need to:
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { toast } = require('react-toastify');
eslintrc.js
to enforce importing react-toastify
using cjs
style:
{
'no-restricted-imports': [
'error',
{
name: 'react-toastify',
message: 'Please use CommonJS importing syntax for this lib - require',
},
]
}
tsconfig
, package.json
or bundler configurationAlso, there is tricky workaround for webpack
.
The idea is to remove exports
section before compilation using a custom plugin:
const { validate } = require('schema-utils');
const path = require('path');
const fs = require('fs');
const schema = {
type: 'object',
properties: {
path: {
type: 'string',
},
},
};
class DualCjsEsmWorkaroundPlugin {
static name = 'DualCjsEsmWorkaroundPlugin';
constructor(options) {
validate(schema, options);
const defaultOptions = {
path: null,
};
this.options = { ...defaultOptions, ...options };
}
apply(compiler) {
if (!this.options.path) {
return;
}
compiler.hooks.initialize.tap(DualCjsEsmWorkaroundPlugin.name, () =>
this._removeExportsWhenPossible(),
);
}
_removeExportsWhenPossible() {
try {
const packageJsonPath = path.join(this.options.path, 'package.json');
const packageJson = require(packageJsonPath);
const canRemoveExports = packageJson.module && packageJson.main && packageJson.exports;
if (!canRemoveExports) {
return;
}
const packageJsonWithoutExports = JSON.stringify(
{ ...packageJson, exports: undefined },
null,
2,
);
fs.writeFileSync(packageJsonPath, packageJsonWithoutExports);
} catch (error) {
console.error(
`[${DualCjsEsmWorkaroundPlugin.name}]: error while trying to remove externals: ${error}`,
);
}
}
}
// webpack.config.js
module.exports = {
// other configs
plugins: [
new DualCjsEsmWorkaroundPlugin({ path: path.join(__dirname, 'node_modules/react-toastify') }),
// other plugins
],
};
Do you want to request a feature or report a bug?
bug
What is the current behavior?
Currently, for apps that mix
cjs
andesm
imports, bundling ofreact-toastify
may result in including both variants of the lib into the final bundle.Assuming we have: (full reproducible example can be found here - https://github.com/mishani0x0ef/react-toastify-esm-cjs-issue)
In the example,
ToastContainer
initialized inesm
file, while also used incjs
file and otheresm
file.However, toast will only be shown for
esm
version because the lib itself is duplicated in the final bundle:https://github.com/fkhadra/react-toastify/assets/12882856/b98b6fd0-d155-47c3-92ad-3437df333bad
Why it may be an issue?
Even though mixing
cjs
andesm
in a single app isn't common -cjs
version may be used by downstream dependency.In my case, it's used as a peer dependency of a component library that provides only the
cjs
version.The actual behavior may be different in different bundling tools. I can definitely say that it's reproducible in
webpack
withesbuild
loader; inesbuild
itself; and in whatevercreate-react-app
has for the setup ofwebpack
.What is the expected behavior?
As per this comment from Evan Wallace the issue is caused by using
exports
along withmain/module
. So, removingexports
frompackage.json
will fix the issue (I have tried it locally and it really works)I totally understand this is a breaking change, and similar decisions should be made very carefully. Also,
exports
may be useful in some other scenarios.So, I respect your time and don't expect a quick resolution.
It just would be nice to hear your thoughts.