Skip to main content

A module package

A module is a Dart package. This is the package of the counter feature that Write a feature module builds:

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 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.

The pubspec​

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.3.0

dev_dependencies:
lints: ^6.0.0
smf_bloc: ^0.3.0
smf_flutter_core: ^0.3.0
smf_get_it: ^0.3.0
smf_go_router: ^0.3.0
smf_pipeline: ^0.3.0
smf_riverpod: ^0.3.0
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:

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');


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


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:

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 for what a template may contain.

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

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:

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.