Skip to content

use module.register(...) in recommended bootstrap code for ESM support #4933

Description

@trentm

The current advice for limited ESM support is to use --experimental-loader=.... Ultimately we should move towards recommending module.register(...) usage instead, because it is on the path to being non-experimental in Node.js core (https://nodejs.org/api/module.html#moduleregisterspecifier-parenturl-options).

To use this, bootstrap code will move to something like:

telemetry.mjs

import { register } from 'module';
import { Hook, createAddHookMessageChannel } from 'import-in-the-middle';

// Yes, createAddHookMessageChannel is new. See below.
const { registerOptions, waitForAllMessagesAcknowledged } = createAddHookMessageChannel();
register('import-in-the-middle/hook.mjs', import.meta.url, registerOptions);

// Init the SDK. This is all the same bootstrap code that we are showing in
// various examples.
// ...

await waitForAllMessagesAcknowledged();

And it would be run like one of the following.

# Some config common to all options.
export OTEL_EXPORTER_OTLP_ENDPOINT=https://{your-otlp-endpoint.example.com}
export OTEL_EXPORTER_OTLP_HEADERS="Authorization={authorization-information}"
export OTEL_SERVICE_NAME=my-service
# ...

# 1.
node --import=./telemetry.mjs app.js

# 2.
export NODE_OPTIONS='--import=./telemetry.mjs'
node app.js

# 3. Using ts-node to directly transpile and run. Note that, IIRC, ts-node
#    supports .ts files used for --import (and --require), which is another
#    opportunity for some user confusion. See note below.
ts-node --import=./telemetry.ts app.ts

# 4. Eventually some auto-instrumentations-node support for `module.register`.
node --import @opentelemetry/auto-instrumentations-node/register app.js

Notes:

  • All of this recommended future requires module.register() and --import, which requires ^18.19.0 || ^20.6.0 || >=22. One option would be document this as a minimum requirement at the top of our "experimental ESM support" and then mention that for some earlier versions of node there is the "unsupported, forever-experimental, unrecommended" (or some working) option using --experimental-loader=. Then we can shunt all that complexity to a separate section.
  • In the bootstrap code I'm referencing a new createAddHookMessageChannel thing (feat: Optionally only wrap modules hooked in --import nodejs/import-in-the-middle#146) from import-in-the-middle that will almost certainly be coming soon, and that we'll want to use.
  • Mini-debate: "./instrumentation.mjs" or "./instrument.mjs" or "./telemetry.mjs"? I know "./instrumentation.mjs" is the current state (with outdated vestiges of "tracer.js" kicking around), but I'd be happy with a shorter one. :) Any non-binding opinions? Of course, this is minor.
  • I'm using the .mjs ext for the bootstrap file. There are some subtleties here.
    • Explicit .mjs makes it clear the file is ESM code, so that it doesn't depend on a "type" entry in the package.json file.
    • We may be tempted to also show a .ts option (see run option 3 above). This may be fine. --import does support getting a CJS file, so even if "tsconfig.json" emits CJS, then --import ... will work.
    • However, I'm using top-level await in the bootstrap code for the coming new import-in-the-middle feature. Does that mean that TypeScript compilation targeting CJS will blow up on that ./instrumentation.ts?
  • I haven't discussed bundling issues at all.
  • This bootstrap code could be written to work for users not using ESM: if (typeof register === 'function') { ... }. Then we could document this one-true-path for all users -- with a callout to a separate "If you are using ESM code ..." section that explains those limitations.

Originally posted by @trentm in #4845 (comment)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions