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:
exporter
: A Node.js package which is an ES module that export
s something (e.g., using export default
).importer
: A Node.js module that tries to import
what exporter
exports, using import something from 'exporter'
.npm link
to locally link exporter
to importer
.Then:
exporter
's package.json
uses the main
property.exporter
's package.json
uses the module
property.
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:
⚡ cd exporter
⚡ npm link
⚡ cd importer
⚡ npm link exporter
⚡ 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:
⚡ node importer.js
Fails:
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'importer\node_modules\exporter\' imported from importer\src\index.js
But Why?
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).