CommonJS to ESM
by Ty Myrddin
Updated on January 3, 2022
The react-base app was originally bootstrapped with
Create React App using
--template typescript
.
The available npm start
, npm test
and npm run build
scripts work fine.
But adding an index.js
with a mix of commonJS and ES modules and doing an import of .json
files, is a problem.
Apparently modules are in transition but not all are ESM yet. We will hold our experimentations with such
index.js
files trying to use node-fetch
and ngrok
for now. We may come
back to this later again. For now, we just used the Nginx on the droplet.
Troubles
Having added an index.js
to the react-base project (node v16.13.1) for trying out ngrok
:
import env from './src/env/.env.json';
const handler = require("serve-handler");
const http = require("http");
const ngrok = require("ngrok");
const fetch = require("node-fetch");
... running node index.js
throws an error and trying to solve that we could be going around in
circles.
ES modules
The implementation is now stable and can be used with the earlier commonJS module system. (Statement valid for version 13.14.0 and above).
This means that files ending with .mjs or .js extensions (with the nearest package.json file with a field type) are treated as ES modules.
First run
$ node index.js
(node:686975) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
[snip]
SyntaxError: Cannot use import statement outside a module
Adding "type: module" to the package.json file enables ES6 modules. In ES6, the source code should use
import
syntax. Previous ES use require
syntax.
Sticking to ES5
We changed the import
in the index.js
to a require
and it worked, but a lot
of other files now give the same error.
With type:module
In the to the index.js
nearest parent package.json
file, we added a top-level "type"
field with a value of "module":
// package.json
{
[snip]
"type": "module"
[snip]
}
Problem solved, BUT, this gives another error message:
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".json" for /react-base/src/env/.env.json
Apparently, JSON module import is experimental? It is supported only in commonJS mode, not in modules.
Supposedly adding the flag —experimental-json-modules
with node fixes this error and make commonJS and
module mode works with .json
file import.
$ node --experimental-json-modules index.js
Results in this error:
file:///react-base/index.js:3
const handler = require("serve-handler");
^
ReferenceError: require is not defined in ES module scope, you can use import instead
This file is being treated as an ES module because it has a '.js' file extension and '/react-base/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
Renaming index.js
to index.cjs
and
$ node --experimental-json-modules index.cjs
results in:
SyntaxError: Cannot use import statement outside a module
And we are back at the beginning.
Rewriting imports
The index.js
tries to use both import and require module patterns, which isn’t possible. We either have
to rewrite the "require" statements in index.js to "imports", like:
import pkg from 'serve-handler';
const { handler } = pkg;
But serve-handler
, http
, and ngrok
are all commonJS modules, and doing this
can lead to unexpected effects. We tried, just for the heck of it.
So we undo the "type": "module",
in package.json
and rewrite the
import env from './src/env/.env.json';
to a commonJS require:
const env = require('./src/env/.env.json');
:
$ node index.js
/react-base/index.js:5
const fetch = require("node-fetch");
^
Error [ERR_REQUIRE_ESM]: require() of ES Module /react-base/node_modules/node-fetch/src/index.js from /react-base/index.js not supported.
Instead change the require of /react-base/node_modules/node-fetch/src/index.js in /react-base/index.js to a dynamic import() which is available in all CommonJS modules.
node-fetch
And the next problem appears: node-fetch
was converted to be an ESM only package in version
3.0.0-beta.10
. It is now an ESM-only module and can not be imported with require. the async import()
function from CommonJS to load node-fetch asynchronously:
const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
Or downgrade to node-fetch version 2.6.6, which is built with CommonJS. <=
For now
WHERE'S MY COW?! Wyrd Sisters