UNPKG

dd-trace

Version:

Datadog APM tracing client for JavaScript

99 lines (76 loc) 3.28 kB
'use strict' const { readFileSync } = require('fs') const { join } = require('path') const { pathToFileURL } = require('url') const log = require('../../../../dd-trace/src/log') const { create } = require('../../../../../vendor/dist/@apm-js-collab/code-transformer') const { traceAsyncIterator, traceIterator } = require('./transforms') const instrumentations = require('./instrumentations') // `dc-polyfill` is referenced from injected `require()` (CJS) and `import` // (ESM) statements that the transformer splices into the rewritten module. // `require()` accepts an absolute filesystem path; the ESM resolver rejects it // with `ERR_INVALID_MODULE_SPECIFIER` and needs a `file://` URL instead. We // pre-compute both forms here so each matcher hands the transformer a // specifier that is valid for the module type it is rewriting. let dcPolyfillCjs let dcPolyfillEsm try { const resolved = require.resolve('dc-polyfill') dcPolyfillCjs = resolved.replaceAll('\\', '/') dcPolyfillEsm = pathToFileURL(resolved).href } catch { // The `dc-polyfill` module is unavailable for some reason (like bundling). // Let's just keep the default of using `diagnostics-channel` as a fallback // which works for most Node versions. } /** @type {Record<string, string>} map of module base name to version */ const moduleVersions = {} const disabled = new Set() const matcherCjs = create(instrumentations, dcPolyfillCjs) const matcherEsm = create(instrumentations, dcPolyfillEsm) for (const matcher of [matcherCjs, matcherEsm]) { matcher.addTransform('traceIterator', traceIterator) matcher.addTransform('traceAsyncIterator', traceAsyncIterator) } function rewrite (content, filename, format) { if (!content) return content if (!filename.includes('node_modules')) return content filename = filename.replace('file://', '') const moduleType = format === 'module' ? 'esm' : 'cjs' const [modulePath] = filename.split('/node_modules/').reverse() const moduleParts = modulePath.split('/') const splitIndex = moduleParts[0].startsWith('@') ? 2 : 1 const moduleName = moduleParts.slice(0, splitIndex).join('/') const filePath = moduleParts.slice(splitIndex).join('/') const version = getVersion(filename, filePath) if (disabled.has(moduleName)) return content const matcher = moduleType === 'esm' ? matcherEsm : matcherCjs const transformer = matcher.getTransformer(moduleName, version, filePath) if (!transformer) return content try { // TODO: pass existing sourcemap as input for remapping const { code, map } = transformer.transform(content, moduleType) if (!map) return code const inlineMap = Buffer.from(map).toString('base64') return code + '\n' + `//# sourceMappingURL=data:application/json;base64,${inlineMap}` } catch (e) { log.error(e) } return content } function disable (instrumentation) { disabled.add(instrumentation) } function getVersion (filename, filePath) { const [basename] = filename.split(filePath) if (!moduleVersions[basename]) { try { const pkg = JSON.parse(readFileSync( join(basename, 'package.json'), 'utf8' )) moduleVersions[basename] = pkg.version } catch {} } return moduleVersions[basename] } module.exports = { rewrite, disable }