Madogiwa Blog

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

Ruby on Rails: sidekiq-schedulerでjobをスケジュール実行するメモ

Railsで非同期jobを利用する際にSidekiqを使っている場合、任意のJobをスケジュール実行したいことがあります。 今までは割とsidekiq-cronを使うことが多かったのですが、最近はsidekiq-schedulerの方が活発にメンテナンスされており、そちらを使ってみたので導入方法とかをメモ📝

github.com

導入方法

導入は簡単で以下のようにinstallして

gem install sidekiq-scheduler # Gemfileに追記してbundle install

既存のconfig/sidekiq.ymlに以下のように追記してあげるだけです。

:schedule:
  hello_world:
    cron: '0 * * * * *'   # Runs once per minute
    class: HelloWorld

この辺のsidekiq.rbをいじらずに使えるのも良いですね ✨

dashboardにスケジュールの情報を表示する

web uiも用意されていて以下をroutes.rbに記載すればdashboardにスケジュールを表示できます。

require 'sidekiq/web'
require 'sidekiq-scheduler/web'
mount Sidekiq::Web, at: '/sidekiq'

その他

またsidekiq-cronと同様にredisに既にエンキューされてないかチェックしてそうなので、複数プロセスで立ち上げた際にスケジュールjobが重複して実行されないようになってそう👀

github.com

おわりに

sidekiq-schedulerよさそう!!

ActiveSupport::ActionableErrorを使って例外発生時に画面から任意の処理を実行するメモ📝

Rails 6から導入されたActiveSupport::ActionableErrorを使うと例外発生時に画面から任意の処理が実行できて便利そうだったので使い方とかをメモ📝

github.com

Rails標準ではActiveRecord::PendingMigrationErrorが発生時にこちらを使ってmigrationが行えるようになってますね!

この機能は通常のRails例外ページにボタンを追加し、ブラウザのエラーページでマイグレーションを実行してActiveRecord::PendingMigrationErrorエラーを解決できるようにします。 Rails 6のB面に隠れている地味にうれしい機能たち(翻訳)|TechRacho by BPS株式会社

ActiveSupport::ActionableErrorの使い方

使い方は簡単で、ActiveSupport::ActionableError

class MyException < StandardError
  include ActiveSupport::ActionableError

  action 'Oops!!' do
    puts 'Oops!! raised MyException!!'
  end
end

class PostsController < ApplicationController
  def new
    raise MyException
  end
end

表示されているOops!!をクリックすると以下のようにログにactionに定義したputs 'Oops!! raised MyException!!'が実行されて結果が出力されています。

Oops!! raised MyException!!

任意を例外をActionableにする

以下のようなmoduleを使ってinitializerとかでrequireをすると既存の任意のエラーをActionableErrorにすることができると思います。

require 'active_support/all'

module EnableActionableError
  extend ActiveSupport::Concern

  included do
    include ActiveSupport::ActionableError

    action 'Oops!!' do
      puts 'your any process.'
    end
  end
end

TargetError.include(EnableActionableError)

(出来なかった)webpackのbuildがされてなかったらボタンクリックでbuildできるようにしたい

個人のサービスではsimpackerを使っているのでyarn buildが行われていなかった時にしたくて以下のようなファイルを作ってrequireはしてみたのですが、上手くいかなかった・・・😭

# frozen_string_literal: true

require 'simpacker/manifest'
require 'active_support/all'
require 'action_view/template/error'

module Patches
  module Simpacker
    module EnableActionableError
      extend ActiveSupport::Concern

      included do
        include ActiveSupport::ActionableError

        action 'Run yarn build for development' do
          system 'yarn build:dev'
        end
      end
    end
  end
end

# MEMO
# Simpacker::Manifest::MissingEntryErrorがControllerで直接発生する場合にはいい感じに動作するが、
# `<%= javascript_pack_tag 'boards/new' %>`のような形でerb内で発生した場合には、
# `ActionView::Template::Error`になってしまうのでPatchが効かない。
# `ActionView::Template::Error`に直接includeしてもなぜか動かないが、
# そもそも`ActionView::Template::Error`にactionを設定するのは微妙な気がする。。。

