AI can fly !!

AI がやりたい Web エンジニアのアウトプット (AI の知識は無い)

【最近のお仕事】モバイル向け Web アプリケーション開発 (アーキテクチャと技術スタックの紹介)

Architecture

はじめに

先日、担当するプロジェクトでモバイル向け Web アプリケーションの二次開発分をリリースしたので、備忘録も兼ねて現時点で採用しているアーキテクチャと技術スタックについて紹介します。

当 Web アプリはマネージャーとエンジニア (私) の二名体制によるスクラッチ開発で昨年ローンチされ、今回のプロジェクトはチャットや外部 API からのデータ取得等の機能追加を目的としたものでした。

プロジェクトメンバは昨年同様、マネージャーとエンジニア (私) の二名体制になります。

開発期間は設計を含めて片手間で三か月程度、実質一か月~一か月半で仕上げましたが、なんとか納期までにはリリースできたものの完全に工数を見誤りました…

後半は土日も含めて毎日深夜まで…おっと、それでは気を取り直して駆け足で紹介していきます。

アプリの詳細な仕様については、守秘義務などの兼ね合いから記載しませんので悪しからず。

アーキテクチャ

バックエンドは Firebase (および、Google Cloud) をメインとしたサーバレスアーキテクチャを採用し、フロントエンドは Angular を使用した SPA (Single Page Application) で構築しています。

当 Web アプリケーションの要求仕様の一つとして、他社ハードウェアからのメール受信 (Gmail) をトリガとするバックエンド処理がありますが、こちらは Gmail APIGoogle Cloud の Cloud Pub/Sub で対応しました。

今気付きましたが、クラウド環境、バックエンドからフロントエンド、ターゲットとなるモバイル端末 (Android) に至るまで Google で統一されていますね。

アーキテクチャ全体を通して、比較的 Google 製品はシンプルで分かりやすく、とっつきやすさと使い勝手が良いと感じています。

最近、別のプロジェクトで Microsoft Azure を使用していますが、断然 Google Cloud の方が扱いやすいです…

技術スタック

フロントエンド

Angular

SPA で Web アプリケーションを構築するにあたり、開発プラットフォームはこれまで複数の開発実績がある Angular を採用しました。

Angular は開発に必要なパッケージやツールのほとんどが公式にサポートされており、別途サードパーティ製のパッケージなどを組み合わせるコストが発生せず、一体感のある開発体験を行えるのがメリットの一つです。

今回バックエンドとして採用した Firebase との親和性も高く、後述する Angular 公式ライブラリの一つである AngularFire を使用することで、より自然に Angular のスタイルに則したコードを記述することができます。

angular.jp

AngularFire

Firebase を操作するための JavaScript SDK をラップし、 Angular に適したスタイルのコードを書くことができる Angular 公式ライブラリです。

Firebase の各製品を操作する SDK を Service として Component に DI したり、 Angular CLI と連携して Angular アプリを Firebase へデプロイするなど、 Angular で Firebase の開発をするには必須のライブラリと言えます。

github.com

Angular/PWA

PWA とは Web アプリケーションを端末へインストールしたり、オフラインでの操作やバックグラウンドでのプッシュ通知の受信など、ざっくり言えば Web アプリでネイティブアプリのような機能を実現するための技術です。

今回は Firebase Cloud Messaging を使用して送信されたプッシュ通知の受信と表示を行うため、 Angular/PWA を使用しました。

Service Worker のおかげで意図せず初期表示が高速になる恩恵が得られましたが、キャッシュのコントロールなどは行っていないため、アプリ更新時は手動でブラウザを更新するなどのひと手間が必要になります。

ただ、当アプリのリリースサイクルはプロジェクト単位でのリリースとなるため、特に問題にはなりませんでした。

developer.mozilla.org

Angular Flex-Layout

Angular でモバイルファーストなレスポンシブデザインを行うため、 Flexbox CSS とメディアクエリを中心とした Angular 公式パッケージの一つである Angular Flex-Layout を使用しました。

当アプリはモバイル (Android) 端末をターゲットとしていますが、 PC ブラウザでもレイアウトが崩れずに操作が可能なレスポンシブデザインを実現しています。

github.com

Angular Material

Angular 公式の UI ライブラリで、 Material Design を採用したコンポーネントが数多く提供されています。

一つ一つのコンポーネントが Angular の Module として提供されているため、コンポーネントのタグを書くことで簡単に配置でき、プロパティバインディングやイベントハンドリングも容易です。

洗練された Material Designコンポーネントを組み合わせるだけで簡単にそれっぽい Web アプリケーションを作ることができますが、デフォルトのデザインのままでは他との差別化が難しいという点はデメリットと言えなくもありません。

material.angular.io

NgRx

Angular に Redux (Flux アーキテクチャ) のような状態管理の各種機能を提供するライブラリです。

Angular ではシングルトンである Service クラスを使用することで状態の管理を行うことができますが、 NgRx を使用すれば決まったルールの中で一貫した思想に基づいたコードを書くことができます。

プロジェクトやエンジニアごとに思想が異なるオレオレ状態管理は、複数名での開発や運用・保守の場などでは非常に大きな障害・負債になりかねず、プロジェクト初期の学習コストを差し引いてもデファクトスタンダードになっている状態管理技術の導入はメリットがあると思います。

ngrx.io

バックエンド

Firebase

言わずと知れたモバイル・ Web アプリケーション開発プラットフォームで、フルマネージドな Web ホスティングサービスやドキュメントデータベースなどがサーバレス (Saas) で提供されています。

提供元は天下の Google 様です。

必要に応じて複数の製品を組み合わせ、インフラを意識せずに、よりフロントエンドに注力したアプリケーション開発を行うことができます。

firebase.google.com

Firebase Hosting

HTML や CSS などの静的コンテンツだけでなく、 Express などの動的コンテンツもホストできる (らしい) Web ホスティングサービスです。

今回は Angular アプリケーションのホスティングと、後述する Cloud Functions へのリダイレクトを行っています。

firebase.google.com

Firebase Authentication

電話番号やメールアドレス、パスワード等を利用したユーザ認証をアプリケーションへ提供するサービスです。

アプリからのユーザ登録や各種アカウントによるログインもサポートしますが、今回は予め決められたユーザ (メールアドレス) のみ利用可能な Web アプリケーションということで、事前登録されたメールアドレスとパスワードによるログイン認証機能を実装しました。

firebase.google.com

Cloud Firestore

スケーラブルなクラウドホストの NoSQL データベースで、アプリケーションから SDK を通して直接アクセスが可能です。

リアルタイムリスナーという仕組みを利用するとアプリケーション内で保持するデータの変更をリアルタイムで取得することができ、 Angular のデータバインディングと組み合わせることで、データベースの最新データを常に画面に表示し続けることなどが簡単に実現できます。

firebase.google.com

Cloud Functions for Firebase

HTTP リクエストやスケジューラなどをトリガーとしてバックエンドコードを実行するサービスで、所謂 FaaS に分類されます。

Firebase における Cloud Functions の実行環境は Node.js ランタイムのみとなっており、対応言語は JavaScript か TypeScript になります。

サーバを意識せずにバックエンドコードを書けるというのは少人数のチームや小規模プロジェクトにとってメリットが大きく、今回は Web アプリケーションからの HTTP リクエストをトリガーとするメール送信や、後述する Cloud Scheduler での定期実行をトリガーとする API 連携などを実現しています。

また、 Cloud Functions (Node.js ランタイム) のバックエンドの実態は Express で構築されているため、送信したメール本文のリンクからのアクセス先として動的コンテンツを配信するなど、ちょっとした Web サーバのような使い方もできます。

firebase.google.com

Firebase Cloud Messaging

Firebase Cloud Messaging Backend を通してクライアントアプリケーションにメッセージを送信したり、クライアント側の SDK で受信したメッセージを表示することができるサービスです。

バックエンドコードを書かなくてもクライアントからクロスプラットフォームにメッセージの送信が行えるため、ブラウザの Angular アプリの処理をトリガーに Android アプリへプッシュ通知を送信することが可能です。

Web アプリの Service Worker の処理は firebase-messaging-sw.js というファイル名にする必要がありますが、ひと手間加えれば Angular/PWA の Service Worker の実体である ngsw-worker.js と併用することができ、 Angular が提供する Service Worker の機能をそのまま活かすことができます。

firebase.google.com

Google Cloud

Firebase も Google が提供するクラウドサービスですが、こちらが Google 本家のクラウドサービスになります。

今回のバックエンドのメインは Firebase ですが、提供元が同じ Google というだけあって Google Cloud の各製品と容易に連携することができます。

cloud.google.com

Cloud Pub/Sub

Publisher がトピックに対してメッセージを送信し、そのトピックを購読している Subscriber が非同期でメッセージを受信する、という Pub/Sub メッセージングモデルを実現するためのマネージドサービスです。

今回のアプリでは Gmail のメール受信時や Cloud Scheduler による定期実行でそれぞれ決められたトピックにメッセージを送信し、そのトピックを購読する Subscriber が Cloud Functions の処理を実行するという流れでいくつかの関数を実行しています。

cloud.google.com

Cloud Scheduler

cron 形式で指定したスケジュールに従い、トピックへのメッセージ送信や HTTP アクセスなどの予め決められた処理を実行するサービスです。

Cloud Scheduler → Cloud Pub/Sub → Cloud Functions と各サービスを連携させることで、Cloud Functions の定期実行を実現しています。

バックエンドコードを書いたら Firebase CLI で Cloud Functions のデプロイコマンドを実行するだけで上記構成を構築できるので、 Cloud Scheduler や Cloud Pub/Sub をほとんど意識することなく非常に簡単に実装することができます。

cloud.google.com

Secret Manager

Google Cloud で使用する API キーやパスワードなどの機密性の高いデータを、一元的に保存・管理するためのストレージサービスです。

今回は Cloud Functions からアクセスする外部サービスの API キーなどを保存するために使用しています。

cloud.google.com

Cloud Source Repositories

Google Cloud でホストされるプライベート Git リポジトリです。

ソースコードのバージョン管理を行うという目的においては必要十分なサービスになっていますが、 GitHub のような Pull Request や Issue の管理といった機能は無く、イマドキの Git ホスティングサービスと比較するとやや見劣りするというのは否定できません。

多分 Google さんも、このサービスにはあまり力を入れていないんじゃないかと思います…

cloud.google.com

Gmail

Gmail API

Gmail を操作するための API で、 JavaScriptPython などの様々な言語に対応しています。

今回は Gmail でのメール送信やメール受信時の Pub/Sub のトピックへのメッセージ送信など、主にバックエンドの処理で使用しました。

developers.google.com

開発環境・開発ツール

Windows 10 Pro の WSL2 上に Docker 環境を構築し、 Node.js の Docker イメージをベースとしたコンテナ内で開発を行いました。

開発ツールは今や高機能エディタのデファクトスタンダードになりつつある Visual Studio Code で、拡張機能を必要に応じて重くならない程度に入れています。

コードフォーマッターとリンターはそれぞれ Prettier と ESLint を使用しています。

Prettier については別記事で詳しく書いていますので、そちらをご覧ください。

ai-can-fly.hateblo.jp

そのうち ESLint についても自分用としてまとめたい…

おわりに

サーバレスという言葉が出て久しいですが、バックエンドをサーバレスで構築した初めてのアプリがこの Web アプリケーションでした。

これまでは誰かにサーバの構築を依頼して、その上に自分が開発したアプリケーションをデプロイするというのが常でしたが、要件さえ合致すればインフラエンジニアにはアドバイザーとしてスポット参戦してもらうだけでも、クラウドサービスを活用すれば何とかできなくもない状態になってきています。

環境構築やアプリケーションのデプロイも用意された CLI のコマンドだけで済む場合が多く、フロントエンド・バックエンドエンジニアにとっては本当に便利な世の中になりました…

これからもクラウドを活用して、フロントエンドからバックエンド、クラウドサービスを守備範囲とするフルスタックエンジニアとしてスキルを伸ばしていこうと思います。

