Madogiwa Blog

主に技術系の学習メモに使っていきます。

Vue.js: Vueアプリケーション内で発生したエラーをRollbarに通知するメモ📝

個人のWebサービスのエラー通知にRollbarを利用しているのですが、以下の通りデフォルトではVueアプリケーション内で発生したエラー(コンポーネント内のロジックでエラーが起きたケース等)は通知されないことに今更気づき、通知されるようにしたので対応したことをメモ📝

Add Rollbar to Vue’s global error handler. Uncaught exceptions within the Vue app are sent to this handler, and appear in the browser console, but are not sent > to the browser’s global error handler. Adding Rollbar here ensures that these errors are reported. https://docs.rollbar.com/docs/vue-js

Rollbarへ通知するためのプラグインを追加

ほぼドキュメントの通りですが、app.config.errorHandlerを設定し、Vueアプリケーションでエラーが発生した場合にRollbarへの通知ロジックが実行されるようなPluginを用意して、

// NOTE: Rollbarにブラウザ側で発生したエラー通知を送る機能を有効化
// https://docs.rollbar.com/docs/browser-js

import Rollbar from "rollbar";
import type { App } from "vue";

const token = process.env.ROLLBAR_POST_CLIENT_ITEM_ACCESS_TOKEN;
const rollbarParamsValidation = () => !!(!(NODE_ENV === "test") && token);

const rollbar = new Rollbar({
  accessToken: token,
  captureUncaught: true,
  captureUnhandledRejections: true,
  enabled: rollbarParamsValidation(),
  payload: {
    environment: NODE_ENV,
  },
});

export default rollbar;

// NOTE: Vueアプリケーション内部のエラーをRollbarに通知するプラグイン
// https://docs.rollbar.com/docs/vue-js
export const VueRollbarPlugin = {
  install(app: App) {
    app.config.errorHandler = (error, _vm, info) => {
      // NOTE: 公式docでは以下となっていたが、おそらく送信時にvueComponentをjsonに変換するのにブラウザがフリーズするため送信しないようにした。
      // rollbar.error(error, { vueComponent: vm, info });
      rollbar.error(error as Error, { info });
      // NOTE: 本来はエラーをコンソールに出力するのは避けるべきだが、
      // エラー発生時にconsole.errorに表示しておいた方が確認しやすいため残している
      // eslint-disable-next-line no-console
      console.error(error);
    };
    app.provide("rollbar", rollbar);
  },
};

Pluginの有効化

以下のようにcreateApp時にuseして有効化するようにしました。

import NavigationBar from "@js/components/organisms/NavigationBar.vue";
import { VueRollbarPlugin } from "@js/services/Rollbar";
import { createApp } from "vue";

const app = createAppWithDefault({ components: { NavigationBar } });
app.use(VueRollbarPlugin);
app.mount("#vue-header");

また、毎回これを書くのはめんどくさそうに思ったのでcreateAppWithDefaultを定義して、

import { createApp, type CreateAppFunction } from "vue";
import { VueRollbarPlugin } from "./Rollbar";
type VueCreateAppType = CreateAppFunction<Element>;

export const createAppWithDefault: VueCreateAppType = (...args: Parameters<VueCreateAppType>) => {
  const app = createApp(...args);
  app.use(VueRollbarPlugin);
  return app;
};

以下のように利用するとcreateApp後に自動的にuseするようにしました。

import NavigationBar from "@js/components/organisms/NavigationBar.vue";
import { createAppWithDefault } from "@js/services/Vue";

const app = createAppWithDefault({ components: { NavigationBar } });
app.mount("#vue-header");

おまけ:Sentryのケース

Sentryの場合には、@sentry/vueが提供されていてDocumentの通りに設定すると自動的にapp.config.errorHandlerをよしなに設定してくれるっぽかった。

https://docs.sentry.io/platforms/javascript/guides/vue/

Ruby on Rails: capybara-playwright-driverでsystem specを実行するMEMO📝

system specをseleniumではなくcapybara-playwright-driverを使ってplaywrightで動かしてみたのでメモ📝

