Skip to content

Commit cc7e708

Browse files
authored
fix(esbuild): do not crash and build properly when esbuild is used with ESM modules (#6513)
1 parent 77d7e65 commit cc7e708

20 files changed

Lines changed: 829 additions & 40 deletions

.github/workflows/platform.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,17 @@ jobs:
4343
dd_api_key: ${{ secrets.DD_API_KEY }}
4444

4545

46+
esbuild:
47+
runs-on: ubuntu-latest
48+
steps:
49+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
50+
- uses: ./.github/actions/node/oldest-maintenance-lts
51+
- uses: ./.github/actions/install
52+
- run: yarn test:esbuild:ci
53+
- uses: ./.github/actions/node/latest
54+
- run: yarn test:esbuild:ci
55+
- uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
56+
4657
instrumentation-bluebird:
4758
runs-on: ubuntu-latest
4859
env:

integration-tests/esbuild/build-and-test-koa.mjs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
#!/usr/bin/env node
22

33
import fs from 'fs/promises'
4+
import assert from 'assert'
45

56
import * as esbuild from 'esbuild'
6-
import assert from 'assert'
77

8+
import versions from '../../version.js'
89
import ddPlugin from '../../esbuild.js'
910

11+
const { NODE_MAJOR } = versions
12+
1013
try {
1114
await esbuild.build({
1215
entryPoints: ['./koa.mjs'],
@@ -27,7 +30,12 @@ try {
2730
// Verify instrumentation
2831
const data = await fs.readFile('./outfile.js', 'utf8')
2932

30-
assert.match(data, /^ {8}package: "koa",$/m, 'Bundle should contain the koa instrumentation')
33+
if (NODE_MAJOR >= 22) {
34+
// it is resolved as ESM module only in node 22+, becaues the require.resolve accepts conditions in node 22+
35+
assert.match(data, /register.*koa.mjs".*"koa"\);$/m, 'Bundle should contain the koa ESM instrumentation')
36+
} else {
37+
assert.match(data, /^ {8}package: "koa",$/m, 'Bundle should contain the koa CJS instrumentation')
38+
}
3139
assert.match(data, /^ {8}package: "@koa\/router",$/m, 'Bundle should contain the @koa/router instrumentation')
3240

3341
console.log('ok') // eslint-disable-line no-console
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env node
2+
3+
import path from 'node:path'
4+
import { fileURLToPath } from 'node:url'
5+
6+
import esbuild from 'esbuild'
7+
8+
import ddPlugin from 'dd-trace/esbuild.js'
9+
10+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
11+
const SCRIPT = path.join(__dirname, 'hono-out.cjs')
12+
13+
const entryPoint = path.join(__dirname, 'hono.mjs')
14+
const external = [
15+
// required if you use native metrics
16+
'@datadog/native-metrics',
17+
18+
// required if you use profiling
19+
'@datadog/pprof',
20+
21+
// @openfeature/core is a peer dependency of @openfeature/server-sdk
22+
// which is used by @datadog/openfeature-node-server
23+
'@openfeature/core',
24+
25+
// required if you use Datadog security features
26+
'@datadog/native-appsec',
27+
'@datadog/native-iast-taint-tracking',
28+
'@datadog/native-iast-rewriter',
29+
30+
// required if you encounter graphql errors during the build step
31+
'graphql/language/visitor',
32+
'graphql/language/printer',
33+
'graphql/utilities',
34+
]
35+
36+
esbuild.build({
37+
entryPoints: [entryPoint],
38+
outfile: SCRIPT,
39+
minify: false,
40+
bundle: true,
41+
platform: 'node',
42+
target: 'node22',
43+
plugins: [ddPlugin],
44+
format: 'cjs',
45+
external
46+
})
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env node
2+
3+
import path from 'node:path'
4+
import { fileURLToPath } from 'node:url'
5+
6+
import esbuild from 'esbuild'
7+
8+
import ddPlugin from 'dd-trace/esbuild.js'
9+
10+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
11+
const SCRIPT = path.join(__dirname, 'hono-out.mjs')
12+
13+
const entryPoint = path.join(__dirname, 'hono.mjs')
14+
const external = [
15+
// required if you use native metrics
16+
'@datadog/native-metrics',
17+
18+
// required if you use profiling
19+
'@datadog/pprof',
20+
21+
// @openfeature/core is a peer dependency of @openfeature/server-sdk
22+
// which is used by @datadog/openfeature-node-server
23+
'@openfeature/core',
24+
25+
// required if you use Datadog security features
26+
'@datadog/native-appsec',
27+
'@datadog/native-iast-taint-tracking',
28+
'@datadog/native-iast-rewriter',
29+
30+
// required if you encounter graphql errors during the build step
31+
'graphql/language/visitor',
32+
'graphql/language/printer',
33+
'graphql/utilities',
34+
]
35+
36+
esbuild.build({
37+
entryPoints: [entryPoint],
38+
outfile: SCRIPT,
39+
minify: false,
40+
bundle: true,
41+
platform: 'node',
42+
target: 'node22',
43+
plugins: [ddPlugin],
44+
format: 'esm',
45+
external
46+
})
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env node
2+
3+
import path from 'node:path'
4+
import { fileURLToPath } from 'node:url'
5+
6+
import esbuild from 'esbuild'
7+
8+
import ddPlugin from 'dd-trace/esbuild.js'
9+
10+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
11+
const SCRIPT = path.join(__dirname, 'esm-http-test-out.cjs')
12+
const entryPoint = path.join(__dirname, 'esm-http-test.mjs')
13+
14+
const external = [
15+
// required if you use native metrics
16+
'@datadog/native-metrics',
17+
18+
// required if you use profiling
19+
'@datadog/pprof',
20+
21+
// @openfeature/core is a peer dependency of @openfeature/server-sdk
22+
// which is used by @datadog/openfeature-node-server
23+
'@openfeature/core',
24+
25+
// required if you use Datadog security features
26+
'@datadog/native-appsec',
27+
'@datadog/native-iast-taint-tracking',
28+
'@datadog/native-iast-rewriter',
29+
30+
// required if you encounter graphql errors during the build step
31+
'graphql/language/visitor',
32+
'graphql/language/printer',
33+
'graphql/utilities',
34+
]
35+
36+
esbuild.build({
37+
entryPoints: [entryPoint],
38+
outfile: SCRIPT,
39+
minify: false,
40+
bundle: true,
41+
platform: 'node',
42+
target: 'node22',
43+
plugins: [ddPlugin],
44+
format: 'cjs',
45+
external
46+
})
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env node
2+
3+
import path from 'node:path'
4+
import { fileURLToPath } from 'node:url'
5+
6+
import esbuild from 'esbuild'
7+
8+
import ddPlugin from 'dd-trace/esbuild.js'
9+
10+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
11+
const SCRIPT = path.join(__dirname, 'esm-http-test-out.mjs')
12+
const entryPoint = path.join(__dirname, 'esm-http-test.mjs')
13+
14+
const external = [
15+
// required if you use native metrics
16+
'@datadog/native-metrics',
17+
18+
// required if you use profiling
19+
'@datadog/pprof',
20+
21+
// @openfeature/core is a peer dependency of @openfeature/server-sdk
22+
// which is used by @datadog/openfeature-node-server
23+
'@openfeature/core',
24+
25+
// required if you use Datadog security features
26+
'@datadog/native-appsec',
27+
'@datadog/native-iast-taint-tracking',
28+
'@datadog/native-iast-rewriter',
29+
30+
// required if you encounter graphql errors during the build step
31+
'graphql/language/visitor',
32+
'graphql/language/printer',
33+
'graphql/utilities',
34+
]
35+
36+
esbuild.build({
37+
entryPoints: [entryPoint],
38+
outfile: SCRIPT,
39+
minify: false,
40+
bundle: true,
41+
platform: 'node',
42+
target: 'node22',
43+
plugins: [ddPlugin],
44+
format: 'esm',
45+
external
46+
})
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import './init.mjs'
2+
3+
import http from 'http'
4+
5+
process.env.DD_TRACE_DEBUG = 'true'
6+
7+
const server = http.createServer((req, res) => {
8+
res.end('Egun on!')
9+
})
10+
11+
server.listen(0, () => {
12+
const port = server.address().port
13+
process.send({ port })
14+
})
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
'use strict'
2+
3+
const path = require('node:path')
4+
const assert = require('node:assert')
5+
const { execSync } = require('node:child_process')
6+
7+
const axios = require('axios')
8+
9+
const { FakeAgent, spawnProc, createSandbox } = require('../helpers')
10+
11+
const esbuildVersions = ['latest', '0.16.12']
12+
13+
function findWebSpan (payload) {
14+
for (const trace of payload) {
15+
for (const span of trace) {
16+
if (span.type === 'web') {
17+
return span
18+
}
19+
}
20+
}
21+
throw new Error('web span not found')
22+
}
23+
24+
esbuildVersions.forEach((version) => {
25+
describe('ESM is built and runs as expected in a sandbox', () => {
26+
let sandbox, agent, cwd
27+
28+
before(async () => {
29+
sandbox = await createSandbox([`esbuild@${version}`, 'hono', '@hono/node-server'], false, [__dirname])
30+
cwd = sandbox.folder
31+
})
32+
33+
beforeEach(async () => {
34+
agent = await new FakeAgent().start()
35+
})
36+
37+
after(() => {
38+
sandbox.remove()
39+
})
40+
41+
afterEach(() => {
42+
agent.stop()
43+
})
44+
45+
it('should build basic esm http server exporting esm and create web traces at runtime', async () => {
46+
const builder = path.join(cwd, 'esbuild', 'build.esm-http-output-esm.mjs')
47+
execSync(`node ${builder}`, { cwd })
48+
49+
const appFile = path.join(cwd, 'esbuild', 'esm-http-test-out.mjs')
50+
const proc = await spawnProc(appFile, {
51+
cwd,
52+
env: {
53+
DD_TRACE_AGENT_URL: `http://localhost:${agent.port}`
54+
},
55+
stdio: 'pipe',
56+
})
57+
58+
await Promise.all([
59+
agent.assertMessageReceived(({ payload }) => {
60+
// http web spans are created in bundled application
61+
const webSpan = findWebSpan(payload)
62+
assert.strictEqual(webSpan.name, 'web.request')
63+
}, 2_500),
64+
axios.get(proc.url)
65+
])
66+
})
67+
68+
it('should build basic esm http server exporting cjs and create web traces at runtime', async () => {
69+
const builder = path.join(cwd, 'esbuild', 'build.esm-http-output-cjs.mjs')
70+
execSync(`node ${builder}`, { cwd })
71+
72+
const appFile = path.join(cwd, 'esbuild', 'esm-http-test-out.cjs')
73+
const proc = await spawnProc(appFile, {
74+
cwd,
75+
env: {
76+
DD_TRACE_AGENT_URL: `http://localhost:${agent.port}`
77+
},
78+
stdio: 'pipe',
79+
})
80+
81+
await Promise.all([
82+
agent.assertMessageReceived(({ payload }) => {
83+
// http web spans are created in bundled application
84+
const webSpan = findWebSpan(payload)
85+
assert.strictEqual(webSpan.name, 'web.request')
86+
}, 2_500),
87+
axios.get(proc.url)
88+
])
89+
})
90+
91+
it('should build basic hono server exporting esm and create web traces at runtime', async () => {
92+
const builder = path.join(cwd, 'esbuild', 'build.esm-hono-output-esm.mjs')
93+
execSync(`node ${builder}`, { cwd })
94+
95+
const appFile = path.join(cwd, 'esbuild', 'hono-out.mjs')
96+
const proc = await spawnProc(appFile, {
97+
cwd,
98+
env: {
99+
DD_TRACE_AGENT_URL: `http://localhost:${agent.port}`
100+
},
101+
stdio: 'pipe',
102+
})
103+
104+
await Promise.all([
105+
agent.assertMessageReceived(({ payload }) => {
106+
// http web spans are created in bundled application
107+
const webSpan = findWebSpan(payload)
108+
assert.strictEqual(webSpan.name, 'hono.request')
109+
}, 2_500),
110+
axios.get(proc.url)
111+
])
112+
})
113+
114+
it('should build basic hono server exporting cjs and create web traces at runtime', async () => {
115+
const builder = path.join(cwd, 'esbuild', 'build.esm-hono-output-cjs.mjs')
116+
execSync(`node ${builder}`, { cwd })
117+
118+
const appFile = path.join(cwd, 'esbuild', 'hono-out.cjs')
119+
const proc = await spawnProc(appFile, {
120+
cwd,
121+
env: {
122+
DD_TRACE_AGENT_URL: `http://localhost:${agent.port}`
123+
},
124+
stdio: 'pipe',
125+
})
126+
127+
await Promise.all([
128+
agent.assertMessageReceived(({ payload }) => {
129+
// http web spans are created in bundled application
130+
const webSpan = findWebSpan(payload)
131+
assert.strictEqual(webSpan.name, 'hono.request')
132+
}, 2_500),
133+
axios.get(proc.url)
134+
])
135+
})
136+
})
137+
})

0 commit comments

Comments
 (0)