Skip to content

Commit 7f2e804

Browse files
authored
feat: Make ESM output valid Node ESM (#10928)
Some bundlers, especially older ones, don't support resolving `.mjs` files out of the box. To retain compatibility with these bundlers without requiring users to adjust their configurations, we need to keep all our ESM output as `.js` files. So node.js loads these as modules, we output a `package.json` into each `./build/esm` output directory containing simply `{ "type": "module" }`. This works because whenever node.js is asked to load a `.js` file, it tries to work out whether this is CJS or ESM by searching up through parent directories until it finds a `package.json` with the `type` set. This PR: - Adds a Rollup plugin that injects the `package.json` into the ESM output - Adds the package `exports` that @AbhiPrasad worked out for #10833 - Fixes an import issue with `next/router` which is CJS (at least in v10) - Fixes an import issue with `@prisma/instrumentation` which is CJS - Ensures that CJS only integrations are not included in the `@sentry/node` default integrations when running as ESM This PR also makes some unrelated changes: - Changes to the old Node SDKs to allow the tests to pass - Removes the bundle size analysis CI job as it doesn't appears to be compatible with the node ESM output
1 parent 8d0b779 commit 7f2e804

File tree

44 files changed

+373
-64
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+373
-64
lines changed

.github/workflows/build.yml

-33
Original file line numberDiff line numberDiff line change
@@ -279,39 +279,6 @@ jobs:
279279
# `job_build` can't see `job_install_deps` and what it returned)
280280
dependency_cache_key: ${{ needs.job_install_deps.outputs.dependency_cache_key }}
281281

282-
job_size_check:
283-
name: Size Check
284-
needs: [job_get_metadata, job_build]
285-
timeout-minutes: 15
286-
runs-on: ubuntu-20.04
287-
if:
288-
github.event_name == 'pull_request' || needs.job_get_metadata.outputs.is_develop == 'true' ||
289-
needs.job_get_metadata.outputs.is_release == 'true'
290-
steps:
291-
- name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
292-
uses: actions/checkout@v4
293-
with:
294-
ref: ${{ env.HEAD_COMMIT }}
295-
- name: Set up Node
296-
uses: actions/setup-node@v4
297-
with:
298-
# The size limit action runs `yarn` and `yarn build` when this job is executed on
299-
# use Node 14 for now.
300-
node-version: '14'
301-
- name: Restore caches
302-
uses: ./.github/actions/restore-cache
303-
env:
304-
DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }}
305-
- name: Check bundle sizes
306-
uses: getsentry/size-limit-action@runForBranch
307-
with:
308-
github_token: ${{ secrets.GITHUB_TOKEN }}
309-
skip_step: build
310-
main_branch: develop
311-
# When on release branch, we want to always run
312-
# Else, we fall back to the default handling of the action
313-
run_for_branch: ${{ (needs.job_get_metadata.outputs.is_release == 'true' && 'true') || '' }}
314-
315282
job_lint:
316283
name: Lint
317284
# Even though the linter only checks source code, not built code, it needs the built code in order check that all

dev-packages/rollup-utils/npmHelpers.mjs

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
makeSetSDKSourcePlugin,
1818
makeSucrasePlugin,
1919
} from './plugins/index.mjs';
20+
import { makePackageNodeEsm } from './plugins/make-esm-plugin.mjs';
2021
import { mergePlugins } from './utils.mjs';
2122

2223
const packageDotJSON = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), './package.json'), { encoding: 'utf8' }));
@@ -104,6 +105,7 @@ export function makeBaseNPMConfig(options = {}) {
104105
...builtinModules,
105106
...Object.keys(packageDotJSON.dependencies || {}),
106107
...Object.keys(packageDotJSON.peerDependencies || {}),
108+
...Object.keys(packageDotJSON.optionalDependencies || {}),
107109
],
108110
};
109111