ということで、今回紹介した事例のような開発を依頼、またはエンジニアを探している方はご連絡お待ちしています (๑´ڡ`๑)

【Docker】初心者のための Dockerfile まとめ

docker-logo

はじめに

普段 Docker を使用して開発を行っていますが、ちょっとした開発程度であれば Docker Hub に公開されている Docker イメージをそのまま利用するだけで十分事足りています。

しかし、少し手の込んだ環境や無駄の無い環境を構築したい場合は、 Dockerfile を使用してオリジナルな Docker イメージを構築する必要があります。

以前 Docker Compose に関する記事 を書きましたが、今回はもう少し Docker に踏み込んで Dockerfile についてまとめてみました。

ai-can-fly.hateblo.jp

まとめた内容については Docker Compose 同様、初心者向けとなっています。

何故なら、僕自身が Docker 初心者だからです!!

動作環境

OS Version
Ubuntu 20.04 LTS

Windows 10 Pro の Windows Subsystem for Linux (WSL 2) を使用

Docker Products Version
Docker Desktop for Windows 4.1.1
Docker Engine 20.10.8

Dockerfile まとめ

元も子もないことを書きますが、 Dockerfile に関する詳細は Docker 公式ドキュメントの該当ページをご覧ください。

docs.docker.com

ここではよく使用する命令を中心に、 Dockerfile の書き方をまとめました。

Dockerfile

# Comments
FROM <base_image_name>[:<tag_name>]
EXPOSE <port>[/<protocol>]
ENV <key> <value>
VOLUME <volume_path>
WORKDIR <workdir_path>
COPY <src> <dest>
RUN <command>
CMD ["<command>", "<param>"]

一般的に Dockerfile のファイル名は Dockerfile (※ 拡張子無し) が使用されます。

Dockerfile に記述された各命令は、基本的に上から順に実行されていきます。

また、行頭に # を記述すると、その一行はコメントとして扱われます。

FROM (ベースイメージ)

FROM <base_image_name>[:<tag_name>]

# e.g.
FROM python:3.9

イメージ構築時のベースとなる Docker イメージとタグを指定します。

多くの場合、ここで指定したイメージ名とタグ名で Docker Hub に公開されている Docker イメージを取得し、ベースイメージとして使用します。

EXPOSE (公開用ポート番号)

EXPOSE <port>[/<protocol>]

# e.g.
EXPOSE 5000/tcp

コンテナから公開するポート番号を指定します。

プロトコルの指定は省略可能で、省略した場合は tcp がデフォルトとして設定されます。

ここでポート番号を指定しただけでは実際に公開はされないため、 docker container run 実行時に -P-p オプションを指定する必要があります。

ENV (環境変数)

ENV <key> <value>
ENV <key1>=<value1> [<key2>=<value2> ...]

# e.g.
ENV FLASK_APP flask_app

コンテナ内で有効な環境変数を指定します。

ここで指定した環境変数は、コンテナ内で有効なだけでなく、 Dockerfile 内の以降の命令においても有効です。

VOLUME (ボリューム)

VOLUME <volume_path>

# e.g.
VOLUME /flask

ボリュームをマウントするコンテナ内のディレクトリを指定します。

ここで指定したボリュームは、 docker container run-v /flask または --volume /flask と同様の動きをします。

-v フラグではホスト側のディレクトリを指定してコンテナ側へマウントすること (Bind Mounts) や、任意の名前を付けたボリュームをマウントすること (Named Volumes) ができますが、 Dockerfile の VOLUME では Docker が自動的に決定したボリューム名でマウントされます。

また、 Dockerfile の VOLUME で作成されたボリュームは、 docker container run コマンドに -rm フラグを付けてコンテナを起動した場合、コンテナの停止時にコンテナと共に削除されることに注意が必要です。

WORKDIR (作業ディレクトリ)

WORKDIR <workdir_path>

# e.g.
WORKDIR /flask

作業ディレクトリを指定します。

ここで指定するディレクトリが存在しない場合、新たにディレクトリが作成されます。

WORKDIR を指定した以降の Dockerfile 内の命令は、ここで指定した作業ディレクトリをカレントディレクトリとして実行されます。

COPY (コピー)

COPY <src> <dest>

# e.g.
COPY ./source/* /flask/

ホストのファイルやディレクトリを、コンテナ内の指定したパスへコピーします。

相対パスを指定した場合、ホスト側はビルドコンテキスト、コンテナ側は WORKDIR をカレントディレクトリとしてコピーが実行されます。

コピー元 (ホスト側) のディレクトリやファイルは複数指定、またはワイルドカード (* など) を使用した場合、コピー先 (コンテナ側) はディレクトリを指定する必要があります。

RUN (コマンド実行 (イメージ構築時))

RUN <command>

# e.g.
RUN pip install -U pip \
 && pip install -r requirements.txt

イメージ構築時に実行するコマンドを指定します。

\ (バックスラッシュ) を使用して、一つの RUN 命令を複数行にわたって記述することが可能です。

コマンドの結果はキャッシュされ、次回以降のイメージ構築時にコマンドに変更が無かった場合、コマンドは実行されずにキャッシュされた結果が使用されます。

そのため、例えば RUN apt update と一行で記述した場合、そのキャッシュが初回以降のイメージ構築時に使用されるため、古いパッケージ一覧を使用してしまう可能性があります。

キャッシュを使用せずにイメージ構築を行うには、 docker image build コマンドに --no-cache フラグを指定する必要があります。

CMD (コマンド実行 (コンテナ開始時))

CMD ["<command>", "<param>"]

# e.g.
CMD ["/bin/bash"]

コンテナ開始時に実行するコマンドを指定します。

exec 形式 (CMD ["<command>", "<param>"]) で記述した場合はシェルが実行されないため、コマンドはフルパスで記述する必要があります。

Docker CLI

作成した Dockerfile を基に Docker イメージを構築するには、 docker image build コマンドを使用します。

image

docker image <command>

Docker イメージを管理するコマンドを実行します。

build (Dockerfile から Docker イメージを構築)

docker image build [options] <build_context>
Options Description Example
--file, -f Dockerfile へのパスを指定 -f ./path/to/Dockerfile
--tag, -t 構築するイメージ名とタグを指定 -t repository/image_name:tag_name
--no-cache キャッシュを使用せずにイメージを構築 --no-cache

指定した Dockerfile とビルドコンテキストから、イメージを構築します。

Dockerfile へのパスを指定しなかった場合、ビルドコンテキストのルートディレクトリに存在する Dockerfile が利用されます。

container

docker container <command>

Docker コンテナを管理するコマンドを実行します。

run (Docker イメージから Docker コンテナを作成・開始)

docker container run [option] <image_name>[:<tag_name>] [command]
Options Description Example
--detach, -d コンテナをバックグラウンドで実行し、コンテナ ID を出力 -d
--interactive, -i アタッチされていなくても、 STDIN を受け付ける -i
--mount コンテナにマウントするボリュームやファイルシステムを指定 --mount type=bind, source=/var/log/flask, destination=/flask/logs
--name 作成するコンテナ名を指定 --name container_name
--publish, -p ホストに対して公開するコンテナのポート番号と対応させるホストのポート番号を指定 -p 5000:5000
--publish-all, -P Dockerfile の EXPOSE 命令で指定されたポート番号を公開し、ホストのランダムポートへ対応させる -P
--rm コンテナ停止時にコンテナを削除 --rm
--tty, -t コンテナに疑似 TTY を割り当て -t
--volume, -v コンテナにマウントするボリュームやファイルシステムを指定 -v /var/log/flask:/flask/logs

指定した Docker イメージから、 Docker コンテナを作成し開始します。

[command] を指定した場合、 Docker イメージの CMD 命令が上書きされ、開始された Docker コンテナ内で指定したコマンドが実行されます。

-d フラグを指定しなかった場合はフォアグラウンドで実行され、 Ctrl + C でコンテナを停止できます。

おわりに

以前書いた Docker Compose の記事で Dockerfile は Docker Compose ほど難しくない的なことを書きましたが、むしろ Dockerfile の方が Docker イメージを無駄なく効率良く作成するために色々と考える必要があり、非常に奥が深いものであると再認識しました…

とはいえ、開発で使用する限りにおいては多少ざっくりと書いても問題になることは少ないと思われるので、本当にシビアに書く必要があるのは本番環境で運用する場合などに限られるのではないかと思います。

仕事関係、特に弊社内ではまだ Docker に関する知見が少なく Docker 人口自体が少ない上、こういうのはインフラエンジニアが担当することが多いこともあって、なかなか実践的な知識を身につける機会がありません…

開発で使用する Dockerfile オレオレベストプラクティスはまだ見付けられていないので、引続き個人で色々と試しながら Docker の知識を深めていきたいと思います。

みんな、もっと Docker 使おう (꒪ཫ꒪; )

【TypeScript】そろそろ TSDoc を始めてみる

TSDoc

はじめに

それなりに TypeScript を書いてきて、ぼちぼち初心者という枠を出るか出ないかというところまできたので (とは言ってもまだまだ初級者) 、ちょうど仕事でドキュメンテーションしっかりしようかっていう流れもあり、今まで雰囲気で書いてきたコメント (アノテーション) を見直すタイミングがやってきました。

JavaScript のコメント (アノテーション) は JSDoc がデファクトスタンダードになっていて、 TypeScript は今のところコレというものは決まっていないようでしたが、今回は JSDoc の TypeScript 版である TSDoc をマークアップ言語として選択しました。

TSDoc は MicrosoftOSS として公開しており、おそらく今後は JSDoc のように TypeScript アノテーションデファクトスタンダードになるであろうと (個人的に) 思ったからです。

2021 年 8 月現在、あまり日本語で書かれた記事を見付けられなかったため、 TSDoc の公式ドキュメント からの引用を中心に、とりあえずこれが分かればなんとか書けそうという内容をまとめてみました。

TL;DR

  • TSDoc は TypeScript のコメント (アノテーション) を標準化するための提案 (今のところ) だよ
  • JSDoc と違って、公式からドキュメント出力してくれるパッケージとかは出てないよ
  • 細かいことは置いといて、使用例だけ見たいって人は ここ を見てね

TSDoc とは

tsdoc.org

前述した通り、 JavaScript のコメント (アノテーション) を書くマークアップ言語のデファクトスタンダードである JSDoc の TypeScript 版として、 Microsoft から OSS として公開されている TypeScript アノテーション用のマークアップ言語です。

JSDoc はデファクトスタンダードとされつつも、どうやら標準化は行われていないようで (本当?) 、 JSDoc で書かれたコメントからドキュメントを出力できる同名の npm パッケージ (jsdoc) の実装がある意味本体となっているようです。 (本当??)

TSDoc はルール (構文) の標準化を目的としており、 TSDoc で書かれたコメントのパース (解析) を行うエンジンコンポーネント (npm パッケージ: @microsoft/tsdoc) や、 ESLint 用のプラグイン (npm パッケージ: eslint-plugin-tsdoc) なども公開されています。

注意点として、 JSDoc と違い、 npm パッケージの @microsoft/tsdoc はパーサーとしてコードや他のツールなどに組み込まれる前提のエンジンコンポーネントなので、 TSDoc で書かれたコメントをドキュメントとして出力したい場合は、別途ツールやパッケージを使用する必要があります。

TSDoc 構文

TSDoc では構文として定められたタグを使用し、アノテーションを追加したい変数や関数などの直前にコメントを記述します。

@microsoft/tsdoc のパーサーに認識してもらうためには、アノテーション/** で始める必要があります。

export class TSDocSyntaxExample() {
  /**
   * 要約
   *
   * @remarks
   * 詳細な説明
   *
   * @param name - パラメータの説明
   * @returns 戻り値の説明
   *
   * @beta (← 装飾タグ)
   */
  public helloMessage(name: string): string {
    return 'Hello ' + name + ' !!';
  }
}

タグ

タグは @ から始まり、キャメルケース (camelCase) で記述します。

TSDoc で定められているタグは、後述する三種類のいずれかに分類されます。

また、各タグは TSDoc が互換性のある各種ドキュメントツールから使用される際に、サポートが期待されるレベルに応じて、同じく後述する三つの標準化グループに分類されます。

タグの種類 (Tag kinds)

tsdoc.org

Block tags (ブロックタグ)

一部のタグを除き、ブロックタグは先頭行にタグ、そして次の行からタグの内容 (タグコンテンツ) を記述します。

ブロックタグから次のブロックタグ、またはモディファイタグまでがタグコンテンツと解釈されます。

ブロックタグに続けてタグコンテンツを一行で書いたり、ブロックタグの次行から複数行に渡ってタグコンテンツを書いたりします。

/**
 * This is the special summary section.
 *
 * @remarks
 * This is a standalone block.
 *
 * @example Logging a warning
 * ```ts
 * logger.warn('Something happened');
 * ```
 *
 * @example Logging an error
 * ```ts
 * logger.error('Something happened');
 * ```
 */

Modifier tags (モディファイタグ)

モディファイタグは通常、コメント (アノテーション) の最下部一行にタグのみを記述し、アノテーション対象を装飾します。

一行に複数のモディファイタグを記述することもできます。

仮にモディファイタグにタグコンテンツが記載されていた場合、パーサーは無視 (破棄) をするか、互換性が向上する場合*1に限り、直前のブロックタグに関連付けられます。

/**
 * This is the special summary section.
 *
 * @remarks
 * This is a standalone block.
 *
 * @public @sealed
 */

Inline tags (インラインタグ)

インラインタグは、ブロックタグのタグコンテンツ内の要素として、常に中括弧 ({}) に囲んで記述します。

class Book {
  /**
   * Writes the book information into a JSON file.
   *
   * @remarks
   * This method saves the book information to a JSON file conforming to the standardized
   * {@link http://example.com/ | Example Book Interchange Format}.
   */
  public writeFile(options?: IWriteFileOptions): void {
    . . .
  }

  /**
   * {@inheritDoc Book.writeFile}
   * @deprecated Use {@link Book.writeFile} instead.
   */
  public save(): void {
    . . .
  }
}

標準化グループ (Standardization groups)

tsdoc.org

Core (コアグループ)

Enum value: Standardization.Core

Core に分類されるタグは必須タグとして標準化され、すべてのツールがこれらをサポートすることが期待されています。

TSDoc パーサ (@microsoft/tsdoc) はこれらのタグへアクセスするための専用 API を提供します。

Extended (拡張グループ)

Enum value: Standardization.Extended

Extended に分類されるタグはオプションとされ、ツールによってサポートする場合としない場合がありますが、サポートする場合は TSDoc の構文と定義された役割に準拠する必要があります。

Discretionary (任意グループ)

Enum value: Standardization.Discretionary

Discretionary に分類されるタグも Extended 同様にオプションとなり、構文は TSDoc によって指定されていますが、意味・役割は実装依存 (ツールにより異なる) になります。

ただ、同じタグは異なるツールであっても、おそらく似た意味で使用されていると予想されています。 (が、やはり必ず同義であることは保証されていません。)

タグリファレンス

TSDoc で定義されているタグを、標準化グループ毎、かつ、タグ種別ごとにまとめました。

各タグで例として記載しているコードは、 TSDoc 公式ドキュメント からの引用がベースとなりますので、安心して参考にしてください。

ただし、いつものことですが、各タグの解説は TSDoc 公式ドキュメント の僕の超訳でお送りします。

解説には、サードパーティ製の各ドキュメントツールから各タグがどのような内容であることを期待されているのか、また、どのように解釈されるのかも、書いたり書かなかったりしています。

Core (コアグループ)

Block tags (ブロックタグ)

@deprecated (非推奨)

TSDoc: @deprecated

/**
 * The base class for controls that can be rendered.
 *
 * @deprecated Use the new {@link Control} base class instead.
 */
export class VisualControl {
  . . .
}

API が非推奨 (サポート対象外) となり、将来的に削除される可能性があることを表します。

@deprecated タグの後に、推奨される代替手段を記述します。

アノテーションの対象が class の場合、クラス自体が非推奨となるため、クラス内のプロパティやメソッドもすべて非推奨となります。

@param (引数)

TSDoc: @param

/**
 * Returns the average of two numbers.
 *
 * @remarks
 * This method is part of the {@link core-library#Statistics | Statistics subsystem}.
 *
 * @param x - The first input number
 * @param y - The second input number
 * @returns The arithmetic mean of `x` and `y`
 *
 * @beta
 */
function getAverage(x: number, y: number): number {
  return (x + y) / 2.0;
}

アノテーションの対象である関数の引数を表します。

@param タグの後に、 パラメータ名 - 説明 の形式で記述します。

@privateRemarks (非公開の備考)

TSDoc: @privateRemarks

/**
 * The summary section should be brief. On a documentation web site,
 * it will be shown on a page that lists summaries for many different
 * API items.  On a detail page for a single item, the summary will be
 * shown followed by the remarks section (if any).
 *
 * @remarks
 *
 * The main documentation for an API item is separated into a brief
 * "summary" section optionally followed by an `@remarks` block containing
 * additional details.
 *
 * @privateRemarks
 *
 * The `@privateRemarks` tag starts a block of additional commentary that is not meant
 * for an external audience.  A documentation tool must omit this content from an
 * API reference web site.  It should also be omitted when generating a normalized *.d.ts file.
 */

一版ユーザ向けではない追加のドキュメントであることを表します。

@remarks (備考)

TSDoc: @remarks

/**
 * The summary section should be brief. On a documentation web site,
 * it will be shown on a page that lists summaries for many different
 * API items.  On a detail page for a single item, the summary will be
 * shown followed by the remarks section (if any).
 *
 * @remarks
 *
 * The main documentation for an API item is separated into a brief
 * "summary" section optionally followed by an `@remarks` block containing
 * additional details.
 *
 * @privateRemarks
 *
 * The `@privateRemarks` tag starts a block of additional commentary that is not meant
 * for an external audience.  A documentation tool must omit this content from an
 * API reference web site.  It should also be omitted when generating a normalized *.d.ts file.
 */

API のメインドキュメントは要約を記載するにとどめ、詳細な説明は @remarks セクションに記載します。

アノテーションの先頭から @remarks タグまでがメインドキュメントの要約セクションとなり、 @remarks タグ以降が詳細な説明を記述する備考セクションであることを表します。

@returns (戻り値)

TSDoc: @returns

/**
 * Returns the average of two numbers.
 *
 * @remarks
 * This method is part of the {@link core-library#Statistics | Statistics subsystem}.
 *
 * @param x - The first input number
 * @param y - The second input number
 * @returns The arithmetic mean of `x` and `y`
 *
 * @beta
 */
function getAverage(x: number, y: number): number {
  return (x + y) / 2.0;
}

アノテーションの対象である関数の戻り値を表します。

@typeParam (型引数)

TSDoc: @typeParam

/**
 * Alias for array
 *
 * @typeParam T - Type of objects the list contains
 */
type List<T> = Array<T>;

/**
 * Wrapper for an HTTP Response
 * @typeParam B - Response body
 * @typeParam <H> - Headers
 */
interface HttpResponse<B, H> {
    body: B;
    headers: H;
    statusCode: number;
}

アノテーションの対象であるジェネリックの型引数を表します。

@typeParam タグの後に、 パラメータ名 - 説明 の形式で記述します。

Modifier tags (モディファイタグ)

@packageDocumentation

TSDoc: @packageDocumentation

// Copyright (c) Example Company. All rights reserved. Licensed under the MIT license.

/**
 * A library for building widgets.
 *
 * @remarks
 * The `widget-lib` defines the {@link IWidget} interface and {@link Widget} class,
 * which are used to build widgets.
 *
 * @packageDocumentation
 */

/**
 * Interface implemented by all widgets.
 * @public
 */
export interface IWidget {
  /**
   * Draws the widget on the screen.
   */
  render(): void;
}

アノテーションが npm パッケージ全体に対して記述するコメントであることを示します。

@packageDocumentation タグは個々の API に対してではなく、パッケージのエントリーポイントにあたるファイルの先頭のコメントに対してのみ使用することができます。

Inline tags (インラインタグ)

@label (ラベル)

TSDoc: @label

export interface Interface {
  /**
   * Shortest name:  {@link InterfaceL1.(:STRING_INDEXER)}
   * Full name:      {@link (InterfaceL1:interface).(:STRING_INDEXER)}
   *
   * {@label STRING_INDEXER}
   */
  [key: string]: number;

  /**
   * Shortest name:  {@link InterfaceL1.(:FUNCTOR)}
   * Full name:      {@link (InterfaceL1:interface).(:FUNCTOR)}
   *
   * {@label FUNCTOR}
   */
  (source: string, subString: string): boolean;

  /**
   * Shortest name:  {@link InterfaceL1.(:CONSTRUCTOR)}
   * Full name:      {@link (InterfaceL1:interface).(:CONSTRUCTOR)}
   *
   * {@label CONSTRUCTOR}
   */
  new (hour: number, minute: number);
}

@label タグは、変数やフィールド、プロパティなどの宣言にラベルを付けるために使用されます。

ラベル付けされた宣言は、 @link タグから参照することができます。

TSDoc: @link

/**
 * Let's learn about the `{@link}` tag.
 *
 * @remarks
 *
 * Links can point to a URL: {@link https://github.com/microsoft/tsdoc}
 *
 * Links can point to an API item: {@link Button}
 *
 * You can optionally include custom link text: {@link Button | the Button class}
 *
 * Suppose the `Button` class is part of an external package.  
 * In that case, we can include the package name when referring to it:
 *
 * {@link my-control-library#Button | the Button class}
 *
 * The package name can include an NPM scope and import path:
 *
 * {@link @microsoft/my-control-library/lib/Button#Button | the Button class}
 *
 * We can refer to a member of the class:
 *
 * {@link controls.Button.render | the render() method}
 *
 * If a static and instance member have the same name, we can use a selector to distinguish them:
 *
 * {@link controls.Button.(render:instance) | the render() method}
 *
 * {@link controls.Button.(render:static) | the render() static member}
 *
 * This is also how we refer to the class's constructor:
 *
 * {@link controls.(Button:constructor) | the class constructor}
 */

API のメンバや @label タグ、インターネット上の URL へのハイパーリンクを作成するために使用します。

実際にハイパーリンクを生成するためにはドキュメント生成ツールなどが必要ですが、 @link タグ自体は他リソースへの参照を表すことができます。

Extended (拡張グループ)

Block tags (ブロックタグ)

@decorator (デコレータ)

TSDoc: @decorator

class Book {
  /**
   * The title of the book.
   * @decorator `@jsonSerialized`
   * @decorator `@jsonFormat(JsonFormats.Url)`
   */
  @jsonSerialized
  @jsonFormat(JsonFormats.Url)
  public website: string;
}

API にデコレータが使用されていることを表します。

これは TypeScript のコンパイラが出力する型定義ファイル (*.d.ts) にデコレータが含まれないため、その回避策として用意されたタグになります。

@defaultValue (既定値)

TSDoc: @defaultValue

enum WarningStyle {
  DialogBox,
  StatusMessage
}

interface IWarningOptions {
  /**
   * Determines how the warning will be displayed.
   *
   * @remarks
   * See {@link WarningStyle| the WarningStyle enum} for more details.
   *
   * @defaultValue `WarningStyle.DialogBox`
   */
  warningStyle: WarningStyle;

  /**
   * Whether the warning can interrupt a user's current activity.
   * @defaultValue
   * The default is `true` unless
   *  `WarningStyle.StatusMessage` was requested.
   */
  cancellable?: boolean;
}

@defaultValue タグは class または interface のメンバーであるフィールドかプロパティに対してのみ使用することが可能で、明示的に値が割り当てられていない場合の既定値を表します。

書き方は二通りあり、既定値を @defaultValue タグの後に記載するか、次の行からタグコンテンツとして既定値の説明文を記述します。

@example (使用例)

TSDoc: @example

/**
 * Adds two numbers together.
 * @example
 * Here's a simple example:
 * ```
 * // Prints "2":
 * console.log(add(1,1));
 * ```
 * @example
 * Here's an example with negative numbers:
 * ```
 * // Prints "0":
 * console.log(add(1,-1));
 * ```
 */
export function add(x: number, y: number): number {
}

/**
 * Parses a JSON file.
 *
 * @param path - Full path to the file.
 * @returns An object containing the JSON data.
 *
 * @example Parsing a basic JSON file
 *
 * # Contents of `file.json`
 * ```json
 * {
 *   "exampleItem": "text"
 * }
 * ```
 *
 * # Usage
 * ```ts
 * const result = parseFile("file.json");
 * ```
 *
 * # Result
 * ```ts
 * {
 *   exampleItem: 'text',
 * }
 * ```
 */
export function parseFile(path: string): object {
}

API の使用方法を表します。

@example タグと同じ行の後続テキストはその例のタイトルとして解釈され、複数の @example タグを記載した場合は、各使用例をインデックスで表すことができます。

上の例のタイトルは ExampleExample2 、下の例のタイトルは Example: Parsing a basic JSON file になります。

また、タグコンテンツには Markdown でコードサンプルを記述することができます。

@see (参照先)

TSDoc: @see

/**
 * Parses a string containing a Uniform Resource Locator (URL).
 * @see {@link ParsedUrl} for the returned data structure
 * @see {@link https://tools.ietf.org/html/rfc1738|RFC 1738} for syntax
 * @see your developer SDK for code samples
 * @param url - the string to be parsed
 * @returns the parsed result
 */
function parseURL(url: string): ParsedUrl;

API に関連する他の API やリソースへの参照リストを作成するために使用します。

@see タグと同じ行の後に、参照先のリンクや説明文を記述します。

@throws (例外)

TSDoc: @throws

/**
 * Retrieves metadata about a book from the catalog.
 *
 * @param isbnCode - the ISBN number for the book
 * @returns the retrieved book object
 *
 * @throws {@link IsbnSyntaxError}
 * This exception is thrown if the input is not a valid ISBN number.
 *
 * @throws {@link book-lib#BookNotFoundError}
 * Thrown if the ISBN number is valid, but no such book exists in the catalog.
 *
 * @public
 */
function fetchBookByIsbn(isbnCode: string): Book;

アノテーションの対象である関数やプロパティからスローされる例外の型を表します。

スローされる可能性のある例外が複数存在する場合、それぞれ個別に @throws タグを使用する必要があります。

@throws タグと同じ行の後に例外の名前を記述しますが、必須ではありません。

Modifier tags (モディファイタグ)

@eventProperty (イベントプロパティ)

TSDoc: @eventProperty

class MyClass {
  /**
    * This event is fired whenever the application navigates to a new page.
    * @eventProperty
    */
  public readonly navigatedEvent: FrameworkEvent<NavigatedEventArgs>;
}

classinterface のプロパティが、アタッチ可能なイベントオブジェクトを返すことを表します。

戻り値のイベントオブジェクトには、 addHandler()removeHandler() といったイベントハンドラを操作するメソッドを持つクラスであることが期待されます。

@override (再定義・オーバーライド)

TSDoc: @override

class Base {
  /** @virtual */
  public render(): void { // サブクラスによって再定義される可能性がある
  }

  /** @sealed */
  public initialize(): void { // サブクラスで再定義してはいけない
  }
}

class Child extends Base {
  /** @override */
  public render(): void; // 基本クラスの定義を再定義している
}

アノテーションの対象がメンバー関数やプロパティの場合、この定義が基本クラスから継承された定義をオーバーライド (再定義) していることを表します。

通常、再定義される可能性のある基本クラスの定義には @virtual タグを記述します。

@readonly (読み取り専用)

TSDoc: @readonly

export class Book {
  /**
   * Technically property has a setter,
   * but for documentation purposes it should be presented as readonly.
   * @readonly
   */
  public get title(): string {
    return this._title;
  }

  public set title(value: string) {
    throw new Error('This property is read-only!');
  }
}

TypeScript の構文として読み取り専用であるかどうかに関わらず、仕様として API が読み取り専用であることを表します。

上の例では、 Setter 関数でプロパティに値を割り当てていないため、 Book.title が読み取り専用であることを示しています。

@virtual (再定義の可能性有)

TSDoc: @virtual

class Base {
  /** @virtual */
  public render(): void { // サブクラスによって再定義される可能性がある
  }

  /** @sealed */
  public initialize(): void { // サブクラスで再定義してはいけない
  }
}

class Child extends Base {
  /** @override */
  public render(): void; // 基本クラスの定義を再定義している
}

アノテーションの対象がメンバー関数やプロパティの場合、この定義をサブクラスがオーバーライド (再定義) する可能性があることを表します。

@sealed (継承・再定義不可)

TSDoc: @sealed

class Base {
  /** @virtual */
  public render(): void { // サブクラスによって再定義される可能性がある
  }

  /** @sealed */
  public initialize(): void { // サブクラスで再定義してはいけない
  }
}

class Child extends Base {
  /** @override */
  public render(): void; // 基本クラスの定義を再定義している
}

アノテーションの対象がクラスの場合は継承不可、メンバー関数やプロパティの場合はサブクラスがオーバーライド (再定義) してはならないことを表します。

Inline tags (インラインタグ)

@inheritDoc (ドキュメントの引用)

TSDoc: @inheritDoc

import { Serializer } from 'example-library';

/**
 * An interface describing a widget.
 * @public
 */
export interface IWidget {
  /**
   * Draws the widget on the display surface.
   * @param x - the X position of the widget
   * @param y - the Y position of the widget
   */
  public draw(x: number, y: number): void;
}

/** @public */
export class Button implements IWidget {
  /** {@inheritDoc IWidget.draw} */
  public draw(x: number, y: number): void {
    . . .
  }

  /**
   * {@inheritDoc example-library#Serializer.writeFile}
   * @deprecated Use {@link example-library#Serializer.writeFile} instead.
   */
  public save(): void {
    . . .
  }
}

対象の API に対し、他のクラスや npm パッケージの APIアノテーションを引用 (コピー) していることを表します。

コピー対象のタグ (コメント) は、

  • 要約セクション
  • @remark
  • @params
  • @typeParam
  • @return

のみとなり、コピー対象外のタグについては @inheritDoc タグ以降に記述する必要があります。

@inheritDoc タグを記述した場合、そのアノテーションにはメインコメントである要約や @remark タグは記述できません。

Discretionary (任意グループ)

Modifier tags (モディファイタグ)

@alpha (アルファ版・未リリース)

TSDoc: @alpha

/**
 * Represents a book in the catalog.
 * @public
 */
export class Book { // Book クラスはリリース済み
  /**
   * The title of the book.
   * @alpha
   */
  public get title(): string; // Book.title は未リリースのアルファ版

  /**
   * The author of the book.
   */
  public get author(): string; // Book.author も Book を継承してリリース済み
};

API がアルファ版 (未リリース) であることを表します。

API を使用することは可能ですが、あくまでもまだリリースはしていないことを示す場合に使用します。

@beta (ベータ版・実験的リリース)

TSDoc: @beta

/**
 * Represents a book in the catalog.
 * @public
 */
export class Book { // Book クラスはリリース済み
  /**
   * The title of the book.
   * @beta
   */
  public get title(): string; // Book.title は実験的リリースのベータ版

  /**
   * The author of the book.
   */
  public get author(): string; // Book.author も Book を継承してリリース済み
};

API がベータ版 (実験的リリース) であることを表します。

目的がフィードバック収集の場合など、リリースはされたものの、まだ運用には適さないことを示す際に使用します。

@experimental (実験段階)

TSDoc: @experimental

/**
 * Represents a book in the catalog.
 * @public
 */
export class Book { // Book クラスはリリース済み
  /**
   * The title of the book.
   * @experimental
   */
  public get title(): string; // Book.title は実験段階

  /**
   * The author of the book.
   */
  public get author(): string; // Book.author も Book を継承してリリース済み
};

@alpha@beta と同様に、未リリースの実験段階であることを表します。

リリースステージとしては、 @experimental@alpha@beta と進みます。 (と思われます。)

@internal (内部用)

TSDoc: @internal

/**
 * Represents a book in the catalog.
 * @public
 */
export class Book { // Book クラスはリリース済み
  /**
   * The title of the book.
   * @internal
   */
  public get _title(): string; // Book._title は内部用

  /**
   * The author of the book.
   */
  public get author(): string; // Book.author も Book を継承してリリース済み
};

API が外部から使用される予定が無いことを表します。

publicprivate などのアクセス修飾子による違いではなく、そもそも API 自体が使用されないということを示しています。

@public (公開版・リリース済)

TSDoc: @public

/**
 * Represents a book in the catalog.
 * @public
 */
export class Book { // Book クラスはリリース済み
  /**
   * The title of the book.
   * @internal
   */
  public get _title(): string; // Book._title は内部用

  /**
   * The author of the book.
   */
  public get author(): string; // Book.author も Book を継承してリリース済み
};

API が公開されているリリース版であることを表します。

使用例

ここまで長々と書きましたが、結局どう書いたらいいの?ということで、 Angular のコードにアノテーションを付けてみました。 (コードは適当なので、そこへのつっこみは無しの方向で…)

簡易なアノテーションから詳細なアノテーションまでいくつかのパターンで記載していますので、この中からプロジェクトやチームに合った形を見付けて参考にしていただければと思います。

これが正解というわけではないので、とりあえずこんな感じでスタートして、徐々にチーム内でブラッシュアップしていけばいいんじゃないでしょうか。

正直、正解はよく分からん…

クラス (Class)

/**
 * ユーザ情報クラスコンポーネント
 *
 * @remarks
 * ユーザ情報を表示する画面を構成するコンポーネントです。
 *
 * @decorator `@Component()`
 *
 * @public
 */
@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.scss']
})
export class UserComponent implements OnInit {
  /**
   * ユーザ ID
   *
   * @remarks
   * ユーザ ID は親コンポーネントから渡されます。
   *
   * @decorator `@Input()`
   */
  @Input()
  userId: string = '';

  /**
   * ユーザ名
   * 
   * @defaultValue `'unknown'`
   */
  userName: string;

  constructor(
    private usrSvc: userService
  ) {
    this.userName = 'unknown';
  }

  /** @override */
  ngOnInit(): void {
    this.showUserName(this.userId);
  }

  /**
   * ユーザ名を表示する。
   *
   * @remarks
   * パラメータとして与えられたユーザ ID からユーザ名を取得し、
   * ユーザ名プロパティへ値をセットします。
   * 
   * @privateRemarks
   * ユーザ情報の型 {@link (:USER) | User model}
   *
   * @param id - ユーザ ID
   *
   * @public
   */
  private showUserName(id: string): void {
    if (id === '') {
      return;
    }

    this.usrSvc.getName<User>(id).subscribe(
      (user: User): void => {
        this.userName = user.name;
      }
    );
  }
}

インターフェース (Interface)

/**
 * ユーザ情報
 *
 * {@label USER}
 *
 * @public
 */
export interface User {
  /** ユーザ ID */
  id: string;
  /** ユーザ名 */
  name: string;
}

ジェネリック (Generics)

@Injectable({
  providedIn: 'root'
})
export class UserService {
  constructor(
    private http: HttpClient
  ) {}

  /**
   * ユーザ名を取得する。
   *
   * @remarks
   * パラメータとして与えられたユーザ ID からユーザ名を含む
   * ユーザ情報を取得し、戻り値として返します。
   * 
   * @privateRemarks
   * ユーザ名だけでなく、ユーザ情報すべてを取得している。
   *
   * @typeParam T - ユーザ情報
   * @param id - ユーザ ID
   * @returns ユーザ名を含むユーザ情報
   *
   * @beta @sealed
   */
  getName<T>(id: string): Observable<T> {
    const options = new HttpParams().set('id', id);
    return this.http.get('/path/to/api/', options);
  }
}

おわりに

今回まとめるにあたって一通りすべてのタグに目を通しましたが、アノテーションにこれらのタグをフルスペックで記述したら、かなり充実するだろうなーと思いました。

プロジェクト要件にプログラム設計書の作成・納品などが含まれている場合、いちいちドキュメントを作成するくらいなら、 TSDoc でしっかりアノテーションを書いて、ツールでドキュメント出力をした方が確実で早いし簡単ですね。

良い意味で「ソースコードが仕様書です!」と言えるんじゃないでしょうか。

今まで悪い意味でしか使ったことが無いですが…

あとは JSDoc のように、 TSDoc 公式からドキュメント出力をしてくれる npm パッケージが公開されたら大変ありがたいのですが、 TSDoc のロードマップ にはそんなこと一切書かれていないし、 @microsoft/tsdoc はあくまでも TSDoc のパーサーという位置付けなんでしょうね。。

TypeDoc とか使えばいいんかな… _(-ω-`_)⌒)_

