15

In my package.json file I've specified that my nodejs app is of type module, because if I do not do that, it seems that I can not use import statements. This is how it looks like now:

{
  "name": "...",
  "version": "1.0.0",
  "description": "....",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "...."
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "lodash": "^4.17.15"
  },
  "type": "module"
}

But if I add the "type": "module" to my package.json file, I can't use require statements anymore, because I get a ReferenceError: require is not defined error.

If I remove the "type": "module" line from package.json, and rewrite all of my imports to requires, everything works without an error.

I can't seem to find any indication, that import and require can not be mixed or used together in the same script, am I missing something here, or I am having some other bug? How could I resolve to use these two kind of statements in the same script?

Why I would need this, is because I want to require some config files based on dynamic paths, and only if the files exists, which I think I can not do with import.

DISCLAIMER: I am rather new to nodejs server side programming, so it is possible that I am approaching this situation very wrongly, if that's the case, please advice me something, based on the Why I've mentioned above.

NOTE: I am running this node script from the server terminal, and not from the browser.

Adam Baranyai
  • 3,635
  • 3
  • 29
  • 68
  • 1
    Have you checked out this answer? https://stackoverflow.com/a/10915442/2313097 – Yogeshwar Singh Dec 22 '19 at 10:58
  • Sorry I missed the part about config files initially. I've updated my answer now, but I think you figured it out by following the `import()` link or something given you'd already accepted the answer. :-) – T.J. Crowder Dec 22 '19 at 11:04

2 Answers2

31

But if I add the "type": "module" to my package.json file, I can't use require statements anymore, because I get a ReferenceError: require is not defined error.

Right. It's either/or. Either you use ESM (JavaScript modules, type = "module") or you use CJS (CommonJS-like Node.js native modules, require).

But there is some interoperability between them. For instance, in an ESM module you can import CJS modules, and you can use createRequire to create a require function to use to load CJS modules.

Why I would need this, is because I want to require some config files based on dynamic paths, and only if the files exists, which I think I can not do with import.

You can still do that while using type="module". You have a few options:

  1. If these "config" files are JavaScript, you can use createRequire:

    import { createRequire } from "module";
    const require = createRequire(import.meta.url);
    const yourData = require("./config.js");
    
  2. If these config files are JSON, you can use the currently-experimental support for importing JSON modules. Since you said you want to allow for files not existing, you'd want to use dynamic import() rather than static import declarations:

    let config = /*...defaults...*/;
    try {
        config = await import("./config.json", { assert: { type: "json" } });
    } catch (error) {
        // ...the file didn't exist or wasn't valid JSON...
    }
    
  3. Or if (again) they're JSON, you can go old school: use readFile and JSON.parse. :-)

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • okay, that seems to solve my issue, just a quick follow up question. It seems, that import does not want to allow me to import a pure JSON file (with `require` it worked). Should I wrap my config file as a javascript file, and export the config object, or is there a way to make import work with JSON files? Scracth this, you already answered my question:) – Adam Baranyai Dec 22 '19 at 11:04
  • @AdamBaranyai - I have? Good. :-) I've also just added a link to `createRequire`, which may be useful for your config files. – T.J. Crowder Dec 22 '19 at 11:06
  • well, partly:) I tought I hit gold with `readFile`, but then I understood that it is dependent on the `fs` module.:) Is there any way, to import a `.json` file, without using any additional module or plugin for the job?:) with pure `import` statements?:) I am yet to check out `createRequire`, so maybe I will find an asnwer there, but in any case, I still ask:) – Adam Baranyai Dec 22 '19 at 11:10
  • 1
    @AdamBaranyai - You can't `import` JSON *yet*, but that's being standardized and it will happen; [details](https://nodejs.org/api/esm.html#esm_experimental_json_modules). Until then, yes, `createRequire` is what you'd do, basically exactly what they show [here](https://nodejs.org/api/esm.html#esm_commonjs_json_and_native_modules): 1. `import` `createRequire` from `'module'`. 2. Create the `require` function using `createRequire` passing in `import.meta.url`. 3. Use that `require` function to import the JSON. I've added an example to the answer. – T.J. Crowder Dec 22 '19 at 11:33
  • That works great. But unfortunately, when I try to import/require a file that *itself* uses the "import" statement, I get that warning again for the imported/required file. I don't want to rename the imported/required file to ".mjs" because it's a large project and there are a lot of dependent files that use the file. Is there a way to mark an imported/file as a "module" via something like the ""type: module" statement in package.json, but for files imported/required by the project? – Robert Oschler Sep 07 '22 at 21:08
  • 1
    @RobertOschler - Usually those will be part of a package, which will have its own `package.json`, which will have the `type`. If you have an individual file you have to do this with in a module with `"type": "commonjs"` (or no `type`), that's a really, really unusual situation. I had hoped dynamic `import()` to work, but it doesn't. I don't think you have a lot of options other than renaming it, migrating the whole project over to ESM, or putting that file in its own package with its own `package.json`. – T.J. Crowder Sep 08 '22 at 06:00
  • tried the 'createRequire' thing.. gives me this error: `Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: require() of ES modules is not supported.` – john k Sep 23 '22 at 15:41
  • @johnktejik - Right. `createRequire` is (as I say above) for using CJS modules in ESM, not using ESM modules in ESM. For that, use an `import` statement (or an dynamic `import()` expression). – T.J. Crowder Sep 23 '22 at 15:46
  • 1
    @T.J.Crowder for me, the reason to use cjs in my ESM files is simple, I need to use [debugjs](https://github.com/debug-js/debug) but it still uses cjs and that is how I found your answer here. – Qiulang May 26 '23 at 08:39
2

Usually you need Babel to transpile your Node.js code that uses ES Modules.

But if you don't want to use Babel: ES Modules is experimental feature of latest Node.js.

You need 3 things:

  1. latest Node.js
  2. Add "type": "module" to the package.json
  3. Add experimental flag when running node.js node --experimental-modules app.js
Sergii Shvager
  • 1,226
  • 9
  • 14
  • 1
    There's no need for Babel when using modules on Node.js. – T.J. Crowder Dec 22 '19 at 10:54
  • As I said ES Modules is experimental feature: https://nodejs.org/api/esm.html#esm_ecmascript_modules – Sergii Shvager Dec 22 '19 at 10:55
  • 1
    While it's true they're marked experimental, there's no need for Babel to use ESM on Node.js. They aren't even behind a flag anymore in v13. I'm a bit surprised the docs still say Experimental, actually. That probably relates to the ongoing work improving interoperability, but ESM import of CJS modules works just fine, so... – T.J. Crowder Dec 22 '19 at 10:57