github.com

playwrightに関しては以下に以前まとめていたので貼り付けておく。

madogiwa0124.hatenablog.com

準備

必要なライブラリをinstallします。

Gemfile

  gem "capybara-playwright-driver"
  gem "playwright-ruby-client"

package.json

    "@playwright/test": "1.41.1",

以下を実行してplaywrightで利用するbrowsersをinstallします。

$ npx playwright install

playwright driverを利用する

RSpec.configuredriven_by:playwrightを指定するだけでplaywrightでsystem specが実行されます🤖

# NOTE: 推奨値に変更
# > It is recommended to set the timeout to 15-30 seconds for Playwright driver.
# > https://playwright-ruby-client.vercel.app/docs/article/guides/rails_integration#update-timeout
Capybara.default_max_wait_time = 15

RSpec.configure do |config|
  config.before(:each, type: :system) do |example|
    example.metadata[:js] ? driven_by(:headless_chrome) : driven_by(:rack_test)
    if example.metadata[:js]
      headless = ENV.fetch("PLAYWIGHT_HEADLESS", "true") == "true"
      browser = ENV.fetch("PLAYWIGHT_BROWSER_TYPE", :chromium).to_sym
      driven_by(:playwright, options: {browser_type: browser, headless: headless})
    else
      driven_by(:rack_test)
    end
  end

スクリーン録画やトレースを利用する

またplaywrightのtraceや録画をcallbackを活用することで取得できるようだったので、

以下のような感じでtmp配下に配置するようにしてみました。

def save_playwright_screen_record(
  example,
  save_dir: "tmp/capybara/screen_records",
  file_name_proc: ->(example) { example.full_description.split(" ").join("_") }
)
  Capybara.current_session.driver.on_save_screenrecord do |video_path|
    file_name = "#{file_name_proc.call(example)}#{Pathname.new(video_path).extname}"
    FileUtils.mkdir_p Rails.root.join(save_dir)
    FileUtils.cp video_path, Rails.root.join(save_dir, file_name)
  end
end

def save_playwright_trace(
  example,
  save_dir: "tmp/capybara/traces",
  file_name_proc: ->(example) { example.full_description.split(" ").join("_") }
)
  Capybara.current_session.driver.on_save_trace do |trace_zip_path|
    file_name = "#{file_name_proc.call(example)}#{Pathname.new(trace_zip_path).extname}"
    FileUtils.mkdir_p Rails.root.join(save_dir)
    FileUtils.cp trace_zip_path, Rails.root.join(save_dir, file_name)
  end
end
RSpec.configure do |config|
  config.before(:each, type: :system) do |example|
    example.metadata[:js] ? driven_by(:headless_chrome) : driven_by(:rack_test)
    if example.metadata[:js]
      headless = ENV.fetch("PLAYWIGHT_HEADLESS", "true") == "true"
      browser = ENV.fetch("PLAYWIGHT_BROWSER_TYPE", :chromium).to_sym
      driven_by(:playwright, options: {browser_type: browser, headless: headless})
+    save_playwright_trace(example) if example.metadata[:trace]
+    save_playwright_screen_record(example) if example.metadata[:screen_record]
    else
      driven_by(:rack_test)
    end
  end

ビデオの保存はdriven_by(:playwright, options: {browser_type: browser, headless: headless, record_video_dir: "tmp/capybara/screen_record"})のようにrecord_video_dirを指定するだけも任意のディレクトリに配置できるがファイル名をいい感じにしたかったのでゴリっと書いてみた🦍 (もっといいやり方ありそうだけど。。。)

we can record the videos without specifying record_video_dir explicitly or preparing a temporary directory. capybara-playwright-driver automatically prepare and set record_video_dir internally. https://playwright-ruby-client.vercel.app/docs/article/guides/recording_video#using-screen-recording-from-capybara-driver

おわりに

capybara-playwright-driver 簡単に乗り換えられてありがたい・・・!!🙏✨

参考

note.com

個人のWebサービスをVite 5.0系にアップデートしたので対応したことMEMO📝

2023/11/16 Vite v5がリリースされました⚡️

