# A module package

A module is a Dart package. This is the package of the counter feature that [Write a feature module](https://doc.saymyframe.com/extending/feature-module.md) builds:

```text
smf_counter/
├── pubspec.yaml
├── analysis_options.yaml
├── bricks/
│   ├── counter/
│   │   ├── brick.yaml
│   │   └── __brick__/lib/features/counter/counter_store.dart
│   ├── counter_bloc/
│   │   ├── brick.yaml
│   │   └── __brick__/lib/features/counter/...
│   └── counter_riverpod/
│       ├── brick.yaml
│       └── __brick__/lib/features/counter/...
├── lib/
│   ├── bundles/
│   │   ├── counter_bundle.dart
│   │   ├── counter_bloc_bundle.dart
│   │   └── counter_riverpod_bundle.dart
│   └── smf_counter.dart
└── test/
    ├── architecture_test.dart
    └── counter_module_test.dart
```

- `lib/smf_counter.dart` holds the module: a class that extends `SmfModule`.
- `bricks/` holds the templates of the files the module adds to an app, as [mason](https://pub.dev/packages/mason) bricks.
- `lib/bundles/` holds the bricks bundled into Dart, which the module contributes. You generate them from `bricks/`.
- `test/` runs the contract harness over the module and checks the rules of a module package; see [Test a module](https://doc.saymyframe.com/extending/testing.md).

## The pubspec

```yaml title="pubspec.yaml"
name: smf_counter
description: A counter feature for apps that SMF generates.
version: 0.1.0

environment:
  sdk: ^3.12.0

dependencies:
  mason: ^0.1.1
  smf_contracts: ^0.8.0

dev_dependencies:
  lints: ^6.0.0
  smf_bloc: ^0.3.5
  smf_flutter_core: ^0.7.0
  smf_gen_l10n: ^0.6.0
  smf_get_it: ^0.4.4
  smf_go_router: ^0.6.2
  smf_pipeline: ^0.8.0
  smf_riverpod: ^0.3.5
  smf_shared_preferences: ^0.4.5
  test: ^1.24.0
```

The code of a module needs `smf_contracts`, and `mason` for the bundles. The rest is for the tests: `smf_pipeline` has the contract harness, and the other modules make the apps of the tests complete. The package depends on another module only when the module declares it in `dependsOn`, as `smf_firebase_analytics` does with `smf_firebase_core`.

A module is plain Dart. It runs inside `smf`, and the Flutter code it adds lives in its bricks. The code in `lib/` does not import `dart:io` or other libraries that reach the machine, and its checks and commands use the environment that the pipeline gives them.

## The module class

A module imports the module model, `package:smf_contracts/smf_contracts.dart`, and declares its id once, as a constant:

```dart
import 'package:smf_contracts/smf_contracts.dart';

final class CounterModule extends SmfModule {
  const CounterModule();

  /// The id of the module, as in `-m counter`.
  static const id = ModuleId('counter');

  @override
  ModuleDescriptor get descriptor => const ModuleDescriptor(
        id: id,
        description: 'A screen that counts taps',
        kind: ModuleKinds.feature,
      );

  @override
  List<Contribution> contribute(ModuleContext context) => [];
}
```

The id is lower snake_case and unique among the modules of a command. Another module refers to it through this constant, as in `dependsOn: {CounterModule.id}`, so a dependency on the module is a dependency on its package too. The description is what the questions of `smf create` show next to the id.

## Bricks

A brick is a directory with a `brick.yaml` and the templates of its files in `__brick__/`, at their paths in the app:

```yaml title="bricks/counter/brick.yaml"
name: counter
description: "The store of the counter feature."
version: 0.1.0+1

environment:
  mason: ^0.1.1
```

A module has as many bricks as it needs. Files that only some apps get, such as the files of a variant, go into a brick of their own, which the module contributes only in those apps. Bricks have no mason hooks. SMF rejects a brick with hooks, and a module checks the machine and runs commands through its contributions instead. See [Templates](https://doc.saymyframe.com/extending/templates.md) for what a template may contain.

Keep the bricks out of the analysis of the package, since their files are templates rather than Dart:

```yaml title="analysis_options.yaml"
include: package:lints/recommended.yaml

analyzer:
  exclude:
    - bricks/**
```

Do not run `dart format` on `bricks/` either, because it breaks templates that parse as Dart.

## Bundles

The module contributes its bricks as bundles, which are Dart files that hold the bricks. Generate them with [mason_cli](https://pub.dev/packages/mason_cli):

```bash
dart pub global activate mason_cli
mason bundle bricks/counter -t dart -o lib/bundles
mason bundle bricks/counter_bloc -t dart -o lib/bundles
mason bundle bricks/counter_riverpod -t dart -o lib/bundles
```

Each command writes `lib/bundles/<brick>_bundle.dart` with a top-level variable such as `counterBundle`, which the module contributes as `BrickContribution(counterBundle)`. Bundle again after every change of a brick, and commit the bundles, since the published package needs them.
