Skip to content

Commit b470331

Browse files
committed
feat: Add rollup, esbuild, vite, rolldown, webpack and rspack bundler plugins
1 parent 1756975 commit b470331

File tree

96 files changed

+5016
-0
lines changed

Some content is hidden

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

96 files changed

+5016
-0
lines changed

.github/workflows/CI.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: 'Build & Test'
2+
on: [push, pull_request]
3+
4+
jobs:
5+
build:
6+
name: Build & Test
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v4
10+
- uses: actions/setup-node@v4
11+
with:
12+
node-version-file: 'package.json'
13+
- name: Install
14+
run: yarn install
15+
- name: Build
16+
run: yarn build
17+
- name: Run Unit Tests
18+
run: yarn test

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules/
2+
dist/
3+
.DS_Store

README.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,110 @@
11
# javascript-debug-ids
2+
3+
Bundler plugins to inject [Debug
4+
IDs](https://github.com/tc39/source-map/blob/main/proposals/debug-id.md) into
5+
both source and source-maps, making build artifacts self-identifying.
6+
7+
## Rollup
8+
9+
`rollup.config.mjs`
10+
```ts
11+
import debugIds from "@sentry/debug-ids/rollup";
12+
13+
export default {
14+
input: "./src/main.js",
15+
plugins: [debugIds()],
16+
output: {
17+
dir: "dist",
18+
format: "esm",
19+
sourcemap: true,
20+
},
21+
};
22+
```
23+
24+
## webpack
25+
26+
`webpack.config.mjs`
27+
```ts
28+
import { DebugIdWebpackPlugin } from "@sentry/debug-ids/webpack";
29+
30+
export default {
31+
entry: "./src/main.js",
32+
plugins: [new DebugIdWebpackPlugin()],
33+
mode: "production",
34+
devtool: "source-map",
35+
output: {
36+
filename: "main.js",
37+
path: "./dist",
38+
},
39+
};
40+
```
41+
42+
## esbuild
43+
44+
`build.mjs`
45+
```ts
46+
import * as esbuild from "esbuild";
47+
import debugIds from "@sentry/debug-ids/esbuild";
48+
49+
await esbuild.build({
50+
entryPoints: ["./src/main.js"],
51+
bundle: true,
52+
format: "esm",
53+
sourcemap: true,
54+
minify: true,
55+
plugins: [debugIds],
56+
outdir: "./dist",
57+
});
58+
```
59+
60+
## vite
61+
62+
`vite.config.mjs`
63+
```ts
64+
import debugIds from "@sentry/debug-ids/vite";
65+
66+
export default {
67+
root: "./src",
68+
mode: "production",
69+
plugins: [debugIds()],
70+
build: {
71+
outDir: "./dist",
72+
sourcemap: true,
73+
},
74+
};
75+
```
76+
77+
## rspack
78+
79+
`rspack.config.mjs`
80+
```ts
81+
import { DebugIdRspackPlugin } from "@sentry/debug-ids/rspack";
82+
83+
export default {
84+
entry: "./src/main.js",
85+
plugins: [new DebugIdRspackPlugin()],
86+
mode: "production",
87+
devtool: "source-map",
88+
output: {
89+
filename: "main.js",
90+
path: "./dist",
91+
},
92+
};
93+
```
94+
95+
## Rolldown
96+
97+
`rolldown.config.mjs`
98+
```ts
99+
import debugIds from "@sentry/debug-ids/rolldown";
100+
101+
export default {
102+
input: "./src/main.js",
103+
plugins: [debugIds()],
104+
output: {
105+
dir: "dist",
106+
format: "esm",
107+
sourcemap: true,
108+
},
109+
};
110+
```

package.json

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"name": "@sentry/debug-ids",
3+
"version": "0.0.1",
4+
"license": "MIT",
5+
"exports": {
6+
"rollup": {
7+
"require": "./dist/rollup.js",
8+
"import": "./dist/rollup.mjs"
9+
},
10+
"webpack": {
11+
"require": "./dist/webpack.js",
12+
"import": "./dist/webpack.mjs"
13+
},
14+
"esbuild": {
15+
"require": "./dist/esbuild.js",
16+
"import": "./dist/esbuild.mjs"
17+
},
18+
"vite": {
19+
"require": "./dist/vite.js",
20+
"import": "./dist/vite.mjs"
21+
},
22+
"rspack": {
23+
"require": "./dist/rspack.js",
24+
"import": "./dist/rspack.mjs"
25+
},
26+
"rolldown": {
27+
"require": "./dist/rolldown.js",
28+
"import": "./dist/rolldown.mjs"
29+
}
30+
},
31+
"scripts": {
32+
"build": "pkgroll --clean-dist --target=es2018 --target=node14 --sourcemap",
33+
"test": "vitest run ./test/**/*.test.ts"
34+
},
35+
"devDependencies": {
36+
"@rspack/cli": "^1.0.8",
37+
"@rspack/core": "^1.0.8",
38+
"@types/node": "14",
39+
"esbuild": "^0.24.0",
40+
"pkgroll": "^2.5.0",
41+
"rolldown": "^0.13.2",
42+
"rollup": "^4.22.5",
43+
"typescript": "^5.6.2",
44+
"vite": "^5.4.8",
45+
"vitest": "^2.1.1",
46+
"webpack": "^5.95.0",
47+
"webpack-cli": "^5.1.4"
48+
},
49+
"volta": {
50+
"node": "20.17.0"
51+
}
52+
}

