require a JSON file throws a Node.js loader error "Error: Cannot find module 'example.json'" when running Babel transpiled code

There are several similar questions related to importing or requiring JSON (.json) files when building the code in TypeScript. My question is specifically about requiring a JSON file in an ES6 module that is transpiled to the current node target using Babel (core and cli). I see no config option like TypeScript's resolveJsonModule for Babel, which leads me to believe it should work without any config.

I am importing a JSON file (example.json) from a JS file (index.js) in the same directory, by doing:

const myObj = {};
myObj.myJSON = require("./example.json");

I have also tried importing using the newer ES6 syntax as:

import * as myJson from "./example.json";

I am using VSCode, and in both cases the auto-complete suggests to me the correct relative path to the JSON file in the source code.

The directory looks like:

src/
  |_dir/
    |_index.js
    |_example.json

In both cases, when transpiling with the babel-cli using the command: babel src --out-dir build, the produced build looks like:

src/
  |_dir/
    |_index.js

For some reason, the example.json file is not included, despite it being required in the compiled index.js file:

...

const myObj = {};

myObj.myJSON = require("./example.json");
...

As such, when running the transpiled build with node using node build/index.js, it throws the error:

internal/modules/cjs/loader.js:883
  throw err;
  ^

Error: Cannot find module './example.json'
Require stack:
- ...
- .../build/index.js
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:880:15)
    at Function.Module._load (internal/modules/cjs/loader.js:725:27)
    at Module.require (internal/modules/cjs/loader.js:952:19)
    at require (internal/modules/cjs/helpers.js:88:18)
    at Object.<anonymous> (.../index.js:15:25)
    at Module._compile (internal/modules/cjs/loader.js:1063:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
    at Module.require (internal/modules/cjs/loader.js:952:19) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [
    ...
  ]
}

Here is the relevant configuration:

package.json

  ...
  "scripts": {
    "build": "babel src --out-dir build",
    "start": "node build/index.js",
    ...
  },
  "devDependencies": {
    "@babel/cli": "^7.16.8",
    "@babel/core": "^7.16.7",
    "@babel/preset-env": "^7.16.8",
    ...
  },
  "dependencies": {
    "@babel/plugin-transform-runtime": "^7.16.8",
    ...
  }

.babelrc

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "node": "current"
        }
      }
    ]
  ],
  "plugins": [
    "@babel/plugin-transform-runtime"
  ]
}

Solution 1:

I think the boring answer is that Babel only processes JavaScript files. Anything that's not JavaScript (e.g. JSON) gets left behind, even though it "feels" like the JSON file is a code dependency that Babel should "just do the right thing with" (since you required or imported it) -- or at least give you an error or a warning. But that is not how Babel was designed in this use-case.

You need to do extra work to copy data files. I suggest three main alternatives to choose from:

  1. Use a bundler like WebPack or Gulp which has tools for copying non-JS files around
  2. Use Babel's --copy-files which copies everything. You can sprinkle in --ignore patterns to try to avoid copying things you don't want copied.
  3. Use something like the shx npm package which lets you run Unix-style filesystem commands in a cross-platform way from inside package.json scripts.

"build": "babel src --out-dir build && npx shx cp src/*.json dist/"