Simpacker::Manifest::MissingEntryError.include(Patches::Simpacker::EnableActionableError)
Simpacker::Manifest::MissingFileError.include(Patches::Simpacker::EnableActionableError)
# ActionView::Template::Error.include(Patches::Simpacker::EnableActionableError)

Rails、template内で例外が発生すると、メッセージとかは実際の例外のものになるが実態はActionView::Template::Errorがraiseされているのと、ActionView::Template::Errorに直接includeしてもなぜか動かない。。。(そもそもActionView::Template::Errorにactionを設定するのは微妙な気がする。。。

The Template::Error exception is raised when the compilation or rendering of the template fails. This exception then gathers a bunch of intimate details and uses it to report a precise exception message.
rails/error.rb at 18707ab17fa492eb25ad2e8f9818a320dc20b823 · rails/rails · GitHub

おわりに

ActiveSupport::ActionableError、手軽に使えるのと、色々出来て便利そうですね!

参考

api.rubyonrails.org

www.bigbinary.com

webpack: thread-loaderで並列build行うメモ📝

webpackの公式guideで紹介されたthread-loaderを使って並列buildしてみたのでやり方とか結果をメモ📝

The thread-loader can be used to offload expensive loaders to a worker pool. https://webpack.js.org/guides/build-performance/#worker-pool

前提

webpack@5.74.0
ts-loader@9.3.1
babel-loader@8.2.5

thread-loaderとは

以下で紹介されている特定のloaderを並列実行できるloaderです。

webpack.js.org

当たり前ですが警告の通りなんでもかんでも並列にすれば良いわけではなく、一定のオーバーヘッドがあるので特定のコストの高いloaderに限ってのみ使用することが推奨されています。

Warning
Don't use too many workers, as there is a boot overhead for the Node.js runtime and the loader. Minimize the module transfers between worker and main process. IPC is expensive.
https://webpack.js.org/guides/build-performance/#worker-pool

thread-loaderを使った並列buildのやり方

今回はTypeScriptとbabelの一番メインのbuildで時間が掛かってそうなのでthread-loaderで並列で処理するようにしてみます。 やり方は並列化したいloaderの前でthread-loaderの設定を入れてあげるだけです。 ※defaultのthread-loaderの並列数(workers)はCPUの数-1になるようです。

      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: "thread-loader",
          },
          {
            loader: "babel-loader",
          },
        ],
      },
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        use: [
          {
            loader: "thread-loader",
          },
          {
            loader: "ts-loader",
            options: {
              appendTsSuffixTo: [/\.vue/],
              transpileOnly: true,
              happyPackMode: true,
            },
          },
        ],
      },

ts-loaderthread-loaderを使うには以下の通りhappyPackModeをtrueにしないと行けないようなのでtrueを設定するようにしています。

happyPackMode
If you're using HappyPack or thread-loader to parallelise your builds then you'll need to set this to true. This implicitly sets transpileOnly to true and WARNING! stops registering all errors to webpack.
https://github.com/TypeStrong/ts-loader#happypackmode

結果

以下の通り2秒ぐらいは早くなってそう。

// before
$ for counter in {1..5}; do yarn build; done
✨  Done in 19.20s.
✨  Done in 20.91s.
✨  Done in 18.89s.
✨  Done in 18.08s.
✨  Done in 19.08s.

// after
for counter in {1..5}; do yarn build; done
✨  Done in 16.51s.
✨  Done in 16.59s.
✨  Done in 16.78s.
✨  Done in 16.55s.
✨  Done in 16.66s.

参考

developers.freee.co.jp

webpack.js.org

eslint/stylelintの実行結果をcacheして実行時間を削減するメモ

eslint、stylelintにもcache用のオプションがあって差分のあったファイルだけ実行して時間を削減できることを知らなかったのでやり方とかをmemo🗒

eslintでcacheを利用する