*1:どういう場合かよく分かりません…

【Docker】初心者のための Docker Compose まとめ

docker-logo

はじめに

2021 年現在、本番環境や開発環境を問わず Docker (コンテナ技術) は当たり前のものとして使用されるようになりました。

いちエンジニアの立場としても、開発環境をコンテナでスピーディに構築するだけでなく、ちょっとしたお試し環境を用意したいときなど、手軽に環境を Scrap and build できる Docker はもはや無くてはならない存在です。

Docker Compose の詳細については Docker 公式ドキュメントの Docker Compose のページ を参照いただくとして、当記事では Docker 初心者が Docker Compose を使用するにあたり、よく使うんだけどまだちゃんと覚えていない…というような内容をまとめました。

当記事でゼロから Docker Compose を学ぶということではなく、 Docker Compose を一通り学んだ初心者がリファレンス的に使用することを想定しています。

慣れてしまえば難しいことはありませんが、そもそも Docker とは、コンテナ技術とは、という部分が不安な方は Docker 公式ドキュメント を一読することをお勧めします。

ただし、 Docker 公式ドキュメントを一読したなら、そもそもこの記事を読む必要は無いと思いますけどね…

動作環境

OS Version
Ubuntu 20.04 LTS

Windows 10 Pro の Windows Subsystem for Linux (WSL 2) を使用

