[{"data":1,"prerenderedAt":27},["ShallowReactive",2],{"articolo-mason-in-flutter-generare-feature-e-boilerplate-con-i-brick":3,"comments-article-mason-in-flutter-generare-feature-e-boilerplate-con-i-brick":26},{"id":4,"title":5,"slug":6,"excerpt":7,"body":8,"cover_image":9,"cover_remote_url":10,"cover_credit":11,"video_url":15,"status":16,"published_at":17,"meta_title":18,"meta_description":19,"category":20,"author":24},127,"Mason in Flutter: generare feature e boilerplate con i brick","mason-in-flutter-generare-feature-e-boilerplate-con-i-brick","Ogni nuova feature Flutter richiede sempre le stesse cartelle, gli stessi file e lo stesso boilerplate. Con Mason puoi trasformare le convenzioni del tuo team in template riutilizzabili: brick, variabili, hook in Dart e integrazione in CI.","## Il problema del boilerplate ripetuto\n\nIn qualsiasi progetto Flutter maturo la creazione di una nuova feature segue uno schema prevedibile: una cartella `data` con repository e data source, una `domain` con i modelli, una `presentation` con la pagina, il controller dello stato e i widget. A questo si aggiungono i file di barrel export, i test scheletro, magari la registrazione della rotta in `go_router`.\n\nIl risultato è che ogni sviluppatore passa 20 minuti a copiare e incollare da una feature esistente, rinominando classi a mano. È un lavoro noioso, soggetto a errori e — soprattutto — è il modo più rapido per far divergere le convenzioni interne al team.\n\n[Mason](https:\u002F\u002Fpub.dev\u002Fpackages\u002Fmason_cli) risolve esattamente questo problema. È un tool CLI (nato da Felix Angelov, lo stesso autore di `bloc`) che permette di definire template di codice chiamati **brick**, parametrizzarli con variabili e generarli con un comando. Non è un code generator a runtime come `build_runner`: Mason lavora *una volta sola*, al momento della creazione dei file, e il codice prodotto è normalissimo codice Dart che poi vive di vita propria.\n\n## Installazione e primi passi\n\nMason si installa come eseguibile globale di Dart:\n\n```bash\ndart pub global activate mason_cli\nmason --version\n```\n\nAssicurati che `~\u002F.pub-cache\u002Fbin` sia nel `PATH`. Dentro al progetto Flutter si inizializza l'ambiente:\n\n```bash\nmason init\n```\n\nIl comando crea un file `mason.yaml` nella root, che funziona come il `pubspec.yaml` dei brick: elenca i template disponibili per quel progetto.\n\n```yaml\nbricks:\n  # brick pubblicato su brickhub.dev\n  cubit:\n    git:\n      url: https:\u002F\u002Fgithub.com\u002Ffelangel\u002Fmason.git\n      path: bricks\u002Fcubit\n  # brick locale, versionato nel repository\n  feature:\n    path: bricks\u002Ffeature\n```\n\nDopo ogni modifica si scaricano le dipendenze e si verifica la lista:\n\n```bash\nmason get\nmason list\n```\n\nPer generare codice da un brick si usa `mason make`:\n\n```bash\nmason make feature --feature_name checkout -o lib\u002Ffeatures\n```\n\nSe non passi le variabili da riga di comando, Mason te le chiede in modo interattivo. Con `-o` indichi la directory di output e con `--on-conflict` decidi cosa fare se un file esiste già (`prompt`, `overwrite`, `append`, `skip`).\n\n## Creare il proprio brick\n\nIl vero valore arriva quando i brick rispecchiano l'architettura del tuo team. Si parte da:\n\n```bash\nmason new feature --desc \"Genera una feature completa a tre livelli\"\n```\n\nViene creata la struttura:\n\n```\nbricks\u002Ffeature\u002F\n├── brick.yaml\n├── README.md\n└── __brick__\u002F\n    └── HELLO.md\n```\n\nLa cartella `__brick__` contiene i file template: tutto ciò che sta lì dentro viene copiato nell'output, con nomi e contenuti processati tramite **mustache**.\n\n### Dichiarare le variabili\n\nIl file `brick.yaml` descrive il brick e soprattutto le sue variabili:\n\n```yaml\nname: feature\ndescription: Genera una feature a tre livelli (data\u002Fdomain\u002Fpresentation)\nversion: 0.1.0+1\n\nenvironment:\n  mason: \">=0.1.0-dev \u003C0.1.0\"\n\nvars:\n  feature_name:\n    type: string\n    description: Nome della feature in snake_case\n    prompt: Come si chiama la feature?\n    default: home\n  state_management:\n    type: enum\n    values:\n      - riverpod\n      - bloc\n      - none\n    default: riverpod\n    prompt: Quale soluzione di state management?\n  with_repository:\n    type: boolean\n    default: true\n    prompt: Vuoi generare anche il repository?\n  endpoints:\n    type: array\n    values:\n      - list\n      - detail\n      - create\n    prompt: Quali endpoint ti servono?\n```\n\nI tipi supportati sono `string`, `number`, `boolean`, `enum`, `array` e `list`. Questo significa che il brick può diventare una vera piccola procedura guidata.\n\n### Template e lambda\n\nNei nomi dei file e nel loro contenuto puoi usare le variabili e le **lambda di case conversion**, che sono la caratteristica più comoda di Mason. Le più usate sono `snakeCase`, `camelCase`, `pascalCase`, `paramCase`, `constantCase`, `titleCase`, `pathCase`, `sentenceCase`.\n\nUn file chiamato `__brick__\u002F{{feature_name.snakeCase()}}\u002Fpresentation\u002F{{feature_name.snakeCase()}}_page.dart` genera, per `feature_name = checkout`, il file `checkout\u002Fpresentation\u002Fcheckout_page.dart`. Il contenuto:\n\n```dart\nimport 'package:flutter\u002Fmaterial.dart';\n{{#with_repository}}import 'package:flutter_riverpod\u002Fflutter_riverpod.dart';{{\u002Fwith_repository}}\n\n\u002F\u002F\u002F Pagina principale della feature {{feature_name.titleCase()}}.\nclass {{feature_name.pascalCase()}}Page extends StatelessWidget {\n  const {{feature_name.pascalCase()}}Page({super.key});\n\n  static const routeName = '\u002F{{feature_name.paramCase()}}';\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('{{feature_name.titleCase()}}')),\n      body: const Center(child: Text('TODO: {{feature_name.titleCase()}}')),\n    );\n  }\n}\n```\n\nLe sezioni mustache `{{#variabile}} ... {{\u002Fvariabile}}` funzionano come condizionali per i booleani e come cicli per gli array; `{{^variabile}} ... {{\u002Fvariabile}}` è il ramo negativo. Esempio su un repository:\n\n```dart\nabstract interface class {{feature_name.pascalCase()}}Repository {\n{{#endpoints}}\n  \u002F\u002F operazione: {{.}}\n{{\u002Fendpoints}}\n{{#with_repository}}\n  Future\u003CList\u003C{{feature_name.pascalCase()}}>> fetchAll();\n{{\u002Fwith_repository}}\n}\n```\n\nUn dettaglio importante: se un file del template genera un nome vuoto (per esempio perché la variabile condizionale è falsa), quel file **non** viene creato. Si può quindi condizionare l'intera esistenza di un file inserendo nel nome una sezione mustache, ad esempio `{{#with_repository}}{{feature_name.snakeCase()}}_repository.dart{{\u002Fwith_repository}}`.\n\n### I partial\n\nPer evitare duplicazione tra template, Mason supporta i **partial**: file il cui nome inizia con `~` (ad esempio `__brick__\u002F~header.dart`) che non vengono generati ma possono essere inclusi altrove con `{{> header.dart }}`. Perfetti per header di licenza, import comuni o blocchi `\u002F\u002F GENERATED` ripetuti.\n\n## Gli hook: logica Dart prima e dopo la generazione\n\nI brick possono eseguire codice Dart arbitrario grazie agli hook. Si crea la cartella `hooks` con un suo `pubspec.yaml` che dipende dal package `mason`:\n\n```yaml\nname: feature_hooks\nenvironment:\n  sdk: \">=3.0.0 \u003C4.0.0\"\ndependencies:\n  mason: ^0.1.0-dev\n```\n\nIl file `hooks\u002Fpre_gen.dart` viene eseguito prima della generazione e può **aggiungere o modificare variabili**:\n\n```dart\nimport 'package:mason\u002Fmason.dart';\n\nvoid run(HookContext context) {\n  final name = context.vars['feature_name'] as String;\n\n  context.vars = {\n    ...context.vars,\n    'generated_at': DateTime.now().toIso8601String(),\n    'is_riverpod': context.vars['state_management'] == 'riverpod',\n  };\n\n  context.logger.info('Genero la feature \"$name\"...');\n}\n```\n\nNota `is_riverpod`: poiché mustache non sa confrontare stringhe, il trucco standard è precalcolare dei booleani nel pre-gen e usarli come sezioni nei template.\n\nIl file `hooks\u002Fpost_gen.dart` è il posto giusto per formattare, installare dipendenze o lanciare `build_runner`:\n\n```dart\nimport 'dart:io';\nimport 'package:mason\u002Fmason.dart';\n\nFuture\u003Cvoid> run(HookContext context) async {\n  final progress = context.logger.progress('Formattazione e dipendenze');\n\n  await Process.run('dart', ['format', '.'], runInShell: true);\n\n  if (context.vars['is_riverpod'] == true) {\n    await Process.run(\n      'dart',\n      ['run', 'build_runner', 'build', '--delete-conflicting-outputs'],\n      runInShell: true,\n    );\n  }\n\n  progress.complete('Feature pronta!');\n  context.logger.success(\n    'Ricordati di registrare la rotta in app_router.dart',\n  );\n}\n```\n\nGli hook hanno accesso a `context.logger` (con `info`, `warn`, `err`, `success`, `progress`, `prompt`, `confirm`) e possono quindi diventare piccoli wizard. Attenzione però a non abusarne: un hook che modifica file esistenti del progetto è potente ma fragile, perché dipende dalla forma esatta di quei file.\n\n## Distribuire i brick al team\n\nCi sono tre strategie, in ordine di accoppiamento crescente con il repository:\n\n1. **Brick locali** (`path: bricks\u002Ffeature`): vivono nel monorepo, si versionano insieme al codice e si evolvono con l'architettura. È la scelta migliore per i brick specifici di un singolo prodotto.\n2. **Brick su Git**: un repository `company_bricks` condiviso tra più progetti, referenziato con `git: { url: ..., path: ..., ref: v1.2.0 }`. Usa sempre un `ref` fissato a un tag per evitare che una modifica upstream cambi il codice generato dalla sera alla mattina.\n3. **Brick su BrickHub**: il registry pubblico (`brickhub.dev`). Si pubblica con `mason login` e `mason publish`, e si consuma con una semplice versione (`feature: ^0.1.0`). Ottimo per brick open source o per un design system condiviso.\n\nEsiste anche `mason bundle`, che impacchetta un brick in un singolo file (Dart o universale) da includere dentro una CLI personalizzata: è il meccanismo con cui tool come `very_good_cli` incorporano i propri template.\n\n```bash\nmason bundle bricks\u002Ffeature -t dart -o lib\u002Fsrc\u002Fbundles\u002F\n```\n\n## Integrazione nel flusso di lavoro\n\nUn brick è codice a tutti gli effetti e come tale va mantenuto. Alcune pratiche che funzionano bene:\n\n- **Testa la generazione in CI.** Un job che esegue `mason make feature --feature_name sample -o build\u002Ftmp` seguito da `dart analyze` sul risultato intercetta subito template che generano codice non compilabile dopo un aggiornamento di Flutter o di un package.\n- **Usa un file di configurazione** per gli scenari ricorrenti: `mason make feature -c feature_config.json` legge le variabili da JSON, utile negli script e nei test.\n- **Mantieni i brick piccoli e componibili.** Meglio tre brick (`feature`, `repository`, `widget`) che un unico mega-template con quindici variabili: la combinatoria esplode e diventa impossibile da mantenere.\n- **Documenta ogni brick nel suo README**, includendo un esempio dell'output generato. Mason inserisce automaticamente l'output di esempio se usi `mason make` con `--output-dir` nel README.\n- **Non generare ciò che può essere una classe base.** Se vedi che lo stesso codice identico viene generato in ogni feature senza variazioni, probabilmente appartiene a un package condiviso, non a un template. Mason serve a generare la *struttura variabile*, non a duplicare logica.\n\n## Quando Mason non serve\n\nPer completezza: Mason non sostituisce `build_runner`. Se hai bisogno di codice che resti sincronizzato con una sorgente di verità (modelli JSON, `freezed`, `riverpod_generator`, `pigeon`), ti serve la generazione a build time. Mason copre il caso opposto, cioè lo *scaffolding iniziale* di codice che poi verrà modificato a mano.\n\nAllo stesso modo, in un progetto piccolo con due o tre schermate l'investimento nella scrittura dei brick non si ripaga. Il punto di pareggio arriva quando il team ha più di due o tre sviluppatori, oppure quando il numero di feature cresce a doppia cifra: lì la coerenza strutturale vale molto più del tempo risparmiato.\n\n## Conclusioni\n\nMason trasforma le convenzioni implicite del team in template eseguibili. Il vantaggio non è solo la velocità: è che l'architettura smette di essere una pagina di wiki che nessuno legge e diventa qualcosa che il tool applica automaticamente a ogni nuova feature. Inizia con un brick semplice — anche solo uno per i widget riutilizzabili — mettilo nel repository, e lascia che cresca insieme al progetto.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Farticles\u002F590b3447-b728-429b-ba04-9380dfb7a241.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1758647672034-2342c44b76fd?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3OTE5NTA0OTR8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Marija Zaric","https:\u002F\u002Funsplash.com\u002F@simplicity","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fdamaged-brick-wall-with-peeling-plaster-HJ_GmXkYnc4",null,"published","2026-10-14T04:01:35+00:00","Mason in Flutter: brick e scaffolding per generare codice","Guida pratica a Mason per Flutter: creare brick, variabili e lambda mustache, hook in Dart, distribuzione su Git e BrickHub e integrazione in CI.",{"id":21,"name":22,"slug":23},1,"Guide","guide",{"id":21,"name":25},"Flutter Bot",[],1792316901667]