TL;DR

eslint --cache --cache-location node_modules/.cache/eslint/ --cache-strategy content

eslintでcacheを利用するには、基本的には以下のoptionを実行時に付与するだけです。

--cache
Store the info about processed files in order to only operate on the changed ones. The cache is stored in .eslintcache by default. Enabling this option can dramatically improve ESLint’s running time by ensuring that only changed files are linted.
https://eslint.org/docs/latest/user-guide/command-line-interface#--cache-location

デフォルトだとプロジェクトのrootにキャッシュ用のファイルが作成されます。.gitignoreのメンテがめんどくさかったのとrootに色々できるのが嫌だったので、--cache-locationを指定してnode_modules/.cache/eslint配下に作成するようにしました。

--cache-location
Path to the cache location. Can be a file or a directory. If no location is specified, .eslintcache will be used. In that case, the file will be created in the directory where the eslint command is executed.
https://eslint.org/docs/latest/user-guide/command-line-interface#--cache-location

またデフォルトのmetadataベースでのchaceではCIではcheckoutして実行する都合上、上手く動かないので--cache-strategy contentを指定してcontentベースでのcacheを利用するようにしました。

--cache-strategy
Strategy for the cache to use for detecting changed files. Can be either metadata or content. If no strategy is specified, metadata will be used. The content strategy can be useful in cases where the modification time of your files change even if their contents have not. For example, this can happen during git operations like git clone because git does not track file modification time.
https://eslint.org/docs/latest/user-guide/command-line-interface#--cache-strategy

実行時間を約1/10に出来ました✨

// キャッシュなし
$ eslint app/javascript spec/javascript --ext .vue,.js,.ts --cache --cache-strategy content --cache-location node_modules/.cache/eslint/
Done in 14.40s.

// キャッシュあり
$ eslint app/javascript spec/javascript --ext .vue,.js,.ts --cache --cache-strategy content --cache-location node_modules/.cache/eslint/
Done in 1.16s.

CIでeslintのcacheを利用する

- name: cache eslint
        uses: actions/cache@v3
        with:
          path: node_modules/.cache/eslint
          key: eslint-v1-${{ github.ref_name }}-${{ github.sha }}
          restore-keys: |
            eslint-v1-
      - name: lint js
        run: yarn run eslint app/javascript spec/javascript --ext .vue,.js,.ts --cache --cache-strategy content --cache-location node_modules/.cache/eslint/

eslintのバージョンアップ等の際にファイルの差分がなくても実行したいケースとかもあるかもなので、cacheのkeyをeslint-v1-${{ hashFiles('**/yarn.lock') }}-${{ github.ref_name }}-${{ github.sha }}とかにしておいてrestore-keyseslint-v1-${{ hashFiles('**/yarn.lock') }}-にしておいても良いかも?※yarn.lockが一致してない場合にはcacheを利用しない。

https://docs.github.com/ja/actions/using-workflows/caching-dependencies-to-speed-up-workflows#matching-a-cache-key

stylelintでcacheを利用する

TL;DR

stylelint --cache --cache-location node_modules/.cache/stylelint/

stylelintでもcacheを利用するには、基本的には以下のoptionを実行時に付与するだけです。

CLI flag: --cache
Store the results of processed files so that Stylelint only operates on the changed ones. By default, the cache is stored in ./.stylelintcache in process.cwd().
Options | Stylelint

デフォルトだとプロジェクトのrootにキャッシュ用のファイルが作成されます。同様に.gitignoreのメンテがめんどくさかったのとrootに色々できるのが嫌だったので、--cache-locationを指定してnode_modules/.cache/stylelint配下に作成するようにしました。

CLI flag: --cache-location
Path to a file or directory for the cache location.
https://stylelint.io/user-guide/usage/options/#cachelocation

私の環境では実行時間が約1/2ぐらいになりました✨

CIでstylelintのcacheを利用する

styelintではmetadataベースのcacheしかサポートされておらずCIで実行時間を削減することはできません😭

