[{"data":1,"prerenderedAt":87},["ShallowReactive",2],{"tutorial-monorepo-flutter-con-melos-package-interni-e-confini-tra-moduli":3,"comments-tutorial-monorepo-flutter-con-melos-package-interni-e-confini-tra-moduli":86},{"id":4,"title":5,"slug":6,"excerpt":7,"intro":8,"cover_image":9,"cover_remote_url":10,"cover_credit":11,"video_url":15,"video":16,"difficulty":20,"estimated_minutes":21,"flutter_version":22,"status":23,"published_at":24,"meta_title":25,"meta_description":26,"category":27,"author":31,"steps":34},95,"Monorepo Flutter con Melos: package interni e confini tra moduli","monorepo-flutter-con-melos-package-interni-e-confini-tra-moduli","Organizza un'app Flutter di grandi dimensioni in un monorepo gestito con Melos: package interni, Dart pub workspaces, script condivisi, regole di dipendenza tra layer e versionamento automatico in CI.","Quando un'app Flutter cresce, il classico progetto monolitico con una cartella `lib\u002F` piena di feature diventa ingestibile: i tempi di analisi si allungano, i confini tra moduli evaporano e ogni refactoring rischia di toccare tutto.\n\nLa risposta più solida è il **monorepo**: un unico repository che contiene l'app (o più app) e una serie di **package interni** versionati e testabili in modo indipendente. [Melos](https:\u002F\u002Fmelos.invertase.dev) è lo strumento standard dell'ecosistema Dart\u002FFlutter per orchestrare questo scenario: collega i package tra loro, esegue comandi in parallelo su tutto il workspace, gestisce versioning e changelog basati sui conventional commit.\n\nIn questo tutorial avanzato costruiremo da zero un monorepo realistico con questa struttura:\n\n```\nacme\u002F\n├── melos.yaml\n├── pubspec.yaml          # workspace root\n├── apps\u002F\n│   └── mobile\u002F           # app Flutter\n├── packages\u002F\n│   ├── core\u002F             # modelli, networking, utility (Dart puro)\n│   ├── design_system\u002F    # widget e tema condivisi (Flutter)\n│   ├── feature_auth\u002F\n│   └── feature_catalog\u002F\n└── tool\u002F\n    └── check_boundaries.dart\n```\n\nVedremo anche come **rendere i confini architetturali eseguibili**: non basta scriverli in un documento, devono fallire in CI quando qualcuno li viola.\n\n> **Prerequisiti**: Flutter 3.27+ \u002F Dart 3.6+ (per i pub workspaces), dimestichezza con `pubspec.yaml`, CLI e GitHub Actions.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Ftutorials\u002Fd08c6308-c559-4d6c-87dc-5003209ec3eb.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1717501218456-c4789b65fc21?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3ODk0NDY3NDJ8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Google DeepMind","https:\u002F\u002Funsplash.com\u002F@googledeepmind","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fa-diagram-of-a-number-of-circles-and-a-number-of-dots-cLx-ilXp_pI","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=-kLoqXAla10",{"youtube_id":17,"duration_seconds":18,"published_at":19},"-kLoqXAla10",139,"2026-09-15T09:42:35+00:00","advanced",60,"3.27+","published","2026-09-15T04:32:22+00:00","Monorepo Flutter con Melos: package e confini","Guida avanzata: crea un monorepo Flutter con Melos, package interni, pub workspaces, regole di dipendenza tra moduli, script condivisi e versioning in CI.",{"id":28,"name":29,"slug":30},3,"Best practice","best-practice",{"id":32,"name":33},1,"Flutter Bot",[35,43,50,56,64,71,79],{"id":36,"position":32,"title":37,"body":38,"code_snippet":39,"code_language":40,"expected_result":41,"demo_url":42,"video_url":42},633,"Installare Melos e creare la radice del workspace","Melos si installa come pacchetto globale, ma va **sempre** dichiarato anche come `dev_dependency` alla radice del repository: in questo modo tutti gli sviluppatori e la CI usano la stessa versione.\n\n```bash\ndart pub global activate melos\n```\n\nCrea la cartella del monorepo e il `pubspec.yaml` di root. Da Dart 3.6 possiamo sfruttare i **pub workspaces**: un unico `pubspec.lock` e una sola cartella `.dart_tool\u002F` condivisa, con risoluzione delle dipendenze coerente tra tutti i package.\n\nPunti chiave del file di root:\n\n- `publish_to: none` — la radice non finisce mai su pub.dev;\n- la sezione `workspace:` elenca i path dei package membri;\n- `melos` come dev dependency, così `dart run melos ...` funziona ovunque.\n\nSe usi ancora Dart \u003C 3.6, salta la sezione `workspace:` e lascia che sia il `melos bootstrap` classico a creare i link tra i package: il resto del tutorial resta valido.","# pubspec.yaml (radice del monorepo)\nname: acme_workspace\npublish_to: none\n\nenvironment:\n  sdk: ^3.6.0\n\n# Dart pub workspaces: un solo lockfile per tutto il monorepo\nworkspace:\n  - apps\u002Fmobile\n  - packages\u002Fcore\n  - packages\u002Fdesign_system\n  - packages\u002Ffeature_auth\n  - packages\u002Ffeature_catalog\n\ndev_dependencies:\n  melos: ^6.3.0","yaml","Eseguendo `melos --version` ottieni la versione installata; la radice del repository contiene un `pubspec.yaml` che dichiara il workspace.",null,{"id":44,"position":45,"title":46,"body":47,"code_snippet":48,"code_language":40,"expected_result":49,"demo_url":42,"video_url":42},634,2,"Creare i package interni e collegarli tra loro","Ogni modulo è un package Dart o Flutter a sé stante. La regola pratica:\n\n- **Dart puro** (`dart create -t package`) per logica che non tocca il framework: modelli, parsing, client HTTP, use case. Si testa in millisecondi con `dart test`.\n- **Flutter package** (`flutter create -t package`) per widget, tema, feature UI.\n\n```bash\nmkdir -p apps packages\nflutter create --template=app --org com.acme apps\u002Fmobile\ndart create --template=package packages\u002Fcore\nflutter create --template=package packages\u002Fdesign_system\nflutter create --template=package packages\u002Ffeature_auth\nflutter create --template=package packages\u002Ffeature_catalog\n```\n\nIn ogni package membro aggiungi `resolution: workspace` e dichiara le dipendenze interne **senza `path:`**: con i pub workspaces (o con `melos bootstrap`) la risoluzione locale è automatica.\n\nUsa inoltre la convenzione **barrel file + cartella `src\u002F`**: tutto ciò che non è esportato dal file pubblico `lib\u002F\u003Cnome>.dart` è codice privato del modulo. Dart lo sa: importare `package:core\u002Fsrc\u002F...` da un altro package attiva il lint `implementation_imports`, che nel prossimo step trasformeremo in errore.","# packages\u002Ffeature_auth\u002Fpubspec.yaml\nname: feature_auth\ndescription: Feature di autenticazione dell'app Acme.\nversion: 0.1.0\npublish_to: none\nresolution: workspace\n\nenvironment:\n  sdk: ^3.6.0\n  flutter: \">=3.27.0\"\n\ndependencies:\n  flutter:\n    sdk: flutter\n  core: ^0.1.0            # risolto dal workspace\n  design_system: ^0.1.0\n\ndev_dependencies:\n  flutter_test:\n    sdk: flutter\n  very_good_analysis: ^6.0.0\n\n# ---------------------------------------------\n# packages\u002Ffeature_auth\u002Flib\u002Ffeature_auth.dart (barrel)\n# library feature_auth;\n#\n# export 'src\u002Fpresentation\u002Flogin_page.dart' show LoginPage;\n# export 'src\u002Fdomain\u002Fauth_repository.dart' show AuthRepository;\n# NIENTE altro: il resto resta interno al modulo.","`melos bootstrap` (o `dart pub get` alla radice) risolve tutte le dipendenze interne senza riferimenti `path:` e genera un unico `pubspec.lock`.",{"id":51,"position":28,"title":52,"body":53,"code_snippet":54,"code_language":40,"expected_result":55,"demo_url":42,"video_url":42},635,"Configurare melos.yaml: package, environment condiviso e IDE","Il file `melos.yaml` alla radice è il cervello del monorepo. Tre sezioni fanno la differenza in un progetto reale:\n\n1. **`packages`** — i glob che definiscono cosa fa parte del workspace (e `ignore` per escludere esempi o build).\n2. **`command.bootstrap.environment\u002Fdependencies\u002Fdev_dependencies`** — la *centralizzazione delle versioni*: Melos riscrive i `pubspec.yaml` dei package che dichiarano quelle dipendenze, allineandoli al valore centrale. Addio a 12 package con 4 versioni diverse di `intl`.\n3. **`command.bootstrap.hooks`** — per lanciare automaticamente la code generation dopo il bootstrap.\n\nDopo la configurazione lancia:\n\n```bash\nmelos bootstrap   # alias: melos bs\n```\n\nMelos risolve le dipendenze di tutto il workspace, genera i file di configurazione per IntelliJ\u002FVS Code e ti mostra il grafo dei package.\n\n> Da Melos 6 puoi anche spostare l'intera configurazione dentro il `pubspec.yaml` di root sotto la chiave `melos:`. Tenere un file separato resta più leggibile quando gli script diventano molti.","# melos.yaml\nname: acme\nrepository: https:\u002F\u002Fgithub.com\u002Facme\u002Facme_app\n\npackages:\n  - apps\u002F*\n  - packages\u002F*\n\nignore:\n  - \"**\u002Fexample\u002F**\"\n\ncommand:\n  bootstrap:\n    # Versioni centralizzate: propagate a tutti i package che le usano\n    environment:\n      sdk: ^3.6.0\n      flutter: \">=3.27.0\"\n    dependencies:\n      intl: ^0.19.0\n      dio: ^5.7.0\n    dev_dependencies:\n      very_good_analysis: ^6.0.0\n      build_runner: ^2.4.13\n      mocktail: ^1.0.4\n    hooks:\n      post: melos run generate --no-select\n\n  version:\n    linkToCommits: true\n    workspaceChangelog: true\n    branch: main","`melos bootstrap` termina con l'elenco dei package trovati (app + package interni) e le versioni delle dipendenze condivise risultano allineate in tutti i pubspec.",{"id":57,"position":58,"title":59,"body":60,"code_snippet":61,"code_language":62,"expected_result":63,"demo_url":42,"video_url":42},636,4,"Rendere eseguibili i confini tra moduli","Un monorepo senza regole degenera in un monolite distribuito. Definiamo quattro layer e le dipendenze **consentite**:\n\n| Layer | Può dipendere da |\n|---|---|\n| `core` | nessun package interno |\n| `design_system` | `core` |\n| `feature_*` | `core`, `design_system` |\n| `apps\u002F*` | tutti |\n\nLa regola più importante è quella implicita: **una feature non può dipendere da un'altra feature**. Se due feature devono parlarsi, il contratto (interfaccia, evento, rotta) sale in `core`.\n\nDue livelli di enforcement:\n\n1. **Analyzer**: in `analysis_options.yaml` alla radice promuovi `implementation_imports` a `error`, così nessuno può importare `package:core\u002Fsrc\u002F...` bypassando il barrel file.\n2. **Script di verifica del grafo**: uno script Dart che legge tutti i `pubspec.yaml` e fallisce quando una dipendenza viola la tabella. Va eseguito in CI.\n\nRicorda di aggiungere `yaml: ^3.1.2` alle dev dependencies della radice per far girare lo script.","\u002F\u002F tool\u002Fcheck_boundaries.dart\nimport 'dart:io';\n\nimport 'package:yaml\u002Fyaml.dart';\n\n\u002F\u002F\u002F Layer -> layer da cui e' lecito dipendere.\nconst _allowed = \u003CString, Set\u003CString>>{\n  'core': \u003CString>{},\n  'design_system': {'core'},\n  'feature': {'core', 'design_system'},\n  'app': {'core', 'design_system', 'feature'},\n};\n\nString _layerOf(String pkg) => switch (pkg) {\n      'core' => 'core',\n      'design_system' => 'design_system',\n      _ when pkg.startsWith('feature_') => 'feature',\n      _ => 'app',\n    };\n\nvoid main() {\n  final files = Directory.current\n      .listSync(recursive: true)\n      .whereType\u003CFile>()\n      .where((f) =>\n          f.uri.pathSegments.last == 'pubspec.yaml' &&\n          !f.path.contains('.dart_tool') &&\n          !f.path.contains('build\u002F'));\n\n  final internal = \u003CString, YamlMap>{};\n  for (final file in files) {\n    final spec = loadYaml(file.readAsStringSync());\n    if (spec is YamlMap && spec['name'] is String) {\n      internal[spec['name'] as String] = spec;\n    }\n  }\n\n  var violations = 0;\n  internal.forEach((name, spec) {\n    final layer = _layerOf(name);\n    final deps = \u003CString>[\n      ...?(spec['dependencies'] as YamlMap?)?.keys.whereType\u003CString>(),\n    ];\n    for (final dep in deps) {\n      if (dep == name || !internal.containsKey(dep)) continue;\n      final depLayer = _layerOf(dep);\n      if (!_allowed[layer]!.contains(depLayer)) {\n        stderr.writeln('X  $name ($layer) non puo dipendere da $dep ($depLayer)');\n        violations++;\n      }\n    }\n  });\n\n  if (violations > 0) {\n    stderr.writeln('\\n$violations violazione\u002Fi dei confini architetturali.');\n    exit(1);\n  }\n  stdout.writeln('OK: confini architetturali rispettati.');\n}","dart","`dart run tool\u002Fcheck_boundaries.dart` stampa \"OK\"; se aggiungi `feature_catalog` alle dipendenze di `feature_auth`, lo script esce con codice 1 e indica la violazione.",{"id":65,"position":66,"title":67,"body":68,"code_snippet":69,"code_language":40,"expected_result":70,"demo_url":42,"video_url":42},637,5,"Script Melos per analisi, test e coverage aggregata","Gli script sono il vero moltiplicatore di produttività di Melos: `melos exec` esegue un comando in ogni package, con filtri e parallelismo.\n\nFiltri che userai di continuo:\n\n- `--flutter` \u002F `--no-flutter`: separa i package Flutter da quelli Dart puro (fondamentale: `flutter test` fallisce su un package Dart puro e viceversa);\n- `--dir-exists=test`: salta i package senza test;\n- `--diff=origin\u002Fmain`: esegue **solo sui package modificati** rispetto a un branch, dimezzando i tempi di CI;\n- `-c N`: grado di concorrenza (`-c 1` per output leggibile).\n\nPer la coverage, ogni package produce il proprio `coverage\u002Flcov.info`: li uniamo in un unico report. Assicurati di avere `lcov` installato (`brew install lcov` \u002F `apt-get install lcov`).\n\nUso quotidiano:\n\n```bash\nmelos run analyze\nmelos run test\nmelos run test:changed   # solo i package toccati dalla tua branch\nmelos run ci\n```","# melos.yaml (sezione scripts)\nscripts:\n  format:\n    run: dart format --set-exit-if-changed .\n    description: Verifica la formattazione di tutto il monorepo.\n\n  analyze:\n    run: melos exec -c 1 -- dart analyze . --fatal-infos\n    description: Analisi statica su ogni package.\n\n  boundaries:\n    run: dart run tool\u002Fcheck_boundaries.dart\n    description: Verifica le regole di dipendenza tra i layer.\n\n  generate:\n    run: melos exec -c 1 --depends-on=build_runner -- \\\n      dart run build_runner build --delete-conflicting-outputs\n\n  test:dart:\n    run: melos exec -c 4 --no-flutter --dir-exists=test -- dart test\n\n  test:flutter:\n    run: melos exec -c 4 --flutter --dir-exists=test -- flutter test\n\n  test:\n    run: melos run test:dart --no-select && melos run test:flutter --no-select\n\n  test:changed:\n    run: melos exec -c 4 --diff=origin\u002Fmain --dir-exists=test -- flutter test\n\n  coverage:\n    run: |\n      melos exec -c 4 --flutter --dir-exists=test -- \\\n        flutter test --coverage && \\\n      mkdir -p coverage && \\\n      lcov $(find . -path '*\u002Fcoverage\u002Flcov.info' -printf '-a %p ') \\\n        -o coverage\u002Flcov.info && \\\n      genhtml coverage\u002Flcov.info -o coverage\u002Fhtml\n\n  ci:\n    run: melos run format && melos run analyze && melos run boundaries && melos run test","`melos run ci` esegue in sequenza formattazione, analisi, controllo dei confini e test di tutti i package, fallendo al primo errore.",{"id":72,"position":73,"title":74,"body":75,"code_snippet":76,"code_language":77,"expected_result":78,"demo_url":42,"video_url":42},638,6,"Versionamento automatico e changelog con conventional commits","Melos calcola le nuove versioni a partire dai **conventional commit** e dal grafo delle dipendenze: se `core` passa da `0.1.0` a `0.2.0`, tutti i package che dipendono da `core` ricevono automaticamente un bump di patch e una riga nel changelog.\n\nConvenzione dei messaggi (lo scope è opzionale ma molto utile, di solito è il nome del package):\n\n- `feat(feature_auth): aggiunge login biometrico` → minor\n- `fix(core): gestisce il timeout della richiesta` → patch\n- `refactor(design_system)!: rinomina AppButton` (o footer `BREAKING CHANGE:`) → major\n- `chore: ...`, `docs: ...` → nessun bump\n\nComandi:\n\n```bash\n# Anteprima: mostra cosa verrebbe versionato, senza scrivere nulla\nmelos version --no-git-tag-version --no-changelog --diff=origin\u002Fmain --dry-run\n\n# Versionamento reale: aggiorna pubspec, CHANGELOG, crea commit e tag\nmelos version --yes\n\n# Pubblicazione dei soli package non privati (publish_to: none esclusi)\nmelos publish --dry-run\n```\n\nCon `workspaceChangelog: true` Melos genera anche un `CHANGELOG.md` aggregato alla radice, perfetto per le release note dell'app. Se alcuni package restano privati (tipico delle feature interne) `melos publish` li salta automaticamente grazie a `publish_to: none`.\n\nPer casi particolari puoi forzare una versione: `melos version core 0.3.0 --yes`.","# Esempio di output di `melos version --dry-run`\n#\n# Melos will version the following packages:\n#\n#  core: 0.1.0 -> 0.2.0\n#    feat(core): nuovo ApiClient con retry\n#  design_system: 0.1.0 -> 0.1.1\n#    (dependency update: core)\n#  feature_auth: 0.1.0 -> 0.1.1\n#    (dependency update: core, design_system)\n#\n# git commit -m \"chore(release): publish packages\"\n# git tag core-v0.2.0 design_system-v0.1.1 feature_auth-v0.1.1","bash","Dopo `melos version --yes` trovi i `pubspec.yaml` aggiornati, un `CHANGELOG.md` per package, uno aggregato alla radice, un commit di release e i tag Git per ogni package versionato.",{"id":80,"position":81,"title":82,"body":83,"code_snippet":84,"code_language":40,"expected_result":85,"demo_url":42,"video_url":42},639,7,"Integrare il monorepo in CI con GitHub Actions","L'ultimo tassello è la pipeline. Tre accorgimenti specifici per un monorepo:\n\n1. **`fetch-depth: 0`** nel checkout: senza la storia completa, `--diff=origin\u002Fmain` e il calcolo delle versioni non funzionano.\n2. **Cache del pub cache**: con i pub workspaces c'è un solo `pubspec.lock`, quindi la chiave di cache è banale ed efficacissima.\n3. **Job di release separato**, attivo solo su `main`, che esegue `melos version` con un bot Git.\n\nProblemi frequenti e soluzioni:\n\n- *«Melos non vede i miei package»* → controlla i glob in `packages:` e che ogni package abbia `name:` nel pubspec; `melos list -l` mostra cosa è stato rilevato.\n- *Analyzer lento o incoerente nell'IDE* → rilancia `melos bootstrap` e riapri il progetto: i file `.idea\u002F`\u002F`.vscode\u002F` con i moduli vengono rigenerati.\n- *Conflitti di versione tra package* → usa `command.bootstrap.dependencies` per centralizzare, non `dependency_overrides` sparsi.\n- *Build dell'app* → la cartella su cui lanciare `flutter build` resta `apps\u002Fmobile`; nulla cambia rispetto a un progetto singolo.\n\nDa qui in poi il monorepo scala: aggiungere una feature significa creare un package, dichiararlo nel workspace e lasciare che gli script e il controllo dei confini facciano il resto.","# .github\u002Fworkflows\u002Fci.yml\nname: CI\n\non:\n  pull_request:\n    branches: [main]\n  push:\n    branches: [main]\n\njobs:\n  verify:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n        with:\n          fetch-depth: 0\n\n      - uses: subosito\u002Fflutter-action@v2\n        with:\n          flutter-version: '3.27.x'\n          channel: stable\n          cache: true\n\n      - name: Cache pub\n        uses: actions\u002Fcache@v4\n        with:\n          path: ~\u002F.pub-cache\n          key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }}\n\n      - name: Install Melos\n        run: dart pub global activate melos\n\n      - name: Bootstrap\n        run: melos bootstrap\n\n      - name: Quality gate\n        run: melos run ci\n\n      - name: Build app\n        run: flutter build apk --debug\n        working-directory: apps\u002Fmobile\n\n  release:\n    needs: verify\n    if: github.ref == 'refs\u002Fheads\u002Fmain'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n        with:\n          fetch-depth: 0\n          token: ${{ secrets.RELEASE_TOKEN }}\n      - uses: subosito\u002Fflutter-action@v2\n        with: { flutter-version: '3.27.x', channel: stable }\n      - run: dart pub global activate melos && melos bootstrap\n      - name: Version packages\n        run: |\n          git config user.name \"acme-bot\"\n          git config user.email \"bot@acme.dev\"\n          melos version --yes --no-private\n          git push --follow-tags","Ogni pull request esegue formattazione, analisi, controllo dei confini, test di tutti i package e build dell'app; il merge su main produce automaticamente versioni, changelog e tag.",[],1792316931154]