getsentry / getsentry/sentry-javascript
Replace SDK package dependencies
- Langage dominant
- TypeScript
- Étoiles
- 8.7k
- Forks
- 1.8k
- Merge moyen
- 1 j 17 h
- PR mergées (30 j)
- 515
Description
Vulnerability alerts and dependency updates haunt us these days, especially when they surface in user-facing dependencies. Obviously, we want to fix these issues as quickly as possible, though with 20+ alerts popping up every week lately (to be clear, the vast majority of these alerts are dev and test dependencies!), the maintenance burden has reached new levels. So we discussed if we can reduce the number of direct and transitive dependencies we ship in our SDK packages. The goal is that we replace dependencies with much smaller, tailored/custom implementations.
I had cursor run an audit across the dependencies of our SDK packages. It presented a couple of opportunities that, after filtering out a couple of suggestions, I think are worth looking into further:
## Replace with Custom Code
### 1. `yargs` — `@sentry/remix`
**Transitive dep count:** ~10 packages (`cliui`, `escalade`, `get-caller-file`,
`require-directory`, `string-width`, `y18n`, `yargs-parser`, `ansi-regex`, `strip-ansi`,
`wrap-ansi`, …)
**Where it is used:**
`packages/remix/scripts/sentry-upload-sourcemaps.js` — a CLI script that ships via the `bin`
field. It parses 8 simple `--flag` / `--flag value` options with no subcommands, no positional
args, no nesting.
**How to replace:**
`util.parseArgs` has been built into Node.js since v18.3.0 and handles everything this script
needs:
```js
// Before
const yargs = require('yargs');
const argv = yargs(process.argv.slice(2))
.option('release', { type: 'string' })
.option('org', { type: 'string' })
.option('project', { type: 'string' })
.option('url', { type: 'string' })
.option('urlPrefix', { type: 'string', default: DEFAULT_URL_PREFIX })
.option('buildPath', { type: 'string', default: DEFAULT_BUILD_PATH })
.option('disableDebugIds', { type: 'boolean', default: false })
.option('deleteAfterUpload', { type: 'boolean', default: true })
.argv;
// After
const { parseArgs } = require('node:util');
const { values: argv } = parseArgs({
args: process.argv.slice(2),
options: {
release: { type: 'string' },
org: { type: 'string' },
project: { type: 'string' },
url: { type: 'string' },
urlPrefix: { type: 'string', default: DEFAULT_URL_PREFIX },
buildPath: { type: 'string', default: DEFAULT_BUILD_PATH },
disableDebugIds: { type: 'boolean', default: false },
deleteAfterUpload: { type: 'boolean', default: true },
},
});
```
The `--usage` / `--help` text can be printed manually on parse error (a plain `console.log`
string). The script is a simple one-shot tool, so the ergonomics of yargs' help formatter are
not required.
---
### 2. `glob` — `@sentry/remix`
**Transitive dep count:** 4 packages (`minimatch`, `brace-expansion`, `balanced-match`,
`minipass`)
**Where it is used:**
`packages/remix/scripts/deleteSourcemaps.js` — finds all `**/*.map` files under a given build
directory and deletes them:
```js
const mapFiles = glob.sync('**/*.map', { cwd: buildPath });
```
**How to replace:**
This is a fully static pattern (`**/*.map`) over a known root directory. A simple recursive
`fs.readdirSync` walk is all that is needed:
```js
const fs = require('node:fs');
const path = require('node:path');
function findMapFiles(dir) {
const results = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
results.push(...findMapFiles(full));
} else if (entry.isFile() && entry.name.endsWith('.map')) {
results.push(full);
}
}
return results;
}
```
---
### 3. `glob` — `@sentry/react-router`
**Transitive dep count:** ~8 packages (`jackspeak`, `minipass`, `path-scurry`, `minimatch`,
`brace-expansion`, `balanced-match`, `@pkgjs/parseargs`, …)
**Where it is used:**
`packages/react-router/src/vite/buildEnd/handleOnBuildEnd.ts` — resolves the
`filesToDeleteAfterUpload` option (typically `["/**/*.map"]`) after a Vite build, then
deletes those files:
```ts
const filePathsToDelete = await glob(updatedFilesToDeleteAfterUpload, {
absolute: true,
nodir: true,
});
```
**How to replace:**
The default value is always a single `**/*.map` pattern. The custom
`filesToDeleteAfterUpload` option that users can pass is already typed as `string | string[]`
matching glob patterns. A small async recursive walker handles the default case; for user-supplied
patterns a lightweight inline matcher is sufficient since `**/*.ext` and `dir/**/*.ext` cover
virtually all real-world values:
```ts
import { readdir } from 'node:fs/promises';
import { join } from 'node:path';
async function findFiles(dir: string, predicate: (name: string) => boolean): Promise {
const results: string[] = [];
for (const entry of await readdir(dir, { withFileTypes: true })) {
const full = join(dir, entry.name);
if (entry.isDirectory()) {
results.push(...(await findFiles(full, predicate)));
} else if (entry.isFile() && predicate(entry.name)) {
results.push(full);
}
}
return results;
}
```
Note: `@sentry/react-router` already requires Node ≥ 20 (`"node": ">=20"` in its `engines`
field), so Node 22's native `fs.glob` could also be used once Node 22 becomes the minimum.
---
### 4. `minimatch` — `@sentry/node`
**Transitive dep count:** 2 packages (`brace-expansion`, `balanced-match`)
**Where it is used:**
`packages/node/src/integrations/tracing/fastify/fastify-otel/index.js` — vendored copy of
`@fastify/otel`. It performs a single glob match of a Fastify route URL against a user-supplied
`ignorePaths` pattern:
```js
const globMatcher = minimatch.minimatch;
this[kIgnorePaths] = routeOptions => globMatcher(routeOptions.url, ignorePaths);
```
**How to replace:**
Route URLs are simple path strings (e.g. `/api/users`, `/health`). Realistic `ignorePaths`
values are things like `/health`, `/api/*`, `/static/**`. Full brace-expansion glob semantics
are not needed. A minimal path glob matcher (~20 lines) handles `*` (single segment) and `**`
(any number of segments):
```js
function matchesGlob(path, pattern) {
// Escape regex special chars except * which we handle ourselves
const regexStr = pattern
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
.replace(/\*\*/g, '{{DOUBLE_STAR}}')
.replace(/\*/g, '[^/]*')
.replace(/{{DOUBLE_STAR}}/g, '.*');
return new RegExp(`^${regexStr}$`).test(path);
}
```
Since `fastify-otel/index.js` is already a vendored file that we maintain (and diverge from
upstream where needed), this change is straightforward.
---
## Replace with a Better Alternative
### 5. `recast` + `@babel/parser` — `@sentry/sveltekit`
**Transitive dep count:** ~8 packages combined
- `recast` brings: `ast-types`, `source-map`, `tslib`
- `@babel/parser` brings: `@babel/types`, `@babel/helper-string-parser`,
`@babel/helper-validator-identifier`, `to-fast-properties`
**Where they are used:**
`packages/sveltekit/src/vite/autoInstrument.ts` and `recastTypescriptParser.ts`. The plugin
reads SvelteKit `+page.ts` / `+layout.server.ts` files at build time, parses them as TypeScript
ASTs, and checks whether a top-level `export const load` or `export function load` declaration
exists. It does **not** actually mutate the AST — if `load` is found, it discards the entire
module and returns a freshly generated wrapper string. Because the AST is only *read* (never
rewritten in place), recast's write-preserving round-trip feature is never exercised, and
`@babel/parser` is only used as the parse step.
**Better alternative: [`acorn`](https://github.com/acornjs/acorn) + [`acorn-typescript`](https://github.com/nicolo-ribaudo/acorn-typescript)**
`acorn` is the parser used by Node.js itself (0 transitive deps). `acorn-typescript` adds
TypeScript syntax support as an acorn plugin (0 transitive deps). The existing check only needs
to walk top-level `ExportNamedDeclaration` nodes, which maps directly onto acorn's AST output.
`magic-string` (already a dependency of `@sentry/sveltekit`) continues to handle the
`append`/`prepend` operations in `injectGlobalValues.ts` unchanged.
**Dep reduction:** Removes `recast`, `ast-types`, `source-map`, `@babel/parser`, `@babel/types`,
`@babel/helper-string-parser`, `@babel/helper-validator-identifier`, `to-fast-properties` (~8
packages). Adds `acorn` + `acorn-typescript` (0 transitive deps each).
Guide de contribution
Ouvrir le guide de contribution
Évaluation
Cette issue n'a pas encore été évaluée.