こんにちは、教育系エンジニアのひらまつ(@hiramatsuu)です。
書籍「ゼロからわかる Linuxコマンド200本ノック(技術評論社)」の著者。Udemy受講者8万人。
プログラミング教育をメインに活動するエンジニアとして、動画教材の作成・技術書の執筆・学習アプリの開発などを行なっています(詳しくはこちら)。
本記事では、Dartパッケージの慣例的なディレクトリ構成について解説します。
pub.devなどで公開するパッケージを開発したい方だけでなく、単にパッケージを使用するだけの方にとっても、ディレクトリ構成の慣例を知っておくことで、使用しているパッケージのソースを読みやすくなるなど、十分にメリットがありますので、しっかり理解しておきましょう。
慣例的なディレクトリ構成の例
以下は、架空のenchiladaパッケージの構成を示したもの。以下に記載されているファイルやディレクトリが、どのような役割なのかを理解できるようになるのが、本記事の目標。
enchilada/
.dart_tool/
pubspec.yaml
pubspec.lock
LICENSE
README.md
CHANGELOG.md
benchmark/
make_lunch.dart
bin/
enchilada
doc/
api/
getting_started.md
example/
main.dart
hook/
build.dart
integration_test/
app_test.dart
lib/
enchilada.dart
tortilla.dart
guacamole.css
src/
beans.dart
queso.dart
test/
enchilada_test.dart
tortilla_test.dart
tool/
generate_docs.dart
web/
index.html
main.dart
style.css
pubspec.yamlとpubspec.lock
enchilada/
pubspec.yaml
pubspec.lock
依存するパッケージなどを記載するファイル。すべてのパッケージは、ルートディレクトリにpubspec.yamlを持つ必要がある。
pubspec.yamlは、dart pub get
/ dart pub upgrade
/ dart pub downgrade
を実行すると、pubspec.yamlを元にして、pubspec.lockが生成される。
pubspec.yamlとpubspec.lockについて、より詳しくは、以下の記事たちを参照。
LICENSE
enchilada/
LICENSE
ライセンスを記載したライセンスファイル。
他の人が再利用できるように、BSD-3-Clauseのような、OSI-approved licenseの利用が推奨されている。ライセンスの選び方は、こちらなどを参照。
README.md
enchilada/
README.md
プロジェクトについて述べた、READMEファイル。
Markdownがレンダリングされて、pub.devのページに表示されるため、pubにおいては特に重要。コードやパッケージについて、他の人に解説するのに最適な場所。
最適なREADMEの書き方は、こちらを参照。
CHANGELOG.md
enchilada/
CHANGELOG.md
パッケージのそれぞれのリリースのメモ書き。
パッケージの利用者が、バグ修正や新機能を発見するのに使うだけでなく、最新のバージョンに更新するのに、どれだけの努力が必要かを判断する材料にもなる。
CHANGELOG.mdをパースするツールのために、以下のフォーマットを使うようにする。
- 各バージョンは、見出しを持つ独自のセクションを持つ。
- バージョンの見出しは、レベル1(# 見出し)かレベル2(## 見出し)のいずれかにする。
- バージョンの見出しのテキストには、パッケージのバージョン番号が含まれる。「v」を先頭に付けても良い。
パッケージをpub.devサイトにアップロードすると、あなたのパッケージのCHANGELOG.mdファイル(ある場合)が、Changelogタブに表示され、Markdownとしてレンダリングされる。
CHANGELOG.mdファイルのサンプルを、以下に示す。
# 1.0.1
* Fixed missing exclamation mark in `sayHi()` method.
# 1.0.0
* **Breaking change:** Removed deprecated `sayHello()` method.
* Initial stable release.
## Upgrading from 0.1.x
Change all calls to `sayHello()` to instead be to `sayHi()`.
# 0.1.1
* Deprecated the `sayHello()` method; use `sayHi()` instead.
# 0.1.0
* Initial development release.
CHANGELOG.mdに限った話ではないが、本記事と合わせて、pub.devから実際のパッケージのGitHubリポジトリにアクセスして、確認しながら学ぶと理解しやすくなる。例えば、google_sign_inなど。
以上がルートに配置されているファイル。以降では、ディレクトリについて見ていく。
.dart_tool/
enchilada/
.dart_tool/
プロジェクト固有のキャッシュの保管場所。
.dart_tool/ディレクトリは、dart pub getの実行時に作成され、いつでも削除される可能性がある。基本的にこのディレクトリを削除するのは安全、ただしキャッシュ情報の再計算は必要になる。
プロジェクトやローカルのマシンに特有のファイルをキャッシュする用途で、様々なツールがこのディレクトリを使用する。例えば、.dart_tool/package_config.jsonファイルによって、「import ‘package:foo/foo.dart’」というような記述で、依存パッケージのキャッシュにアクセスできるようになる。より詳しくは「【Flutter・Dart】パッケージの使用について深く理解する」を参照。
.gitignore内で指定することで、バージョン管理しないようにする。その理由は「lockfileはコミットすべきか?【Flutter・Dart】」を参照。
benchmark/
enchilada/
benchmark/
make_lunch.dart
ベンチマーク計測用のファイルを含むディレクトリ。
パフォーマンスが極めて重要なコードを含むパッケージは、benchmarkディレクトリを含む場合もある。APIを正確さのためではなく、スピード、メモリ使用量や、その他のメトリクスの計測のために用意される。
bin/
enchilada/
bin/
enchilada
ユーザーに公開する、コマンドラインから実行するファイルを格納するディレクトリ。
コマンドラインから直接実行できるプログラムを持つパッケージもある。これは、シェルスクリプトであったり、Dartを含むスクリプト言語の可能性もある。
もしこのようなコードをパッケージが持つなら、配置場所はbin/にする。bin内のスクリプトは、どこからでもコマンドラインで実行できるようになる(ただし、dart pub global
を使ってセットアップする必要はある)。
doc/
enchilada/
doc/
api/
getting_started.md
コード内のdoc commentsから生成された、ドキュメントが格納されるディレクトリ。
dart docコマンドを使うと、/doc/apiディレクトリが作成され、その中にソースコードのdoc commentsから生成されたドキュメントが配置される。生成されるディレクトリ・ファイルなので、バージョン管理するべきではない。
生成されたapiディレクトリを除いては、特にドキュメントのフォーマットや構成に関するガイドラインを、Dartは持たないので、自分の好きなガイドラインを採用して良い。
example/
enchilada/
example/
main.dart
パッケージの使用例を入れるディレクトリ。
pub.devならExampleタブに掲載される。使用例があることで、ユーザーがパッケージの使用方法を理解しやすくなる。
exampleの中では、「package:」を使ってパッケージ内のファイルをimportするようにする。そうすることで、パッケージのユーザーと同じ使い方をすることができる。
exampleが複雑な場合は、サブディレクトリを作るなどして、それぞれのexampleごとに構造化してもよい。また、パッケージを公開する可能性がある場合は、以下のいずれかの名前でファイルを作成するようにする。
- example/example[.md]
- example[/lib]/main.dart
- example[/lib]/package_name.dart
- example[/lib]/package_name_example.dart
- example[/lib]/example.dart
- example/README[.md]
上記のファイルの一つ、または複数を含むパッケージを公開すると、pub.devサイトは「Example」タブを作成して、最初に見つかったファイルを表示する(上記のリストに示された順序で検索される)。
ただし、パッケージのexampleディレクトリに多くのファイルが含まれており、README.mdという名前のファイルがある場合、パッケージの「Example」タブは、example/README.mdの内容を表示する(Markdownとして解析される)。
hook/
enchilada/
hook/
build.dart
Dart SDKやFlutter SDKから呼び出される、hookを定義するディレクトリ。
試験的に公開されている機能だが、Dart SDKやFlutter SDKから呼び出されるhookを定義することができる。hookは事前に定義されたCLIで、もしあればSDKツールによって呼び出される。
integration_test/
enchilada/
integration_test/
app_test.dart
インテグレーションテストのファイルを格納するディレクトリ。
Flutterアプリのパッケージは、integration_testパッケージを使った、インテグレーションテストを持つ場合もある。
大事なことは、遅いインテグレーションテストと、高速なユニットテストを分割すること。注意点として、integration_testディレクトリは、「dart test integration_test」というように、dart testコマンドの引数に指定しないと実行されない。
lib/
enchilada/
lib/
enchilada.dart
tortilla.dart
ユーザーに公開する、ライブラリの格納場所。他のパッケージに公開されるディレクトリは、lib/とbin/の2つ。lib/は公開ライブラリ(public library)の置き場で、bin/は公開ツール(public tool)の置き場。
ほとんどのパッケージは、ユーザーがimportできる単一のライブラリを定義している。その場合、ライブラリの名前は、パッケージの名前と一致させるべき。もちろん、他のライブラリを定義することも可能。
上記のようなlib/だと、次のようにライブラリをimportできるようになる。
import 'package:enchilada/enchilada.dart';
import 'package:enchilada/tortilla.dart';
libの中にサブディレクトリを作成することも可能。その場合は、それに応じてimportディレクティブの記述も変える。
例えば、以下のような構成になっているとする。
enchilada/
lib/
some/
path/
olives.dart
この場合、olives.dartは次のようにimportする。
import 'package:enchilada/some/path/olives.dart';
ただし、多くのライブラリでは、直接importされるライブラリは/lib/直下に置き、直接importされないライブラリは/lib/src/に置く慣例に従っている。1特にWebアプリにおいては、パフォーマンス上の理由から、このような構成にする必要がある。
libの中には、ライブラリだけを入れるべきであることに注意。エントリーポイント2は入れてはいけない。エントリーポイントは、エントリーポイントディレクトリに配置するようにする。
エントリーポイントディレクトリ(エントリーポイントを含めることができるディレクトリ)は、benchmark、bin、example、test、tool、web(Flutterアプリにおいては、libも可)。binを除いた、これらのサブディレクトリにも、エントリーポイントを含めることができる。3
enchilada/
lib/
guacamole.css
lib/内には、Dartコードだけでなく、その他の種類のコンテンツを再利用してもらうことも可能。
例えば、Bootstrapのためのパッケージは、パッケージのユーザーのために、いくつかのCSSファイルを含むかもしれない。こういったファイルは、libディレクトリ内に入れることができるし、サブディレクトリの構成も好きに用意できる。
enchilada/
lib/
src/
beans.dart
queso.dart
ライブラリの内部でのみ使用し、インターフェースが公開されていないコードは、/lib/srcディレクトリに配置する。このディレクトリ以下の構成は自由に決められる。
/lib/srcディレクトリ内のライブラリは、パッケージ内からはどこからでも自由にアクセスすることができるが、パッケージの外でimportすべきではない。これらのインターフェースは公開されていない(ドキュメントで定義されていない)ため、インターフェースが変更される可能性がある。
lib内でのimportは、次のように相対パスで指定し、
import 'src/beans.dart';
testディレクトリなど、lib外でのimportは、package:を使うようにする。
import 'package:enchilada/src/beans.dart';
test/
enchilada/
test/
enchilada_test.dart
tortilla_test.dart
テストコードを格納するディレクトリ。
ほとんどのテストコードは、testディレクトリ内(もしくは任意のサブディレクトリ内)におき、名前の末尾に、「_test」をつける。
インテグレーションテストは、integration_test/に置く。
tool/
enchilada/
tool/
generate_docs.dart
ユーザーに公開しない、コマンドラインから実行するファイルを格納するディレクトリ
tool/ディレクトリは、ユーザーに公開しないスクリプト(例えばテストランナーなど)を配置するディレクトリ。公開するスクリプトは、bin/に入れる。
web/
enchilada/
web/
index.html
main.dart
style.css
Webパッケージにおいて、エントリーポイントを配置するディレクトリ。
サブディレクトリは自由に設定可能。
理解度チェック
本記事の最初に掲載した、パッケージ構成を再掲。すべて意味がわかるようになっているか、確認してみてください。
enchilada/
.dart_tool/
pubspec.yaml
pubspec.lock
LICENSE
README.md
CHANGELOG.md
benchmark/
make_lunch.dart
bin/
enchilada
doc/
api/
getting_started.md
example/
main.dart
hook/
build.dart
integration_test/
app_test.dart
lib/
enchilada.dart
tortilla.dart
guacamole.css
src/
beans.dart
queso.dart
test/
enchilada_test.dart
tortilla_test.dart
tool/
generate_docs.dart
web/
index.html
main.dart
style.css
補足
これは通常のパッケージの話で、Flutterアプリのようなアプリケーションパッケージでは、assetのための独自ディレクトリを用意することができる。詳しくはこちら。
参考文献
脚注
- https://dart.dev/guides/libraries/create-packagesも参照。 ↩︎
- エントリーポイントは、pubの文脈では「依存グラフのルートになるパッケージ」と言える。より詳しくは→https://dart.dev/tools/pub/glossary#entrypoint-directory ↩︎
- https://dart.dev/tools/pub/glossary#entrypoint-directoryも参照。 ↩︎