【Dart/Flutter】パッケージの構成要素まとめ

Flutter

こんにちは、教育系エンジニアのひらまつ(@hiramatsuu)です。

ひらまつの簡単な自己紹介

書籍「ゼロからわかる Linuxコマンド200本ノック(技術評論社)」の著者。Udemy受講者7万人。
プログラミング教育をメインに活動するエンジニアとして、動画教材の作成・技術書の執筆・教育アプリの開発などを行なっています(詳しくはこちら)。

本記事では、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のための独自ディレクトリを用意することができる。詳しくはこちら

参考文献

Package layout conventions
Learn more about the directory structure used by Dart's package management tool, pub.

脚注

  1. https://dart.dev/guides/libraries/create-packagesも参照。 ↩︎
  2. エントリーポイントは、pubの文脈では「依存グラフのルートになるパッケージ」と言える。より詳しくは→https://dart.dev/tools/pub/glossary#entrypoint-directory ↩︎
  3. https://dart.dev/tools/pub/glossary#entrypoint-directoryも参照。 ↩︎
タイトルとURLをコピーしました