开发技巧

给 Vite 前端接入 Sentry,并在构建时上传 source map

给 Vite 前端接入 Sentry,并在构建时上传 source map:线上报错的堆栈能还原到 src/cart.js 第几行,但 .map 文件和源码不会公开给访客。附带构建后检查脚本。

BINBHO · 图文教程

给 Vite 前端接入 Sentry,并在构建时上传 source map:线上报错的堆栈能还原到 src/cart.js 第几行,但 .map 文件和源码不会公开给访客。附带构建后检查脚本。

先看清成品

本机用 Vite 8.3.5 + @sentry/browser 11.8.0 + @sentry/vite-plugin 11.8.0 实际构建:插件提示没有令牌、不创建 release、不上传;产物中注入了 Debug ID,没有 sourceMappingURL,.map 文件在构建结束后被删除,检查脚本通过。没有提供真实令牌上传,也没有在浏览器中触发错误并到 Sentry 查看还原后的堆栈。

注意:Sentry 有免费和付费计划,额度与价格以官方价格页为准。错误数据里可能包含用户个人信息,上线前确认采集范围符合你所在地区的个人信息保护法规(如《个人信息保护法》),并在隐私政策中说明。

做完之后你会得到什么?

成果 说明
前端错误上报 带 release、environment
source map 上传 构建时自动完成
不公开源码 hidden source map + 上传后删除
检查脚本 Debug ID 已注入、无 sourceMappingURL、无残留 .map

需要准备哪些环境?

项目 要求 说明
Node.js 当前 LTS(本机 v22)
Sentry 项目 平台选 Browser JavaScript DSN 写进 VITE_SENTRY_DSN
Auth Token 组织令牌,用于上传 只放在 CI 的 Secret 里

文件结构

25-sentry-B-src/
├── package.json
├── vite.config.js
├── index.html
├── check-maps.mjs
└── src/
    ├── main.js
    └── cart.js

第 1 步:安装

npm install @sentry/browser
npm install -D vite @sentry/vite-plugin

package.json:

{
  "name": "shop-web",
  "private": true,
  "version": "0.1.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "check-maps": "node check-maps.mjs"
  },
  "dependencies": {
    "@sentry/browser": "^11.8.0"
  },
  "devDependencies": {
    "@sentry/vite-plugin": "^11.8.0",
    "vite": "^8.3.5"
  }
}

第 2 步:前端代码

src/main.js:

import * as Sentry from "@sentry/browser";
import { calcTotal } from "./cart.js";

Sentry.init({
  dsn: import.meta.env.VITE_SENTRY_DSN, // 为空时不发送
  release: __APP_RELEASE__,
  environment: import.meta.env.MODE,
  sampleRate: 1.0,
});

document.getElementById("pay").addEventListener("click", () => {
  // 故意传错数据,制造一个带堆栈的错误
  const total = calcTotal([{ price: 10, qty: 2 }, { price: 5 }]);
  alert(`合计 ${total} 元`);
});

src/cart.js:

export function calcTotal(items) {
  return items.reduce((sum, item) => {
    if (!Number.isInteger(item.qty)) {
      throw new TypeError(`商品数量无效:${JSON.stringify(item)}`);
    }
    return sum + item.price * item.qty;
  }, 0);
}

index.html:

<!doctype html>
<html lang="zh-CN">
  <head><meta charset="utf-8"><title>小店</title></head>
  <body>
    <button id="pay">去支付</button>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

第 3 步:Vite 配置

vite.config.js:

import { defineConfig } from "vite";
import { sentryVitePlugin } from "@sentry/vite-plugin";

const release = process.env.APP_RELEASE || "shop-web@0.1.0";

export default defineConfig({
  define: {
    __APP_RELEASE__: JSON.stringify(release),
  },
  build: {
    sourcemap: "hidden", // 生成 .map 但不在 JS 末尾写 sourceMappingURL,浏览器看不到
  },
  plugins: [
    sentryVitePlugin({
      org: process.env.SENTRY_ORG,
      project: process.env.SENTRY_PROJECT,
      authToken: process.env.SENTRY_AUTH_TOKEN, // 没有令牌时插件只注入 Debug ID,不上传
      release: { name: release },
      sourcemaps: {
        filesToDeleteAfterUpload: ["./dist/**/*.map"], // 构建结束删除 .map(本机实测:没有令牌未上传时也会删除)
      },
      telemetry: false,
    }),
  ],
});

