Why importing from a linked local ES modules package using the package name works with "main" property but fails with "module"
Asked Answered
B

1

1

Question

Why does importing from a linked local NPM pacakage (built as an ES module) using the pacakge name works when the pacakge.json has the "main" property, but fails when it has the "module" property?

Setup

More spicifically, if we have the following setup:

  1. exporter: A Node.js package which is an ES module that exports something (e.g., using export default).
  2. importer: A Node.js module that tries to import what exporter exports, using import something from 'exporter'.
  3. Using npm link to locally link exporter to importer.

Then:

  • The setup runs successfully if exporter's package.json uses the main property.
  • The setup run fails if exporter's package.json uses the module property.
    • This failure can be "fixed" by using import something from 'exporter/dist/bundle.js', but that's unacceptable.

Why is that? I guess I'm doing something wrong?

Code

exporter

|- webpack.config.js
|- package.json
|- /src
  |- index.js
|- /dist
  |- bundle.js

webpack.config.js:

import path from "path";
import { fileURLToPath } from "url";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

export default {
  mode: "development",     
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist"),
    library: {
      type: "module",
    },
  },
  experiments: {
    outputModule: true,
  },
};

package.json:

{
  "name": "exporter",
  "version": "1.0.0",
  "main": "dist/bundle.js", <-- *** NOTE THIS LINE ***
  "scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "webpack": "^5.51.1",
    "webpack-cli": "^4.8.0"
  },
  "type": "module"
}

index.js:

function util() {
  return "I'm a util!";
}
export default util;

importer

|- package.json
|- /src
  |- index.js

package.json

{
  "name": "importer",
  "version": "1.0.0",
  "type": "module"
}

index.js

import util from 'exporter';

console.log(util());

Then:

  1. Linking:
⚡  cd exporter
⚡  npm link
⚡  cd importer
⚡  npm link exporter
  1. Executing:
⚡  node importer.js 
I'm a util!

However, if exporter's package.json is changed to:

{
  "name": "exporter",
  "version": "1.0.0",
  "module": "dist/bundle.js", <-- *** NOTE THIS LINE ***
  "scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "webpack": "^5.51.1",
    "webpack-cli": "^4.8.0"
  },
  "type": "module"
}

Then:

  1. Executing:
⚡  node importer.js 

Fails:

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'importer\node_modules\exporter\' imported from importer\src\index.js

But Why?

Brainwash answered 25/8, 2021 at 20:1 Comment(0)
C
2

When resolving a node_modules package, Node.js first checks if there's a "exports" field in the package.json, if there's none it looks for a "main" field, and if it's. also not there, it checks if there's a file named index.js – although this behavior is deprecated and may be removed in a later version of Node.js, I would advise against relying on it and always specify "exports" and/or "main" fields. You can check out Package entry points section of Node.js docs to get more info on that.

"module" is simply not a field Node.js uses, it's used by some other tools so it's certainly okay to have it defined in your package.json, but you should also have "main" and/or "exports" fields. Node.js will use the file extension to determine if the file is an ES module (dist/bundle.js is using .js as extension and you have "type": "module" in your package.json, so you're all set).

Consumedly answered 26/8, 2021 at 21:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.