vitejs.dev

リリースから2ヶ月経ちライブラリ側のサポートも揃ってきたので、 個人のWebサービスをVite 4系からVite v5系にアップデートしたので対応したこととかをメモしておきます📝

前提事項

利用しているライブラリは以下のような感じです。(Ruby on Railsのサービスでフロントエンド関連のファイルのビルドにViteを利用しています。)

Vite v5アップデートで対応したこと

以下のドキュメントに公式のマイグレーションガイドがあるので読みつつ進めました。

以下に具体的に必要だった対応事項を記載します。

Vite v5 からCSSファイルはトップレベルに含まれなくなったのでstylesheet_pack_tagの利用をやめる

以下の通りVite v5 からCSSファイルはminifest.jsonのトップレベルに含まれなくなりました。

対応する CSS ファイルは manifest.json ファイルのトップレベル項目としてリストされない Vite 4 では、JavaScript エントリーポイントに対応する CSS ファイルもマニフェストファイル (build.manifest) のトップレベルエントリーとしてリストされていました。 これらのエントリーは意図せずに追加されたもので、単純な場合にのみ機能しました。 https://ja.vitejs.dev/guide/migration.html#%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8B-css-%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AF-manifest-json-%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E3%83%88%E3%83%83%E3%83%95%E3%82%9A%E3%83%AC%E3%83%98%E3%82%99%E3%83%AB%E9%A0%85%E7%9B%AE%E3%81%A8%E3%81%97%E3%81%A6%E3%83%AA%E3%82%B9%E3%83%88%E3%81%95%E3%82%8C%E3%81%AA%E3%81%84

元々は以下のような感じでトップレベルのCSSを直接参照していたのですが上記の破壊的変更によりよみこめなくなりエラーとなっていました。

<%= vite_stylesheet_tag 'application' %>

