opentelemetry-js/doc/esm-support.md

132 lines
5.7 KiB
Markdown

# ECMAScript Modules vs. CommonJS
Node.js uses a different module loader for ECMAScript Modules (ESM) vs. CommonJS (CJS).
To verify whether your application is ESM or CJS, refer to [Node.js docs for Determining Module System](https://nodejs.org/api/packages.html#determining-module-system).
An `.mjs` extension or `type:module` in the built app's `package.json` indicates the app is ESM.
**Much of OpenTelemetry JS documentation is written assuming the compiled application is run as CJS.**
ESM support is ongoing; a few adjustments are needed for configuration and startup commands.
For more explanation about CJS and ESM, see the [Node.js docs](https://nodejs.org/api/modules.html#enabling).
## TypeScript
Many TypeScript projects today are written using ESM syntax, regardless of how they are compiled.
In the `tsconfig.json`, there is an option to compile to ESM or CJS.
If the compiled code is ESM, those import statements will remain the same (e.g. `import { foo } from 'bar';`).
If the compiled code is CJS, those import statements will become `require()` statements (e.g. `const { foo } = require('bar');`)
## Initializing the SDK
Instrumentation setup and configuration must be run before your application code.
If the SDK is initialized in a separate file (recommended), ensure it is imported first in application startup, or use the `--require` or `--import` flag during startup to preload the module.
For CJS, the `NODE_OPTIONS` for the startup command should include `--require ./telemetry.js`.
For ESM, minimum Node.js version of `18.19.0` is required.
The `NODE_OPTIONS` for the startup command should include `--import ./telemetry.js`.
## Instrumentation Hook Required for ESM
If your application is written in JavaScript as ESM, or compiled to ESM from TypeScript, then a loader hook is required to properly patch instrumentation.
The custom hook for ESM instrumentation is `--experimental-loader=@opentelemetry/instrumentation/hook.mjs`.
This flag must be passed to the `node` binary, which is often done as a startup command and/or in the `NODE_OPTIONS` environment variable.
### Additional Notes on Experimental Loaders
Though the OpenTelemetry loader currently relies on `import-in-the-middle`, direct usage of `import-in-the-middle/hook.mjs` may cease to work in the future.
The only currently supported loader hook is `@opentelemetry/instrumentation/hook.mjs`.
**Note:** Eventually the recommendation for how to setup OpenTelemetry for usage with ESM will change to no longer require `--experimental-loader=@opentelemetry/instrumentation/hook.mjs`.
Instead the bootstrap code (in `./telemetry.js`) will use Node.js's newer `module.register(...)`.
Refer to this [issue](https://github.com/open-telemetry/opentelemetry-js/issues/4933) for details.
Because of ongoing issues with loaders running TypeScript code as ESM in development environments, results may vary.
To use `ts-node` to run the uncompiled TypeScript code, the module must be CJS.
To use `tsx` to run the uncompiled TypeScript code as ESM, the `--import` flag must be used.
## Using the Zero Code Option with `auto-instrumentations-node`
The `auto-instrumentations-node` package contains a `register` entry-point that can be used with `--require` or `--import` to setup and start the SDK easily, without application code changes.
For ESM, the package also requires the usage of the loader hook.
Startup command for CJS:
```sh
node --require @opentelemetry/auto-instrumentations-node/register app.js
```
Startup command for ESM:
```sh
node --experimental-loader=@opentelemetry/instrumentation/hook.mjs --import @opentelemetry/auto-instrumentations-node/register app.js
```
## Examples
### Example Written in JavaScript as CJS
```javascript
/*telemetry.cjs*/
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { ConsoleSpanExporter } = require('@opentelemetry/sdk-trace-node');
const {
getNodeAutoInstrumentations,
} = require('@opentelemetry/auto-instrumentations-node');
const sdk = new NodeSDK({
traceExporter: new ConsoleSpanExporter(),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
```
Startup command:
```sh
node --require ./telemetry.cjs app.js
```
### Example Written in JavaScript as ESM or TypeScript
```typescript
/*telemetry.ts | telemetry.mjs*/
import { NodeSDK } from '@opentelemetry/sdk-node';
import { ConsoleSpanExporter } from '@opentelemetry/sdk-trace-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
const sdk = new NodeSDK({
traceExporter: new ConsoleSpanExporter(),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
```
Startup command for compiled CJS:
```sh
node --require ./telemetry.js app.js
```
Startup command for compiled ESM:
```sh
node --experimental-loader=@opentelemetry/instrumentation/hook.mjs --import ./telemetry.js app.js
```
### ESM Options for Different Versions of Node.js
The entire startup command should include the following `NODE_OPTIONS`:
| Node.js Version | NODE_OPTIONS |
| ----------------- | ----------------------------------------------------------------------------------------- |
| 16.x | `--require ./telemetry.cjs --experimental-loader=@opentelemetry/instrumentation/hook.mjs` |
| >=18.1.0 <18.19.0 | `--require ./telemetry.cjs --experimental-loader=@opentelemetry/instrumentation/hook.mjs` |
| ^18.19.0 | `--import ./telemetry.mjs --experimental-loader=@opentelemetry/instrumentation/hook.mjs` |
| 20.x | `--import ./telemetry.mjs --experimental-loader=@opentelemetry/instrumentation/hook.mjs` |
| 22.x | `--import ./telemetry.mjs --experimental-loader=@opentelemetry/instrumentation/hook.mjs` |