Docker Products Version
Docker Desktop for Windows 3.5.2
Docker Engine 20.10.7
Docker Compose 1.29.2

Docker Compose まとめ

前述した通り、 Docker Compose についての詳細は Docker 公式ドキュメントをご参照ください。

docs.docker.com

ざっくりと超訳すれば、 Docker Compose は複数のコンテナをまとめて定義・管理することができる Docker のためのツールです。

Compose ファイル (docker-compose.yml)

version: '3.8'

services:
  [service_name]:
    build: [context_path]
    image: [image_name]:[tag_name]
    container_name: [container_name]
    command: [command]
    environment:
      [variable_name]: [value]
    volumes:
      - [volume_name]:[container_directory_path]:[mode]
      - [host_directory_path]:[container_directory_path]:[mode]
      - type: volume
        source: [volume_name]
        target: [container_directory_path]
      - type: bind
        source: [host_directory_path]
        target: [container_directory_path]
    networks:
      - [network_name]
    ports:
      - '[host_port]:[container_port]'
    tty: [boolean]
    restart: [restart_policy]
    depends_on:
      - [other_service_name]
  [other_service_name]:
    ...

volumes:
  [volume_name]:

networks:
  [network_name]:

Compose ファイルには、 Docker Compose で実行する Docker アプリケーションの設定を定義します。

ファイルは YAML 形式で記述しますが、 JSON 形式でも記述可能です。