読み込めなくなっていたもののvite_javascript_tagの以下の処理でentryのjs配下のcssstylesheet_link_tagで挿入されるため、

  def vite_javascript_tag(*names, # 省略
    # 省略
    tags << stylesheet_link_tag(*entries.fetch(:stylesheets), media: media, **options) unless skip_style_tags

    tags
  end

ref: https://github.com/ElMassimo/vite_ruby/blob/369facf440f41162efee825a87d9491ff83a03b8/vite_rails/lib/vite_rails/tag_helpers.rb#L52

stylesheet_pack_tagの利用自体が不要だったので削除しました。

- <%= vite_stylesheet_tag 'application' %>

Vite v5からcjsが非推奨になったのでpackge.jsonのtypeをmoduleに変更する

以下の通りVite v5から CJS Node API の非推奨化され警告が出ていたので、

CJS Node API の非推奨化 Vite の CJS Node API は非推奨になりました。 今後、require('vite') を呼ぶときは、非推奨の警告メッセージが出力されます。 ファイルやフレームワークを更新して、代わりに Vite の ESM ビルドをインポートするとよいでしょう。 https://ja.vitejs.dev/guide/migration.html#cjs-node-api-%E3%81%AE%E9%9D%9E%E6%8E%A8%E5%A5%A8%E5%8C%96

The CJS build of Vite's Node API is deprecated. See https://vitejs.dev/guide/troubleshooting.html#vite-cjs-node-api-deprecated for more details.

以下のようにcjsの記法をESM形式に修正する or ファイルの拡張子を.cjsに修正し、

- const autoprefixer = require("autoprefixer");
+ import autoprefixer from 'autoprefixer';
+ import tailwindcss from 'tailwindcss';

- module.exports = {
-   plugins: [require("tailwindcss"), autoprefixer({ grid: true })],
+ export default {
+   plugins: [tailwindcss, autoprefixer({ grid: true })],
};

package.jsontypemoduleに変更しました。

+ "type": "module",

historeによるコンポーネントカタログのbuild時に、まだ警告が出ていますがhistoire側の問題なので対応していません

github.com

おわりに

基本的にVite公式のマイグレーションガイドに従ってバージョンアップできました🙌 日本語訳もあり非常にありがたかったです・・・!

Git管理されたプロジェクト内の配下のTypeScriptプロジェクトだけ型チェックを行うスクリプトメモ📝

Git管理されたプロジェクトにいくつかTypeScriptのプロジェクトがあり、それらをまとめて型チェックしたくスクリプトを書いたのでメモ📝

以下がそのスクリプトです。

git ls-files -- root/dir | \
grep "tsconfig.json" | sed 's/tsconfig.json//' | \
xargs -I {} sh -c "cd {}; echo \"\n\"; pwd; npm install; npm run tsc --noEmit"

git ls-files -- root/dirで特定ディレクトリ配下のGit管理下のファイルリストを出力し、 grep "tsconfig.json" | sed 's/tsconfig.json//'tsconfig.jsonが配置されているディレクトリリストを取得します。

その後、xargs -I {} sh -c "cd {}; echo \"\n\"; pwd; npm install; npm run tsc --noEmit"でそれぞれのディレクトリに移動し、npm install後にtscで型チェックします。 ※上述のスクリプトでは対象が分かりやすいようにpwdで現在のディレクトリも表示しています。

ちなみにxargsで実行された複数のスクリプトの一部が失敗した場合、exit codeは123になるようです。 GitHub Actionsはexit codeが0以外は失敗扱いになるから、xargsで成功と失敗が混在した場合には失敗になるので便利ですね!

docs.github.com

参考

yujiorama.hatenablog.com

git-scm.com

Ruby: Ruby 3.3アップデート後に`bin/rails`系のコマンド実行時にconcurrent-rubyでSegmentation faultが発生する件のメモ📝

個人のWebサービスRuby 3.3アップデート後にbin/rails系のコマンド実行時にconcurrent-rubyでSegmentation faultが発生したのでメモ📝

$ bin/rails c

/app/vendor/bundle/ruby/3.3.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/atomic/lock_local_var.rb:14: [BUG] Segmentation fault at 0x007effff843e06c0
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [aarch64-linux]

結論としてはRuby v3.3のバグのようだった。(arm64系のCPUを利用していると発生するっぽい?)

bugs.ruby-lang.org

concurrent-ruby側でもrubyの問題として上記のチケットへ誘導するためのissueが立てられている🎫

github.com

masterには対応のPRがマージされておりRuby 3.3.1のリリースで修正されるとのこと🙏

github.com

アップデートはRuby 3.3.1まで待つことにした・・・!

余談) Ruby 3.3のバックポートのリストを見ると、今後リリースされる予定のbug fixとかを見れることを知った📝

bugs.ruby-lang.org

2023年振り返り📝

今年も一年が終わるということで今年も振り返ってみる。

今年の振り返り

アウトプット

BLOG

BLOGは、今年1年で51記事書いていて、1週間に1記事ぐらい書いてるので、そこそこ書いてた✍ (月間PV数は5000PVくらい)

pickup

madogiwa0124.hatenablog.com

madogiwa0124.hatenablog.com

madogiwa0124.hatenablog.com

madogiwa0124.hatenablog.com

公開したツールとか

今思い返すとgemとかツールとか作って公開した💎

RailsのView層を隠蔽してControllerから直接Vue.jsのコンポーネント等をpropsを渡しつつレンダリングできるgem

github.com

Vue.js/TypeScript/AstroでGitHub Pagesで公開したRails newのコマンドをブラウザからポチポチして作れるツール

github.com

OSS活動

今年はsimpackerのテストの修正やpicture_pack_tagを追加したり、

github.com

github.com

Vue.js系のドキュメントのtypo等々の修正をしたり、

github.com

github.com

chibivueというVue.jsの内部実装を解説しているドキュメントのtypo等々の修正をしたりしてました。

github.com

ref: 今年のPRリスト

インプット

今年は、結構頑張ってて107冊読んでました。 年間100冊以上読んだのは初めてでしたが、2~3日に1冊のペースで読めると、タイムラグが少なく必要なタイミングで書籍からインプットできるので良いなぁと今になっては思います。

印象に残ってる本を載せておきます。

技術書

オブジェクトに着目してUIを考えるというのがバックエンドからキャリアを始めたのもあってか分かりやすく納得感があった。

ボブおじさんの新作、Clean Coder等で語られている内容も多かったけど、改めて読んでも良い内容だった。

SmartHRさんのデザインシステムの検討から運用までの流れをベースにデザインシステムについて書かれた本、かなり現場感もあり良かった。

マネジメント系は結構あった気がするけど、技術系のエンジニアキャリアについて書かれており興味深かった。

CDNやnginxといったリバースプロキシを駆使して、どうパフォーマンス良く・安定的に配信するか書かれていて、この辺りの知識があんまり無かったので勉強になった。

ブラウザにおけるパフォーマンス改善の指標やツールの使い方が一通り解説されていて分かりやすく勉強になった。

その他

話題の三体とプロジェクト・ヘイルメアリーを読んだ。どちらもSF長編でドラマチックな展開で面白かった。

インテル元CEOの本、マネジメントの本だけど実際の業務を進め方を考える上でも本書で書かれている、テコ作用の利用や、モチベーションの向上と訓練といった内容は参考になったと思う。

ずっと読みたいなーと思っていた小説だったけど、読んでみたらやはり面白かった。

結構古典を色々読んでみたけど、やはり長く読まれている本は面白かった。

去年の目標と結果

目標 メモ 結果
心身ともに健やか/穏やかに過ごす 割と頑張りすぎず健やか・穏やかに過ごせた気がするので O
フロントエンド系のスキルをキャッチアップして一定活躍出来るようになる 静的解析やビルド周りのキャッチアップは結構できた。個人の開発でCSSフレームワークから独自実装に移行したりして一定のUI開発はできそうな感じがしてきたが、業務レベルかと言われると微妙 
身近な課題を解決するなにかを作る Vue.js、Astro、GitHub Pagesみたいな構成でツールを実装して公開できた O

今年の目標

目標 メモ
publicな場での活動をもうちょっと頑張る 会社のテックブログとか、登壇とかOSS活動とか今年よりも、もうちょっと頑張りたい。
新しい言語にチャレンジする 基本的な構文・思想を理解しており読み書きできる言語を増やす(Rustとか
フロントエンド技術を用いたUI実装の一定自信を持照るようになる フロントエンドエンジニアとしてのコアなスキルな気がするので自信を持っておきたい。
心身ともに穏やかに健康で過ごす あまり無理せず穏やかに検討で過ごす

おわりに

今年はプライベートで色々と大きなイベントがあり、バタバタした一年だったが一定コードも書きつつ目標もそこそこ達成できたかなと思うので、良い一年だったかなと思う。

業務的にもずっと対応していたVue3のバージョンアップをEOLまでにやり切ることができてよかった。

来年も、無理せず心身ともに健やか/穏やかに楽しみを感じて過ごしていけるようにしたい。

Vue.js: `eslint-plugin-vue-scoped-css`を使って静的解析でScoped CSSを強制するメモ📝

Vue.jsのSFCを利用している場合に極力Scoped CSSを利用してCSSの統制を取りたいと思いますが、eslint-plugin-vue-scoped-css を利用すると静的解析で強制することができることを最近学んだのでメモ📝

future-architect.github.io

やり方は簡単で公式ドキュメントの通りですが、以下でインストールし、

$ npm install --save-dev eslint-plugin-vue-scoped-css vue-eslint-parser

ESLintの設定ファイルに以下を追記するだけです。

module.exports = {
  extends: [
+ 'plugin:vue-scoped-css/base'
  ],
  rules: {
+ "vue-scoped-css/enforce-style-type": ["error", { allows: ["scoped"] }],
  }
}

これでScoped CSSを利用してない場合にESLintで以下のエラーが発生するようになりました✨

  159:1  error  Missing attribute `scoped`  vue-scoped-css/enforce-style-type

eslint-plugin-vue-scoped-css、他にもVue3で非推奨な::v-deepや、Vue3で破壊的変更があったtransition系のclassの利用も検知できるの非常に便利ですね🙌