sugiken のメモ帳

知ったことを書いていく

create-react-app をあまりいじらずに Sentry にだけ SourceMap を送る方法

タイトルみたいな方法が意外と調べても出てこなかったのでまとめる。多分みんなちゃんと webpack の設定を書くことで解決してるんだろうな

SourceMap について

SourceMap は Minify されたりして圧縮&難読化された JS ファイルを元のソースコードレベルまで戻すことのできる鍵みたいなものだ。なのでこれを本番環境には deploy したくない。ありがたいことに Chrome の dev tool を使うと SourceMap があるだけで簡単に元のソースコードを見ることができてしまうからだ。

しかし今回の目的である Sentry のようなエラー収集 SaaS の画面では、難読化されたコードではなくソースコードでエラーの箇所が示されたほうが絶対にわかりやすい。これを実現するには create-react-app に二手間くらい加えないといけなかった。

JSファイルとの関係

例えば create-react-app で build したいくつかの JS ファイルを見ると、最終行にコメントアウト.js.map というファイル名の path( sourceMappingURL ) が書かれている。その path にあるファイルがこのファイルの SourceMap だ。 SourceMap は設定にもよるが、JS ファイルと同じディレクトリに生成されているのではないだろうか。

create-react-app(CRA) における SourceMap

単に source map を生成したくない時のビルドコマンドは以下の様にすれば良い。

GENERATE_SOURCEMAP=false react-scripts build

こうすると、上述した bundle された JS ファイルの最終行にコメントはされないし、当然 SourceMap も生成されない。本番環境ではこの状態で deploy されるべきである。調べた限り SourceMap について CRA が用意しているオプションはこれだけだった。

これだと Build 時にしか生成できない SourceMap を Sentry に送るのは無理だ。(念の書いておくが、Buildごとに bundle のされ方が変わるので、SourceMap 有り無しで2回 build するのは意味がない)

Sentry と SourceMap

上述した通り、SourceMap を送信してあげると収集したエラーの発生箇所を分かりやすく表示してくれる。ここまで読めば分かると思うが、ユーザーのクライアントで bundle された JS の情報を受け取り、それを Sentry 側で予めアップロードしてくれている SourceMap で解読できるからだ。

Sentry にはどうやってアップロードするか

これにはいくつかの方法がある CLI ツールもあれば、色々なライブラリ、プラットフォーム向けのツールを Sentry が作ってくれている。

Uploading Source Maps for JavaScript | Sentry Documentation

今回は以下の webpack 向けの plugin を使う。 docs.sentry.io

Sentry の仕様

ここまで読むととある疑問が浮かぶだろう。

Sentry はどうやって bundle された JS ファイルと SourceMap を突合するのか

設定にもよるが、bundle された結果複数の JS ファイルが deploy されることは多々ある。当然 SourceMap はその JS ファルの数だけ生成される。通常 bundle された JS ファイルに書かれる sourceMappingURL コメントによって SourceMap を特定するが、今回の方法では sourceMappingURL は JS ファイルに含まないようにしている。

これは、build するときは sourceMappingURL ありで bundle するが、deploy 直前に shell script でそのコメントを消すことで解決している。

create-react-app のカスタマイズ

では本題だ。そもそも CRA のカスタマイズは rewire といって推奨されるものではない。なぜなら CRA のアップデートによってそのカスタマイズが正常に動かなくなったり、カスタマイズが CRA にどういう影響を与えるのかが未知数だからだ(調べればいいが)。なのでこれ自体は自己責任でおなしゃす。

使用するのはこちら github.com

プロジェクトルートに config-overrides.js というファイルを作り、そこに webpack の設定を書くことで CRA をカスタマイズできる。コマンドの実行は react-scripts build から react-app-rewired build になるので注意。

override の内容

そのまんま貼り付けます。

const SentryCliPlugin = require('@sentry/webpack-plugin');

let plugins = [];

// ref. https://tech.pepabo.com/2021/03/10/enabling-source-map-with-sentry-and-heroku/
if (process.env.NODE_ENV === 'production') {
  plugins.push(
    new SentryCliPlugin({
      authToken: process.env.SENTRY_AUTH_TOKEN,
      org: process.env.SENTRY_ORG,
      project: process.env.SENTRY_PROJECT,
      include: './build',
      urlPrefix: '~/',
    })
  );
}

module.exports = {
  devServer: function (configFunction) {
    return function (proxy, allowedHost) {
      const config = configFunction(proxy, allowedHost);
      return config;
    };
  },
  jest: function (config) {
    return config;
  },
  // webpack.configのカスタマイズ
  webpack: function (config, env) {
    config.output = {
      ...config.output,
      sourceMapFilename: '[name].[chunkhash].js.map',
    };
    config.plugins = [...config.plugins, ...plugins];
    return config;
  },
};

SentryCliPlugin によって build 時に Sentry に bundle された JS ファイルと SourceMap を送ってくれる。

  • include: build後のファイルたちのパス
  • urlPrefix: SourceMap の build 先ディレクトリからの相対パス(この設定は結構重要)。bundle された JS ファイルは build/static/js 以下に生成されるが、SourceMap は build 直下に生成されていた。なので ~/ としている。

sourceMappingURL を削除

bundle された JS に残っているコメントを削除するための shell script を用意する 参考↓ Sentry だけに Production 環境のソースマップを表示させる | grgr-dkrkのブログ

#!/bin/bash
find 'build' -type f \( -iname \*.js -o -iname \*.css \) -exec sed -i -E 's/\/(\/|\*)# sourceMappingURL=[^ ]*\.(js|css)\.map(\*\/)?//' {} +

package.json

その結果できた package.json はこんな感じ。 最後に *.js.map を自体を削除している(これも shell script ファイルにまとめてよかったな)。

  "scripts": {
    "start": "react-scripts start",
    "build": "export REACT_APP_VERSION=$(git rev-parse HEAD) && react-app-rewired build; yarn removeSourceMapRef; rm -rf ./build/*.js.map",
    "removeSourceMapRef": "bash removeSourceMapRef.sh",
  },

export REACT_APP_VERSION=$(git rev-parse HEAD) はクライアントで利用して Sentry の Release 機能に使っている。 Sentry では git commit hash を Release name とすることができる。クライアントでその commit hash を指定することで、「この Release version で発生した Issue」というのが分かるのである。

こんな感じで init ↓

Sentry.init({
  dsn: process.env.REACT_APP_SENTRY_DSN,
  integrations: [new Integrations.BrowserTracing()],
  release: process.env.REACT_APP_VERSION,
  tracesSampleRate: 1.0,
});

最後に

ここまでするなら webpack の設定をゼロから書いても一緒かなーと思いつつ、現時点ではいい感じに CRA の恩恵を受けつつ Sentry に SourceMap を送れている。