src/common.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import * as crypto from "crypto";
2+
3+
export const DEFAULT_EXTENSIONS = [".js", ".mjs", ".cjs"];
4+
5+
export function stringToUUID(str: string): string {
6+
const md5sum = crypto.createHash("md5");
7+
md5sum.update(str);
8+
const md5Hash = md5sum.digest("hex");
9+
10+
// Position 16 is fixed to either 8, 9, a, or b in the uuid v4 spec (10xx in binary)
11+
// RFC 4122 section 4.4
12+
const v4variant = ["8", "9", "a", "b"][
13+
md5Hash.substring(16, 17).charCodeAt(0) % 4
14+
] as string;
15+
16+
return (
17+
md5Hash.substring(0, 8) +
18+
"-" +
19+
md5Hash.substring(8, 12) +
20+
"-4" +
21+
md5Hash.substring(13, 16) +
22+
"-" +
23+
v4variant +
24+
md5Hash.substring(17, 20) +
25+
"-" +
26+
md5Hash.substring(20)
27+
).toLowerCase();
28+
}
29+
30+
export function addDebugIdToSource(input: string, debugId: string): string {
31+
return input.replace(
32+
/\s*(?:\/\/# debugId=.+)?\s*(\/\/# sourceMappingURL=.+)?\s*$/,
33+
`\n//# debugId=${debugId}\n$1`
34+
);
35+
}
36+
37+
export function addDebugIdToSourcemap(input: string, debugId: string): string {
38+
const sourceMapObj = JSON.parse(input);
39+
sourceMapObj.debugId = debugId;
40+
return JSON.stringify(sourceMapObj);
41+
}

src/esbuild.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { PluginBuild } from "esbuild";
2+
import {
3+
addDebugIdToSource,
4+
DEFAULT_EXTENSIONS,
5+
stringToUUID,
6+
addDebugIdToSourcemap,
7+
} from "./common";
8+
import { readFile, writeFile } from "fs/promises";
9+
import * as path from "path";
10+
11+
export default {
12+
name: "esbuild-plugin-debug-ids",
13+
setup({ onEnd, initialOptions }: PluginBuild) {
14+
if (
15+
initialOptions.bundle !== true ||
16+
initialOptions.sourcemap === undefined
17+
) {
18+
return;
19+
}
20+
21+
initialOptions.metafile = true;
22+
23+
onEnd(async (result) => {
24+
const assets = Object.keys(result.metafile?.outputs || {});
25+
const assetsWithCorrectExt = assets.filter((file) =>
26+
DEFAULT_EXTENSIONS.some((ext) => file.endsWith(ext))
27+
);
28+
29+
if (assetsWithCorrectExt.length === 0) {
30+
return;
31+
}
32+
33+
const promises = assetsWithCorrectExt.map(async (key) => {
34+
const sourcemapKey = `${key}.map`;
35+
36+
// If we don't have a sourcemap, don't do anything
37+
if (!assets.includes(sourcemapKey)) {
38+
return Promise.resolve();
39+
}
40+
41+
const sourcePath = path.join(process.cwd(), key);
42+
const source = await readFile(sourcePath, { encoding: "utf-8" });
43+
const debugId = stringToUUID(source);
44+
const updatedSource = addDebugIdToSource(source, debugId);
45+
await writeFile(sourcePath, updatedSource);
46+
47+
const sourcemapPath = path.join(process.cwd(), sourcemapKey);
48+
const sourcemap = await readFile(sourcemapPath, { encoding: "utf-8" });
49+
const updatedSourcemap = addDebugIdToSourcemap(sourcemap, debugId);
50+
await writeFile(sourcemapPath, updatedSourcemap);
51+
});
52+
53+
await Promise.all(promises);
54+
});
55+
},
56+
};

src/rolldown.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import rollupPlugin from "./rollup";
2+
3+
export default rollupPlugin;

src/rollup.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { OutputAsset, OutputChunk, Plugin } from "rollup";
2+
import {
3+
addDebugIdToSourcemap,
4+
addDebugIdToSource,
5+
stringToUUID,
6+
DEFAULT_EXTENSIONS,
7+
} from "./common";
8+
9+
export default function debugIds(): Plugin {
10+
return {
11+
name: "rollup-plugin-debug-ids",
12+
generateBundle: function (
13+
_,
14+
bundle: { [fileName: string]: OutputAsset | OutputChunk }
15+
) {
16+
for (const [key, value] of Object.entries(bundle)) {
17+
// We only add debugId where there is a linked sourcemap file
18+
if (!("sourcemapFileName" in value) || !value.sourcemapFileName) {
19+
continue;
20+
}
21+
22+
// We only add to specific file types
23+
if (!DEFAULT_EXTENSIONS.includes(key.slice(-3))) {
24+
continue;
25+
}
26+
27+
// Check we have a sourcemap in the output and it has source property
28+
const sourceMapFile = bundle[value.sourcemapFileName];
29+
if (!sourceMapFile || !("source" in sourceMapFile)) {
30+
continue;
31+
}
32+
33+
const debugId = stringToUUID(value.code);
34+
value.code = addDebugIdToSource(value.code, debugId);
35+
sourceMapFile.source = addDebugIdToSourcemap(
36+
sourceMapFile.source.toString(),
37+
debugId
38+
);
39+
40+
// vite has plugins that run after us which can modify the sourcemap so we
41+
// proxy the sourceMapFile to re-add the debugId if the source gets set again
42+
bundle[value.sourcemapFileName] = new Proxy(
43+
bundle[value.sourcemapFileName],
44+
{
45+
set: function (target, prop, value) {
46+
if (prop === "source") {
47+
target[prop] = addDebugIdToSourcemap(value, debugId);
48+
} else {
49+
target[prop] = value;
50+
}
51+
return true;
52+
},
53+
}
54+
);
55+
}
56+
},
57+
};
58+
}

src/rspack.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { DebugIdWebpackPlugin } from "./webpack";
2+
3+
export class DebugIdRspackPlugin extends DebugIdWebpackPlugin {}

src/vite.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import rollupPlugin from "./rollup";
2+
import { Plugin } from "vite";
3+
4+
export default function debugIds(): Plugin {
5+
return {
6+
...rollupPlugin(),
7+
name: "vite-plugin-debug-ids",
8+
apply: "build",
9+
enforce: "post",
10+
};
11+
}

0 commit comments

Comments
 (0)