issueは上がっているようなので、今後はできるようになるかもしれません。

github.com

(10/16追記) Stylelint v14.13.0でcacheStorategyが追加されcontentベースのcacheが利用できるようになりました🙌

github.com

おわりに

意外と静的解析系のライブラリこういうオプション指定するだけ系で実行時間が大分変わりそうなので、指定できるものは指定しておくと効率が上がってよさそうですね🗒

`@vue/tsconfig`を使ってVue.jsを使用するプロジェクトのTypeScriptの設定をいい感じに行うMEMO📝

TypeScriptを設定周り毎回新しいプロジェクトを作成するときに迷うので以下のようなコミュニティが提供しているような設定を使いたいなぁという気持ちになるのですが、

github.com

Vue.jsのものは無さそうで(Nuxt用のはある)、いい感じのものが無いかなと思ったらVue.js公式のものがあったので使い方とかをメモ📝

github.com

@vue/tsconfig の使い方

使い方は簡単でリポジトリ記載の通り、以下でinstallしたあとに

// npm
npm install -D @vue/tsconfig
// yarn
yarn add -D @vue/tsconfig

tsconfig.jsonでextendしてあげるだけです。

{
  "extends": "@vue/tsconfig/tsconfig.web.json"
}

公式Guideによるとcreate-vueの構成相当のようです 📗

Projects scaffolded via create-vue include pre-configured tsconfig.json. The base config is abstracted in the @vue/tsconfig package. Using Vue with TypeScript | Vue.js

無効化したり変更したい値があればtsconfig.jsonで上書きしてあげれば大丈夫です。

{
  "extends": "@vue/tsconfig/tsconfig.web.json",
  "compilerOptions": {
    "lib": ["DOM", "ES2020"]
    "target": "ES2015",
    "types": ["node", "jest"],
  }
}

おわりに

こういう設定周り、独自でメンテしているとベストプラクティスから逸脱してたり、バージョンアップで対応が漏れたりと割と大変なこともあったりするのでコミュニティで推奨されているものが公開されていれ、それが利用できるのは良いですね✨

参考

zenn.dev

vuejs.org

Vue.js: Transitionでコンポーネントにanimationを設定するMEMO

最近、Vue.jsのTransitionを使う機会があったので使い方とかをMEMO📝

Transitionとは

Vue.jsのコンポーネントで簡単にアニメーションを設定できる仕組み。

vuejs.org

transitionで囲んだコンポーネントv-showv-ifといったコンポーネントの表示に関わる処理を検知して動的に該当コンポーネントの要素にclassを追加してくれる。

https://vuejs.org/guide/built-ins/transition.html#css-based-transitions

transitionname属性を付与することで設定されるclass名のprefixをコントロールすることができる。(デフォルトはv)

Named Transitions For a named transition, its transition classes will be prefixed with its name instead of v. For example, the applied class for the above transition will be fade-enter-active instead of v-enter-active. https://vuejs.org/guide/built-ins/transition.html#named-transitions

サンプルコード

以下が実際にTransitionを使って以下のアニメーションを付与してみたサンプルコードです。

  • 表示時に 右 -> 左のスライドイン
  • 非表示時に フェードアウト
<script setup>
import { ref } from 'vue'
const title = ref('Hello World!')
const body = ref('Hello World! Hello World! Hello World!')
const isShow = ref(true)
const handleOnClose = () => isShow.value = false
const handleOnShow = () => isShow.value = true

</script>

<template>
  <transition>
    <article class="message" v-show="isShow">
      <h3 class="message__title">
    {{ title }}
      </h3>
      <div class="message__body">
        <p class="message__body-text">
          {{ body }}
        </p>
      </div>
      <div class="message__footer">
        <button class="message__footer-button" @click="handleOnClose"> Close </button>
      </div>
    </article>
  </transition>
  <div>
    <br>
    <button @click="handleOnShow"> Open </button>
  </div>
