LaravelにTailwind CSSをインストールする

基本的には公式の手順に従っていきます。公式の手順とは異なり、sail環境で実行するため、すべてのコマンドにsailがついています。また、Viteを使います。

Tailwind CSSをインストールする

すでにnpm installしてある状態で、次のコマンドを実行します。

$ sail npm install -D tailwindcss postcss autoprefixer
$ sail npx tailwindcss init -p 

コマンドを実行すると、tailwind.config.jspostcss.config.jsが作成されます。

テンプレートのパスを設定する

tailwind.config.jsを編集してテンプレートファイルのパスを追加します。

修正前のtailwind.config.jsは次のようになっています。

/** @type {import('tailwindcss').Config} */
export default {
  content: [],
  theme: {
    extend: {},
  },
  plugins: [],
}

contentを次のように編集します。

/** @type {import('tailwindcss').Config} */
export default {
  content: [
      "./resources/**/*.blade.php",
      "./resources/**/*.js",
      "./resources/**/*.vue",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

公式の手順どおりに編集していますが、Vue.jsを使わないのであれば最後の1行は不要です。

CSSファイルにTailwindディレクティブを追加する

@tailwindディレクティブをresources/css/app.cssに追加します。

@tailwind base;
@tailwind components;
@tailwind utilities;

ビルドプロセスを開始する

すでにnpm run devを実行中の場合は一度停止してから再度実行してください。

$ sail npm run dev

これでインストール作業は完了し、Tailwind CSSが使用可能な状態になります。

Tailwind CSSを使ってみる

では実際に使ってみましょう。

welcome.blade.phpを修正して機能しているかを確認します。

まずは、<head>タグに@vite('resources/css/app.css')を埋め込みます。

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
      ...
      @vite('resources/css/app.css')
      ...
    </head>

これで使用する準備が整いましたが、welcome.blade.phpの場合、すでに<style>タグでTailwind CSSが埋め込まれているので、これをコメントアウトしておきます。

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        ...
        <!-- Styles -->
        <!-- style>
            /* ! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com */ ...
        </style -->
  </head>

この修正は他のBladeファイルでは不要です。

では実際に使ってみます。

公式サイトの手順にあった<h1>タグを記述します。

        <h1 class="text-3xl font-bold underline">
            Hello World!
        </h1>

画面を表示してみて、

のように表示され、デベロッパーツール上でも

のようにapp.cssに定義されたTailwind CSSのクラスが適用されていることが確認できたら正常にインストールできています。

RStudioのグラフの日本語が文字化けするのを解消する

RStudioのグラフが文字化けする場合、raggパッケージを使用することで文字化けが解消できます。

raggパッケージをインストールする

raggパッケージをインストールするにはConsoleで次のコマンドを実行します。

> install.packages("ragg")

Graphics DeviceのBackendをAGGに変更する

Tools > Global OptionsでOptionsダイアログを表示し、GeneralメニューのGraphicsタブを選択します。

Graphics Device > BackendがAGGになっていなければAGGに変更し、OKボタンをクリックしてください。

再度、グラフを表示すると、文字化け(豆腐表示)が解消されていることが確認できます。

まとめ

一度設定すれば以降再設定する必要はありませんが、RやRStudioのバージョンアップに伴うインストールを行った場合は再度設定する必要があるかもしれません。

わたしも以前設定したはずですが、RとRStudioを上書きインストールしたあとに発生したので、バージョンアップ時には再設定が必要なのではないかと思います。

Biomeを使ってLintとFormatをおこなう

フロンドエンド向け垂直統合ツールチェーンRomeが開発終了となり、Rome開発チームのひとりが作成したのがBiomeです。

Rustで構築されたBiomeは既存のFormatter、Linterと比べ非常に高速に動作するように設計されています。設定も非常に簡単でエディタの拡張機能・プラグインも提供されていることから今後のデファクトスタンダードになりそうな予感があります。

本記事ではBiomeのインストールから基本的な使い方、エディタの統合について解説します。

Biomeをインストール

Biomeはグローバルインストールすることは可能ですが推奨されていないため、プロジェクトにインストールします。また、バージョン範囲演算子を使用しないでインストールすることを強く推奨しています。

$ mkdir biome-example
$ cd biome-example
$ npm init -y
$ npm install --save-dev --save-exact @biomejs/biome
$ npx @biomejs/biome init

Node.jsをインストールせずにBiomeを使用したい場合は、Homebrewなどを使ってインストールするスタンドアロン実行形式での利用も可能ですが、本記事では割愛します。

インストール後に初期化をおこなうとbiome.jsonが生成されます。

{
  "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json",
  "organizeImports": {
    "enabled": true
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  }
}

基本的な設定は済んでいるので、すぐに使い始められます。

Biomeの使い方

Biomeの基本的な使い方を見ていきます。

Formatter

Prettierのようにソースコードを整形してくれます。指定したディレクトリ内のファイルに対してソースコードの整形をおこなうには次のようなコマンドを実行します。

$ npx @biomejs/biome format src --write

このコマンドではsrcディレクトリを指定してフォーマットを行っていますが、単一ファイルやワイルドカード指定も可能です。

Linter

こちらはESLintの置き換えになります。次のようなコマンドを実行します。

$ npx @biomejs/biome lint src

こちらも単一ファイルの指定やワイルドカードでの指定が可能です。

Formatter+Lint+α

前述のFormatterとLinterに加えて、インポート文の再構成をまとめて実行するには次のようなコマンドを実行します。

$ npx @biomejs/biome check --apply src

通常はこのコマンドを使うことになると思います。

package.jsonにスクリプトを設定する

コマンドが少し長いのでpackage.jsonにスクリプトとして定義しておきましょう。

{
  "name": "biome-example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "format": "biome format src --write",
    "lint": "biome lint src",
    "check": "biome check --apply src"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@biomejs/biome": "1.5.3"
  }
}

Visual Studio Codeの拡張機能を使う

開発時に都度コマンド入力するのは手間ですので、エディタ向けの拡張機能を利用するのがよいでしょう。

まずはVisual Studio Codeの拡張機能を使ってみます。

settings.jsonに以下の設定を追加します。

{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "[javascript]": {
    "editor.defaultFormatter": "biomejs.biome"
  },
  "editor.codeActionsOnSave": {
    "quickfix.biome": true,
    "source.organizeImports.biome": true
  }
}

以下の設定で、保存時にFormatterが実行されるように設定しています。

  "editor.formatOnSave": true,

デフォルトのFormatterはPrettierにしています。

  "editor.defaultFormatter": "esbenp.prettier-vscode",

言語モードがJavaScriptの場合、FormatterはBiomeになるよう設定しています。

  "[javascript]": {
    "editor.defaultFormatter": "biomejs.biome"
  },

保存時のアクションに、 Biomeによるフォーマット("quickfix.biome": true)とインポート文の再構成("source.organizeImports.biome": true)をおこなうように設定します。

この設定を行うことで、ファイルの保存時にソースコードの整形とインポート文の再構成をおこなってくれるようになります。

WebStormのプラグインを使う

IntelliJ向けのプラグインも提供されていますので、WebStormで使ってみます。

こちらは⌥⇧ ⌘ L or Ctrl+Alt+Lでファイルの整形ダイアログを表示して必要なオプションにチェックを入れて実行ボタンを押します。

まとめ

Biomeのインストール方法からエディタとの統合までを確認しました。 1つのパッケージで導入できてデフォルトで利用可能となっているだけでなく、エディタとの統合もサポートされているため、小さく始めて大きく育てるには非常に適したツールチェーンだといえます。

これまではPrettierとESLintを使っていましたが今後はBiomeを使ってみようと思います。

Laravel Sail+PHPStormで開発環境を構築する

Laravelで作りたいアプリケーションがあるため、macOS上で開発するための環境を構築します。

Laravel Sailでインストールする

macOS上でDocker Desktopを導入している場合、もっとも簡単に開発環境を構築できる手段になると思います。

作成したいアプリケーションがexample-appの場合、次のようにコマンドを入力します。

$ curl -s "https://laravel.build/example-app" | bash

最後にパスワードが求められますので入力してください。

初期設定を行う

PHPStormで開発を行うため、アプリケーションを開いた後必要な初期設定を行います。

まずはコンテナを起動します。

$ ./vendor/bin/sail up

コンテナが起動している状態で、設定をひらきます。

PHP言語レベル、CLIインタープリターを設定します。

今回はLaravel 10.xがサポートしているバージョンのうち、8.3を使用していますので、PHP言語レベルは8.3を指定しています。

また、CLIインタープリターはローカルのPHPではなく、Dockerコンテナー上のPHPを使用することで、ローカルにPHPをインストールしなくて済みます。CLIインタープリターの右の…ボタンをクリックし、CLIインタープリターダイアログの左上の+ボタンをクリックし、「From Docker, Vagrant, VM, WSL, Remote…」をクリックしてください。

リモートPHPインタープリターの構成ダイアログで、Dockerを選択し、

サーバーの右ある新規…ボタンをクリックし、

OKボタンをクリックしてください。

OKボタンをクリックすると設定は完了します。

デバッグの設定を行う

次にデバッグの設定を行います。docker-compose.ymlを確認すると、SAIL_XDEBUG_MODE環境変数がない場合、デバッグモードがOFFになっています。

        environment:
            WWWUSER: '${WWWUSER}'
            LARAVEL_SAIL: 1
            XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}'
            XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}'
            IGNITION_LOCAL_SITES_PATH: '${PWD}'