コンテナ (services) だけでなく、各コンテナ間のネットワーク (networks) やボリューム (volumes) についても定義でき、各セクションの設定項目は多岐にわたります。

ここでは、 (僕が) よく使用する項目を中心に記載しました。

version (Compose ファイルフォーマットのバージョン)

version: "3.8"

Compose ファイルフォーマットのバージョンを指定します。

使用する Docker Engine のバージョン毎にサポートされる Compose ファイルフォーマットのバージョンが異なるので、 Docker Engine のバージョンに合わせて Compose ファイルのバージョンを指定する必要があります。

以下はその一例です。

Compose file format Docker Engine release
3.8 19.03.0+
3.7 18.06.0+
3.0 1.13.0+

※ 注意点として、 Compose ファイルのバージョンを指定する場合は、メジャーバージョンとマイナーバージョンの両方を明示的に記述 (version: "3.8") する必要があります。 もしメジャーバージョンのみを記述した場合、マイナーバージョンは最新バージョンではなくデフォルトの 0 が補完されます。 (version: "3.0")

docs.docker.com

services セクション

services:

サービス (コンテナ) を定義します。

services セクション直下に複数のサービスを列記することができます。

[service_name] (サービス名)

  [service_name]:

  # e.g.
  web:

サービスの名前を指定します。

後述する buildimage で構築される Docker イメージに名前を付けていない場合、構築されるイメージ名は [project_name]_[service_name] になります。

プロジェクト名 (project_name) が特に指定されていない場合は、 Compose ファイルが配置されているディレクトリ名がプロジェクト名になります。

build (Dockerfile)
    build: [context_path]

    # e.g.
    build: .

build の指定方法の一つとして、 Dockerfile を含むディレクトリのパスを指定します。

Dockerfile のファイル名やビルド引数などの詳細を指定する方法もありますが、今回は割愛します。

後述する image を指定することで、構築される Docker イメージに名前を付けることができます。

Dockerfile については 別記事 にまとめていますので、そちらをご覧ください。

ai-can-fly.hateblo.jp

image (Docker イメージ名)
    image: [image_name]:[tag_name]

    # e.g.
    image: python-flask:latest

コンテナで起動する Docker イメージを指定します。

前述の build が指定されている場合は、 build で指定された Dockerfile から Docker イメージが構築され、そのイメージ名が image で指定した名前になります。

build を指定しない (Dockerfile からイメージを構築しない) 場合、ここで指定された Docker イメージからコンテナを作成しますが、指定した Docker イメージがローカルに存在しない場合は、 Docker Hub から Pull (取得) した上でコンテナが作成されます。

container_name (Docker コンテナ名)
    container_name: [container_name]

    # e.g.
    container_name: python-flask

構築された Docker イメージから作成されるコンテナの名前を指定します。

command (コマンド)
    command: [command]

    # e.g.
    command: flask run

Docker Compose でコンテナを開始する際に実行されるコマンドを指定します。

build セクションや image セクションの Dockerfile や Docker イメージで定義されている CMD に代わり、ここで指定したコマンドが実行されます。

environment (環境変数)
    environment:
      [variable_name]: [value]

    # e.g.
    environment:
      FLASK_ENV: development
      SECRET_KEY

コンテナで使用する環境変数を指定します。

環境変数名のみを指定した場合、 Docker Compose が起動しているシェルで定義されている同名の環境変数がコンテナへ渡されます。

注意点として、 build セクションで Dockerfile をビルドする際はここで指定した環境変数は使用できません。

ビルド時の環境変数build セクションのサブセクションである args セクションで指定します。

volumes (ボリューム)
    volumes:
      - [volume_name]:[container_directory_path]:[mode]
      - [host_directory_path]:[container_directory_path]:[mode]
      - type: volume
        source: [volume_name]
        target: [container_directory_path]
      - type: bind
        source: [host_directory_path]
        target: [container_directory_path]

    # e.g.
    volumes:
      - ./src:/src:rw

コンテナで使用するボリュームを指定します。

ボリュームの指定には、 type (ボリュームのマウント種類) などを省略して一行で書く方法と、詳細を複数行にわたって細かく指定する二通りの書き方があります。

type source target
volume volumes セクションで定義した名前付きボリューム ([volume_name]) を指定 ボリュームがマウントされるコンテナのパス ([container_directory_path]) を指定
bind ホストマシンのパス ([host_directory_path]) を指定 ボリュームがマウントされるコンテナのパス ([container_directory_path]) を指定
tmpfs - ボリュームがマウントされるコンテナのパス ([container_directory_path]) を指定

bind で指定するホストマシンのパス ([host_directory_path]) には、 Compose ファイルからの相対パス (./) やユーザのホームディレクトリからの相対パス (~/) が指定できます。

コンテナのパス ([container_directory_path]) に Docker イメージに存在しないディレクトリを指定した場合、自動的に当該ディレクトリ (親パス含む) が作成され、ボリュームがマウントされます。

一行で指定する場合の mode には、読み取り専用の ro と読み書き可の rw が指定でき、省略時のデフォルトは rw になります。

Bind mounts の注意点

マウントするホスト側のディレクトリとマウントされるコンテナ側のディレクトリのいずれかが空でない場合、ホスト側のディレクトリが優先されることになります。

Host side directory Container side directory Mounted result
空になる
空でない ホスト側のディレクトリ内のデータがマウントされる
空でない 空になる
空でない 空でない ホスト側のディレクトリ内のデータがマウントされる

簡単に言ってしまえば、一部の例外的な Docker イメージを除き、常にホスト側のディレクトリでコンテナ側のディレクトリが置き換わります。

これはディレクトリの統合 (merge) ではないため、たとえホスト側のディレクトリが空であっても、コンテナ側のディレクトリ内のファイル有無に関わらず、結果としてコンテナ内のマウント先ディレクトリは空になります。

誤ってマウント先のファイルを消してしまうことがないように注意しましょう。

ただ、間違った設定をしてしまったとしても、 Compose ファイルを修正して再度コンテナを作成すれば問題ありません。

これも Scrap and build が容易な Docker の良いところです。

networks (ネットワーク)
    networks:
      - [network_name]

コンテナが接続するネットワークを指定します。

後述する最上位の networks セクションで定義したネットワークを指定することで、明示的にコンテナが接続するネットワークを選択できます。

独自のネットワーク設定を行わなかった場合、コンテナは既定のネットワーク ([project_name]_default) に接続され、各サービスのコンテナ間は [service_name] をホスト名としてアクセスすることができます。

ports (ポート)
    ports:
      - "[host_port]:[container_port]"

    # e.g.
    ports:
      - "5000:5000"

ホスト側ポートとコンテナ側ポートのマッピングを指定します。

コンテナ側のポート番号のみを指定した場合、ホスト側のポート番号は空ポートが自動的に割り当てられます。

注意点として、設定にもよりますが、コンテナ内で起動した開発サーバなどが localhost (127.0.0.1) からの接続のみを許可している場合、ホスト側から localhost (127.0.0.1) で接続することができないことがあります。

これはホスト側からポートフォワードされた際、 localhost (127.0.0.1) がコンテナ内の OS の IP アドレスに変換され、外部 (localhost (127.0.0.1) 以外) からのアクセスと判断されてしまうためで、解決策としてコンテナ内で起動するサーバへのアクセス制限を無くす必要があります。

tty (疑似 TTY)
    tty: [boolean]

疑似 TTY の割り当てを指定します。

true を指定すると、コンテナを開始時に疑似 TTY が割り当てられます。

restart (サービス再起動ポリシー)
    restart: [restart_policy]

サービスの再起動ポリシーを指定します。

ここでは、 Docker を起動した際にコンテナを自動的に開始するかどうかを制御することができます。

Restart policies Description
"no" 自動でコンテナを開始しない (既定値)
always 常にコンテナを開始する
on-failure エラー発生によりコンテナが終了した場合、コンテナを開始する
unless-stopped コンテナを手動等で停止した場合を除き、常にコンテナを開始する

いずれの場合も、手動でコンテナを停止した直後にコンテナが再起動することはありません。

alwaysunless-stopped の違いは、手動でコンテナを停止した後、 Docker の (再) 起動時にコンテナが自動的に開始されるかどうかになります。

depends_on (サービス間の依存関係)
    depends_on:
      - [other_service_name]

サービス間の依存関係を定義します。

ここで他サービスを指定した場合、 docker-compose up コマンドによるコンテナの開始時、依存する他サービス ([other_service_name]) のコンテナを開始してから当サービス ([service_name]) のコンテナを開始します。

また、 docker-compose stop コマンドによるコンテナの停止時も、依存する他サービスのコンテナが停止してから当サービスのコンテナを停止します。

volumes セクション

volumes:
  [volume_name]:

サービスで使用するボリュームを定義します。

volumes セクション直下に列記した名前付きボリュームは、前述した services セクションの各サービスの volumes に指定して使用できます。

詳細な設定については、今回は割愛します。

networks セクション

networks:
  [network_name]:

サービスで使用するネットワークを定義します。

networks セクション直下に列記したネットワークは、前述した services セクションの各サービスの network に指定して使用できます。

詳細な設定については、今回は割愛します。

Docker Compose CLI

docker-compose [options] [command] [arguments]

Compose ファイルに定義された複数のサービスは、 docker-compose コマンドで一括、または個別に操作することができます。

既定ではカレントディレクトリにある docker-compose.yml を Compose ファイルとして使用しますが、以下の -f オプションを使用すれば、任意の場所にある Compose ファイルを指定することができます。

Options Description Example
-f [compose file] 任意の Compose ファイルを指定。 -f ./others/docker-compose-sub.yml
--compatibility 各サービスの deploy セクションの内容を読み取り、 Compose ファイルのバージョン 2 のパラメータとして変換・実行する。 --compatibility

build (イメージ構築)

docker-compose build [options] [service]

Compose ファイルに定義された各サービスの Docker イメージを構築します。

サービスの Dockerfile やイメージ構築時のディレクトリ構成・内容を変更した場合は、 docker-compose build コマンドでイメージを再構築する必要があります。

Options Description
--no-cache イメージ構築時に、イメージのキャッシュを使用しない。

run (コンテナを開始してコマンド実行)

docker-compose run [option] [service] [command] [arguments]

Compose ファイルに定義されたサービスのコンテナを開始し、開始したコンテナ内でコマンドを実行します。

run サブコマンドではサービスの指定は必須です。

イメージが存在しない場合は、合わせて構築されます。

オプションを指定しない場合、デフォルトでは以下の動作となります。

  • Compose ファイルのサービスで定義されたコマンドを実行するが、 run コマンドで実行するコマンドを指定した場合、コンテナ内で実行されるコマンドを上書きすることが可能
  • Compose ファイルのサービスで定義されたポートを作成しない
  • 疑似ターミナル (TTY) が割り当てられる
Options Description Example
--name 開始するコンテナに名前を設定。 --name container_name
-d コンテナをバックグラウンドで実行。 -d
--rm コンテナを実行後に削除。バックグラウンドで実行した場合は無視される。 --rm
--service-ports Compose ファイルで定義したポートを作成。 --service-ports
-p, --publish コンテナのポートをホストへ公開。 -p 8080:80
-T 疑似ターミナル (TTY) を割り当てしない。 -T

start (コンテナ開始)

docker-compose start [service]

サービスのコンテナを開始します。

stop (コンテナ停止)

docker-compose stop [service]

稼働中のサービスのコンテナを削除せずに停止します。

停止したサービスのコンテナは、 start コマンドで再度開始できます。

restart (コンテナ再起動)

docker-compose restart [service]

停止中・実行中にかかわらず、 Compose ファイルで定義されたサービスのコンテナすべてを再起動します。

注意点として、 Compose ファイルを変更して restart コマンドを実行しても、変更内容はコンテナには反映されません。

rm (コンテナ削除)

docker-compose rm [option] [service]

Compose ファイルで定義された停止中のサービスのコンテナを削除します。

デフォルトでは、コンテナにアタッチした匿名ボリュームは削除されません。

Options Description
-v コンテナにアタッチした匿名ボリュームを削除。

up (イメージ構築、コンテナ作成・開始・アタッチ)

docker-compose up [option] [service]

Compose ファイルで定義されたサービスのイメージの構築、コンテナの作成・開始・アタッチを行います。

もし、既に存在するコンテナのサービスの設定やイメージが変更されていた場合、設定を反映するためにコンテナの停止・再作成が行われます。

Options Description
-d コンテナをバックグラウンドで実行。
--force-recreate Compose ファイルや使用されるイメージに変更が無くても、コンテナを再作成。
--no-recreate コンテナが存在する場合、再作成しない。
--no-build イメージを構築しない。イメージが存在しない場合はエラー。
--build イメージを構築後、コンテナを開始。イメージに変更があれば、コンテナは再作成される。
--no-start コンテナを開始しない。

down (コンテナ停止・削除)