</template>
<style scoped>
  .message {
    background-color: #eee;
    padding: 10px;
  }
  
  .message__title {
    margin: initial;
  }
  
  .message__footer {
    text-align: right;
  }
  
  .message__footer-button {
    border: none;
    cursor: pointer;
    outline: none;
    appearance: none;
    background-color: #fff;
    padding: 5px;
        box-shadow: 2px 2px 2px #ddd;
    border-radius: 10%;
  }
  

  .v-enter-from {
    opacity: 0;
    transform: translate(100%, 0);
  }

  .v-enter-active {
    transition: 0.6s cubic-bezier(0.16, 1, 0.3, 1);
  }

  .v-leave-active {
    transition: opacity 0.5s ease;
  }

  .v-leave-to {
    opacity: 0;
  }
</style>

また表示時には以下のサイトを参考にイージングを入れています。

easings.net

Transitionのイベントを取得する

Transitionを指定している場合、コンポーネントを表示したときにemitしたいといった場合にアニメーションの終了を待ちたいケースがありますが、その場合にはTransitionで各Eventをhookしてemitすることで適切なタイミングでイベントを発火させることができます。

You can hook into the transition process with JavaScript by listening to events on the component:

<Transition
  @before-enter="onBeforeEnter"
  @enter="onEnter"
  @after-enter="onAfterEnter"
  @enter-cancelled="onEnterCancelled"
  @before-leave="onBeforeLeave"
  @leave="onLeave"
  @after-leave="onAfterLeave"
  @leave-cancelled="onLeaveCancelled"

https://vuejs.org/guide/built-ins/transition.html#javascript-hooks

おわりに

よしなにイベントフックしてくれて簡単にアニメーションを設定できるTransition便利!✨

参考

qiita.com

stackoverflow.com

webpack v5で追加されたpersistent cachingでbuild結果をcacheして高速化するメモ

webpackのbuildのキャッシュ、checksumを保存して一致してたら実行しないといった仕組みを用意しないといけないのかなと思っていたのですが、webpack v5からはpersistent cachingの機能が追加されていて簡単にキャッシュして高速化できたのでメモ📝

↓公式ドキュメントはこちら

github.com

webpack.js.org

persistent cachingでbuild結果をcacheする

やり方は簡単で以下のようにcacheの設定をしてあげるだけです。 type: "filesystem"を指定し、buildDependenciesconfig: [__filename]を指定してあげることでファイルにcacheすることができるようになります。 ※デフォルトではnode_modules/.cache/webpackにcacheが保存されます。

process.env.NODE_ENV = "test";

const { merge } = require("webpack-merge");
const common = require("../../webpack.common.js");

module.exports = merge(common, {
  mode: "production",
  cache: {
    type: "filesystem",
    buildDependencies: {
      config: [__filename],
    },
  },
});

個人のWebサービスでの計測結果ですが、cacheによってbuild時間が結構に早くなりました🚀 ※webpack 5.73.0を使用

# no cache
$ yarn build:test
✨  Done in 11.23s.

# cached
$ yarn build:test
✨  Done in 1.10s.

CircleCIでwebpackのbuild結果をキャッシュして高速化する

ファイルにcacheすることによりCIでcacheを利用し、差分がなければcacheを利用することで高速化が出来ました。以下がCircleCIでwebpackのchacheをCIでcacheして利用するようにしてみたサンプルです。

commands:
  build_webpack:
    steps:
      - restore_cache:
          keys:
            - v1-webpack-cache-{{ .Branch }}-{{ .Revision }}
            - v1-webpack-cache-{{ .Branch }}-
            - v1-webpack-cache-
      - run:
          name: build webpack
          command: yarn build:test
      - save_cache:
          key: v1-webpack-cache-{{ .Branch }}-{{ .Revision }}
          paths:
            - ./node_modules/.cache/webpack

特に工夫しなくても依存ファイルに変更があった場合にはcacheを破棄してbuildしてくれるので便利ですね✨

GitHub Action / GitLab CIのサンプルは以下の公式ドキュメントを参照ください。

webpack.js.org

参考

blog.hiroppy.me