docker-compose.ymlを修正するのではなく、.envファイルにSAIL_XDEBUG_MODEを追加します。

SAIL_XDEBUG_MODE=debug

また、Settings…>PHP>サーバーの「パスマッピングを使用する」のチェックボックスを外しておきます。

これがあると以下のような警告が表示され、ブレークポイントで止まりません。

環境変数を反映するためにいったんDockerコンテナーを停止して起動しなおします。

動作確認のためにroutes/web.phpにデバッグを設定し、

デバッグを有効化(虫のマークをクリックして以下のようにします)

http://localhostにアクセスすると、以下のようなダイアログが表示されます。

このまま進めると、ブレークポイントで止まります。

これで環境構築は完了です。

まとめ

プロジェクトの作成から初期設定、デバッグの設定までを行いました。デバッグについては一応動作していますが、若干動作が怪しいので、今後の開発で調整するかもしれません。

Gitで初回コミットを取り消す(git resetでエラーになる場合)

リポジトリを新規作成し、コミットしたところで.gitignoreファイルを作り忘れていることに気づくことはないでしょうか?

このような場合、直前のコミットを取り消そうとすると次のようなエラーが表示されます。

$ git reset --soft HEAD~1
fatal: ambiguous argument 'HEAD~1': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

このエラーは、取り消したいコミットの一つ前のコミットHEAD~1が存在しないことが原因で起きています。

初回コミットを取り消したい場合の方法について解説します。

ここで解説する方法の中には非常に強力な操作が含まれています。使い方を誤ると復旧困難な状態に陥る可能性がありますので、十分に注意してください。

もっとも簡単な方法

もっとも簡単に初回コミットを取り消す方法は、.gitディレクトリを削除することです。当然ながらgit initからやり直しになりますが、初回コミット時という状況に限定するのであれば、この方法がもっとも簡単かつおすすめです

$ rm -rf .git

初回コミットを取り消すコマンド

では、.gitディレクトリを削除せずに初回コミットを取り消す方法はないのでしょうか?

もちろん初回コミットを取り消すコマンドはあります。次のコマンドを実行することで、初回コミットを取り消すことができます。

$ git update-ref -d HEAD

取り消し後、変更はステージングエリアに残ります。ステージングエリアにある変更をワーキングディレクトリに戻す場合は、

$ git reset

で、ステージングエリアにあるすべての変更をワーキングディレクトリに戻すか、

$ git reset node_modules

で、特定のファイルまたはディレクトリのみをワーキングディレクトリに戻してください。

git update-ref -d HEADとはどんなコマンドなのか?

git update-ref -dは指定した参照(ref)を削除するコマンドです。ここではHEADという参照を指定していますので、HEAD参照が削除され、何もコミットしていない状態まで戻されますので、初回コミットが取り消されることになります。

git update-ref -dリポジトリの履歴を変更する非常に強力な操作です。使い方を誤ると復旧が困難な状態に陥る場合がありますので、十分に注意して使用してください。不安な場合は前述のもっとも簡単な方法を実施してください。

(おまけ)初回コミット以外のコミットの取り消す

おまけになりますが、git resetコマンドを使用した初回コミット以外のコミットを取り消す方法について確認しておきます。

取り消した変更をステージングエリアに保持

次のコマンドは、直前のコミットを取り消し、そのコミットの変更をステージングエリアに残します。

$ git reset --soft HEAD~1

取り消した変更をワーキングディレクトリに保持

次のコマンドは、直前のコミットを取り消し、その変更をワーキングディレクトリに残しますが、ステージングエリアには残しません。--mixed オプションはデフォルトなので、--mixed は省略可能です。

$ git reset --mixed HEAD~1

変更を完全に取り消し

次のコマンドは、直前のコミットを取り消し、そのコミットの変更を完全に取り消します。

$ git reset --hard HEAD~1

(おまけ)ステージングエリアの変更を取り消す

すでに説明済みですが、ステージングエリアの変更を取り消す方法についても確認します。

ステージングエリアにあるすべてのファイルやディレクトリを戻す

ステージングエリアにあるすべてのファイルやディレクトリをワーキングディレクトリに戻す場合は、次のコマンドを実行します。

$ git reset

ステージングエリアにある特定のファイルまたはディレクトリを戻す

ステージングエリアにある特定のファイルまたはディレクトリをワーキングディレクトリに戻す場合は、次のコマンドを実行します。

$ git reset node_modules

まとめ

初回コミットを取り消す方法について解説しましたが、使用タイミングを誤ると復旧不可能な事態に陥るリスクがあので注意が必要です。

ステージングエリアに上げたあとに過不足がないことをレビューすることが大切です。ステージングエリアに上げる、ステージングエリアからワーキングディレクトリに戻すという操作は安全に実施できる操作ですし、ステージングエリアと直前のコミットとの差分は、git diff --cachedまたはgit diff --stagedで比較できますので、積極的に活用してレビューを行ってください。

giコマンドで.gitignoreを作成しよう

リポジトリ管理のツールとして、Gitはデファクトスタンダードととも言えるツールですが、その利用に際して特に重要なのが.gitignoreファイルの適切な設定です。このファイルはGitリポジトリで無視するべきファイルやディレクトリを指定し、不要なファイルがリポジトリに追加されるのを防ぎます。しかし、多様な開発環境や言語に対応するための.gitignoreファイルを一から作成するのは意外と手間がかかるものです。そこで役立つのが、Toptalによって提供されるgitignore.ioサービスと、それを利用したgiコマンドです。gitignore.ioは、さまざまな開発環境や言語に合わせた.gitignoreテンプレートを提供し、giコマンドを通じてこれらのテンプレートを簡単に取得し、利用することができます。

この記事では、giコマンドを使用して、効率的かつ正確に.gitignoreファイルを作成する方法を紹介します。

giコマンドのインストール

gitignore.ioのコマンドラインツールのページにアクセスして、インストール用のコマンドをコピーします。

本記事ではzshを使用している場合のコマンドを使用します。

$ echo "function gi() { curl -sLw \"\\\n\" https://www.toptal.com/developers/gitignore/api/\$@ ;}" >> \
~/.zshrc && source ~/.zshrc

実行しても特に何もメッセージは出ませんがコマンドのインストールは完了しています。実行するためにはcurlコマンドが必要ですのでインストールしていない場合は別途インストールしてください。

giコマンドの確認

まずはgiコマンドの使い方を確認していきましょう。オプションなしでgiコマンドを表示するとヘルプを確認できます。

$ gi
  list    - lists the operating systems, programming languages and IDE input types
  :types: - creates .gitignore files for types of operating systems, programming languages or IDEs

少しわかりづらいですが、listサブコマンドを使用すると使用可能なタイプな一覧が表示されます。ただし、かなり量があるのご注意ください。

$ gi list
1c,1c-bitrix,a-frame,actionscript,ada
adobe,advancedinstaller,adventuregamestudio,agda,al
・・・
yarn,yeoman,yii,yii2,zendframework
zephir,zig,zsh,zukencr8000

数が多いので、grepコマンドを併用するとよいでしょう。

$ gi list | grep node
modelsim,modx,momentics,monodevelop,mplabx
ninja,node,nodechakratimetraveldebug,nohup,notepadpp

Node.jsの.gitignoreはnodeというタイプで指定することがわかります。

$ gi list | grep visualstudiocode
virtualenv,virtuoso,visualbasic,visualstudio,visualstudiocode

VSCodeの.gitignorevscodeではなくvisualstudiocodeだということもわかりました。

giコマンドで.gitignoreを作成する

では、実際に.gitignoreを作成してみます。ここでは、Node.jsのプロジェクトを作成し、VSCodeでコーディングするとします。

使用するタイプはnodevisualstudiocodeの2つになり、複数選択する場合はカンマ区切り(間にはスペースを入れない)でタイプを並べます。また、コマンド自体は標準出力に表示するだけですので、ファイルにリダイレクトしてファイル化します。

$ gi node,visualstudiocode > .gitignore

生成された.gitignoreファイルの中身は以下のようになります。

# Created by https://www.toptal.com/developers/gitignore/api/node,visualstudiocode
# Edit at https://www.toptal.com/developers/gitignore?templates=node,visualstudiocode

### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

### Node Patch ###
# Serverless Webpack directories
.webpack/

# Optional stylelint cache

# SvelteKit build / generate output
.svelte-kit

### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets

# Local History for Visual Studio Code
.history/

# Built Visual Studio Code Extensions
*.vsix

### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide

# End of https://www.toptal.com/developers/gitignore/api/node,visualstudiocode

まとめ

Githubのgithub/gitignoreリポジトリから対象の.gitignoreを探したり、gitignore.ioを使うのもよいですが、giコマンドを使って.gitignoreを生成する方が簡単に利用できて便利です。また、コマンドということはシェルスクリプトへの組み込みなど様々な使い道があるのもよい点です。

giコマンドを用いることで、テンプレートからプロジェクトに必要な情報をすばやく設定でき、開発の手間を大幅に削減することができます。ぜひ、giコマンドを活用してよりスムーズな開発を実現してみてください。

textlintで文章の校正を行う