docker-compose down [options]

Compose ファイルで定義されたサービスのコンテナを停止し、 up コマンドで作成されたイメージ・コンテナ・ネットワーク・ボリュームを削除します。

オプションを指定しなければ、デフォルトで削除されるのは以下のコンテナとネットワークのみです。

  • Compose ファイルで定義されたサービスのコンテナ
  • Compose ファイルの network セクションで定義されたネットワーク
  • default ネットワーク(使用している場合のみ)
Options Description Example
--rmi [type] イメージを削除。 type に all を指定するとすべてのイメージを、 local を指定すると image フィールドにカスタムタグのないイメージだけを削除します。 --rmi all
-v, --volumes volumes セクションの名前付きボリューム、および、コンテナにアタッチした匿名ボリュームを削除。 -v

exec (稼働中のコンテナでコマンド実行)

docker-compose exec [options] [service] [command] [arguments]

Compose ファイルで定義されたサービスの稼働中のコンテナ内で、指定したコマンドを実行します。

コンテナが開始していない場合は、コマンドは実行されません。

exec サブコマンドではサービスの指定は必須です。

オプションを指定しない場合、デフォルトでは疑似ターミナル (TTY) が割り当てられます。

Options Description Example
-d, --detach コマンドをバックグラウンドで実行。 -d
-T 疑似ターミナル (TTY) を割り当てしない。 -T
-e, --env 環境変数を設定。 -e KEY=value
-w, --workdir コマンドを実行するディレクトリを指定。 -w /path/to/workdir

ps (コンテナ一覧表示)

docker-compose ps [options] [service]

Compose ファイルで定義されたサービスのコンテナを一覧表示します。

オプションを指定しなければ、デフォルトで表示されるのは稼働中のコンテナのみです。

Options Description
-q, --quiet コンテナ ID のみを表示。
--services コンテナのサービス名を表示。
-a, --all 停止中のコンテナを含んだすべてのコンテナを表示。

logs (ログ表示)

docker-compose logs [options] [service]

Compose ファイルで定義されたサービスのログを表示します。

Options Description
-f, --follow ログを出力し続けます。ログ出力の停止は Ctrl + C で行います。
-t, --timestamps ログにタイムスタンプを出力します。

おわりに

とりあえず Docker Compose を使用するだけなら今回まとめた内容で事足りると思いますが、 Docker Compose は Dockerfile ほどではないにせよ奥が深く、より効率良く運用するためには Docker 公式ドキュメント をしっかり理解する必要があります。

また、 Docker が登場してから既に 8 年以上が経過し、その間、既に非推奨になった内容や新たに追加された仕様も複数存在します。

今回は Docker 公式ドキュメントを参考にしつつ 2021 年 8 月現在の最新情報でまとめたつもりなので、これから Docker Compose を本格的に使用しようと考えている方は、いったんここで最低限必要な内容を押さえ、あとは使いながら深い内容をキャッチアップしていっていただければと思います。

と、これまでさも知っている風に色々書いてきましたが、僕が Docker を使い始めたのは今年からになります。

はい、僕も完全に初心者ですね。イキりました。すいません _(┐「ε¦)_

【Angular】angular-gridster2 の使い方をまとめてみた (日本初かもしれない)

Angular

はじめに

Angular で Windows 10 のスタートメニューにあるような Drag and Drop ができるタイルを実装するにあたり、何か良さげなパッケージが無いか探していたところ、 angular-gridster2 というパッケージを教えてもらったので、その使い方を調べてみました。

github.com

angular-gridster2 は Drag and Drop で移動可能なタイル群を Grid Layout で配置でき、タイルの追加・削除・リサイズも簡単に実装することができる素敵パッケージです。

また、オプションやイベントが複数用意されているため、行数・列数の上限やタイル周囲の margin などの細かい指定のほか、ユーザの操作に応じたコールバック関数の実行も可能です。

ライセンスは MIT License なので、著作権表示および本許諾表示といったルールさえ順守すれば商用利用も問題ありません。

そんな魅力あふれるパッケージではありますが、色々調べる中で直面した大きな問題が…

それは、日本語で書かれたドキュメントが皆無だということ!!

使い方は難しくないですが、 GitHub の公式リポジトリ公式 Demo サイト を参考にしつつ、実際にコードを書いて動かしながら記事にまとめるのはなかなか大変でした。。

tiberiuzuld.github.io

もしかしたら、この記事が angular-gridster2 を日本語で取り上げた最初のエントリーかもしれません!?

動作環境

OS Version
Ubuntu 20.04 LTS

Windows 10 Pro の Windows Subsystem for Linux (WSL 2) を使用

Environment Version
Node.js 14.17.0
npm 6.14.13
Package Version
@angular/cli 12.1.2
angular-gridster2 12.1.0

前提条件

  • Angular CLI がインストールされている
  • Angular CLI を使用した Angular アプリケーションが作成されている

angular-gridster2 概論

導入

インストール

npm install angular-gridster2 --save

インストールは npm でサクッと行います。

--save オプションって、いつの間にか指定しなくてもデフォルトで ON になっていたらしい…

知らなかった… (npm 5.0.0 以降だそうです)

使い方

NgModule (app.module.ts)

...
import { GridsterModule } from 'angular-gridster2';

@NgModule({
  ...
  imports: [
    ...
    GridsterModule,
  ],
})
export class AppModule {}

Angular のコンポーネントで使用するために、まずは NgModule で GridsterModule クラスをインポートします。

HTML (app.component.html)

<button type="button" (click)="addItem()">タイル追加</button>

<gridster [options]="options">
  <gridster-item [item]="item" *ngFor="let item of items; let index = index">
    <div class="draggable-handler">
      Header
    </div>
    <div>
      Content
    </div>
    <div>
      <button type="button" (click)="removeItem(index)">削除</button>
    <div>
  </gridster-item>
</gridster>
Element Property: Type Description
gridster option: GridsterConfig 各種設定やコールバック関数を指定
gridster-item item: GridsterItem タイルの位置やサイズを指定

gridster タグを配置し、 option プロパティへ GridsterConfig 型のデータをバインドします。

gridster の Style には width: 100%;height: 100%; が指定されているので、配置先の親要素と同サイズまで広がります。

gridster タグ内部の gridster-item タグが一つのタイルに相当し、 item プロパティへ GridsterItem 型のデータをバインドすることでタイルの位置やサイズを設定します。

Component (app.component.ts)

...
import { GridsterConfig, GridsterItem } from 'angular-gridster2';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  options: GridsterConfig = {};
  items: Array<GridsterItem> = [];

  ngOnInit(): void {
    this.options = {
      draggable: {
        enabled: true,
        ignoreContent: true,
        dragHandleClass: 'draggable-handler',
      },
      resizable: {
        enabled: true,
      },
      swap: true,
    };

    this.items = [
      { x: 0, y: 0, rows: 1, cols: 1 },
      { x: 0, y: 1, rows: 1, cols: 1 },
    ];
  }

  /** タイル追加 */
  addItem(): void {
    this.items.push({ x: 1, y: 0, rows: 1, cols: 1 });
  }

  /** タイル削除 */
  removeItem(index: number): void {
    this.items.splice(index, 1);
  }
}

gridster コンポーネントoption プロパティにバインドする GridsterConfig データと、 gridster-item コンポーネントitem プロパティにバインドする GridsterItem データを定義します。

また、タイル (gridster-item コンポーネント) の追加や削除も、このサンプルコードのように動的に行うことができます。

今回はサンプルのため固定位置にタイルを追加していますが、追加するタイルの位置とサイズには、本来配置したい任意の位置とサイズを指定してください。

リファレンス

主要そうなプロパティをリファレンスとしてまとめました。

各項目の説明は僕の独断と偏見に満ちた超訳になりますので、正確な内容は GitHub の公式リポジトリ公式 Demo サイト などの一次ソースをご確認ください。

