Meteor can’t find package eta/core even though it is installed

Summary

A Meteor application fails to resolve the eta/core module during the build process, despite the eta package being correctly installed in node_modules. The error originates from the oidc-provider package, which attempts to import eta/core in its view handling code. This failure occurs because Meteor’s build tool does not automatically include the CommonJS sub-path export eta/core, causing the build to abort with a “missing module” error. The core issue is a mismatch between Node.js module resolution behavior and Meteor’s static analysis/bundling strategy.

Root Cause

The root cause is Meteor’s static dependency analysis failing to recognize the sub-path export eta/core.

  • Sub-path Exports: The oidc-provider package uses a modern Node.js feature called “sub-path exports” (defined in package.json of eta). It imports eta/core expecting Node to resolve this to an internal file like node_modules/eta/core.js.
  • Missing Dependency Declaration: oidc-provider lists eta as a peer dependency. It assumes the consuming application (your Meteor app) will install eta. However, oidc-provider does not explicitly list the internal file eta/core in its own static dependency list for Meteor to scan.
  • Meteor Build Failure: Meteor reads oidc-provider/lib/views/index.js, sees require("eta/core"), but cannot find a static reference to eta or eta/core in oidc-provider‘s package.json dependencies or a compatible import statement. Consequently, it omits eta from the client-side or server-side bundle, resulting in the “Unable to resolve some modules” error.

Why This Happens in Real Systems

This scenario is common when integrating legacy CommonJS packages with modern ESM features.

  • Hybrid Node.js Modules: Many libraries like oidc-provider are evolving to support both CommonJS and ESM, often utilizing package exports maps. When an older CommonJS require tries to access an alias defined in exports (like eta/core), build tools that rely on static analysis (like Meteor) can get confused if the package.json structure isn’t perfectly traversed.
  • Peer Dependency Ambiguity: oidc-provider treats eta as a “peer dependency” because it supports multiple view engines. It doesn’t force eta to be installed, expecting the user to do so. However, Meteor relies on explicit imports to bundle dependencies. If the link between the require statement and the package.json peer dependency isn’t detected, the module is treated as “missing.”
  • Build Tool Heuristics: Meteor’s build linker parses code to determine what to include. It looks for import statements or require calls where the string is a direct path to a package. It may skip deep imports or aliased exports like eta/core if it cannot find the package.json entry for that specific string, assuming it’s an external file not meant for bundling.

Real-World Impact

  • Build/Deploy Breakage: The application cannot build or deploy. This is a blocking error that stops development pipelines immediately.
  • Third-Party Library Lock-in: You are effectively blocked from using the specific version of oidc-provider that requires this structure until the build configuration is fixed.
  • Developer Productivity Loss: Developers spend significant time debugging node_modules inconsistencies, running npm install, and trying to force module resolution, rather than focusing on business logic.

Example or Code

If oidc-provider requires eta/core internally, the fix usually involves “shimming” the dependency or forcing Meteor to include it.

// Example of a shim file (e.g., imports/fix-eta.js) that can be imported early in the app
// This forces the module into the bundle.
import eta from 'eta';
export default eta;

However, the specific code causing the issue looks like this within oidc-provider:

// node_modules/oidc-provider/lib/views/index.js
// This line fails in Meteor because "eta/core" is not statically resolvable
const { compile, render } = require('eta/core');

How Senior Engineers Fix It

Senior engineers resolve this by bridging the gap between the build tool and the module system.

  • Check package.json Exports: They inspect node_modules/eta/package.json to see how eta/core is mapped. If it points to ./core.js, they ensure the file exists.
  • Explicit Import/Re-export: The most robust fix is to manually import the dependency in a startup file. By adding import 'eta'; (or import { compile } from 'eta';) in a top-level application file (e.g., server/main.js), Meteor is forced to parse and include eta in the bundle.
  • Runtime Compatibility Layer: If oidc-provider is trying to load eta/core at runtime (dynamic require), the Senior Engineer might use Meteor.npmRequire or a dynamic import wrapper if supported by the specific Meteor version, but usually, static inclusion is safer.
  • Meteor Settings: They might configure meteor-main-module or build plugins if the project is complex, but usually, adding the missing eta import to the application code is the immediate fix.

Why Juniors Miss It

Junior engineers often lack the experience with bundler internals and Node.js ESM/CJS interop.

  • Blind Re-installation: They often run npm install repeatedly (as suggested by the error) thinking the package is missing, when in reality, the package is there, but the bundler cannot see it.
  • Ignoring Peer Dependencies: They might not realize that oidc-provider requires eta to be explicitly installed by them (the app developer), not just the library.
  • Misinterpreting “Missing Module”: They interpret “Unable to resolve” as a file not found error on disk, rather than a bundling/scope resolution error.
  • Lack of Debugging Tools: They don’t know how to inspect the Meteor build process (e.g., looking at .meteor/local/build or using meteor shell to test requires) to verify if eta is actually bundled.