文章を書く時、文法的な誤りや用語の揺らぎは避けたいものです。しかし、自分だけでは気づかないところがあったり、煩雑すぎたりする場合があります。そこで、textlintというツールを使えば、文章の校正を行うことができます。本記事では、textlintについて詳しく紹介し、文章の校正に役立てる方法を解説します。

今回試したコードはhttps://github.com/t0k0sh1/emendationにあります。

textlintがチェック対象としているのは、テキストファイルやマークダウンファイルなどのテキストファイルのみです。WordやExcelなどのファイルを処理する場合はテキストを抽出するなどの工夫をする必要があります。

textlintの導入

自然言語向けのLinterであるtextlintを導入します。

ここでは、校正チェックを行うプロジェクトemendationを作成します。textlintはNode.jsで利用可能はパッケージですので、npm initコマンドで初期化しておきます。

$ mkdir emendation
$ cd emendation
$ npm init -y

textlintのインストール

textlintをインストールします。特段前提条件となるパッケージやライブラリはなく、普通にインストール可能です。

$ npm install textlint

ルールプリセットのインストール

次にルールプリセットをインストールします。技術書や技術系の記事、設計書の執筆を行っている場合は、技術書向けのルールを使用するのがよいでしょう。

$ npm install textlint-rule-preset-ja-technical-writing

lintスクリプトの設定

package.jsonlintを実行できるスクリプトを追加します。filesディレクトリ内にあるテキストファイルを対象にするようにしています。

  "scripts": {
    "lint": "textlint ./files/*.txt"
  },

ルールの適用方法

textlintで使用するルールを適用する方法には、以下の2つの方法があります。

  1. textlintコマンドに—presetオプションで使いたいルールを指定する
  2. .textlintrc.jsonに適用するルールを記述する

実用上は細かいカスタマイズを必要とするため、後者の方法でルールを適用するようにします。

まずは、textlint --initコマンドで.textlintrc.jsonファイルを作成します。

$ textlint --init
.textlintrc.json is created.

コマンドを実行すると以下のようなファイルが生成されます。

{
  "plugins": {},
  "filters": {},
  "rules": {}
}

生成時点ではルールは何も設定されていません。先ほどインストールしたルールプリセットを適用する記述を追加します。

{
  "plugins": {},
  "filters": {},
  "rules": {
    "preset-ja-technical-writing": true
  }
}

動作を確認するために、filesディレクトリに以下のようなsample.txtを作成します。

Vueで作成した確認ダイアログを表示し、OKボタンをクリックしたときだけ処理を続行する方法をボタンクリックの場合とフォームを使ったサブミットの2パターンの実装方法を見ていきました
この考え方は他のフレームワークでも適用可能です。今回はVue 3.3.4で動作確認していますが、Vue 3で実装された機能を使っていないため、Vue 2でも動作すると思います。

このファイルは1行目の「。」が漏れているので、この点が検出されると思います。

では、実際に実行してみます。

$ npm run lint

