9
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

and factory.incAdvent Calendar 2024

Day 3

年末大掃除:`flutter_gen`で使っている未使用の画像リソースを抽出する

Last updated at Posted at 2024-12-02

この記事はand factoryの2024年アドベントカレンダーの記事です。

Flutterでアプリ開発している渡邉です。

年末も近づいてきて、Flutterのプロジェクトも大掃除(リファクタリング)したいところです。
この度、DevToolsでアプリのリソースを確認したところアプリ内の画像リソースが多くを占めていることが分かりましたので、どのリソースが実質使われていないのかを特定してみました。

DevToolsではAppSizeタブがありサイズ情報を確認することができます。詳しくは公式のドキュメントをご参照ください。

開発中のプロジェクトでは、以下のような理由でアプリ内の不要な画像リソースが残っていることがあります。

  • 古いUIで使用されていた画像が未削除
  • 機能のリファクタリング後に使用されなくなった画像
  • テスト用リソースが残存

今回は、Flutterプロジェクトで flutter_gen を使って生成された画像リソースの中から、未使用の画像リソースを効率的に抽出する方法を紹介します。


flutter_gen とは?

flutter_gen は、Flutterプロジェクトで定義されたアセット(画像やフォントなど)を安全に利用するための自動生成ツールです。pubspec.yaml に記述されたアセット情報から、型安全なDartコードを生成します。

例えば、以下のようなコードを生成します:

/// File path: assets/images/sample_image.webp
AssetGenImage get sampleImage =>
    const AssetGenImage('assets/images/sample_image.webp');

これにより、手動でファイルパスを記述する手間を省き、コードの保守性を高めます。


未使用リソースを抽出するスクリプト

以下は、flutter_gen が生成したコードを解析し、未使用の画像リソースを抽出するDartスクリプトです。

このスクリプトは以下の手順で動作します:

  1. flutter_gen が生成したファイル(通常は lib/gen/assets.gen.dart)を解析して、全てのアセット情報を取得
  2. プロジェクト全体をスキャンし、各アセットがどのファイルで使用されているか記録する
  3. 未使用のアセットをリストアップ

コードが汚かったのでChatGPTに整形していただきました。

import 'dart:io';

void main() {
  // Generatedアセットファイルを指定
  final assetsFile = File('lib/gen/assets.gen.dart');

  // ファイルが存在しない場合のエラーチェック
  if (!assetsFile.existsSync()) {
    print('assets.gen.dart が見つかりません。パスを確認してください。');
    return;
  }

  // assets.gen.dart 内のゲッターメソッド名とアセットパスを取得
  final getterNames = <String>[]; // 生成されるゲッター名
  final assetPaths = <String, String>{}; // {getterName: filePath}

  final lines = assetsFile.readAsLinesSync();
  for (var i = 0; i < lines.length; i++) {
    // ファイルパスを取得
    final filePathMatch =
        RegExp(r'/// File path: (.+)').firstMatch(lines[i]);
    if (filePathMatch != null && i + 1 < lines.length) {
      final filePath = filePathMatch.group(1)!;

      // 次の行でゲッターメソッドを検索
      final getterMatch =
          RegExp(r'AssetGenImage get ([a-zA-Z0-9_]+) =>').firstMatch(lines[i + 1]);
      if (getterMatch != null) {
        final getterName = getterMatch.group(1)!;
        getterNames.add(getterName);
        assetPaths[getterName] = filePath;
      }
    }
  }

  if (getterNames.isEmpty) {
    print('アセットが見つかりませんでした。');
    return;
  }

  // プロジェクト全体で使用されているアセットゲッターを検索
  final projectFiles = Directory('./lib').listSync(recursive: true);
  final usageDetails = <String, List<String>>{}; // {getterName: [filePaths]}

  for (var getter in getterNames) {
    usageDetails[getter] = []; // 初期化
  }

  for (var file in projectFiles) {
    if (file is File && file.path.endsWith('.dart')) {
      // ./lib/gen/assets.gen.dart を除外
      if (file.path.contains('lib/gen/assets.gen.dart')) {
        continue;
      }

      final content = file.readAsLinesSync();
      for (var line in content) {
        // コメント行をスキップ
        if (line.trim().startsWith('//')) {
          continue;
        }

        for (var getter in getterNames) {
          if (line.contains(getter)) {
            usageDetails[getter]!.add(file.path);
          }
        }
      }
    }
  }

  // 使用状況をログに出力
  print('\nアセット使用状況 (使用回数順):');
  final sortedUsageDetails = usageDetails.entries.toList()
    ..sort((a, b) => b.value.length.compareTo(a.value.length)); // 使用回数でソート

  for (var entry in sortedUsageDetails) {
    final getter = entry.key;
    final usageCount = entry.value.length;
    print('${getter} (${assetPaths[getter]}): $usageCount回使用');
    if (usageCount > 0) {
      print('  使用ファイル:');
      for (var path in entry.value) {
        print('    - $path');
      }
    }
  }

  // 未使用アセットを特定
  final unusedAssets = sortedUsageDetails.where((entry) => entry.value.isEmpty);

  if (unusedAssets.isEmpty) {
    print('\nすべてのアセットが使用されています。');
  } else {
    print('\n未使用のアセット:');
    for (var entry in unusedAssets) {
      print('${entry.key}: ${assetPaths[entry.key]}');
    }
  }
}

スクリプトの実行方法

  1. プロジェクトのルートディレクトリにスクリプトを保存(例: check_unused_assets.dart
  2. 以下のコマンドで実行します
dart check_unused_assets.dart

出力例

以下はスクリプトを実行した際のサンプル出力です。

アセット使用状況

アセット使用状況 (使用回数順):
imgSample (assets/gifs/img_sample.webp): 3回使用
  使用ファイル:
    - lib/screens/home_screen.dart
    - lib/screens/my_page_screen.dart
    - lib/widgets/header.dart
anotherImage (assets/images/another_image.webp): 0回使用

未使用アセット

未使用のアセット:
anotherImage: assets/images/another_image.webp

VSCodeのターミナルからCmd+クリックでファイルに飛べますのでサクサク作業できます。

まとめ

このスクリプトを使うことで、flutter_gen で管理されている画像リソースの中から未使用のものを効率的に見つけることができます。未使用リソースを削除することで、アプリのビルドサイズを削減し、パフォーマンスの向上が期待できます。

年末の大掃除の一環として、ぜひ活用してみてください

9
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?