要点:

  • sourcemap: "hidden":生成 .map,但 JS 里不写引用,浏览器开发者工具不会自动加载。
  • 插件在产物中注入 Debug ID,Sentry 用它把错误和上传的 source map 对应起来,不依赖文件名。
  • release.name 与 Sentry.init 的 release 一致。

第 4 步:构建并检查

check-maps.mjs:

// 构建后检查:JS 里注入了 Debug ID,且不包含 sourceMappingURL;上传后 dist 中不应再有 .map
import { readdirSync, readFileSync } from "node:fs";
import { join } from "node:path";

const dir = "dist/assets";
const files = readdirSync(dir);
const js = files.filter((f) => f.endsWith(".js"));
const maps = files.filter((f) => f.endsWith(".map"));
let ok = true;
for (const f of js) {
  const code = readFileSync(join(dir, f), "utf8");
  const hasId = /sentry-dbid-|_sentryDebugIds/.test(code);
  const hasUrl = /sourceMappingURL=/.test(code);
  console.log(`${f}: Debug ID ${hasId ? "✓" : "✗"},sourceMappingURL ${hasUrl ? "✗ 存在" : "✓ 无"}`);
  if (!hasId || hasUrl) ok = false;
}
console.log(`.map 文件:${maps.length} 个${maps.length ? "(未上传或未删除,不要部署到线上)" : ""}`);
process.exit(ok ? 0 : 1);
npm run build
npm run check-maps

检查结果(本机实际输出,未设置令牌):

[sentry-vite-plugin] Warning: No auth token provided. Will not create release. ...
[sentry-vite-plugin] Warning: No auth token provided. Will not upload source maps. ...
dist/assets/index-BT0ABWbZ.js  90.74 kB │ gzip: 31.48 kB │ map: 1,002.90 kB
index-BT0ABWbZ.js: Debug ID ✓,sourceMappingURL ✓ 无
.map 文件:0 个

第 5 步:在 CI 中上传(本文未执行)

- run: npm ci && npm run build && npm run check-maps
  env:
    SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
    SENTRY_ORG: 你的组织 slug
    SENTRY_PROJECT: 你的项目 slug
    APP_RELEASE: shop-web@${{ github.sha }}
    VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }}

预期结果:构建日志显示上传成功;部署后点“去支付”,Sentry 里 TypeError: 商品数量无效 的堆栈指向 src/cart.js 的 throw 那一行。

调试示例

本机构建时没有设置 SENTRY_AUTH_TOKEN,插件只发出警告、构建照常成功(见第 4 步输出)。这适合本地开发;但在 CI 里同样不会失败,容易“以为上传了其实没有”。建议在 CI 中先检查变量:

test -n "$SENTRY_AUTH_TOKEN" || { echo "缺少 SENTRY_AUTH_TOKEN"; exit 1; }

另外,本机实测 filesToDeleteAfterUpload 在没有上传时也删除了 .map。如果你需要在本地保留 .map 排查问题,把这一项改为只在 CI 中启用。

出错了怎么办?

现象 原因 解决
堆栈仍是压缩后的代码 没上传或 Debug ID 不匹配 确认 CI 日志有上传记录;上传和部署要用同一次构建产物
上传报 401 / 403 令牌无效或权限不足 重新生成组织令牌
线上能下载到 .map 用了 sourcemap: true 且没删除 用 hidden,并保留删除配置
事件没有 release 前端和插件 release 不一致 两处都用 APP_RELEASE
本地开发也上报了 设置了 VITE_SENTRY_DSN 本地不设,或按 MODE 区分

模板规则

  • source map 只给 Sentry,不给访客。
  • 令牌只放 CI Secret,不写进代码或 .env。
  • 构建后运行检查脚本再部署。

项目资源

用途 地址
Vite 上传 source map https://docs.sentry.io/platforms/javascript/sourcemaps/uploading/vite/
Sentry 浏览器 SDK https://docs.sentry.io/platforms/javascript/
Auth Token https://docs.sentry.io/account/auth-tokens/
Releases https://docs.sentry.io/product/releases/
Vite 构建选项 https://vite.dev/config/build-options

前端错误只有能还原到源码才有用。source map 交给 Sentry、不交给访客,既能排查问题,又不会把整份源码公开出去。