> emendation@1.0.0 lint
> textlint ./files/*.txt


/Users/t0k0sh1/Workspace/emendation/files/sample.txt
  1:89  error  文末が"。"で終わっていません。           ja-technical-writing/ja-no-mixed-period
  2:84  error  弱い表現: "思います" が使われています。  ja-technical-writing/ja-no-weak-phrase

✖ 2 problems (2 errors, 0 warnings)

2つのエラーが検出されました。

1つは想定どおり「。」が漏れていることを検出していますが、もう1つは想定していなかったエラーです。このエラーは「〜だと思う」という弱い言い回しを検出しているようです。

ルールのカスタマイズ①不要なルールの抑止

不要なルールの抑止の説明を行うためにja-no-weak-phraseのルールを無効化してみます。以下のように記述することでルールを無効化できます。

{
  "plugins": {},
  "filters": {},
  "rules": {
    "preset-ja-technical-writing": {
      "ja-no-weak-phrase": false
    }
  }
}

では、再度実行してルールが抑止されていることを確認します。

$ npm run lint

> emendation@1.0.0 lint
> textlint ./files/*.txt


/Users/t0k0sh1/Workspace/emendation/files/sample.txt
  1:89  error  文末が"。"で終わっていません。  ja-technical-writing/ja-no-mixed-period

✖ 1 problem (1 error, 0 warnings)

先ほどまで検知されていたja-no-weak-phraseのルールが検知されなくなっていることが確認できました。

ルールのカスタマイズ②独自の辞書ルールの追加

辞書ルールを定義するファイルを作成することで、独自の辞書ルールを追加することが可能です。

ファイル名に制約はないですが、ここではprh.yamlファイルを作成します。

まずはルールを使用するように定義を修正します。

{
  "plugins": {},
  "filters": {},
  "rules": {
    "preset-ja-technical-writing": {
      "ja-no-weak-phrase": false
    },
    "prh": {
      "rulePaths": ["./prh.yml"]
    }
  }
}

次に辞書ルールを定義します。

version: 1
rules:
  - expected: View
    patterns:
      - Vue

ここでは動作を確認するために、ViewVueとタイポする想定でルールを作成しています。expectedに正しい書き方、patternsに誤った書き方を記述します。

$ npm run lint

> emendation@1.0.0 lint
> textlint ./files/*.txt


/Users/t0k0sh1/Workspace/emendation/files/sample.txt
  1:1   ✓ error  Vue => View                     prh
  1:89  error    文末が"。"で終わっていません。  ja-technical-writing/ja-no-mixed-period
  2:28  ✓ error  Vue => View                     prh
  2:49  ✓ error  Vue => View                     prh
  2:72  ✓ error  Vue => View                     prh

✖ 5 problems (5 errors, 0 warnings)
✓ 4 fixable problems.
Try to run: $ textlint --fix [file]

Vueがエラーとして検出できることを確認しました。

辞書ルールを定義することで、固有名詞の揺らぎや言い回しの不統一などを検知することが可能になります。

辞書ルール定義の発展した方法

辞書ルールが増えたときに管理が煩雑になりがちです。これを解消するために辞書ルールを分割することを考えます。

以下のように記述することで、ルールを外出しすることが可能です。

version: 1

imports:
  - ./rules/すでに.yml
  - ./rules/かつ.yml
  - ./rules/また.yml

さらに各ルールを指定パスに定義しておきます。

version: 1
rules:
  - expected: すでに
    patterns:
      - 既に
version: 1
rules:
  - expected: かつ
    patterns:
      - /且つ|且/
version: 1
rules:
  - expected: また$1
    patterns:
      - /又(、|は|は、)/
specs:
  - from: 又
  - to: また

この例は、副詞をひらく書き方を正として、ひらいていない書き方を検出するルールとなります。

以下のように例文を用意して動作確認します。

既に会議は始まっており、私たちは遅れている。且つ、プレゼンテーション資料もまだ完成していない。又は、次の会議までに資料を仕上げ、改めて説明することも一つの選択肢だ。

以下が実行結果です。

$ npm run lint

> emendation@1.0.0 lint
> textlint ./files/*.txt


/Users/t0k0sh1/Workspace/emendation/files/sample.txt
  1:1   ✓ error  既に => すでに                                                                                                  prh
  1:23  ✓ error  且つ => かつ                                                                                                      prh
  1:48  ✓ error  又は => または                                                                                                  prh
  1:75  ✓ error  一つ => 1つ
数量を表現し、数を数えられるものは算用数字を使用します。任意の数に置き換えても通用する語句がこれに該当します。  ja-technical-writing/arabic-kanji-numbers

✖ 4 problems (4 errors, 0 warnings)
✓ 4 fixable problems.
Try to run: $ textlint --fix [file]

辞書ルールで定義した箇所が検出されることが確認できます。

自動訂正を行う

textlintにはルールによって自動訂正を行うことができます。–fixオプションを使うことで、訂正可能なルールについて自動的に訂正を行うことが可能です。

package.jsonlint:fixスクリプトを追加します。

  "scripts": {
    "lint": "textlint ./files/*.txt",
    "lint:fix": "textlint --fix ./files/*.txt"
  },

先ほどチェックしたテキストに対して、lint:fixコマンドを実行することで、自動訂正されることを確認します。

まずは、修正前のテキストを再掲します。

既に会議は始まっており、私たちは遅れている。且つ、プレゼンテーション資料もまだ完成していない。又は、次の会議までに資料を仕上げ、改めて説明することも一つの選択肢だ。

再度、チェックのみを行います。

$ npm run lint

> emendation@1.0.0 lint
> textlint ./files/*.txt


/Users/t0k0sh1/Workspace/emendation/files/sample.txt
  1:1   ✓ error  既に => すでに                                                                                                  prh
  1:23  ✓ error  且つ => かつ                                                                                                      prh
  1:48  ✓ error  又は => または                                                                                                  prh
  1:75  ✓ error  一つ => 1つ
数量を表現し、数を数えられるものは算用数字を使用します。任意の数に置き換えても通用する語句がこれに該当します。  ja-technical-writing/arabic-kanji-numbers

✖ 4 problems (4 errors, 0 warnings)
✓ 4 fixable problems.
Try to run: $ textlint --fix [file]

検出したエラーのうち、チェックマークがついているものが自動訂正可能なものです。できないものもありますが、今回検出したエラーはすべて自動訂正可能です。

では、lint:fixコマンドを実行して自動訂正します。

$ npm run lint:fix

> emendation@1.0.0 lint:fix
> textlint --fix ./files/*.txt


/Users/t0k0sh1/Workspace/emendation/files/sample.txt
  1:75  ✔   一つ => 1つ
数量を表現し、数を数えられるものは算用数字を使用します。任意の数に置き換えても通用する語句がこれに該当します。  ja-technical-writing/arabic-kanji-numbers
  1:1   ✔   既に => すでに                                                                                                  prh
  1:23  ✔   且つ => かつ                                                                                                      prh
  1:48  ✔   又は => または                                                                                                  prh

✔ Fixed 4 problems

メッセージからも訂正されたことが確認できますが、修正後のテキストも確認しておきます。

すでに会議は始まっており、私たちは遅れている。かつ、プレゼンテーション資料もまだ完成していない。または、次の会議までに資料を仕上げ、改めて説明することも1つの選択肢だ。

検知された点が修正されていることが確認できました。

ルールを書くときの注意点(正規表現)

「かつ」が正しい書き方だとした場合、誤った書き方は「且つ」または「且」となります。このような場合、正規表現で長い方を先に書くようにすることで、「且つ」を「かつつ」に訂正しないようにできます。

version: 1
rules:
  - expected: かつ
    patterns:
      - /且つ|且/

また、「又」が誤った書き方で「また」に訂正したい場合、「又」が別の単語で使われていてそちらを訂正しないようにするためには、正規表現で接続詞としての「又」のみを検知しつつ、適切に訂正できるようになります。この例では、正規表現(、|は|は、)部分は$1で充当されるため、「又は」は「または」に訂正されるようになります。

version: 1
rules:
  - expected: また$1
    patterns:
      - /又(、|は|は、)/

正規表現の設定ミスなどにより思わぬ訂正が行われる可能性もあります。必ずチェックで正しく検出できているかを確認のうえ、訂正するようにしてください。

まとめ

textlintは、文章の自動校正ツールとして非常に優れています。自己チェックと併用してtextlintを使うことで、より良い文章を効率的に作成することができるようになるでしょう。ぜひ、textlintを使ってみてください。

Storybookを使ってモックアップを作成する

プロジェクト開発において、高品質なモックアップを作成することは非常に重要です。しかし、モックアップ作成プロセスはしばしば面倒で、費用がかかります。ReactなどのUIフレームワークのデザインガイドライン作成ツールとしてよく知られているStorybookを使うことで、手軽にモックアップを作成できます。この記事では、どのようにStorybookがモックアップ作成に役立つかについて紹介します。

Storybookのモックアッププロジェクトを作成する

Storybookを使ったモックアッププロジェクトを作成します。

今回は、UIフレームワークを選定していない状況で、とりあえずBootstrapとjQueryを使ってモックアップを作成する状況を想定していますので、--typeオプションでhtmlを指定します。

$ storybook-mockup
$ cd storybook-mockup
$ npm init -y
$ npx storybook@latest init --type html

本記事では、storybook-mockupというプロジェクト名で進めますが、実際に試す際はプロジェクトに合わせて命名してください。

最後のコマンドを実行すると、Storybookが自動的に起動します。

Storybookを停止したい場合はターミナルでCtrl+Cを入力して停止してください。これ以降、Storybookを実行したい場合は、package.jsonがあるディレクトリで以下のコマンドを実行してください。

$ npm run storybook

storiesディレクトリを整理する

この手順は必ずしも必要ではありませんが、モックアップ作成作業がやりやすくなるようにstoriesディレクトリを整理しておきます。

どのように整理するのかはプロジェクトの方針やメンバーの好みによって変わりますが、物理ディレクトリの構造とStorybookのサイドメニューの構造が一致している方が探しやすくなると思います。

作成直後の状態では、EXAMPLEというサイドメニュー表示されていますが、ボタンやドロップダウンリストなどのパーツを「コンポーネント(components)」、モックアップを「ページ(pages)」に分けてファイルを整理し、サイドメニューも同様に分けるようにします。

また、Storybookでは1コンポーネント1ファイルにはなりません。このまま、複数のコンポーネントやページを同じディレクトリに並べておくと見にくくなるので、コンポーネント名でディレクトリを作成して、ファイルをまとめることにしておきます。

ご使用のIDEやエディタの機能によって、ファイル移動に伴ってimport文が自動的に修正されますが、操作によっては適切に修正されない場合があります。その場合はimport文を直接修正してください。

Storybookのサイドメニューはstoriesディレクトリの構造に合わせて作成しているわけではなく、.Stories.jsファイル内に定義されているtitle属性の値で構造が決まります。

Button.stories.jsHeader.stories.jstitle属性のExampleComponentsに、Page.stories.jstitle属性のExamplePagesに修正しておきます。

すべてのコードを示すとかなりの量になるので、修正した箇所だけ説明します。コード全体を確認したい場合は、https://github.com/t0k0sh1/storybook-mockupを参照してください。

import { createButton } from "./Button";

// More on how to set up stories at: https://storybook.js.org/docs/writing-stories
export default {
  title: "Components/Button",
  tags: ["autodocs"],
  render: ({ label, ...args }) => {

title属性を"Example/Button"から"Components/Button"に修正しています。

import "./header.css";
import { createButton } from "../Button/Button";

Header.jsからButton.jsを参照していますが、そのimport文のパスをディレクトリ構造に合わせて修正しています。

import { createHeader } from "./Header";

export default {
  title: "Components/Header",
  // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
  tags: ["autodocs"],
  render: (args) => createHeader(args),

title属性を"Example/Header"から"Components/Header"に修正しています。

import "./page.css";
import { createHeader } from "../../components/Header/Header";

Page.jsからHeader.jsを参照していますが、そのimport文のパスをディレクトリ構造に合わせて修正しています。

import { expect, userEvent, within } from "@storybook/test";
import { createPage } from "./Page";

export default {
  title: "Pages/Page",
  render: () => createPage(),

title属性を"Example/Page"から"Pages/Page"に修正しています。

StorybookにBootstrapとjQueryを導入する

StorybookにBootstrapとjQueryを導入していきます。導入方法にはいくつかの方法がありますが、ここではCDNを使って手軽に導入する方法を解説します。

Bootstrapを使用できるようにする

StorybookでCDNを使用する場合、.storybook/preview-head.htmlを新規作成し、そこにCDNにアクセスlinkタグを記述します。

<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
  integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
  crossorigin="anonymous"
/>

jQueryを使用できるようにする

こちらも同様に.storybook/preview-head.htmlにCDNにアクセスするscriptタグを記述します。

<script
  src="https://code.jquery.com/jquery-3.7.1.min.js"
  integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
  crossorigin="anonymous"
></script>

これでモックアップ作成の準備が整いました。

ButtonコンポーネントをBootstrapベースに書き換える

最後にすでに存在するButtonコンポーネントをBootstrapベースに書き換えてみましょう。

まずは、現在の実装を確認してみます。

import { createButton } from "./Button";

// More on how to set up stories at: https://storybook.js.org/docs/writing-stories
export default {
  title: "Components/Button",
  tags: ["autodocs"],
  render: ({ label, ...args }) => {
    // You can either use a function to create DOM elements or use a plain html string!
    // return `<div>${label}</div>`;
    return createButton({ label, ...args });
  },
  argTypes: {
    backgroundColor: { control: "color" },
    label: { control: "text" },
    onClick: { action: "onClick" },
    primary: { control: "boolean" },
    size: {
      control: { type: "select" },
      options: ["small", "medium", "large"],
    },
  },
};

// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
export const Primary = {
  args: {
    primary: true,
    label: "Button",
  },
};

export const Secondary = {
  args: {
    label: "Button",
  },
};

export const Large = {
  args: {
    size: "large",
    label: "Button",
  },
};

export const Small = {
  args: {
    size: "small",
    label: "Button",
  },
};

今回注目するのは、primary属性とsize属性です。他の属性もありますが、Button.stories.jsは修正せずにいきます。

import './button.css';

export const createButton = ({
  primary = false,
  size = 'medium',
  backgroundColor,
  label,
  onClick,
}) => {
  const btn = document.createElement('button');
  btn.type = 'button';
  btn.innerText = label;
  btn.addEventListener('click', onClick);

  const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
  btn.className = ['storybook-button', `storybook-button--${size}`, mode].join(' ');

  btn.style.backgroundColor = backgroundColor;

  return btn;
};

属性に応じてclassNameに設定する値を制御します。方針は以下です。

  • primaryが指定されたらbtn-primaryを指定し、指定されなかったらbtn-secondaryを指定する
  • sizesmallならbtn-smを指定し、largeならbtn-lgを指定し、それ以外は何も指定しない

修正後のコードは以下になります。storybook-buttonクラスをbtnに修正してあります。

import "./button.css";

export const createButton = ({
  primary = false,
  size = "medium",
  backgroundColor,
  label,
  onClick,
}) => {
  const btn = document.createElement("button");
  btn.type = "button";
  btn.innerText = label;
  btn.addEventListener("click", onClick);

  const mode = primary ? "btn-primary" : "btn-secondary";
  let btnSize = size === "small" ? "btn-sm" : size === "large" ? "btn-lg" : "";
  btn.className = ["btn", btnSize, mode].join(" ");

  btn.style.backgroundColor = backgroundColor;

  return btn;

表示した結果は以下のようになります。

まとめ

以上のように、Storybookはプロジェクト開発において、手軽かつ効果的なモックアップ作成ツールであることが分かりました。Storybookを使用すると、コンポーネント化やパーツ化が手軽に推進できるので、デザインガイドラインを作成しながらモックアップを作成できます。Storybookを使うことで、モックアップ作成の開発プロセスを改善し、開発者のストレスを軽減することができます。皆さんにも一度、Storybookを使用して、モックアップ作成をスムーズに進めてみてはいかがでしょうか?

pipxでPoetryをインストールする

公式サイトでのPoetryのインストール方法の一つにpipxを使ったインストール方法があって、それが気になったので、pipxを導入してPoetryをインストールしていこうと思います。手順はmacOSの場合になります。

pipxのインストール

Homebrewを使ってpipxをインストールします。

$ brew install pipx
==> Downloading https://formulae.brew.sh/api/formula.jws.json
##O=-#      #
==> Downloading https://formulae.brew.sh/api/cask.jws.json
##O=-#      #
==> Downloading https://ghcr.io/v2/homebrew/core/pipx/manifests/1.3.3
Already downloaded: /Users/t0k0sh1/Library/Caches/Homebrew/downloads/af94290372652b3f23470aa9b2dc3e6cc6f6ac908d786fa0b2ccf9c0f53db957--pipx-1.3.3.bottle_manifest.json
==> Fetching pipx
==> Downloading https://ghcr.io/v2/homebrew/core/pipx/blobs/sha256:31547c41734fa46c13276ada25e3e8548db97281d0c513b9cdcb5268adcc74ff
Already downloaded: /Users/t0k0sh1/Library/Caches/Homebrew/downloads/a4c05e49cf7f84a6647e146f9d68d9983028ea48180d87771a41cdb1f5d27b45--pipx--1.3.3.arm64_sonoma.bottle.tar.gz
==> Pouring pipx--1.3.3.arm64_sonoma.bottle.tar.gz
==> Caveats
zsh completions have been installed to:
  /opt/homebrew/share/zsh/site-functions
==> Summary
🍺  /opt/homebrew/Cellar/pipx/1.3.3: 108 files, 697.7KB
==> Running `brew cleanup pipx`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).

インストール後、pipx ensurepathコマンドを実行します。

$ pipx ensurepath
Success! Added /Users/t0k0sh1/.local/bin to the PATH environment variable.

Consider adding shell completions for pipx. Run 'pipx completions' for instructions.

You will need to open a new terminal or re-login for the PATH changes to take effect.

Otherwise pipx is ready to go! ✨ 🌟 ✨

上記メッセージ中に、

You will need to open a new terminal or re-login for the PATH changes to take effect.

とあるので、新しいターミナルを開くかログインし直す必要があります。今回はこのままインストールを進めたいので、設定ファイルを読み込み直します。

$ exec $SHELL -l

これでインストールは完了です。

activate-global-python-argcompleteのlinkでエラー

Homebrewでインストールする際に以下のエラーに遭遇しました。

Error: The `brew link` step did not complete successfully
The formula built, but is not symlinked into /opt/homebrew
Could not symlink bin/activate-global-python-argcomplete
Target /opt/homebrew/bin/activate-global-python-argcomplete
already exists. You may want to remove it:
  rm '/opt/homebrew/bin/activate-global-python-argcomplete'

To force the link and overwrite all conflicting files:
  brew link --overwrite python-argcomplete

To list all files that would be deleted:
  brew link --overwrite --dry-run python-argcomplete

事象としては、activate-global-python-argcompleteのsymlinkに失敗したようです。解消方法としてはいくつかありますが、ここでは一番最初に書かれている/opt/homebrew/bin/activate-global-python-argcompleteを削除する方法で進めます。

指示どおりに対応してきます。まずは、activate-global-python-argcompleteが存在するかをチェックします。

$ ls /opt/homebrew/bin/activate*
/opt/homebrew/bin/activate-global-python-argcomplete

存在していることを確認できました。これを削除します。

$ rm /opt/homebrew/bin/activate-global-python-argcomplete
$ ls /opt/homebrew/bin/activate*
zsh: no matches found: /opt/homebrew/bin/activate*

削除されたことを確認しました。では、元の手順に戻って、再度pipx installコマンドを実行します。

Poetryをインストールする

pipxを使ってPoetryをインストールします。

$ pipx install poetry
  installed package poetry 1.7.1, installed using Python 3.12.1
  These apps are now globally available
    - poetry
⚠️  Note: '/Users/t0k0sh1/.local/bin' is not on your PATH environment variable. These apps will not be globally accessible until your PATH is updated. Run `pipx ensurepath` to automatically add it, or
    manually modify your PATH in your shell's config file (i.e. ~/.bashrc).
done! ✨ 🌟 ✨

インストール後、コマンドが使えるようになっていることを確認します。

$ poetry -V
Poetry (version 1.7.1)

これでPoetryのインストールは完了しました。

まとめ

pipxのインストールで少しトラブルがありましたが、比較的簡単にインストールを進めることができました。

PYthon製のCLIをインストールする際は、pipxを使っていくのが良さそうです。

Vueで確認ダイアログ(Confirmation Dialog)を実装する

Vueで確認ダイアログを実装する簡単な方法を説明します。

axiosなどを使ってデータを送信する場合と、フォームを使ってデータを送信する場合とで実装が少し異なるため、それぞれのパターンで説明します。

プロジェクトの作成

まずは動作確認を行うプロジェクトを作成します。

$ npm create vue@latest
Vue.js - The Progressive JavaScript Framework

✔ Project name: … vue-confirm-dialog
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add an End-to-End Testing Solution? › No
✔ Add ESLint for code quality? … No / Yes

Scaffolding project in /Users/t0k0sh1/Workspace/vue-confirm-dialog...

Done. Now run:

  cd vue-confirm-dialog
  npm install
  npm run dev

設定に制約は特にありませんが、以降ではJavaScriptでコードを示します。

確認ダイアログコンポーネント

確認ダイアログコンポーネントを作成します。

<template>
  <div v-if="isVisible" class="dialog">
    <div class="dialog-content">
      <!-- ダイアログの内容 -->
      <button @click="submit" class="button ok">OK</button>
      <button @click="cancel" class="button cancel">キャンセル</button>
    </div>
  </div>
</template>

<style>
.dialog {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background-color: white;
  border: 1px solid #ccc;
  padding: 20px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  z-index: 1000;
}

.dialog-content {
  text-align: center;
}

.button {
  margin: 10px;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.3s;
}

.ok {
  background-color: #4caf50;
  color: white;
}

.ok:hover {
  background-color: #45a049;
}

.cancel {
  background-color: #f44336;
  color: white;
}

.cancel:hover {
  background-color: #d73829;
}
</style>

<script>
export default {
  props: ["isVisible"],
  methods: {
    submit() {
      this.$emit("confirm");
    },
    cancel() {
      this.$emit("cancel");
    },
  },
};
</script>

この確認ダイアログは表示/非表示の制御をv-ifで行い、その制御は親コンポーネントに委ねています。また、OKボタンを押したか、キャンセルボタンを押したかを$emitで返すようにしています。

ダイアログの表示/非表示

ダイアログの表示/非表示の制御はv-if="isVisible"で行っています。

<template>
  <div v-if="isVisible" class="dialog">
    ・・・
  </div>
</template>

isVisiblepropsに定義されており、親コンポーネントから子コンポーネントへの単方向データフローによって値が渡されます。

そのため、親コンポーネントで以下のように定義した場合、

<confirm-dialog-component :is-visible="isDialogVisible"></confirm-dialog-component>

親コンポーネントでisDialogVisible = trueとすればダイアログが表示され、isDialogVisible = falseとすればダイアログが非表示になります。propsではキャメル形式になっていますが、コンポーネント使用時に指定する名前はケバブ形式になっている点に注意してください。

OKボタンを押下したときの処理

OKボタンを押下したときの処理について見ていきます。

<template>
  ・・・
      <button @click="submit" class="button ok">OK</button>
  ・・・
</template>

OKボタンには@click="submit"が付与されていて、このボタンをクリックするとmethodsに定義されているsubmit関数を呼び出します。

submit関数では、$emit関数を使って"confirm"というイベントを発生させ、子コンポーネントから親コンポーネントへの単方向データフローによってイベントと必要であればデータを渡します。親コンポーネントがバインドしたコールバック関数を呼び出している感覚に近いイメージになります。

<script>
export default {
  ・・・
  methods: {
    submit() {
      this.$emit("confirm");
    },
    ・・・
  },
}
</script>

confirmイベントが発生すると、親コンポーネントがこのコンポーネントに設定している関数を呼び出します。

例えば、親コンポーネントがこのコンポーネントを以下のように定義している場合、confirmイベントが発生すると、親コンポーネントのhandleConfirm関数が呼び出されます。

<template>
    ・・・
    <confirm-dialog-component @confirm="handleConfirm"></confirm-dialog-component>
    ・・・
</template>

親コンポーネントではOKボタンが押下されたことを検知できるので、サーバーへのデータ送信やダイアログを非表示にする処理を実装します。

<script>
import axios from 'axios';  

export default {
  ・・・
  methods: {
    handleConfirm() {
      axios.post(・・・)
        .then(response => {
          // サーバーへのデータ送信が成功したときの処理
        })
        .catch(error => {
          // サーバーへのデータの送信が失敗したときの処理
        });
      // 確認ダイアログを非表示にする
      this.isDialogVisible = false;
    },
    ・・・
</script>

上記の例では、axiosを使ってサーバーにデータを送信しつつ、確認ダイアログを非表示にしています。

キャンセルボタンを押下したときの処理

同様にキャンセルボタンを押下したときの処理を見てみましょう。

<template>
  ・・・
      <button @click="cancel" class="button cancel">キャンセル</button>
  ・・・
</template>

クリック時に呼び出される関数が異なるだけで基本的にはOKボタン押下時と同じで、methodsに設定しているcancel関数が呼び出されます。

<script>
export default {
  ・・・ 
   cancel() {
      this.$emit("cancel");
    },
    ・・・
  },
}
</script>

キャンセルボタンを押下したときは$emit関数を使って"cancel"というイベントを発生させます。

OKボタンのときと同様に、親コンポーネントでcancelイベントを処理する関数を定義します。

<template>
  ・・・
    <confirm-dialog-component @cancel="handleCancel"></confirm-dialog-component>
  ・・・
</template>

handleCancel関数では単に確認ダイアログを閉じるだけにします。

export default {
  ・・・
  methods: {
    ・・・
    handleCancel() {
      // 確認ダイアログを非表示にする
      this.isDialogVisible = false;
    },
     ・・・
  },
}
</script>

ボタンクリックによる確認ダイアログの表示

axiosなど使ってサーバーにデータを送信するような使い方の場合はこちらが適しています。

前述のとおり、作成した確認ダイアログコンポーネントは以下のように

  • ダイアログ表示・非表示制御用のフラグ(isDialogVisible
  • ダイアログを表示する処理(handleClick
  • OKボタン押下時の処理(handleConfirm
  • キャンセルボタン押下時の処理(handleCancel

を用意することで確認ダイアログを使用することができます。。

<template>
  <input type="button" value="クリック" @click.prevent="handleClick" />
    <confirm-dialog-component
      :is-visible="isDialogVisible"
      @confirm="handleConfirm"
      @cancel="handleCancel"
    ></confirm-dialog-component>
  </form>
</template>

<script>
import ConfirmDialogComponent from "./ConfirmDialogComponent.vue";

export default {
  components: {
    ConfirmDialogComponent,
  },
  data() {
    return {
      isDialogVisible: false,
    };
  },
  methods: {
    handleConfirm() {
      axios.post(・・・)
        .then(response => {
          // サーバーへのデータ送信が成功したときの処理
        })
        .catch(error => {
          // サーバーへのデータの送信が失敗したときの処理
        });
      this.isDialogVisible = false;
    },
    handleCancel() {
      this.isDialogVisible = false;
    },
    handleClick(e) {
      this.isDialogVisible = true;
    },
  },
};
</script>

ボタンクリック時に呼び出されるhandleClick関数はダイアログ表示後にすぐに処理が終了するため、@click.preventをつけることで、preventDefault関数を呼び出したのと同様にクリック処理が発火しないようにしています。

ほとんどのコードはすでに説明済みですが、ダイアログを表示する関数だけ説明していなかったので、コードを示しておきます。といっても確認ダイアログコンポーネントに設定しているisDialogVisibleをtrueに変更してダイアログを表示するだけの処理になります。

export default {
  ・・・
  methods: {
    ・・・
    handleClick(e) {
      this.isDialogVisible = true;
    },
  },
}
</script>

サブミットによる確認ダイアログの表示

サーバーへのデータ送信をフォームで行う場合はこちらが適しています。

前述のとおり、確認ダイアログの表示中は呼び出し元関数の処理をブロックしません。また、サブミットはENTERキー押下でも行えるため、これらに対する対策が必要となります。

<template>
  <form @submit.prevent="handleSubmit">
    <input type="submit" value="サブミット" />
    <confirm-dialog-component
      :is-visible="isDialogVisible"
      @confirm="handleConfirm"
      @cancel="handleCancel"
    ></confirm-dialog-component>
  </form>
</template>

<script>
import ConfirmDialogComponent from "./ConfirmDialogComponent.vue";

export default {
  components: {
    ConfirmDialogComponent,
  },
  data() {
    return {
      isDialogVisible: false,
      resolveDialog: null,
    };
  },
  methods: {
    showDialog() {
      this.isDialogVisible = true;
      return new Promise((resolve, reject) => {
        this.resolveDialog = resolve;
      });
    },
    handleConfirm() {
      if (this.resolveDialog) {
        this.resolveDialog(true);
      }
      this.isDialogVisible = false;
    },
    handleCancel() {
      if (this.resolveDialog) {
        this.resolveDialog(false);
      }
      this.isDialogVisible = false;
    },
    async handleSubmit(e) {
      // show confirm dialog
      const confirmed = await this.showDialog();
      if (confirmed) {
        e.target.submit();
      }
    },
  },
};
</script>

ここではプロミスを使っていますが、プロミスを使わなくても実装は可能です。ただ、少しだけスマートに実装できるようになるため、プロミスを使って実装ています。

ダイアログ表示時にプロミスを作成する

まず、ダイアログを表示するときにプロミスを作成し、それを返します。このとき、resolvedataに保持しておきます。

<script>
import ConfirmDialogComponent from "./ConfirmDialogComponent.vue";

export default {
  components: {
    ConfirmDialogComponent,
  },
  data() {
    return {
      isDialogVisible: false,
      resolveDialog: null,
    };
  },
  methods: {
    showDialog() {
      this.isDialogVisible = true;
      return new Promise((resolve, reject) => {
        this.resolveDialog = resolve;
      });
    },
    ・・・
  },
};
</script>

プロミス内でdataに保存しているresolveはOKボタン、キャンセルボタンが処理結果を返すために使用します。

OKボタンを押下したときの処理

OKボタンを押下するときに呼び出されるhandleConfirm関数では、ダイアログを非表示にする処理以外にプロミス作成時にdataに保存したresolveresolveDialog)にtrueを設定しています。

export default {
  ・・・
  methods: {
    ・・・
    handleConfirm() {
      if (this.resolveDialog) {
        this.resolveDialog(true);
      }
      this.isDialogVisible = false;
    },
     ・・・
  },
}
</script>

また、フォームでデータを送信するため、handleConfirm関数ではサーバーでデータを送信する処理は行いません。

キャンセルボタンを押下したときの処理

同様にキャンセルボタンを押下したときに呼び出されるhandleCancel関数を見ていきます。

export default {
  ・・・
  methods: {
    ・・・
    handleCancel() {
      if (this.resolveDialog) {
        this.resolveDialog(false);
      }
      this.isDialogVisible = false;
    },
     ・・・
  },
}
</script>

違いはresolveresolveDialog)にfalseを設定している点でそれ以外に違いはありません。

ダイアログを開く処理

ダイアログを開く処理を見ていきます。

まず、ダイアログを開く関数はフォームに設定し、このとき使用するのは@submit.preventになります。

<form @submit.prevent="handleSubmit">

サブミットボタンではなく、フォームのsubmitイベントに関数を設定し、.preventをつけてフォームの送信処理を行わないようにする点がポイントとなります。これによりサブミットボタンをクリックしてもENTERキーを押下してもフォームの送信は行われなくなります。そのため、別の手段でフォームの送信を行う必要が出てきます。

次に@submit.preventに設定したhandleSubmit関数を見ていきます。

export default {
  ・・・
  methods: {
    ・・・
    async handleSubmit(e) {
      // show confirm dialog
      const confirmed = await this.showDialog();
      if (confirmed) {
        e.target.submit();
      }
    },
  },
}
</script>

この関数はshowDialog関数が返すプロミスの結果を受け取るためにasync関数として実装します。そして、showDialog関数をawaitをつけて同期化し、ダイアログが閉じるまで(OKボタンまたはキャンセルボタンでresolveを呼び出されるまで)待つようにします。これがダイアログが閉じるまでの間ブロックしない問題に対する対策です。

showDialog関数が返すプロミスの結果はtrue(OKボタン押下時)、false(キャンセルボタン押下時)のいずれかであるため、trueのときにe.target(これはフォームそのもの)のsubmit関数を呼び出すことでフォームの送信を行うことができます。falseが返ってきたときは何も処理をしなければpreventpreventDefault関数と同じ効果)によってフォームの送信は行われません。

前述のとおり、プロミスを使わず、async-awaitを使わずにhandleConfirm関数でフォームのsubmit関数を呼び出すことも可能ですが、フォームを探す一手間があるため、見やすくするためにプロミスとasync-awaitを使っています。

まとめ

Vueで作成した確認ダイアログを表示し、OKボタンをクリックしたときだけ処理を続行する方法をボタンクリックの場合とフォームを使ったサブミットの2パターンの実装方法を見ていきました。

この考え方は他のフレームワークでも適用可能です。今回はVue 3.3.4で動作確認していますが、Vue 3で実装された機能を使っていないため、Vue 2でも動作すると思います。

動作可能なコードははhttps://github.com/t0k0sh1/vue-confirm-dialog.gitにあります。

ab(Apache Bench)の使い方

ストレステストツールの一つであるabの基本的な使い方について解説します。

abのインストール

macOSではデフォルトでインストール済みのため、すぐに使用できます。

Ubuntuの場合は、以下のコマンドでインストールできます。

sudo apt install apache2-utils

基本的な使い方

最も重要な-nオプションと-cオプションについて説明します。

ab -n [全リクエスト数] -c [同時接続数] [URL]
  • -n:総リクエスト数を指定します
  • -c:同時に送信するリクエスト数(同時接続数)を指定します
  • [URL]:テスト対象のURL

-nで指定したリクエスト数を-cで指定した多重度で送信します。-cで指定した多重度でそれぞれが-nで指定したリクエスト数を送信するのではない点に注意してください。

例えば、総リクエスト数1,000(-n 1000)を4多重(-c 4)で実行した場合、実際に送信されるリクエストは4,000ではなく1,000になります。ただし、総リクエスト数は多重度で割り切れる必要はなく、総リクエスト数1,000に対して3多重という設定も可能です。

実行例

例えば、http://localhost:8080に対して、計1,000回のリクエストを10多重で送信する場合は以下のようにします。

$ ab -n 1000 -c 10 http://localhost:8080/
This is ApacheBench, Version 2.3 <$Revision: 1903618 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        Apache/2.4.56
Server Hostname:        localhost
Server Port:            8080

Document Path:          /
Document Length:        49730 bytes

Concurrency Level:      10
Time taken for tests:   4.907 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      50006000 bytes
HTML transferred:       49730000 bytes
Requests per second:    203.77 [#/sec] (mean)
Time per request:       49.075 [ms] (mean)
Time per request:       4.907 [ms] (mean, across all concurrent requests)
Transfer rate:          9950.92 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.2      0       5
Processing:    35   48  12.5     46     266
Waiting:       34   45   7.8     44      88
Total:         36   48  12.5     46     266

Percentage of the requests served within a certain time (ms)
  50%     46
  66%     49
  75%     52
  80%     54
  90%     59
  95%     65
  98%     73
  99%     76
 100%    266 (longest request)

実行結果の見方

先ほど示した実行例をもとに実行結果のうち、主な項目についての見方を説明します。

Requests per secondは、1秒あたりのリクエスト数をあらわしています。実行例では203.77となっており、1秒あたり203リクエストを処理していることがわかります。

Time per requestは、1リクエストあたりの平均応答時間をあらします。実行例では2つのTime per requestがあり、最初のTime per requestは1リクエストあたりの平均応答時間で、平均49.075msで処理できていることがわかります。もう一つのTime per requestは、多重度を考慮した平均応答時間で、今回は10多重にしているため、1/10にあたる4.907msで1リクエストが処理できていることをあらわしています。

Transfer rateは、平均転送速度で、1秒あたりに転送されるデータ量をあらわします。実行例では、9950.92Kbytes/secで転送しており、1秒あたり10MB近い転送速度になっています。

Complete requestsは完了したリクエスト数をあらわし、Failed requestsは正常に処理できなかったリクエスト数をあらわします。実行例では、1,000リクエスト中、正常に処理できなかったリクエストは0になっています。負荷を上げていくとFailed requests0でなくなるタイミングがでてきます。これが性能限界をあらわしています。

例えば、実行例と同じ環境で20,000リクエストを200多重で行った結果は以下のようになりました。2リクエストはレスポンスの長さが想定と異なるエラーが発生しています。

Complete requests:      20000
Failed requests:        2
   (Connect: 0, Receive: 0, Length: 2, Exceptions: 0)

括弧内のエラーの意味は以下のようになります。

  1. Connect: サーバへの接続の際のエラー数。ネットワークの問題やサーバの過負荷、サーバのダウンなどが原因で接続できなかった場合にこの数が増えます
  2. Receive: サーバからのレスポンスの受信中に発生したエラー数。サーバからの応答が途中で切断されるなどの理由でこの数が増えることがあります
  3. Length: レスポンスの内容の長さが予期したものと異なる場合のエラー数。たとえば、予期していたHTMLページのサイズと実際に受信したサイズが異なる場合にこのエラーがカウントされます
  4. Exceptions: ab自体がエラーを捕捉した場合の数。これは通常、予期しないエラーや問題がabの実行中に発生した場合にカウントされます

Length

今回使用しているURLは固定のページを返しているため、リクエストの度にレスポンスの長さが変わることはありません。このことから、2回のLengthはエラーであるといえます。しかし、このエラー判定は同じURLが返すレスポンスの長さは常に同じという前提のもとに判定されているため、Webアプリケーションによってはリクエストごとにレスポンスの長さが変わる場合もありますので、必ずしもエラーとなりません。また、このLengthはHTTPヘッダーのContent Lengthと一致していないという判定でもない点に注意が必要です。

404エラー、500エラー

もう一つ重要な点が404エラーや500エラーを検知しない点です。負荷を上げたときにサーバーで500エラー担った場合でもレスポンスが返せているのであれば、ExceptionsやReceiveではなく、Lengthとして検知されます。

そのため、設定が誤っていて404エラーになるURLを指定してしまった場合でもエラーにはならずすべて正常に処理されます。

エラーの発生状況や正しいURLを実行できていることはアクセスログなどのサーバーログを併用して確認することが重要です。

その他の主要オプション

その他の主要オプションとして、-Aオプション、-Cオプション、-Hオプションを紹介します。

BASIC認証情報を設定する-Aオプション

BASIC認証でアクセス制限を行っている場合、-Aオプションを使って認証情報を設定します。

-A <ユーザーID>:<パスワード>

の形式で認証を情報を設定します。例えば、ユーザーID「user」、パスワード「password」で認証できるBASIC認証が設定されているサイトの場合、

$ ab -n 1000 -c 10 -A user:password http://localhost:8080/

のように指定します。

Cookieを設定する-Cオプション

認証情報をCookieで設定する場合などは-Cオプションを使用してCookieを設定します。

-C "<Cookie名>=<値>"

の形式で設定し、複数ある場合は-Cオプションを複数設定します。例えば、JWTトークン12345tokenというCookie名で送信する場合、

$ ab -n 1000 -c -C "token=12345" http://localhost:8080/

のように指定します。

HTTPヘッダーを設定する-Hオプション

HTTPヘッダーを設定する場合は、-Hオプションを使用します。

-H "<ヘッダー名>: <値>"

の形式で設定し、複数ある場合は-Hオプションを複数設定します。例えば、Accept-EncodingヘッダーとAccept-Languageヘッダーを設定する場合は、

$ ab -n 1000 -c 10 -H "Accept-Encoding: gzip, deflate, br" -H "Accept-Language: en-US,en;q=0.9,ja;q=0.8" http://localhost:8080/

のように指定します。

実行結果をファイルに出力する

実行結果をファイルに出力するオプションがいくつか用意されています。

HTML形式で出力する

テキスト形式で表示していた実行結果をHTML形式で出力するには-wオプションを使用します。このオプションを使用することでこれまでテキスト形式で出力されていた内容をHTML形式で出力できるようになります。コンソールにそのまま表示してもわかりにくいだけですので、基本的にはリダイレクトでファイルに出力して使用します。

$ ab -n 1000 -c 10 -w http://localhost:8080/ > result.html

最低限のタグ付けがされているだけですので、見た目はそれほどきれいという訳ではありません。

CSV形式で出力する

-eオプションで出力先のファイル名を指定することで、テキスト形式で表示していた内容とは別に0%から100%までのパーセンタイル情報がCSV形式で出力されます。

$ ab -n 1000 -c 10 -e result.csv http://localhost:8080/
$ head result.csv
Percentage served,Time in ms
0,34.739
1,36.607
2,37.105
3,37.471
4,37.737
5,38.012
6,38.288
7,38.449
8,38.511 

0の行が最速のレスポンス時間、50の行が中央値のレスポンス時間、100の行が最遅のレスポンス時間ををあらわします。

実行結果に表示されているPercentage of the requests served within a certain time (ms)のうち、50%が50の行、100%が100の行に対応しています。

TSV形式で出力する

最も使用することが多いのが-gオプションです。TSV形式としていますが、正確にはgnuplotフォーマットになりますが、実質的にTSVファイルとして扱って問題がありません。

$ ab -n 1000 -c 10 -g result.tsv http://localhost:8080/
$ head result.tsv
starttime	seconds	ctime	dtime	ttime	wait
Sun Oct 29 00:05:31 2023	1698505531	0	36	36	35
Sun Oct 29 00:05:29 2023	1698505529	0	36	37	36
Sun Oct 29 00:05:30 2023	1698505530	0	37	37	35
Sun Oct 29 00:05:29 2023	1698505529	0	37	37	36
Sun Oct 29 00:05:29 2023	1698505529	0	37	37	35
Sun Oct 29 00:05:30 2023	1698505530	0	36	37	35
Sun Oct 29 00:05:29 2023	1698505529	0	37	37	36
Sun Oct 29 00:05:29 2023	1698505529	0	37	37	36
Sun Oct 29 00:05:30 2023	1698505530	0	37	37	36

各カラムの意味は以下の通りです。

  • starttime: リクエストが開始された時刻。通常はエポックタイム(1970年1月1日からの経過秒数)で表される
  • seconds: リクエスト開始時間をエポックタイム(秒)
  • ctime: 接続時間。ターゲットサーバーへの接続が確立されるまでの時間(ミリ秒)
  • dtime: 処理時間。リクエストが送信されてからレスポンスが完全に受信されるまでの時間(ミリ秒)
  • ttime: 合計時間。接続の確立からレスポンスの完全な受信までの合計時間(ミリ秒)
  • wait: 応答待機時間。リクエストが完全に送信されてから最初のレスポンスバイトが受信されるまでの時間(ミリ秒)

この形式での出力ではいろいろな情報が得られます。

例えば、ctimeが長すぎる場合はサーバーが待ち受けられる接続の上限に達している可能性がありますし、waitが長すぎる場合はサーバーの処理に遅延が発生していたり、ワーカースレッドが足りない可能性があります。リクエストがエラーになっている原因やパフォーマンスが低下している原因を推測するための一助となる情報が得られる点では重要です。

abではできないこと

abはストレステストツールとして優秀ですが、シナリオテストような一連のURLを順番にアクセスするといった使い方はできません。シナリオテストで負荷をかける場合は、JMeterなどを使用してください。

まとめ

abはランディングページなど静的なサイトでのストレステストでは積極的に採用できます。ただし、クライアントで状態を管理していたり、特定の手順で負荷をかけたい場合はJMeterなど他のツールを採用する必要があります。

すばやく導入でき、複雑な設定なしで使用可能なため、サクッと負荷をかけたい場合は是非abを使ってみてください。

RabbitMQサーバーを立ち上げる

最近、MQサーバーに強い関心があり、その一つとしてRabbitMQサーバーを立ち上げてみました。

RabbitMQサーバーとは

RabbitMQは、メッセージ指向のミドルウェアで、アプリケーション間のメッセージのやり取りを仲介するためのメッセージキューサーバです。

RabbitMサーバーは、Erlang言語で書かれていて、クラスタリングとフェイルオーバーのためにOpen Telecom Platformフレームワークで構築されています。

元々はRabbit Technologies社で開発していましたが、2010年4月にSpringFrameworkの開発元であるSpringSource社に買収され、その後SpringSource社がVMware社に買収されました。そして、EMC、VMware、GEの出資で設立されたPivotal Software社がRabbitMQの開発・サポートを行っていましたが、2019年にVMware社がPivotal Software社を吸収合併し、今はVMware社がサポートを行っています。

特徴

RabbitMQは以下の特徴を持っています。

  1. 言語の中立性
    RabbitMQのクライアントは多くのプログラミング言語で利用できるため、異なる言語で書かれたアプリケーション間でもメッセージのやり取りが可能です。
  2. 耐障害性
    RabbitMQは、メッセージの永続化やクラスタリング機能を提供しており、障害時のデータの喪失リスクを低減することができます。
  3. 拡張性
    クラスタリングやフェデレーションといった機能を利用することで、大量のメッセージトラフィックを扱うことができます。
  4. 柔軟性
    トピックベースのルーティング、ワークキュー、RPCなど、さまざまなメッセージパターンをサポートしています。

主な概念

RabbitMQの主な概念は以下になります。

  1. Producer
    メッセージを生成してRabbitMQに送信するエンティティです。
  2. Consumer
    キューからメッセージを受け取って処理するエンティティです。
  3. Channel
    1つのコネクションを仮想的に分離する概念です。
  4. Queue
    メッセージが保存される場所。消費者はここからメッセージを取得します。
  5. Exchange
    プロデューサから受け取ったメッセージをキューにルーティングする役割を持ちます。RabbitMQにはいくつかの標準的な交換タイプ(direct, topic, fanout, headers)があります。

使われている用語はMQサーバー固有の用語でないため、他のMQサーバーでは少し違う用語で定義されている場合がありますので、ご注意ください。

RabbitMQサーバーの構築

RabbitMQサーバーの起動方法はいくつかありますが、ここでは手軽にDockerで起動することにします。

$ docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.12-management

Publisherを作成する

MQサーバーにメッセージを送信するPublisherを作成します。RabbitMQでは複数のプログラミング言語をサポートしていますが、ここではPythonのpikaパッケージを使用して作成していきます。

RabbitMQサーバーへのコネクションを確立する

RabbitMQサーバーへのコネクションを確立します。ここではローカルで起動しているRabbitMQサーバーへ接続するため、host='localhost'を指定しています。

import pika

# RabbitMQサーバーへのコネクションを確立する
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))

BlockingConnectionを使用してコネクションを確立すると、同期的に処理を実行することができます。

Channelを作成する

次にChannelを作成します。

import pika

# RabbitMQサーバーへのコネクションを確立する
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))

# チャネルを作成する
channel = connection.channel()

今回の例ではPublisherは1つのキューにメッセージを送信する経路だけがあればよいので、1つだけチャネルを作成しています。

キューを定義する

メッセージを送信するキューを作成します。キューにはキューを一意に識別するための名前をつける必要があり、ここではqueue='hello'を指定しています。

import pika

# RabbitMQサーバーへのコネクションを確立する
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))

# チャネルを作成する
channel = connection.channel()

# キューを作成する
channel.queue_declare(queue='hello')

メッセージを送信する

では、実際にメッセージを送信してみましょう。

メッセージの送信方法にはいくつかありますが、ここではチュートリアルでも使われている最もシンプルな方法で実装します。

import pika

# RabbitMQサーバーへのコネクションを確立する
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))

# チャネルを作成する
channel = connection.channel()

# キューを作成する
channel.queue_declare(queue='hello')

# メッセージを送信する
channel.basic_publish(exchange='', routing_key='hello', body='Hello World!!')

Exchangeタイプを''にするとデフォルトのExchangeタイプとなり、routing_keyで指定したキューに直接メッセージを送ります。

コネクションをクローズする

最後にコネクションをクローズします。

import pika

# RabbitMQサーバーへのコネクションを確立する
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))

# チャネルを作成する
channel = connection.channel()

# キューを作成する
channel.queue_declare(queue='hello')

# メッセージを送信する
channel.basic_publish(exchange='', routing_key='hello', body='Hello World!!')

# コネクションをクローズする
connection.close()

Consumerを作成する

次にメッセージを受け取るConsumerを作成します。Consumerはあるキューにメッセージが追加されるのを待ち、メッセージが追加されたらそのメッセージを取得して処理を行います。

コネクションを確立し、キューを定義する

コネクションを確立〜キューを作成するまでの流れはPublisherと同じです。キュー名はPublisherで定義したキュー名を同じにします。

import pika

# RabbitMQサーバーへのコネクションを確立する
pika_param = pika.ConnectionParameters(host='localhost')
connection = pika.BlockingConnection(pika_param)

# チャネルを作成する
channel = connection.channel()

# キューを作成する
channel.queue_declare(queue='hello')

メッセージ受信時に実行するコールバック関数を作成する

メッセージを受信したときに実行するコールバック関数を作成します。この関数はメッセージを受信するたびに呼び出されます。

import pika

# RabbitMQサーバーへのコネクションを確立する
pika_param = pika.ConnectionParameters(host='localhost')
connection = pika.BlockingConnection(pika_param)

# チャネルを作成する
channel = connection.channel()

# キューを作成する
channel.queue_declare(queue='hello')


def callback(ch, method, properties, body):
    """ メッセージを受信したときに実行されるコールバック関数 """
    print(f" {body} Received")
    ch.basic_ack(delivery_tag=method.delivery_tag)

メッセージの受信を待ち受ける

最後にメッセージの受信を待ち受けます。待ち受けるキューのキュー名とメッセージを受信した時に実行するコールバック関数を指定します。

import pika

# RabbitMQサーバーへのコネクションを確立する
pika_param = pika.ConnectionParameters(host='localhost')
connection = pika.BlockingConnection(pika_param)

# チャネルを作成する
channel = connection.channel()

# キューを作成する
channel.queue_declare(queue='hello')


def callback(ch, method, properties, body):
    """ メッセージを受信したときに実行されるコールバック関数 """
    print(f" {body} Received")
    ch.basic_ack(delivery_tag=method.delivery_tag)


# メッセージを受信する
channel.basic_consume(queue='hello', on_message_callback=callback)
channel.start_consuming()

まとめ

今回示したのは、非常に単純な内容です。実際に業務システムに適用するには、もっと複雑な設定が必要となる場合があります。

また、MQサーバーに関する記事で「デファクト」「デファクトスタンダード」という記述を見かけますが、これまで実務で使用している限りでは「とりあえずこれを使っておけばいい」といえるデファクトスタンダードは存在していないと考えています。

どういった目的で使用するのか、動作するプラットフォームは何か、MQサーバーを管理するか/しないか、どれくらいの頻度でメッセージの送信・受信が行われるのか、どうメッセージを配信するのか、エコシステムで使用される関連するミドルウェアは何か、などの複数の要因でどのMQサーバーが最適か、どのMQサーバーは適していないか、が変わります。

そのため、複数のMQサーバーを比較・検討してどのMQサーバーを使用するかを選定する必要がありますが、MQサーバーによって用語が異なっていたり、同じ用語でも指し示しているものが少し違っていたりするため、用語を正しく理解することは非常に重要です。

マイクロサービスが浸透してくるにつれてサービス間を疎結合に保つことが一般的になってきました。また、大規模・超大規模なシステムではSOAの考え方を引き継いでいるものもあり、そういったシステムではシステム間をMQやメッセージバスによって接続している場合もあります。これからはもっとMQサーバーを利用することが多くなってくることが予想されるため、基本的な使用方法だけでも知っておいた方がよいと思います。

モバイルバージョンを終了