@@ -120,7 +122,7 @@ export function makeBaseNPMConfig(options = {}) {
120122
export function makeNPMConfigVariants(baseConfig) {
121123
const variantSpecificConfigs = [
122124
{ output: { format: 'cjs', dir: path.join(baseConfig.output.dir, 'cjs') } },
123-
{ output: { format: 'esm', dir: path.join(baseConfig.output.dir, 'esm') } },
125+
{ output: { format: 'esm', dir: path.join(baseConfig.output.dir, 'esm'), plugins: [makePackageNodeEsm()] } },
124126
];
125127

126128
return variantSpecificConfigs.map(variant => deepMerge(baseConfig, variant));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* Outputs a package.json file with {type: module} in the root of the output directory so that Node
3+
* treats .js files as ESM.
4+
*/
5+
export function makePackageNodeEsm() {
6+
return {
7+
name: 'make-package-node-esm',
8+
generateBundle() {
9+
this.emitFile({
10+
type: 'asset',
11+
fileName: 'package.json',
12+
source: '{ "type": "module" }',
13+
});
14+
},
15+
};
16+
}

packages/browser/package.json

+13
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@
1818
"main": "build/npm/cjs/index.js",
1919
"module": "build/npm/esm/index.js",
2020
"types": "build/npm/types/index.d.ts",
21+
"exports": {
22+
"./package.json": "./package.json",
23+
".": {
24+
"import": {
25+
"types": "./build/npm/types/index.d.ts",
26+
"default": "./build/npm/esm/index.js"
27+
},
28+
"require": {
29+
"types": "./build/npm.types/index.d.ts",
30+
"default": "./build/npm/cjs/index.js"
31+
}
32+
}
33+
},
2134
"typesVersions": {
2235
"<4.9": {
2336
"build/npm/types/index.d.ts": [

packages/bun/package.json

+13
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@
1818
"main": "build/esm/index.js",
1919
"module": "build/esm/index.js",
2020
"types": "build/types/index.d.ts",
21+
"exports": {
22+
"./package.json": "./package.json",
23+
".": {
24+
"import": {
25+
"types": "./build/types/index.d.ts",
26+
"default": "./build/esm/index.js"
27+
},
28+
"require": {
29+
"types": "./build/types/index.d.ts",
30+
"default": "./build/cjs/index.js"
31+
}
32+
}
33+
},
2134
"typesVersions": {
2235
"<4.9": {
2336
"build/npm/types/index.d.ts": [

packages/core/package.json

+13
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@
1818
"main": "build/cjs/index.js",
1919
"module": "build/esm/index.js",
2020
"types": "build/types/index.d.ts",
21+
"exports": {
22+
"./package.json": "./package.json",
23+
".": {
24+
"import": {
25+
"types": "./build/types/index.d.ts",
26+
"default": "./build/esm/index.js"
27+
},
28+
"require": {
29+
"types": "./build/types/index.d.ts",
30+
"default": "./build/cjs/index.js"
31+
}
32+
}
33+
},
2134
"typesVersions": {
2235
"<4.9": {
2336
"build/types/index.d.ts": [

packages/deno/package.json

+9
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@
88
"license": "MIT",
99
"module": "build/index.mjs",
1010
"types": "build/index.d.ts",
11+
"exports": {
12+
"./package.json": "./package.json",
13+
".": {
14+
"import": {
15+
"types": "./build/index.d.ts",
16+
"default": "./build/index.mjs"
17+
}
18+
}
19+
},
1120
"publishConfig": {
1221
"access": "public"
1322
},

packages/feedback/package.json

+13
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@
1818
"main": "build/npm/cjs/index.js",
1919
"module": "build/npm/esm/index.js",
2020
"types": "build/npm/types/index.d.ts",
21+
"exports": {
22+
"./package.json": "./package.json",
23+
".": {
24+
"import": {
25+
"types": "./build/npm/types/index.d.ts",
26+
"default": "./build/npm/esm/index.js"
27+
},
28+
"require": {
29+
"types": "./build/npm/types/index.d.ts",
30+
"default": "./build/npm/cjs/index.js"
31+
}
32+
}
33+
},
2134
"typesVersions": {
2235
"<4.9": {
2336
"build/npm/types/index.d.ts": [

packages/gatsby/package.json

+13
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@
2626
"main": "build/cjs/index.js",
2727
"module": "build/esm/index.js",
2828
"types": "build/types/index.d.ts",
29+
"exports": {
30+
"./package.json": "./package.json",
31+
".": {
32+
"import": {
33+
"types": "./build/types/index.d.ts",
34+
"default": "./build/esm/index.js"
35+
},
36+
"require": {
37+
"types": "./build/types/index.d.ts",
38+
"default": "./build/cjs/index.js"
39+
}
40+
}
41+
},
2942
"typesVersions": {
3043
"<4.9": {
3144
"build/types/index.d.ts": [

packages/integration-shims/package.json

+13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@
55
"main": "build/cjs/index.js",
66
"module": "build/esm/index.js",
77
"types": "build/types/index.d.ts",
8+
"exports": {
9+
"./package.json": "./package.json",
10+
".": {
11+
"import": {
12+
"types": "./build/types/index.d.ts",
13+
"default": "./build/esm/index.js"
14+
},
15+
"require": {
16+
"types": "./build/types/index.d.ts",
17+
"default": "./build/cjs/index.js"
18+
}
19+
}
20+
},
821
"typesVersions": {
922
"<4.9": {
1023
"build/types/index.d.ts": [

packages/nextjs/package.json

+11
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@
1313
"module": "build/esm/index.server.js",
1414
"browser": "build/esm/index.client.js",
1515
"types": "build/types/index.types.d.ts",
16+
"exports": {
17+
"./package.json": "./package.json",
18+
".": {
19+
"browser": {
20+
"import": "./build/esm/index.client.js",
21+
"require": "./build/cjs/index.client.js"
22+
},
23+
"node": "./build/cjs/index.server.js",
24+
"types": "./build/types/index.types.d.ts"
25+
}
26+
},
1627
"typesVersions": {
1728
"<4.9": {
1829
"build/npm/types/index.d.ts": [

packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,13 @@ import {
1414
stripUrlQueryAndFragment,
1515
} from '@sentry/utils';
1616
import type { NEXT_DATA as NextData } from 'next/dist/next-server/lib/utils';
17-
import { default as Router } from 'next/router';
17+
import RouterImport from 'next/router';
18+
19+
// next/router v10 is CJS
20+
//
21+
// For ESM/CJS interoperability 'reasons', depending on how this file is loaded, Router might be on the default export
22+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
23+
const Router: typeof RouterImport = RouterImport.events ? RouterImport : (RouterImport as any).default;
1824

1925
import { DEBUG_BUILD } from '../../common/debug-build';
2026

packages/nextjs/test/performance/pagesRouterInstrumentation.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { WINDOW } from '@sentry/react';
22
import { JSDOM } from 'jsdom';
33
import type { NEXT_DATA as NextData } from 'next/dist/next-server/lib/utils';
4-
import { default as Router } from 'next/router';
4+
import Router from 'next/router';
55

66
import { pagesRouterInstrumentation } from '../../src/client/routing/pagesRouterRoutingInstrumentation';
77

packages/node-experimental/package.json

+13
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@
1818
"main": "build/cjs/index.js",
1919
"module": "build/esm/index.js",
2020
"types": "build/types/index.d.ts",
21+
"exports": {
22+
"./package.json": "./package.json",
23+
".": {
24+
"import": {
25+
"types": "./build/types/index.d.ts",
26+
"default": "./build/esm/index.js"
27+
},
28+
"require": {
29+
"types": "./build/types/index.d.ts",
30+
"default": "./build/cjs/index.js"
31+
}
32+
}
33+
},
2134
"typesVersions": {
2235
"<4.9": {
2336
"build/types/index.d.ts": [

packages/node-experimental/src/integrations/node-fetch.ts

+13-13
Original file line numberDiff line numberDiff line change
@@ -27,30 +27,29 @@ const _nativeNodeFetchIntegration = ((options: NodeFetchOptions = {}) => {
2727
const _breadcrumbs = typeof options.breadcrumbs === 'undefined' ? true : options.breadcrumbs;
2828
const _ignoreOutgoingRequests = options.ignoreOutgoingRequests;
2929

30-
function getInstrumentation(): [Instrumentation] | void {
30+
async function getInstrumentation(): Promise<[Instrumentation] | void> {
3131
// Only add NodeFetch if Node >= 16, as previous versions do not support it
3232
if (NODE_MAJOR < 16) {
3333
return;
3434
}
3535

3636
try {
37-
// eslint-disable-next-line @typescript-eslint/no-var-requires
38-
const { FetchInstrumentation } = require('opentelemetry-instrumentation-fetch-node');
37+
const pkg = await import('opentelemetry-instrumentation-fetch-node');
3938
return [
40-
new FetchInstrumentation({
39+
new pkg.FetchInstrumentation({
4140
ignoreRequestHook: (request: { origin?: string }) => {
4241
const url = request.origin;
4342
return _ignoreOutgoingRequests && url && _ignoreOutgoingRequests(url);
4443
},
45-
4644
onRequest: ({ span }: { span: Span }) => {
4745
_updateSpan(span);
4846

4947
if (_breadcrumbs) {
5048
_addRequestBreadcrumb(span);
5149
}
5250
},
53-
}),
51+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
52+
} as any),
5453
];
5554
} catch (error) {
5655
// Could not load instrumentation
@@ -60,13 +59,14 @@ const _nativeNodeFetchIntegration = ((options: NodeFetchOptions = {}) => {
6059
return {
6160
name: 'NodeFetch',
6261
setupOnce() {
63-
const instrumentations = getInstrumentation();
64-
65-
if (instrumentations) {
66-
registerInstrumentations({
67-
instrumentations,
68-
});
69-
}
62+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
63+
getInstrumentation().then(instrumentations => {
64+
if (instrumentations) {
65+
registerInstrumentations({
66+
instrumentations,
67+
});
68+
}
69+
});
7070
},
7171
};
7272
}) satisfies IntegrationFn;

packages/node-experimental/src/integrations/tracing/prisma.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { registerInstrumentations } from '@opentelemetry/instrumentation';
2-
import { PrismaInstrumentation } from '@prisma/instrumentation';
2+
// When importing CJS modules into an ESM module, we cannot import the named exports directly.
3+
import * as prismaInstrumentation from '@prisma/instrumentation';
34
import { defineIntegration } from '@sentry/core';
45
import type { IntegrationFn } from '@sentry/types';
56

@@ -10,7 +11,7 @@ const _prismaIntegration = (() => {
1011
registerInstrumentations({
1112
instrumentations: [
1213
// does not have a hook to adjust spans & add origin
13-
new PrismaInstrumentation({}),
14+
new prismaInstrumentation.PrismaInstrumentation({}),
1415
],
1516
});
1617
},

packages/node-experimental/src/sdk/init.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ import { defaultStackParser, getSentryRelease } from './api';
3939
import { NodeClient } from './client';
4040
import { initOtel } from './initOtel';
4141

42+
function getCjsOnlyIntegrations(isCjs = typeof require !== 'undefined'): Integration[] {
43+
return isCjs ? [modulesIntegration()] : [];
44+
}
45+
4246
/** Get the default integrations for the Node Experimental SDK. */
4347
export function getDefaultIntegrations(options: Options): Integration[] {
4448
// TODO
@@ -59,9 +63,8 @@ export function getDefaultIntegrations(options: Options): Integration[] {
5963
contextLinesIntegration(),
6064
localVariablesIntegration(),
6165
nodeContextIntegration(),
62-
modulesIntegration(),
6366
httpIntegration(),
64-
nativeNodeFetchIntegration(),
67+
...getCjsOnlyIntegrations(),
6568
...(hasTracingEnabled(options) ? getAutoPerformanceIntegrations() : []),
6669
];
6770
}

packages/node-experimental/tsconfig.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"include": ["src/**/*"],
55

66
"compilerOptions": {
7-
"lib": ["es2017"]
7+
"lib": ["es2017"],
8+
"module": "Node16"
89
}
910
}

0 commit comments

Comments
 (0)