また、こちらで紹介するプロパティは一部だけなので、さらに多くのプロパティを知りたい場合は、同じく公式 (ry

そして、今回は時間の都合上、イベントとコールバック関数については取り上げていないので、こちらについても (ry

GridsterConfig

Property Description Type Default
gridType Grid 内部の幅と高さの設定方法。 'fit' はスクロールバーが表示されないように Grid 内部の幅と高さの中で調整される。 String 'fit'
compactType Grid 内の Item の自動整列方法 (上寄せや左寄せなど) 。 'none' は自動整列無し。 String 'none'
displayGrid Grid 内の境界線の表示方法。 'onDrag&Resize' はドラッグ中やリサイズ中のみ、境界線を表示する。 String 'onDrag&Resize'
margin Grid 外枠と内部の Item の余白。 Grid の padding と同義。 Number 10
outerMargin Grid 外枠の内部の Item の余白有無。 Boolean true
outerMarginTop Grid 外枠と内部の Item の上部余白。 margin の値より優先される。 Grid の padding-top と同義。 Number null
outerMarginRight Grid 外枠と内部の Item の右部余白。 margin の値より優先される。 Grid の padding-right と同義。 Number null
outerMarginBottom Grid 外枠と内部の Item の下部余白。 margin の値より優先される。 Grid の padding-bottom と同義。 Number null
outerMarginLeft Grid 外枠と内部の Item の左部余白。 margin の値より優先される。 Grid の padding-left と同義。 Number null
fixedColWidth gridTypefixed を指定した場合に適用される、一列あたりの固定幅。 Number 250
fixedRowHeight gridTypefixed を指定した場合に適用される、一行あたりの固定高さ。 Number 250
minCols Grid の最小列数。 Number 1
maxCols Grid の最大列数。 Number 100
minRows Grid の最小行数。 Number 1
maxRows Grid の最大行数。 Number 100
defaultItemCols Grid 内の Item の横方向の規定サイズ。最小値は 1 。 Item の cols が省略された場合に使用される。 Number 1
defaultItemRows Grid 内の Item の縦方向の規定サイズ。最小値は 1 。 Item の rows が省略された場合に使用される。 Number 1
swap Grid 内の Item を移動する際、移動先に別の Item が存在した場合の制御。 true を指定すると、移動先の Item と位置の入れ替えを行う。 Boolean true
pushItems Grid 内の Item を移動・リサイズする際、移動・リサイズ先に別の Item が存在した場合の制御。 true を指定すると、移動先の Item を移動させる。 Boolean false
disablePushOnDrag Grid 内の Item を移動する際、移動先に別の Item が存在した場合の制御。 true を指定すると、移動先の Item を移動しない。 pushItems より優先される。 Boolean false
disablePushOnResize Grid 内の Item をリサイズする際、リサイズ先に別の Item が存在した場合の制御。 true を指定すると、リサイズ先の Item を移動しない。 pushItems より優先される。 Boolean false
pushResizeItems Grid 内の Item をリサイズする際、リサイズ先に別の Item が存在した場合の制御。 true を指定すると、リサイズ先の Item のサイズを縮小する。 pushItems と併用した場合、先に移動先の Item が可能な限り縮小された後に移動させる。 Boolean false
draggable Grid 内の Item のドラッグ制御方法。 Draggable -
resizable Grid 内の Item のリサイズ制御方法。 Resizable -

GridsterConfig.Draggable

Property Description Type Default
enabled Grid 内の Item のドラッグ可否。 true を指定すると Item がドラッグ可能になる。 Boolean false
ignoreContent Grid 内の Item 全域のドラッグ可否。 true を指定すると、 dragHandleClass で指定したクラスを持つ要素以外のドラッグが不可になる。 Boolean false
dragHandleClass ignoreContenttrue にした場合、ここで指定したクラスを持つ要素のみがドラッグ可能要素となる。ドラッグの可否は enabled の値に準ずる。 String ‘drag-handler’

GridsterConfig.Resizable

Property Description Type Default
enabled Grid 内の Item のリサイズ可否。 true を指定すると Item がリサイズ可能になる。 Boolean false

GridsterItem

Property Description Type Default
x Item の横方向の表示開始位置。 0 始まり。省略時、または既に Item が存在する場合は、自動的に空き位置に配置される。 Number undefined
y Item の縦方向の表示開始位置。 0 始まり。省略時、または既に Item が存在する場合は、自動的に空き位置に配置される。 Number undefined
cols Item の横方向のサイズ。最小値は 1 。省略時は GridsterConfig.defaultItemCols の値が使用される。 Number undefined
rows Item の縦方向のサイズ。最小値は 1 。省略時は GridsterConfig.defaultItemRows の値が使用される。 Number undefined
dragEnabled Item のドラッグ可否。 GridsterConfig.Draggable.enabled を Item 毎に上書きしたい場合に使用する。 Boolean undefined
resizeEnabled Item のリサイズ可否。 GridsterConfig.Resizable.enabled を Item 毎に上書きしたい場合に使用する。 Boolean undefined

おわりに

今まで OSS や商用ソフトウェアを含む様々なパッケージやライブラリを使用してきましたが、日本語の公式ドキュメントや多くの日本語ブログなどに、知らず知らずのうちに助けられてきたのだなと改めて痛感させられました。

自分の力だけでやってきたと思い込んできたものも、先人たちの偉大な教えや知恵の上で回っていたに過ぎなかったのです…

そして、これもまた過去何度も思ったことではありますが、やっぱり英語ができるエンジニアは有利だなと思いました。

日本語のドキュメントが無いだけで一気に作業効率がガタ落ちしますし、大体どこの (公式を含む) ドキュメントを見ても一次ソースは英語であることがほとんどです。

やっぱり、少しずつ英語の勉強もしないといかんな… _(-ω-`_)⌒)_

【Cloud】大手クラウドサービスの IaaS 運用コストを比較してみた

Cloud

はじめに

有名どころのクラウドサービスの IaaS の運用コストを調べる機会があったので、費用面から見た各社の特徴をまとめてみました。

ちなみに、僕は開発でクラウドサービスを利用したことはあるものの、導入や構築などの経験は一切ありませんので、完全なる素人が調べた内容になっています。

間違っているところがあれば、コメントで指摘頂けると助かります (´ε`; )

良い意味で浅い内容に仕上がっていると思うので、これからクラウドサービスを利用したいと考えている方には丁度良い取っ掛かりになるかもしれません。

また、タイトルには "運用コストを比較" と書いていますが、利用する規模や頻度等によって金額が大きく異なるため、具体的な金額等は一切書いていません。

一通り各社の料金形態について把握したら、それぞれの公式サイトにある料金計算ツールを使用して金額を見積もってみましょう。 (丸投げ)

比較対象

今回の比較対象となる各社の IaaS はこちら。

すべて 2021 年 6 月現在のデータでの比較となります。

え?それ IaaS なの?というつっこみは受け付けません。

クラウドサービスの運用コスト

Amazon Web Service (AWS) - Amazon Elastic Compute Cloud (Amazon EC2)

AWS の料金については、こちらに詳しく記載されています。

aws.amazon.com

以降は上記の公式ページからの引用と、僕の超訳まとめになります。

従量制料金

aws.amazon.com

Amazon EC2 は使用した分だけ課金される従量制料金となっています。

必要に応じてスケールすることが容易なため柔軟で無駄の無い運用を行うことができますが、例えば Amazon EC2 を利用して定額制のサービスを展開する場合、使用時間やトラフィックを見誤った料金設定を行うと赤字になる可能性があるため注意が必要です。

Amazon EC2 の主な支払いのプランは以下の通りです。

オンデマンド

aws.amazon.com

契約期間の縛りなどは無く、使用するインスタンス (Amazon EC2 上で稼働する仮想サーバ) の稼働時間に応じた料金を後払いします。

必要な規模の仮想サーバを必要なときに調達でき、料金も使用した分を後払いするため、無駄なコストや手間を省くことが可能です。

ただし、特に割引等は無いため、予め一定期間以上使用することが決まっていたり、必要なキャパシティが一定などの場合は、後述する割引有のプランがお勧めです。

スポットインスタンス

aws.amazon.com

ざっくり言うと、 AWS 上の余った Amazon EC2 領域を格安で利用できるプランです。

Amazon EC2 の余り領域が無くなればインスタンスは中断されてしまうことになるので、停止しても問題が無い用途のインスタンスに限るか、その他のプランのインスタンスと組合せて使用することになります。

利用料金は需給状況によってリアルタイムに変化しますが、割引率は大きく設定されており、上手く運用できればオンデマンドの最大 90% OFF で Amazon EC2 を使用することができるため、運用コストの大幅な削減に繋がります。

リザーブインスタンス (RI)

aws.amazon.com

予め指定したインスタンスを 1 年または 3 年といった期間で契約し、そのインスタンスを使用する場合に限り、オンデマンドの料金から最大 75% OFF で利用することができます。

また、オンデマンドやスポットインスタンスと違い、料金を前払いすることで更に割引率を高めることも可能です。

デメリットとしては、契約期間中は使用しなくても料金が発生することになるため、必ず一定のコストがかかることになります。

一定のスペックで定常的な仮想サーバの稼働が見込まれる場合はオンデマンドと比較してメリットがありますが、稼働状況に波がある場合は結果的にオンデマンドよりも高額になってしまう可能性があります。

Savings Plans

aws.amazon.com

リザーブインスタンスが特定のインスタンスを一定期間使用するという契約なのに対し、 Savings Plans はインスタンス全体の一定期間の特定の使用量を契約するようなイメージです。

ちょっと分かりづらいですが、 1 年または 3 年の期間でどのくらい使用するかを前もって契約しておくことで、オンデマンドで使用した場合と比較して最大 72 % OFF の割引を受けることができます。

リザーブインスタンスと違い、インスタンスを指定する必要が無く、 Amazon EC2インスタンス全体に対して割引が適用されるため、柔軟性のあるプランになっています。

デメリットは…うーん、特に無いように感じます。。

Microsoft Azure - Azure Virtual Machines

Microsoft Azure の料金についてはこちら。

azure.microsoft.com

以降は AWS と同様、公式ページからの引用と僕の超訳まとめです。

従量制料金

azure.microsoft.com

Azure Virtual Machines も Amazon EC2 と同様に、従量課金制の料金形態となっています。

割引についても Amazon EC2 と概ね同様ですが、 Microsoft Azure には Windows Server や SQL Server といった Microsoft 製品を使用する場合に適用される割引があるなど、 Microsoft ならではの強みが存在します。

Azure ハイブリッド特典 (Azure Hybrid Benefit)

azure.microsoft.com

もし既に Windows Server や SQL Server などのライセンス、またはサブスクリプション契約を有している場合、それを Azure Virtual Machines へ適用することができます。

この特典を後述する Azure Reserved VM instances などと組み合わせれば、通常の従量料金と比較して最大 85% OFF のコスト削減が可能になります。

オンプレミスの Windows Server からの移行や余っているライセンスがある場合は、他のクラウドサービスと比べて資源を有効活用できるため、現在 Microsoft 製品を主に使用しているなら Microsoft Azure を選択するメリットは大きくなります。

これこそ Microsoft ならではと言えますね。

Azure Spot Virtual Machines

azure.microsoft.com

これは Amazon EC2 のスポットインスタンスとほぼ同様のプランになります。

Amazon EC2 の場合は停止の 2 分前に通知が来ますが、こちらは 30 秒前の通知なので若干猶予が少ないです。

詳細は割愛します。 (決して該当の公式ページが翻訳されていなかったからではない)

Azure Reserved VM Instances

azure.microsoft.com

こちらも Amazon EC2リザーブインスタンスとほぼ同様のプランですが、支払いは全額一括前払いか毎月の分割前払いになります。 (Amazon EC2 は前払い無しも選択できます)

また、 Amazon EC2 との違いの一つとして、契約時に決めていた利用期間やインスタンスの変更、途中解約による一部返金など、後からでも契約内容を変更することが可能という点が挙げられます。

Google Cloud - Compute Engine

Google Cloud の料金についてはこちら。

cloud.google.com

以降は AWSMicrosoft Azure 同様、公式ページからの引用と僕の超訳まとめになります。

従量制料金

cloud.google.com

Google Cloud の Compute Engine も、前述の 2 社と同様、従量課金制となっています。

後述する割引については Amazon EC2Microsoft Azure と比べて若干少なく感じますが、今回詳細まで調べてはいないものの、そもそもの通常料金が他社よりも安いとの情報がインターネット上で散見されました。

単純に同程度のインスタンスを使用したケースで比較すると Google Cloud の方が安いようなので、後述する割引を組み合わせれば、もしかしたら一番運用コストを安く抑えることができるのかもしれません。 (もちろん使用状況によると思います)

確定利用割引

cloud.google.com

これまで見てきた Amazon EC2リザーブインスタンスMicrosoft Azure の Azure Reserved VM Instances と同様に、 1 年または 3 年間の利用契約をすることで、最大 57% OFF の割引を受けることができます。

途中でインスタンスのスペックを変更しても割引が適用されるので、稼働状況に合わせて柔軟にスケールすることが可能です。

継続利用割引

cloud.google.com

他社を含めたこれまでの割引と違い、こちらはインスタンスの実行時間が一定の割合を超過した際に自動で適用されるものになります。

この割引を適用させるための設定等は不要のため、つまりはたくさん使えば勝手に安くなってくれる長期優良割引みたいなものです。

ただし、前述の確定利用割引との併用はできないという点には注意が必要です。

おわりに

2021 年 6 月現在、様々なクラウドサービスが展開されているなとは思っていましたが、各社の料金形態だけでも非常に複雑なルールやプランが存在するということが分かりました。

さらに、今回取り上げた 3 社はそれぞれ数十から数百のクラウドサービスを展開しており、ちょっと調べた程度ではその全貌を知ることは不可能に近いです…

この他に Oracle Cloud や Alibaba Cloud といった他社クラウドサービスもそれぞれ独自のプランや強みを持っていて、これらの中から最適なクラウドサービスを見付けるのは至難の業です。

ただ、逆に考えればこれらすべてとは言わないまでも、いくつかのクラウドサービスに精通した知識や経験を有することは他のエンジニアとの差別化にも繋がり、より自分自身の市場価値の向上に寄与するなとも思いました。

これまであまりクラウドサービスを使用する機会は多くありませんでしたが、時代に取り残されないように、今後はもっとプライベートでも積極的にクラウドサービスを使っていきたいと思います。

なんてことを 2021 年の今頃言っているようでは、もうずいぶん出遅れてしまっているんでしょうけれど ( っ・∀・)≡⊃ ゚∀゚)・∵.

【脱IE】Internet Explorer のサポート終了が与えるレガシーな社内 Web システムへの影響と対策

Internet Explorer

はじめに

先日、 Microsoft から Internet Explorer デスクトップアプリ (以下「IE」と称する) のサポート終了が発表され、一部の Windows OS のバージョンを除き、 2022 年 6 月 15 日 (水) をもって IE はその長い歴史に幕を閉じることになりました。

blogs.windows.com

IE のサポート終了の流れは既に数年前から始まっていて、主要な Windows OS においては 2016 年 1 月に IE11 以外のバージョンのサポートを終了しており、それから 5 年以上の年月を経て、ようやくの IE 終了に歓喜する Web エンジニアも少なくないと思います。

直近 5 年間の国内外の Web ブラウザシェアの推移を見ると、 IE の全体に占める割合は一桁台で減少の一途を辿っており、人によって見方は変わると思いますが、モダンブラウザへの移行は概ね順調であると言えます。

gs.statcounter.com

しかし、この話題でよく問題になるのは、 IE 独自の機能拡張 *1 に依存した処理が至る所に実装された、昔から存在する企業内の業務用社内 Web システムです。

Web エンジニアにはお馴染みですが、 IEMicrosoft によって独自の機能拡張が施されており、 Web 標準に準拠していない IE でしか使用できない機能が存在します。

これらが多用されたレガシーな Web システムをモダンブラウザに対応させるための工数は少なくなく、特に社内向け Web システムは利益に直結しないケースも多いことから、開発が先送りされてきたのが現状だと思います。

ということで今回は、レガシーな社内 Web システムでよく使用されている IE 独自の主だった機能拡張 *2 とその代替手段、そして IE からモダンブラウザへの移行時に合わせて検討した方がいいと思われることを独断と偏見でまとめてみました。

モダンブラウザでは使用できない IE 独自の機能

IE 独自の機能拡張については、 Windows Blog に Windows 10 の標準ブラウザである Microsoft Edge でサポートされない IE の機能や API がまとめられており、 Microsoft Edge でサポートされない ≒ Web 標準でない ≒ IE 独自の機能拡張 と理解しても、大きく間違ってはいないと思います。

blogs.windows.com

これらの中から私の経験を踏まえて、おそらく多くの現場で使われているであろう技術とその代替手段を取り上げていきます。 (完全に独断と偏見です)

ActiveX

ActiveXMicrosoft が開発したインターネット関連技術群の総称で、初っ端からですが、もしかしたらこれが一番厄介かもしれない機能です。

IEJavaScript エンジンである Chakra (jscript9.dll) では、細かいことをすっ飛ばすと、 ActiveXObject を使用することで Windows 上のファイル操作やコマンドが実行できるという、今となってはとんでもない機能が用意されています。

もちろん、これを使用するためには IE の設定でセキュリティレベルを下げるなどの明示的な対応が必要なため、一般的には安全だと思われるイントラネット内での社内 Web システムだからこその機能であると言えます。

よくある例

var fso = new ActiveXObject('Scripting.FileSystemObject');

fso.DeleteFile('ファイルパス', true); // 悪用厳禁

代替手段

ActiveXObject を使用すればある程度何でもできてしまうので社内向け Web システムでは意外と多く使用されてきたと思いますが、当然セキュリティはガバガバになるわけで、モダンブラウザにおいてはこんな操作ができるわけがなく、 Web 標準や ECMAScript にもこれに代わる機能は存在しません。

モダンブラウザでサポートされる WebAssembly でも、将来的にどうなるかは分かりませんが、現時点ではセキュリティ上の制約からローカルファイルへのアクセスは難しそうです。

基本的には設計を見直すか、何かしらのファイルをダウンロードしてローカル PC 上で手動で実行してもらうなどの方法を取らざるを得ないため、確実に仕様を変える必要が出てきます。

一番厄介と書いた理由は、これを決裁権を持つ偉い人に説明して納得してもらうのが難しい可能性があるからです…

これを説明しなければならない担当者の方には、大変ですねとしか言えません…

VBScript (Web クライアントスクリプト処理)

VBScript には、 IE で実行される Web クライアントスクリプト処理と Internet Information Service (IIS) で実行される Web サーバスクリプト処理 (Acrive Server Pages (ASP)) があり、今回対象となるのは IE で実行される Web クライアントスクリプト処理になります。

IIS で実行される Web サーバスクリプト処理はブラウザには依存しないため、 IE のサポート終了による影響を受けません。 (IIS 10.0 のサポート終了日は 2027 年となっているので、まだまだ VBScript 自体は健在のようです…)

Windows Blog で言及されていますが、既に IE11 では VBScript の実行が非推奨となっており、既定の設定では VBScript は実行されません。

blogs.windows.com

ただし、後述する Document mode による互換表示設定を行うことで VBScript の実行が可能になるため、 VBScript を使用している社内 Web システムでは、 IE の設定や HTML の meta 要素などで互換表示設定を行っているケースが多いと思います。

よくある例

<script language="VBScript">
MsgBox(
  "モダンブラウザへ移行してよろしいですか?", 
  vbOkCancel + vbQuestion, 
  "モダンブラウザ移行"
)
</script>

代替手段

VBScript に関しては、基本的には JavaScript への書き換えが可能です。

VBScript の組み込み関数のすべてが JavaScript の標準組み込みオブジェクトでカバーできるわけではありませんし、もちろん VBScript で書かれたコードを解析して JavaScript に書き換えるという手間は発生しますが、大きな問題にはならないはずです。

"基本的に" と書いたのは、 VBScript の組み込み関数で用意されている CreateObject 関数が問題で、これを使用すれば COM オブジェクト (ActiveX オブジェクト) への参照が取得でき、前述の ActiveX で書いたこととと同様のことができてしまいます。

Dim Fso

Set Fso = CreateObject("Scripting.FileSystemObject")

Fso.DeleteFile("ファイルパス") ' やっぱり消せちゃう

やはりこれに対しても前述した通り、設計を見直すなどの対応が必要となり、偉い人へ説明する担当者の方は (ry

Document modes (互換表示)

Web 標準は W3C (World Wide Web Consortium) 、 JavaScript の規格である ECMAScriptECMA International のもとでそれぞれ標準化が進められており、その時々のニーズに沿った新しい仕様・規格が活発に議論されています。

各モダンブラウザは基本的にこれらの標準化された仕様に準拠するよう実装され、新機能の先行実装や非推奨になった機能の削除など、比較的頻繁にアップデートが行われています。

IE もバージョンが上がる度に徐々に Web 標準への移行が進み、最終バージョンである IE11 では比較的モダンブラウザに近いレンダリングや動作をしますが、 IE8 やそれ以前のバージョンはかなり癖があると言っても過言ではありません。

こうした癖のある古い Web ページや Web システムを動作させるため、 MicrosoftIE後方互換性を維持するために実装した機能が Document modes です。

Document modes が使えなくなるということは、前述した二つ (ActiveXVBScript)) も含め、 IE の過去バージョンでのエミュレート (互換表示) ができなくなることを意味するため、モダンブラウザへ移行するためには Web 標準や ECMAScript に準拠したコードへの修正が必要になります。

よくある例

<!-- Document modes でエミュレートするバージョンを指定 -->
<meta http-equiv="X-UA-Compatible" content="IE=5">
<script>
  // attachEvent() は IE8 以前でしか動かない
  // showModalDialog() は多くのモダンブラウザで廃止済み
  document.getElementById('button')
    .attachEvent('onclick', function () {
      window.showModalDialog('modal.html');
    });
</script>
<frameset cols="30%, *">
  <frame src="side_menu.html" name="side_menu">
  <frame src="main.html"> name="main">
  <noframes>
    現在ご利用のブラウザは frameset に対応していません。
  </noframes>
</frameset>

代替手段

Web 標準の HTML / CSS を書こう!
そして、 ECMAScript に準拠した JavaScript を書こう!

これに尽きます。

とは言っても、先にも述べた通り社内 Web システムではなるべく工数を抑えたいという事情があるので、 JavaScript については attachEventshowModalDialog といったキーワードで検索して必要な箇所のみ修正を行い、レイアウト (HTML / CSS) については非常に面倒ではありますが、実際にモダンブラウザで表示させながら修正を加えていくしかないのかなと思います。

もちろん、 frameset などの分かりやすいものであれば、キーワードで検索して修正するのも一つの方法です。

特に古い quirks mode については Microsoft Docs で確認することができますが、これを一つ一つ検索して対応するのは至難の業です…

docs.microsoft.com

こうした対応をするにあたって、ここであえて社内 Web システムの良いところを挙げるならば、エンドユーザが社内の人であることが多いので、多少の不具合は何とかなるということでしょうか。

ただし、これもまた担当者の立場が外注や派遣だったりすると (ry

モダンブラウザ移行時に対応したいこと

ここからは、 IE からモダンブラウザへ移行する際、合わせて検討してもいいと思うことを挙げていきます。

ちなみに、どうせモダンブラウザへ移行するなら、 Angular や Vue.js で SPA 化したら?というのは無しの方向でお願いします。

あくまでも基本的な路線はなるべく工数をかけずに、でも、どうせやるなら一緒に対応できることは合わせてやってしまいたいという目線で考えてみました。

jQuery

IE を使用する社内 Web システムでは、 jQuery を使用しているケースが多いと思います。 (hrgm 調べ)

jQuery は 2021 年 6 月現在で 3.6.0 が最新バージョンとなっていますが、 IE で使用できる jQuery のバージョンは大きく二つに分類されます。

IE jQuery
~ 8 1.n
9 ~ 2.n, 3.n

IE8 と IE9 の間を境にして、 IE8 以前はバージョン 1.nIE9 以降はバージョン 2.n3.njQuery が使用可能で、 jQuery のバージョンが上がれば使用できるメソッドなどに若干の違いが出ますが、バージョンアップにより軽量化やパフォーマンス向上の恩恵が受けられます。

また、各モダンブラウザについては、基本的に最新版とその一つ前のバージョンへの対応が明記されています。

jquery.com

もしモダンブラウザへの移行を考えているのであれば、合わせて jQuery の最新化も検討してみてはいかがでしょうか。

ただし、 jQuery のバージョンアップには少ないながらもいくつか破壊的な変更が含まれており、元々非推奨だったメソッドの削除などが行われているため、移行には多少注意が必要です。

jQuery 公式から提供されている移行用ライブラリを使用するか、もしくは社内 Web システム内で使用している jQuery の機能には一定の傾向があると思うので、例えば load メソッドなど多用している場合はキーワード検索で修正箇所を特定するなど、ピンポイントの対応でもいいかもしれません。

github.com

Bootstrap

Bootstrap も比較的使用されていることが多いと思いますが、つい最近 Bootstrap 5 がリリースされ、このバージョンから jQuery が不要になるなどの大きな変化がありました。

Bootstrap も jQuery と同じく、せっかくモダンブラウザへ移行するならバージョンアップを検討してもいいと思いますし、もし将来的に IE だけでなく jQuery からの脱却も視野に入れているならば、このタイミングで Bootstrap 5 へ移行してしまうのもアリかと思います。

かなり端折った Bootstrap の各バージョンとブラウザへの対応状況は以下の通りですが、 Bootstrap はバージョンが変わるとデザインや書き方も多少変わるため、 jQuery と同じくコードの修正やターゲットブラウザでの確認が必要になります。

Bootstrap jQuery Browser
3 要 (※) IE8 ~, 各モダンブラウザ
4 要 (※) IE10 ~, 各モダンブラウザ
5 不要 IE 非対応, 各モダンブラウザ

※ Bootstrap で用意されている Components を使用しない場合、 jQuery は必須ではない。

Bootstrap 3 のサポートは既に終了していますが、 Bootstrap 4 以降は基本的に jQuery と同様に各モダンブラウザの最新版とその一つ前のバージョンがサポートされます。

Bootstrap のマイグレーションについては公式ドキュメントにも記載があるので、これを見ながら対応すれば大丈夫だと思います。 (簡単だとは言ってない)

getbootstrap.jp

getbootstrap.jp

その他のライブラリについて

jQuery や Bootstrap といった有名なライブラリだけでなく、中にはあまり知られていないライブラリを使用していることもあると思います。

これらは既に開発が止まってしまっているケースも多く、今まで述べてきたような IE に依存する実装が内部でなされている場合は、別のライブラリへ切り替えるか自前で実装を行わなければいけません。

運良く開発が続いていてモダンブラウザへの対応がされていたり、何もしなくてもそのままモダンブラウザで動けばラッキーくらいに考えておき、基本的には対応が必要になると思っていた方が無難かもしれません。

社内 Web システムでは外部のライブラリを使用するために許可が必要だったりするので、あまり無いことだとは思いますが、過去または現在の担当者が勝手にライブラリを入れていたりすると、なかなか面倒なことになったりします…

IE を使い続けることはできないか

Microsoft による IE のサポートは終了しますが、アプリ自体が使えなくなるわけではないはずなので、おそらく継続して IE を使用することはできると思います。

ただし、サポートが終了するということはセキュリティアップデートも当然行われないため、特に企業内での既定ブラウザとして IE を使い続けることはお勧めできません。

それではモダンブラウザへの移行に伴い、これまで使用してきた社内 Web システムを必ず改修しなければいけないかと聞かれたら、そういうわけでもありません。

Microsoft EdgeInternet Explorer モード

ここまで色々書きましたが、 IE の後継ブラウザである Microsoft Edge には IE モードが搭載されており、実はこの機能を使用すれば、これまで取り上げた ActiveX コントロールや Document mode などをそのまま使用することができます。

docs.microsoft.com

もちろん、この IE モードにもサポート期限は存在しますが、現時点においては少なくとも 2029 年までのサポートが明言されています。

docs.microsoft.com

これはつまり、 2022 年 6 月に IE のアプリとしてのサポートは終了するものの、使用する Web ブラウザを Microsoft Edge へ移行してしまえば、さらにあと 7 ~ 8 年は今まで同様にレガシーな Web システムが利用可能ということを意味します。

各企業の方針にもよるとは思いますが、基本的に機能追加などを改修をほとんど行わない場合は Microsoft Edge への移行のみを行い、 Web システム自体には手を加えずに IE モードのサポート期限ギリギリまで粘るということも不可能ではありません。

しかし、機能追加や改修を頻繁に行うような Web システムになると、対応を先送りすればするほど IE モードのサポート終了まで技術的な負債を積み上げることになるので、やはりどこかのタイミングで Web 標準への対応を行った方が、長い目で見れば工数が少なくて済むという見方もできます。

この辺りは社内 Web システムの開発頻度や使用状況、モダンブラウザ対応を行うにあたってかけられる工数や費用によって変わってくるので、担当部門や各ステークホルダーと要相談になるかと思います。

おわりに

現在のブラウザシェアでは一桁台となりましたが、 IE はこれまで長きに渡り Web ブラウザのデファクトスタンダードの地位を守ってきました。

それ故にバージョンアップやサポート終了による影響力は甚大で、ここまでずっと Microsoft によって後方互換性が維持されてきたのも頷けます。

IE のサポートが終了すると言っても、前述した通り Microsoft Edge では引続き IE モードによって既存の Web サイトや Web システムを使用することができるため、一番取り残されているであろう社内 Web システムの Web 標準への対応はまだ先になるかと思います。

ただ、アプリとしての IE が無くなり、社内の既定ブラウザが Microsoft Edge を含むモダンブラウザへ移行することには大きな意味があると考えます。

それは、もし今後新規で Web システムを構築する場合、何の気兼ねもなく最新の Web 関連技術を使用することができるからです。

今はまだ涙をこらえて秀丸でコードを書いて IE を手動でリロードしながら開発をしていても (数年前の僕です) 、 Visual Studio Code でコードを書いて CLI でタスクランナーを走らせながらブラウザをライブリロードするなんて未来がやってくるかもしれません! (適当)

でもこれ、偉い人が理解して許可してくれるかどうかは…おっと、長くなったので今日はここまで。

それでは、社内 Web システム開発に関わるすべての Web エンジニアに幸多からんことを (*˘人˘*)

*1:紛らわしいが、アドオン (拡張機能) ではない。

*2:しつこいようですが、アドオン (拡張機能) ではない。