diff --git a/i18n/en/docusaurus-plugin-content-docs/current.json b/i18n/en/docusaurus-plugin-content-docs/current.json index 9af1eb6531..e7aa54077f 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current.json +++ b/i18n/en/docusaurus-plugin-content-docs/current.json @@ -4,7 +4,7 @@ "description": "The label for version current" }, "sidebar.getstartedSidebar.category.Tutorials": { - "message": "Tutorials", + "message": "Tutoriels", "description": "The label for category Tutorials in sidebar getstartedSidebar" }, "sidebar.aboutSidebar.category.Alternatives": { @@ -12,11 +12,11 @@ "description": "The label for category Alternatives in sidebar aboutSidebar" }, "sidebar.aboutSidebar.category.Promote": { - "message": "Promote", + "message": "Promouvoir", "description": "The label for category Promote in sidebar aboutSidebar" }, "sidebar.guidesSidebar.category.Examples": { - "message": "Examples", + "message": "Exemples", "description": "The label for category Examples in sidebar guidesSidebar" }, "sidebar.guidesSidebar.category.Migration": { @@ -24,15 +24,15 @@ "description": "The label for category Migration in sidebar guidesSidebar" }, "sidebar.guidesSidebar.category.Tech": { - "message": "Tech", + "message": "Technologies", "description": "The label for category Tech in sidebar guidesSidebar" }, "sidebar.conceptsSidebar.category.Issues": { - "message": "Issues", + "message": "Problèmes", "description": "The label for category Issues in sidebar conceptsSidebar" }, "sidebar.referenceSidebar.category.Layer": { - "message": "Layer", + "message": "Couche", "description": "The label for category Layer in sidebar referenceSidebar" } } diff --git a/i18n/fr/code.json b/i18n/fr/code.json new file mode 100644 index 0000000000..d7dfa4c17c --- /dev/null +++ b/i18n/fr/code.json @@ -0,0 +1,234 @@ +{ + "pages.home.features.title": { + "message": "Fonctionnalités", + "description": "Features" + }, + "pages.home.features.logic.title": { + "message": "Logique métier explicite", + "description": "Feature title" + }, + "pages.home.features.logic.description": { + "message": "Architecture facilement découvrable grâce aux domaines", + "description": "Feature description" + }, + "pages.home.features.adaptability.title": { + "message": "Adaptabilité", + "description": "Feature title" + }, + "pages.home.features.adaptability.description": { + "message": "Les composants d'architecture peuvent être remplacés et ajoutés pour de nouveaux besoins", + "description": "Feature description" + }, + "pages.home.features.debt.title": { + "message": "Dette technique & refactorisation", + "description": "Feature title" + }, + "pages.home.features.debt.description": { + "message": "Chaque module peut être modifié ou réécrit indépendamment sans effets de bord", + "description": "Feature description" + }, + "pages.home.features.shared.title": { + "message": "Réutilisation de code explicite", + "description": "Feature title" + }, + "pages.home.features.shared.description": { + "message": "Un équilibre est maintenu entre DRY et la personnalisation locale", + "description": "Feature description" + }, + "pages.home.concepts.title": { + "message": "Concepts", + "description": "Concepts" + }, + "pages.home.concepts.public.title": { + "message": "API publique", + "description": "Concept title" + }, + "pages.home.concepts.public.description": { + "message": "Chaque module doit déclarer son API publique au niveau supérieur", + "description": "Concept description" + }, + "pages.home.concepts.isolation.title": { + "message": "Isolation", + "description": "Concept title" + }, + "pages.home.concepts.isolation.description": { + "message": "Le module ne doit pas dépendre directement des autres modules du même niveau ou de niveaux supérieurs", + "description": "Concept description" + }, + "pages.home.concepts.needs.title": { + "message": "Axé sur les besoins", + "description": "Concept title" + }, + "pages.home.concepts.needs.description": { + "message": "Orientation vers les besoins métiers et utilisateurs", + "description": "Concept description" + }, + "pages.home.scheme.title": { + "message": "Schéma", + "description": "Scheme" + }, + "pages.home.companies.using": { + "message": "Entreprises utilisant FSD", + "description": "Companies using FSD" + }, + "pages.home.companies.add_me": { + "message": "Votre entreprise utilise FSD ?", + "description": "FSD is used in your company?" + }, + "pages.home.companies.tell_us": { + "message": "Faites-le nous savoir", + "description": "Tell us" + }, + "pages.examples.title": { + "message": "Exemples", + "description": "Page title" + }, + "pages.examples.subtitle": { + "message": "Liste de sites créés avec Feature-Sliced Design", + "description": "Page subtitle" + }, + "pages.examples.add_me.title": { + "message": "Ajouter un exemple", + "description": "Request to add example" + }, + "pages.examples.repo.title": { + "message": "Dépôt", + "description": "Examples repository label" + }, + "pages.examples.versions": { + "message": "Voir aussi la liste des versions", + "description": "Versions reminder" + }, + "pages.versions.title": { + "message": "Versions de Feature-Sliced Design", + "description": "Feature-Sliced Design versions" + }, + "pages.versions.current": { + "message": "La documentation de la version actuelle est disponible ici", + "description": "Description for current version" + }, + "pages.versions.legacy": { + "message": "La documentation des anciennes versions de {of} est disponible ici ", + "description": "Description for legacy version" + }, + "pages.nav.title": { + "message": "🧭 Navigation", + "description": "NavPage title" + }, + "pages.nav.legacy.title": { + "message": "Itinéraires hérités", + "description": "NavPage section=legacy title" + }, + "pages.nav.legacy.details": { + "message": "Suite à la restructuration de la documentation, certains itinéraires ont changé. Vous trouverez ci-dessous la page que vous recherchiez.", + "description": "NavPage section=legacy details" + }, + "pages.nav.legacy.subdetails": { + "message": "Des redirections sont disponibles pour les anciens liens pour assurer la compatibilité", + "description": "NavPage section=legacy subdetails" + }, + "features.feedback-badge.label": { + "message": "Donnez votre avis sur la documentation 🤙", + "description": "Feedback share button label" + }, + "features.feedback-badge.url": { + "message": "https://forms.gle/nsYua6bMMG5iBB3v7", + "description": "Feedback share form url" + }, + "features.feedback-doc.button-text": { + "message": "Retour d'expérience", + "description": "The text on a floating button to leave feedback about the docs" + }, + "features.feedback-doc.email-placeholder": { + "message": "Votre email (facultatif)", + "description": "The placeholder for email input" + }, + "features.feedback-doc.error-message": { + "message": "Un problème est survenu. Veuillez réessayer plus tard.", + "description": "The error message displayed when feedback form submission fails" + }, + "features.feedback-doc.modal-title-error-403": { + "message": "L'URL de la demande ne correspond pas à celle définie dans PushFeedback pour ce projet.", + "description": "The title of the modal displayed when feedback form submission fails with 403 error" + }, + "features.feedback-doc.modal-title-error-404": { + "message": "Nous n'avons pas trouvé l'identifiant de projet fourni dans PushFeedback.", + "description": "The title of the modal displayed when feedback form submission fails with 404 error" + }, + "features.feedback-doc.message-placeholder": { + "message": "Commentaires", + "description": "The placeholder for message input" + }, + "features.feedback-doc.modal-title": { + "message": "Partagez votre avis", + "description": "The title of the modal to leave feedback about the docs" + }, + "features.feedback-doc.modal-title-error": { + "message": "Oups !", + "description": "The title of the modal displayed when feedback form submission fails" + }, + "features.feedback-doc.modal-title-success": { + "message": "Merci pour votre retour !", + "description": "The title of the modal displayed when feedback form submission succeeds" + }, + "features.feedback-doc.screenshot-button-text": { + "message": "Prendre une capture d'écran", + "description": "The text on a button to take a screenshot" + }, + "features.feedback-doc.screenshot-topbar-text": { + "message": "SÉLECTIONNEZ UN ÉLÉMENT SUR LA PAGE", + "description": "The text displayed in the top bar when selecting an element to take a screenshot" + }, + "features.feedback-doc.send-button-text": { + "message": "Envoyer", + "description": "The text on a button to send feedback" + }, + "features.feedback-doc.rating-placeholder": { + "message": "Cette page vous a-t-elle été utile ?", + "description": "The placeholder for rating input" + }, + "features.feedback-doc.rating-stars-placeholder": { + "message": "Comment évalueriez-vous cette page", + "description": "The placeholder for rating stars input" + }, + "features.hero.tagline": { + "message": "Méthodologie d'architecture pour les projets frontend", + "description": "Architectural methodology for frontend projects" + }, + "features.hero.get_started": { + "message": "Commencer", + "description": "Get Started" + }, + "features.hero.examples": { + "message": "Exemples", + "description": "Examples" + }, + "features.hero.previous": { + "message": "Version précédente", + "description": "Previous version" + }, + "shared.wip.title": { + "message": "L'article est en cours de rédaction", + "description": "Admonition title" + }, + "shared.wip.subtitle": { + "message": "Pour accélérer la publication de l'article, vous pouvez :", + "description": "Admonition subtitle" + }, + "shared.wip.var.feedback.base": { + "message": "📢 Partagez votre avis ", + "description": "Variant for contribute (base)" + }, + "shared.wip.var.feedback.link": { + "message": "sur l'article (commentaire/réaction emoji)", + "description": "Variant for contribute (link)" + }, + "shared.wip.var.material.base": { + "message": "💬 Collectez les ", + "description": "Variant for contribute (base)" + }, + "shared.wip.var.material.link": { + "message": "sources de l'article", + "description": "Variant for contribute (link)" + } +} \ No newline at end of file diff --git a/i18n/fr/docusaurus-plugin-content-docs/community/index.mdx b/i18n/fr/docusaurus-plugin-content-docs/community/index.mdx new file mode 100644 index 0000000000..944335e3db --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/community/index.mdx @@ -0,0 +1,40 @@ +--- +hide_table_of_contents: true +--- + +# 💫 Communauté {#community} + +

+Ressources communautaires, matériel supplémentaire +

+ +## Principal {#main} + +import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" +import { StarOutlined, SearchOutlined, TeamOutlined, VerifiedOutlined } from "@ant-design/icons"; + + + + + diff --git a/i18n/fr/docusaurus-plugin-content-docs/community/team.mdx b/i18n/fr/docusaurus-plugin-content-docs/community/team.mdx new file mode 100644 index 0000000000..c2ae3f489d --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/community/team.mdx @@ -0,0 +1,18 @@ +--- +sidebar_class_name: sidebar-item--wip +sidebar_position: 2 +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# Équipe {#team} + + + +## Équipe principale {#Core-team} + +### Champions {#champions} + +## Contributeurs {#contributors} + +## Entreprises {#companies} diff --git a/i18n/fr/docusaurus-plugin-content-docs/current.json b/i18n/fr/docusaurus-plugin-content-docs/current.json new file mode 100644 index 0000000000..9af1eb6531 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current.json @@ -0,0 +1,38 @@ +{ + "version.label": { + "message": "v2.0.0 🍰", + "description": "The label for version current" + }, + "sidebar.getstartedSidebar.category.Tutorials": { + "message": "Tutorials", + "description": "The label for category Tutorials in sidebar getstartedSidebar" + }, + "sidebar.aboutSidebar.category.Alternatives": { + "message": "Alternatives", + "description": "The label for category Alternatives in sidebar aboutSidebar" + }, + "sidebar.aboutSidebar.category.Promote": { + "message": "Promote", + "description": "The label for category Promote in sidebar aboutSidebar" + }, + "sidebar.guidesSidebar.category.Examples": { + "message": "Examples", + "description": "The label for category Examples in sidebar guidesSidebar" + }, + "sidebar.guidesSidebar.category.Migration": { + "message": "Migration", + "description": "The label for category Migration in sidebar guidesSidebar" + }, + "sidebar.guidesSidebar.category.Tech": { + "message": "Tech", + "description": "The label for category Tech in sidebar guidesSidebar" + }, + "sidebar.conceptsSidebar.category.Issues": { + "message": "Issues", + "description": "The label for category Issues in sidebar conceptsSidebar" + }, + "sidebar.referenceSidebar.category.Layer": { + "message": "Layer", + "description": "The label for category Layer in sidebar referenceSidebar" + } +} diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/about/alternatives.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/about/alternatives.mdx new file mode 100644 index 0000000000..614359722c --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/about/alternatives.mdx @@ -0,0 +1,153 @@ +--- +sidebar_class_name: sidebar-item--wip +sidebar_position: 3 +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# Alternatives + + + +Historique des approches architecturales + +## Big Ball of Mud + + + +> Qu'est-ce que c'est ? Pourquoi est-ce si courant ? Quand cela commence-t-il à poser des problèmes ? Que faire et comment FSD aide-t-il à cela ? + +- [(Article) Oleg Isonen - Derniers mots sur l'architecture UI avant qu'une IA ne prenne le relais](https://oleg008.medium.com/last-words-on-ui-architecture-before-an-ai-takes-over-468c78f18f0d) +- [(Rapport) Julia Nikolaeva, iSpring - Big Ball of Mud et autres problèmes du monolithe, comment nous les avons gérés](http://youtu.be/gna4Ynz1YNI) +- [(Article) DD - Big Ball of Mud](https://thedomaindrivendesign.io/big-ball-of-mud/) + + +## Composants intelligents et stupides + + + +> À propos de l'approche ; De son applicabilité dans le frontend ; Position de la méthodologie + +À propos de l'obsolescence, d'une nouvelle vision selon la méthodologie + +Pourquoi l'approche des composants-conteneurs est-elle nuisible ? + +- [(Article) Den Abramov - Présentation et composants Conteneur (TLDR : obsolète)](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) + + +## Principes de conception + + + +> De quoi parle-t-on ; Position de FSD + +SOLID, GRASP, KISS, YAGNI, ... - et pourquoi ils ne fonctionnent pas bien ensemble en pratique + +Et comment cela agrège ces pratiques + +- [(Talk) Ilya Azin - Feature-Sliced Design (extrait sur les principes de conception)](https://youtu.be/SnzPAr_FJ7w?t=380) + + +## DDD + + + +> À propos de l'approche ; Pourquoi cela fonctionne mal en pratique + +Quelle est la différence, comment cela améliore l'applicabilité, où cela adopte des pratiques + +- [(Article) DDD, Hexagonal, Onion, Clean, CQRS, ... Comment je mets tout cela ensemble](https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/) +- [(Talk) Ilya Azin - Feature-Sliced Design (extrait sur Clean Architecture, DDD)](https://youtu.be/SnzPAr_FJ7w?t=528) + + +## Clean Architecture + + + +> À propos de l'approche ; De son applicabilité dans le frontend ; Position de FSD + +Comment elles sont similaires (à bien des égards), comment elles diffèrent + +- [(Fil de discussion) À propos de l'usage du cas/interacteur dans la méthodologie](https://t.me/feature_sliced/3897) +- [(Fil de discussion) À propos de l'injection de dépendances dans la méthodologie](https://t.me/feature_sliced/4592) +- [(Article) Alex Bespoyasov - Clean Architecture dans le frontend](https://bespoyasov.me/blog/clean-architecture-on-frontend/) +- [(Article) DDD, Hexagonal, Onion, Clean, CQRS, ... Comment je mets tout cela ensemble](https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/) +- [(Talk) Ilya Azin - Feature-Sliced Design (extrait sur Clean Architecture, DDD)](https://youtu.be/SnzPAr_FJ7w?t=528) +- [(Article) Idées fausses sur Clean Architecture](http://habr.com/ru/company/mobileup/blog/335382/) + + +## Frameworks + + + +> De l'applicabilité dans le frontend ; Pourquoi les frameworks ne résolvent pas les problèmes ; Pourquoi il n'y a pas d'approche unique ; Position de FSD + +Approche agnostique aux frameworks, approche conventionnelle + +- [(Article) À propos des raisons de la création de la méthodologie (extrait sur les frameworks)](/docs/about/motivation) +- [(Fil de discussion) À propos de l'applicabilité de la méthodologie pour différents frameworks](https://t.me/feature_sliced/3867) + + +## Atomic Design + +### Qu'est-ce que c'est ? +Dans l'Atomic Design, la portée de responsabilité est divisée en couches standardisées. + +L'Atomic Design est divisé en **5 couches** (de haut en bas) : + +1. `pages` - Fonctionnalité similaire à la couche `pages` dans FSD. +2. `templates` - Composants qui définissent la structure d'une page sans être liés à un contenu spécifique. +3. `organisms` - Modules constitués de molécules ayant une logique métier. +4. `molecules` - Composants plus complexes qui ne contiennent généralement pas de logique métier. +5. `atoms` - Composants UI sans logique métier. + +Les modules à une couche n'interagissent qu'avec les modules des couches inférieures, ce qui est similaire à FSD. +C'est-à-dire que les molécules sont construites à partir des atomes, les organismes à partir des molécules, les templates à partir des organismes, et les pages à partir des templates. +L'Atomic Design implique également l'utilisation d'API publiques au sein des modules pour l'isolation. + +### Applicabilité au frontend + +L'Atomic Design est relativement courant dans les projets. Il est plus populaire chez les designers web que chez les développeurs. +Les designers web utilisent souvent l'Atomic Design pour créer des designs évolutifs et facilement maintenables. +En développement, l'Atomic Design est souvent mélangé avec d'autres méthodologies architecturales. + +Cependant, étant donné que l'Atomic Design se concentre sur les composants UI et leur composition, un problème surgit lorsqu'il s'agit d'implémenter +la logique métier dans l'architecture. + +Le problème est que l'Atomic Design ne fournit pas un niveau clair de responsabilité pour la logique métier, +ce qui entraîne sa distribution à travers divers composants et niveaux, compliquant la maintenance et les tests. +La logique métier devient floue, rendant difficile la séparation claire des responsabilités et rendant +le code moins modulaire et réutilisable. + +### Comment cela se rapporte-t-il à FSD ? + +Dans le contexte de FSD, certains éléments de l'Atomic Design peuvent être appliqués pour créer des composants UI flexibles et évolutifs. +Les couches `atoms` et `molecules` peuvent être implémentées dans `shared/ui` dans FSD, simplifiant la réutilisation et +la maintenance des éléments UI de base. + +``` +├── shared +│ ├── ui +│ │ ├── atoms +│ │ ├── molecules +│ ... +``` +Une comparaison de FSD et d'Atomic Design montre que les deux méthodologies visent la modularité et la réutilisabilité +mais se concentrent sur des aspects différents. L'Atomic Design est orienté vers les composants visuels et leur composition. +FSD se concentre sur la division de la fonctionnalité de l'application en modules indépendants et leurs interconnexions. + +- [Méthodologie Atomic Design](https://atomicdesign.bradfrost.com/table-of-contents/) +- [(Fil de discussion) À propos de l'applicabilité dans shared/ui](https://t.me/feature_sliced/1653) +- [(Vidéo) Brève présentation d'Atomic Design](https://youtu.be/Yi-A20x2dcA) +- [(Talk) Ilya Azin - Feature-Sliced Design (extrait sur Atomic Design)](https://youtu.be/SnzPAr_FJ7w?t=587) + +## L'approche "Feature Driver" + + + +> À propos de l'approche ; De son applicabilité dans le frontend ; Position de FSD + +À propos de la compatibilité, du développement historique et de la comparaison + +- [(Talk) Oleg Isonen - Feature Driven Architecture](https://youtu.be/BWAeYuWFHhs) +- [Feature Driven - Spécification courte (du point de vue de FSD)](https://github.com/feature-sliced/documentation/tree/rc/feature-driven) diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/about/index.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/about/index.mdx new file mode 100644 index 0000000000..051d9d417a --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/about/index.mdx @@ -0,0 +1,39 @@ +# 🍰 À propos + +ORIENTÉE VERS LE CONTEXTE + +

+Informations générales sur la méthodologie, l'équipe, la communauté et l'historique de développement +

+ +## Principal + +import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" +import { StarOutlined, TrophyOutlined, BulbOutlined, TeamOutlined } from "@ant-design/icons"; + + + + + diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/about/mission.md b/i18n/fr/docusaurus-plugin-content-docs/current/about/mission.md new file mode 100644 index 0000000000..f247e20b92 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/about/mission.md @@ -0,0 +1,51 @@ +--- +sidebar_position: 1 +--- + +# Mission + +Ici, nous décrivons les objectifs et les limitations de l'applicabilité de la méthodologie — ce qui nous guide lors du développement de la méthodologie. + +- Nous voyons notre objectif comme un équilibre entre idéologie et simplicité +- Nous ne serons pas en mesure de créer une solution universelle qui convienne à tout le monde + +**Néanmoins, la méthodologie doit être proche et accessible à un large éventail de développeurs** + +## Objectifs + +### Clarté intuitive pour un large éventail de développeurs + +La méthodologie doit être accessible — pour la plupart des membres de l’équipe dans les projets + +*Car même avec tous les outils futurs, cela ne suffira pas si seule une petite partie des développeurs (les seniors/leads) comprend la méthodologie* + +### Résolution des problèmes quotidiens + +La méthodologie doit exposer les raisons et les solutions à nos problèmes quotidiens lors du développement de projets + +**Et aussi — fournir des outils pour tout cela (cli, linters)** + +Ainsi, les développeurs pourront utiliser une approche *prouvée en conditions réelles* qui leur permet d'éviter les problèmes de longue date liés à l'architecture et au développement. + +> *@sergeysova : Imaginez qu'un développeur écrit du code dans le cadre de la méthodologie et qu'il rencontre 10 fois moins de problèmes, simplement parce que d'autres personnes ont trouvé des solutions à de nombreux problèmes.* + +## Limitations + +Nous ne voulons pas *imposer notre point de vue*, tout en comprenant que *beaucoup de nos habitudes, en tant que développeurs, gênent au quotidien*. + +Chacun a son propre niveau d'expérience en conception et développement de systèmes, **il convient donc de comprendre ce qui suit :** + +- **Ne fonctionnera pas** : très simple, très clair, pour tout le monde + > *@sergeysova : Certains concepts ne peuvent être compris intuitivement que lorsque vous êtes confronté à des problèmes et que vous passez des années à les résoudre.* + > + > - *Dans le monde des mathématiques : la théorie des graphes.* + > - *En physique : la mécanique quantique.* + > - *En programmation : l'architecture des applications.* + +- **Possible et souhaitable** : simplicité, extensibilité + +## Voir aussi + +- [Problèmes d'architecture][refs-architecture--problems] + +[refs-architecture--problems]: /docs/about/understanding/architecture#problems diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/about/motivation.md b/i18n/fr/docusaurus-plugin-content-docs/current/about/motivation.md new file mode 100644 index 0000000000..994bb0ac6f --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/about/motivation.md @@ -0,0 +1,148 @@ +--- +sidebar_position: 2 +--- + +# Motivation + +L'idée principale de **Feature-Sliced Design** est de faciliter et de réduire le coût du développement de projets complexes et en évolution, basée sur [la combinaison des résultats de recherches, la discussion de l'expérience de divers développeurs][ext-discussions]. + +Évidemment, cela ne sera pas une solution miracle, et bien sûr, la méthodologie aura ses propres [limites d'applicabilité][refs-mission]. + +Néanmoins, il existe des questions légitimes concernant *la faisabilité d'une telle méthodologie dans son ensemble*. + +:::note + +Plus de détails [discutés dans la discussion][disc-src] + +::: + +## Pourquoi les solutions existantes ne suffisent-elles pas ? + +> Il s'agit généralement des arguments suivants : +> +> - *"Pourquoi avez-vous besoin d'une nouvelle méthodologie si vous avez déjà des approches et des principes de conception bien établis comme `SOLID`, `KISS`, `YAGNI`, `DDD`, `GRASP`, `DRY`, etc."* +> - *"Tous les problèmes sont résolus par une bonne documentation de projet, des tests et des processus structurés"* +> - *"Les problèmes ne surviendraient pas si tous les développeurs suivaient tous les principes mentionnés ci-dessus"* +> - *"Tout a été inventé avant vous, vous ne savez juste pas l'utiliser"* +> - *"Prenez \{FRAMEWORK_NAME\} - tout a déjà été résolu pour vous là-bas"* + +### Les principes à eux seuls ne suffisent pas + +**L'existence de principes à elle seule n'est pas suffisante pour concevoir une bonne architecture** + +Tout le monde ne les connaît pas entièrement, encore moins les comprend et les applique correctement. + +*Les principes de conception sont trop généraux et ne donnent pas de réponse spécifique à la question : "Comment concevoir la structure et l'architecture d'une application évolutive et flexible ?"* + +### Les processus ne fonctionnent pas toujours + +*Documentation / Tests / Processus* sont, bien sûr, bons, mais hélas, même avec un coût élevé pour ceux-ci - **ils ne résolvent pas toujours les problèmes posés par l'architecture et l'introduction de nouvelles personnes dans le projet**. + +- Le temps d'entrée de chaque développeur dans le projet n'est pas considérablement réduit, car la documentation est souvent volumineuse / obsolète. +- Il faut constamment s'assurer que tout le monde comprend l'architecture de la même manière - cela nécessite également une énorme quantité de ressources. +- N'oublions pas le facteur de risque (bus-factor). + +### Les frameworks existants ne peuvent pas être appliqués partout + +- Les solutions existantes ont généralement un seuil d'entrée élevé, ce qui rend difficile la recherche de nouveaux développeurs. +- De plus, le choix de la technologie est souvent déjà déterminé avant que des problèmes sérieux ne surviennent dans le projet, il faut donc savoir "travailler avec ce qui est disponible" - **sans être lié à la technologie**. + +> Q : *"Dans mon projet `React/Vue/Redux/Effector/Mobx/{YOUR_TECH}` - comment puis-je mieux construire la structure des entités et les relations entre elles ?"* + +### En résumé + +Nous obtenons des projets *"uniques comme des flocons de neige"*, chacun nécessitant une longue immersion de l'employé, et des connaissances qui seront probablement inutilisables sur un autre projet. + +> @sergeysova : *"C'est exactement la situation qui existe actuellement dans notre domaine du développement frontend : chaque lead va inventer différentes architectures et structures de projet, alors qu'il n'est même pas garanti que ces structures passeront l'épreuve du temps. Résultat, un maximum de deux personnes peuvent développer le projet à part lui, et chaque nouveau développeur doit encore être immergé dans le projet."* + +## Pourquoi les développeurs ont-ils besoin de la méthodologie ? + +### Se concentrer sur les fonctionnalités métiers, pas sur les problèmes d'architecture + +La méthodologie permet d'économiser des ressources sur la conception d'une architecture évolutive et flexible, tout en dirigeant l'attention des développeurs sur le développement de la fonctionnalité principale. En même temps, les solutions architecturales elles-mêmes sont standardisées d'un projet à l'autre. + +*Une question séparée est que la méthodologie doit gagner la confiance de la communauté, afin qu'un autre développeur puisse s'y familiariser et s'en remettre pour résoudre les problèmes de son projet dans le temps qui lui est imparti.* + +### Une solution éprouvée par l'expérience + +La méthodologie est conçue pour les développeurs qui cherchent *une solution éprouvée pour concevoir une logique métier complexe*. + +*Cependant, il est clair que la méthodologie repose généralement sur un ensemble de bonnes pratiques, d'articles abordant certains problèmes et cas durant le développement. Par conséquent, la méthodologie sera également utile pour les autres développeurs - ceux qui rencontrent des problèmes de développement et de conception.* + +### Santé du projet + +La méthodologie permettra de *résoudre et suivre les problèmes du projet à l'avance, sans nécessiter une énorme quantité de ressources.* + +**La plupart du temps, la dette technique s'accumule au fil du temps, et la responsabilité de sa résolution incombe à la fois au lead et à l'équipe.** + +La méthodologie permettra de *prévenir* les problèmes potentiels liés à l'évolutivité et au développement du projet à l'avance. + +## Pourquoi une entreprise a-t-elle besoin d'une méthodologie ? + +### Onboarding rapide + +Avec la méthodologie, il est possible d'embaucher une personne pour le projet qui **est déjà familière avec cette approche, sans avoir à la former à nouveau.** + +*Les gens commencent à comprendre et à contribuer au projet plus rapidement, avec des garanties supplémentaires pour trouver des personnes pour les prochaines itérations du projet.* + +### Une solution éprouvée par l'expérience + +Avec la méthodologie, l'entreprise obtient *une solution pour la plupart des problèmes qui surviennent lors du développement de systèmes.* + +Puisque le plus souvent, une entreprise souhaite obtenir un cadre/solution qui résout la majorité des problèmes rencontrés pendant le développement du projet. + +### Applicabilité pour les différentes étapes du projet + +La méthodologie peut bénéficier au projet *tant au stade de la maintenance et du développement que pendant la phase de MVP.* + +Oui, la chose la plus importante pour un MVP est *"les fonctionnalités, pas l'architecture posée pour l'avenir"*. Mais même dans des conditions de délais limités, connaître les bonnes pratiques de la méthodologie permet de *"faire avec moins de sang versé"*, lors de la conception de la version MVP du système, en trouvant un compromis raisonnable +(plutôt que de modéliser les fonctionnalités "au hasard"). + +*On peut dire la même chose à propos des tests.* + +## Quand notre méthodologie n'est-elle pas nécessaire ? + +- Si le projet aura une durée de vie courte. +- Si le projet n'a pas besoin d'une architecture maintenable. +- Si l'entreprise ne perçoit pas le lien entre la base de code et la vitesse de livraison des fonctionnalités. +- Si l'entreprise privilégie la fermeture rapide des commandes, sans maintenance future. + +### Taille de l'entreprise + +- **Petite entreprise** - a souvent besoin d'une solution prête à l'emploi et rapide. Ce n'est que lorsque l'entreprise grandit (au moins à une taille moyenne) qu'elle comprend qu'il faut consacrer du temps à la qualité et à la stabilité des solutions développées. +- **Entreprise de taille moyenne** - comprend généralement tous les problèmes liés au développement, et même si elle doit *"accélérer la course aux fonctionnalités"*, elle prend tout de même du temps pour améliorer la qualité, refactoriser et effectuer des tests (et bien sûr, pour avoir une architecture extensible). +- **Grande entreprise** - dispose généralement déjà d'un large public, d'un personnel et d'un ensemble de pratiques plus étendues, et probablement même de sa propre approche de l'architecture. Ainsi, l'idée de prendre celle de quelqu'un d'autre ne leur vient pas aussi souvent. + +## Plans + +La majeure partie des objectifs [est définie ici][refs-mission--goals], mais il vaut la peine de parler aussi de nos attentes concernant la méthodologie dans le futur. + +### Combinaison d'expériences + +Nous essayons maintenant de combiner toute notre expérience diversifiée de l'`équipe principale`, pour obtenir une méthodologie consolidée par la pratique. + +Bien sûr, nous pourrions obtenir Angular 3.0 en résultat, mais il est bien plus important ici d'**examiner le problème même de la conception de l'architecture des systèmes complexes**. + +*Et oui, nous avons des plaintes concernant la version actuelle de la méthodologie, mais nous voulons travailler ensemble pour parvenir à une solution unique et optimale (en tenant compte, entre autres, de l'expérience de la communauté).* + +### Vie au-delà de la spécification + +Si tout se passe bien, la méthodologie ne sera pas limitée uniquement à la spécification et à l'outil. + +- Peut-être qu'il y aura des rapports, des articles. +- Il pourrait y avoir des `CODE_MODEs` pour les migrations vers d'autres technologies des projets écrits selon la méthodologie. +- Il est possible qu'au final nous puissions atteindre les mainteneurs de grandes solutions technologiques. + - *Surtout pour React, comparé à d'autres frameworks - c'est le principal problème, car cela ne dit pas comment résoudre certains problèmes.* + +## Voir aussi + +- [(Discussion) Vous n'avez pas besoin d'une méthodologie ?][disc-src] +- [À propos de la mission de la méthodologie : objectifs et limitations][refs-mission] +- [Types de connaissances dans le projet][refs-knowledge] + +[refs-mission]: /docs/about/mission +[refs-mission--goals]: /docs/about/mission#goals +[refs-knowledge]: /docs/about/understanding/knowledge-types + +[disc-src]: https://github.com/feature-sliced/documentation/discussions/27 +[ext-discussions]: https://github.com/feature-sliced/documentation/discussions diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/about/promote/_category_.yaml b/i18n/fr/docusaurus-plugin-content-docs/current/about/promote/_category_.yaml new file mode 100644 index 0000000000..170959dbb9 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/about/promote/_category_.yaml @@ -0,0 +1,3 @@ +label: Promouvoir +position: 11 +collapsed: true diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/about/promote/for-company.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/about/promote/for-company.mdx new file mode 100644 index 0000000000..387fdf08b4 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/about/promote/for-company.mdx @@ -0,0 +1,18 @@ +--- +sidebar_position: 4 +sidebar_class_name: sidebar-item--wip +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# Promouvoir dans l'entreprise + + + +## Le projet et l'entreprise ont-ils besoin d'une méthodologie ? + +> Concernant la justification de l'application, ces devoirs + +## Comment puis-je soumettre une méthodologie à une entreprise ? + +## Comment préparer et justifier un plan pour passer à la méthodologie ? diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/about/promote/for-team.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/about/promote/for-team.mdx new file mode 100644 index 0000000000..af72871786 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/about/promote/for-team.mdx @@ -0,0 +1,18 @@ +--- +sidebar_position: 3 +sidebar_class_name: sidebar-item--wip +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# Promouvoir dans l'équipe + + + +- Intégrer les nouveaux arrivants +- Lignes directrices de développement ("où chercher le module N", etc...) +- Nouvelle approche pour les tâches + +## Voir aussi +- [(Fil de discussion) La simplicité des anciennes approches et l'importance de la pleine conscience](https://t.me/feature_sliced/3360) +- [(Fil de discussion) À propos de la commodité de la recherche par couches](https://t.me/feature_sliced/1918) diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/about/promote/integration.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/about/promote/integration.mdx new file mode 100644 index 0000000000..4e1d7b9875 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/about/promote/integration.mdx @@ -0,0 +1,16 @@ +--- +sidebar_position: 1 +--- + +# Aspects de l'intégration + +**Avantages**: +- [Vue d'ensemble](/docs/get-started/overview#advantages) +- Revue de code +- Intégration des nouveaux + +**Inconvénients**: +- Complexité mentale +- Barrière d'entrée élevée +- "Enfer des couches" +- Problèmes typiques des approches basées sur les fonctionnalités diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/about/promote/partial-application.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/about/promote/partial-application.mdx new file mode 100644 index 0000000000..2a6586f235 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/about/promote/partial-application.mdx @@ -0,0 +1,12 @@ +--- +sidebar_position: 2 +sidebar_class_name: sidebar-item--wip +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# Application partielle + + + +> Comment appliquer partiellement la méthodologie ? Est-ce que cela a du sens ? Que se passe-t-il si je l'ignore ? \ No newline at end of file diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/about/understanding/_category_.yaml b/i18n/fr/docusaurus-plugin-content-docs/current/about/understanding/_category_.yaml new file mode 100644 index 0000000000..54c0800244 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/about/understanding/_category_.yaml @@ -0,0 +1,2 @@ +label: Compréhension +position: 3 diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/about/understanding/abstractions.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/about/understanding/abstractions.mdx new file mode 100644 index 0000000000..1a48ec6c03 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/about/understanding/abstractions.mdx @@ -0,0 +1,88 @@ +--- +sidebar_position: 3 +sidebar_class_name: sidebar-item--wip +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# Alternatives + + + +## Big Ball of Mud + + + +- [(Article) DDD - Big Ball of Mud](https://thedomaindrivendesign.io/big-ball-of-mud/) + +## Composants intelligents et stupides + + + +- [(Article) Dan Abramov - Presentational and Container Components (TLDR: Non recommandé)](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) + +## Principes de conception + + + +## DDD + + + +## Voir aussi {#see-also} + +- [(Article) DDD, Hexagonal, Onion, Clean, CQRS, … Comment tout assembler](https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/) + +## Architecture propre + + + +- [(Article) DDD, Hexagonal, Onion, Clean, CQRS, … Comment tout assembler](https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/) + +## Framework + + + +- [(Article) Pourquoi créer FSD (Fragments concernant les frameworks)](/docs/about/motivation) + +## Design atomique + +### Qu'est-ce que c'est ? + +Le design atomique divise les responsabilités en couches standardisées. + +Le design atomique se divise en **5 couches** (de haut en bas). + +1. `pages` - A le même objectif que la couche `pages` de FSD. +2. `templates` - Composants définissant la structure de pages indépendantes du contenu. +3. `organisms` - Modules composés de molécules ayant une logique métier. +4. `molecules` - Composants plus complexes, généralement sans logique métier. +5. `atoms` - Composants UI sans logique métier. + +Les modules d'une même couche interagissent uniquement avec ceux de la couche inférieure, comme dans FSD. +Ainsi, une molécule (molecule) est construite à partir d'un atome (atom), un organisme (organism) est construit à partir de molécules, un modèle (template) est construit à partir d'organismes et une page (page) est construite à partir de modèles. +Le design atomique suppose également l'utilisation d'**API publiques** au sein des modules. + +### Applicabilité dans le frontend +Le design atomique est assez couramment rencontré dans les projets. Il est surtout populaire parmi les web designers, plutôt que les développeurs. Les designers l'utilisent pour créer des designs évolutifs et faciles à maintenir. +Dans le développement, le design atomique est souvent combiné avec d'autres méthodologies d'architecture. + +Cependant, le design atomique se concentre sur les composants UI et leur structure, ce qui entraîne des problèmes pour l'implémentation de la logique métier dans l'architecture. + +Le problème réside dans le fait que le design atomique ne fournit pas un niveau de responsabilité clair pour la logique métier, ce qui conduit à la dispersion de la logique métier dans divers composants et niveaux, rendant ainsi la maintenance et les tests plus complexes. +La logique métier devient floue, la séparation claire des responsabilités devient difficile et le code n'est plus modulable ni réutilisable. + +### Intégration avec FSD +Dans le contexte de FSD, certains éléments du design atomique peuvent être utilisés pour créer des composants UI flexibles et évolutifs. Les couches `atoms` et `molecules` peuvent être implémentées dans le `shared/ui` de FSD, simplifiant ainsi la réutilisation et la maintenance des éléments UI de base. + +```sh +├── shared +│   ├── ui  +│   │   ├── atoms +│   │   ├── molecules +│   ... + + + +- [(Conférence) Feature Driven Architecture - Oleg Isonen](https://youtu.be/BWAeYuWFHhs) +- [Feature Driven - Spécification courte (du point de vue de FSD)](https://github.com/feature-sliced/documentation/tree/rc/feature-driven) diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/about/understanding/architecture.md b/i18n/fr/docusaurus-plugin-content-docs/current/about/understanding/architecture.md new file mode 100644 index 0000000000..c08dc84f79 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/about/understanding/architecture.md @@ -0,0 +1,97 @@ +--- +sidebar_position: 1 +--- + +# À propos de l'architecture + +## Problèmes + +En général, la conversation sur l'architecture est soulevée lorsque le développement s'arrête en raison de certains problèmes dans le projet. + +### Bus-factor et Onboarding + +Seule un nombre limité de personnes comprennent le projet et son architecture. + +**Exemples :** + +- *"C'est difficile d'ajouter une personne au développement"* +- *"Pour chaque problème, tout le monde a son propre avis sur la manière de contourner cela" (envions Angular)* +- *"Je ne comprends pas ce qui se passe dans ce gros morceau de monolithe"* + +### Conséquences implicites et incontrôlées + +De nombreux effets secondaires implicites pendant le développement/refactoring *("tout dépend de tout")* + +**Exemples :** + +- *"La fonctionnalité importe la fonctionnalité"* +- *"J'ai mis à jour le store d'une page, et la fonctionnalité a cessé de fonctionner sur l'autre"* +- *"La logique est dispersée dans toute l'application, et il est impossible de suivre où commence et où finit chaque chose"* + +### Réutilisation incontrôlée de la logique + +Il est difficile de réutiliser/modifier la logique existante + +En même temps, il y a généralement [deux extrêmes](https://github.com/feature-sliced/documentation/discussions/14) : + +- Soit la logique est écrite entièrement à partir de zéro pour chaque module *(avec des répétitions possibles dans le code existant)* +- Soit il y a une tendance à transférer tous les modules implémentés vers les dossiers `shared`, créant ainsi un grand dumping de modules *d'où beaucoup ne sont utilisés que dans un seul endroit* + +**Exemples :** + +- *"J'ai **N** implémentations de la même logique métier dans mon projet, pour lesquelles je paye toujours"* +- *"Il y a 6 composants différents de bouton/pop-up/... dans le projet"* +- *"Un dumping de helpers"* + +## Exigences + +Il semble donc logique de présenter les *exigences pour une architecture idéale :* + +:::note + +Chaque fois qu'il est dit "facile", cela signifie "relativement facile pour un large éventail de développeurs", car il est évident que [il ne sera pas possible de créer une solution idéale pour absolument tout le monde](/docs/about/mission#limitations) + +::: + +### Clarté + +- Il devrait être **facile de maîtriser et d'expliquer** le projet et son architecture à l'équipe +- La structure devrait refléter les **valeurs réelles du projet** +- Il doit y avoir des **effets secondaires et des connexions explicites** entre les abstractions +- Il devrait être **facile de détecter la logique dupliquée** sans interférer avec les implémentations uniques +- Il ne devrait pas y avoir de **dispersion de la logique** dans tout le projet +- Il ne devrait pas y avoir **trop d'abstractions et de règles hétérogènes** pour une bonne architecture + +### Contrôle + +- Une bonne architecture devrait **accélérer la résolution des tâches, l'introduction de nouvelles fonctionnalités** +- Il devrait être possible de contrôler le développement du projet +- Il devrait être facile de **développer, modifier ou supprimer le code** +- La **décomposition et l'isolation** des fonctionnalités doivent être respectées +- Chaque composant du système doit être **facilement remplaçable et supprimable** + - *[Pas besoin d'optimiser pour les changements][ext-kof-not-modification] - nous ne pouvons pas prédire l'avenir* + - *[Mieux vaut optimiser pour la suppression][ext-kof-but-removing] - en fonction du contexte déjà existant* + +### Adaptabilité + +- Une bonne architecture doit être applicable **à la plupart des projets** + - *Avec les solutions d'infrastructure existantes* + - *À n'importe quelle étape du développement* +- Il ne doit pas y avoir de dépendance au framework ou à la plateforme +- Il doit être possible de **faire évoluer facilement le projet et l'équipe**, avec la possibilité de paralléliser le développement +- Il devrait être facile **de s'adapter aux exigences et circonstances changeantes** + +## Voir aussi + +- [(Conférence React Berlin) Oleg Isonen - Feature Driven Architecture][ext-kof] +- [(React SPB Meetup #1) Sergey Sova - Feature Slices][ext-slices-spb] +- [(Article) À propos de la modularisation des projets][ext-medium] +- [(Article) À propos de la séparation des préoccupations et de la structuration par fonctionnalités][ext-ryanlanciaux] + +[ext-kof-not-modification]: https://youtu.be/BWAeYuWFHhs?t=1631 +[ext-kof-but-removing]: https://youtu.be/BWAeYuWFHhs?t=1666 + +[ext-slices-spb]: https://t.me/feature_slices +[ext-kof]: https://youtu.be/BWAeYuWFHhs +[ext-medium]: https://alexmngn.medium.com/why-react-developers-should-modularize-their-applications-d26d381854c1 +[ext-ryanlanciaux]: https://ryanlanciaux diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/about/understanding/knowledge-types.md b/i18n/fr/docusaurus-plugin-content-docs/current/about/understanding/knowledge-types.md new file mode 100644 index 0000000000..0d82428a85 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/about/understanding/knowledge-types.md @@ -0,0 +1,29 @@ +--- +sidebar_position: 3 +sidebar_label: Types de connaissances +--- + +# Types de connaissances dans le projet + +Les "types de connaissances" suivants peuvent être distingués dans tout projet : + +* **Connaissances fondamentales** + Connaissances qui ne changent pas beaucoup au fil du temps, telles que les algorithmes, l'informatique, les mécanismes du langage de programmation et ses API. + +* **Stack technologique** + Connaissances de l'ensemble des solutions techniques utilisées dans un projet, y compris les langages de programmation, les frameworks et les bibliothèques. + +* **Connaissances spécifiques au projet** + Connaissances spécifiques au projet actuel et qui ne sont pas précieuses en dehors de celui-ci. Ces connaissances sont essentielles pour que les développeurs nouvellement intégrés puissent contribuer efficacement. + +:::note + +**Feature-Sliced Design** est conçu pour réduire la dépendance aux "connaissances spécifiques au projet", prendre plus de responsabilités et faciliter l'intégration des nouveaux membres de l'équipe. + +::: + +## Voir aussi {#see-also} + +- [(Vidéo 🇷🇺) Ilya Klimov - Sur les types de connaissances][ext-klimov] + +[ext-klimov]: https://youtu.be/4xyb_tA-uw0?t=249 diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/about/understanding/naming.md b/i18n/fr/docusaurus-plugin-content-docs/current/about/understanding/naming.md new file mode 100644 index 0000000000..0519514218 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/about/understanding/naming.md @@ -0,0 +1,48 @@ +--- +sidebar_position: 4 +--- + +# Nommage + +Les différents développeurs ont des expériences et des contextes variés, ce qui peut entraîner des malentendus au sein de l'équipe lorsque les mêmes entités sont appelées différemment. Par exemple : + +- Les composants pour l'affichage peuvent être appelés "ui", "composants", "ui-kit", "vues", … +- Le code qui est réutilisé dans toute l'application peut être appelé "core", "shared", "app", … +- Le code de logique métier peut être appelé "store", "model", "state", … + +## Nommage dans le Feature-Sliced Design {#naming-in-fsd} + +La méthodologie utilise des termes spécifiques tels que : + +- "app", "process", "page", "feature", "entity", "shared" en tant que noms de couches, +- "ui", "model", "lib", "api", "config" en tant que noms de segments. + +Il est très important de s'en tenir à ces termes pour éviter toute confusion parmi les membres de l'équipe et les nouveaux développeurs rejoignant le projet. L'utilisation de noms standard aide également lorsqu'on demande de l'aide à la communauté. + +## Conflits de Nommage {#when-can-naming-interfere} + +Des conflits de nommage peuvent survenir lorsque les termes utilisés dans la méthodologie FSD se chevauchent avec les termes utilisés dans le domaine métier : + +- `FSD#process` vs processus simulé dans une application, +- `FSD#page` vs page de journal, +- `FSD#model` vs modèle de voiture. + +Par exemple, un développeur qui voit le mot "process" dans le code passera du temps supplémentaire à essayer de comprendre quel processus est désigné. De telles **collisions peuvent perturber le processus de développement**. + +Lorsque le glossaire du projet contient des terminologies spécifiques à FSD, il est crucial de faire attention lorsque l'on discute de ces termes avec l'équipe et les parties prenantes non techniques. + +Pour communiquer efficacement avec l'équipe, il est recommandé d'utiliser l'abréviation "FSD" pour préfixer les termes de la méthodologie. Par exemple, lorsqu'on parle d'un processus, on pourrait dire : "Nous pouvons mettre ce processus dans la couche FSD des fonctionnalités." + +Inversement, lorsqu'on communique avec des parties prenantes non techniques, il est préférable de limiter l'utilisation de la terminologie FSD et de s'abstenir de mentionner la structure interne de la base de code. + +## Voir aussi {#see-also} + +- [(Discussion) Adaptabilité du nommage][disc-src] +- [(Discussion) Sondage sur le nommage des entités][disc-naming] +- [(Discussion) "processus" vs "flux" vs ...][disc-processes] +- [(Discussion) "modèle" vs "store" vs ...][disc-model] + +[disc-model]: https://github.com/feature-sliced/documentation/discussions/68 +[disc-naming]: https://github.com/feature-sliced/documentation/discussions/31#discussioncomment-464894 +[disc-processes]: https://github.com/feature-sliced/documentation/discussions/20 +[disc-src]: https://github.com/feature-sliced/documentation/discussions/16 diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/about/understanding/needs-driven.md b/i18n/fr/docusaurus-plugin-content-docs/current/about/understanding/needs-driven.md new file mode 100644 index 0000000000..8838fb2329 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/about/understanding/needs-driven.md @@ -0,0 +1,162 @@ +--- +sidebar_position: 2 +--- + +# Besoins motivés + +:::note TL;DR + +— _Ne pouvez-vous pas formuler l'objectif que la nouvelle fonctionnalité résoudra ? Ou peut-être que le problème vient du fait que la tâche elle-même n'est pas formulée ? **L'idée est aussi que la méthodologie aide à extraire la définition problématique des tâches et des objectifs**_ + +— _Le projet ne vit pas dans un état statique – les exigences et les fonctionnalités changent constamment. Avec le temps, le code devient un "pâté", car au début, le projet a été conçu uniquement pour répondre aux premières impressions des souhaits. **Et la tâche d'une bonne architecture est également de s'affiner pour les conditions de développement changeantes.**_ + +::: + + + + +## Pourquoi ? + +Pour choisir un nom clair pour une entité et comprendre ses composants, **il faut bien comprendre quelle tâche sera résolue avec tout ce code.** + +> _@sergeysova : Pendant le développement, nous essayons de donner à chaque entité ou fonction un nom qui reflète clairement les intentions et la signification du code exécuté._ + +Après tout, sans comprendre la tâche, il est impossible d'écrire les bons tests qui couvrent les cas les plus importants, de mettre des erreurs qui aident l'utilisateur aux bons endroits, voire de ne pas interrompre le flux de l'utilisateur à cause d'erreurs non critiques mais réparables. + +## De quelles tâches parlons-nous ? + +Le frontend développe des applications et des interfaces pour les utilisateurs finaux, nous résolvons donc les tâches de ces consommateurs. + +Lorsqu'une personne vient vers nous, **elle veut résoudre une de ses douleurs ou satisfaire un besoin.** + +_Le rôle des gestionnaires et des analystes est de formuler ce besoin, et celui des développeurs de le mettre en œuvre en tenant compte des spécificités du développement web (perte de communication, erreur backend, faute de frappe, curseur ou doigt manqué)._ + +**Cet objectif, avec lequel l'utilisateur est venu, est la tâche des développeurs.** + +> _Une petite problématique résolue est une fonctionnalité dans la méthodologie Feature-Sliced Design – vous devez découper l'ensemble des tâches du projet en petits objectifs._ + +## Comment cela affecte-t-il le développement ? + +### Décomposition des tâches + +Lorsqu'un développeur commence à implémenter une tâche, pour simplifier la compréhension et le support du code, il **la découpe mentalement en étapes** : + +* d'abord, _séparer en entités de haut niveau_ et _les implémenter_, +* puis ces entités _les diviser en plus petites_ +* et ainsi de suite + +_Dans le processus de découpage en entités, le développeur est obligé de leur donner un nom qui reflète clairement son idée et aide à comprendre quelle tâche le code résout lorsqu'on lit la liste_ +_En même temps, on n'oublie pas que nous essayons d'aider l'utilisateur à réduire la douleur ou réaliser des besoins_ + +### Comprendre l'essence de la tâche + +Mais pour donner un nom clair à une entité, **le développeur doit en savoir suffisamment sur son objectif** + +* comment va-t-il utiliser cette entité, +* quelle partie de la tâche de l'utilisateur implémente-t-elle, où cette entité peut-elle être appliquée ailleurs, +* dans quelles autres tâches peut-elle participer, +* et ainsi de suite + +Il n'est pas difficile de tirer une conclusion : **tandis que le développeur réfléchira au nom des entités dans le cadre de la méthodologie, il pourra trouver des tâches mal formulées même avant d'écrire le code.** + +> Comment donner un nom à une entité si vous ne comprenez pas bien quelles tâches elle peut résoudre, comment pouvez-vous même diviser une tâche en entités si vous ne la comprenez pas bien ? + +## Comment la formuler ? + +**Pour formuler une tâche résolue par des fonctionnalités, il faut comprendre la tâche elle-même**, et cela relève déjà de la responsabilité du chef de projet et des analystes. + +_La méthodologie peut seulement indiquer au développeur quelles tâches le chef de produit doit examiner attentivement._ + +> _@sergeysova : Le frontend tout entier est avant tout un affichage d'informations, tout composant, au départ, affiche, et ensuite la tâche "montrer quelque chose à l'utilisateur" n'a pas de valeur pratique._ +> +> _Même sans prendre en compte les spécificités du frontend, on peut demander "pourquoi dois-je te montrer ça ?", et on peut continuer à poser des questions jusqu'à ce qu'on sorte de la douleur ou du besoin du consommateur._ + +Dès que nous avons pu arriver aux besoins ou douleurs de base, nous pouvons revenir et réfléchir **à la manière exacte dont votre produit ou service peut aider l'utilisateur à atteindre ses objectifs** + +Toute nouvelle tâche dans votre gestionnaire est destinée à résoudre des problèmes commerciaux, et l'entreprise essaie de résoudre les tâches de l'utilisateur tout en gagnant de l'argent avec. Cela signifie que chaque tâche a certains objectifs, même s'ils ne sont pas explicitement formulés dans le texte de description. + +_**Le développeur doit comprendre clairement quel objectif cette ou cette tâche poursuit**, mais toutes les entreprises ne peuvent pas se permettre de construire des processus parfaitement, bien que cela soit un autre sujet, néanmoins, le développeur peut tout à fait "alerter" les bons gestionnaires pour découvrir cela et accomplir sa partie du travail de manière efficace._ + +## Et quel est l'avantage ? + +Voyons maintenant le processus dans son ensemble. + +### 1. Comprendre les tâches des utilisateurs + +Lorsque le développeur comprend sa douleur et comment l'entreprise la résout, il peut proposer des solutions qui ne sont pas disponibles pour l'entreprise à cause des spécificités du développement web. + +> Mais bien sûr, tout cela ne fonctionne que si le développeur n'est pas indifférent à ce qu'il fait et pourquoi, sinon _pourquoi alors la méthodologie et certaines approches ?_ + +### 2. Structuration et organisation + +Avec la compréhension des tâches vient **une structure claire, à la fois dans la tête et dans les tâches ainsi que le code** + +### 3. Compréhension de la fonctionnalité et de ses composants + +**Une fonctionnalité est une utilité pour l'utilisateur** + +* Lorsque plusieurs fonctionnalités sont implémentées dans une seule fonctionnalité, cela constitue **une violation des frontières** +* La fonctionnalité peut être indivisible et croissante - **et ce n'est pas mauvais** +* **Mauvais** - lorsqu'une fonctionnalité ne répond pas à la question _"Quelle est la valeur commerciale pour l'utilisateur ?"_ +* Il ne peut y avoir de fonctionnalité "carte-bureau" + * Mais `réservation-réunion-sur-carte`, `recherche-employé`, `changement-de-lieu-de-travail` - **oui** + +> _@sergeysova : L'idée est que la fonctionnalité contient uniquement le code qui implémente la fonctionnalité elle-même_, sans détails inutiles et solutions internes (idéalement)* +> +> *Ouvrez le code de la fonctionnalité **et voyez uniquement ce qui se rapporte à la tâche** - rien de plus* + +### 4. Profit + +Les entreprises changent rarement de direction radicalement, ce qui signifie que **la réflexion des tâches commerciales dans le code de l'application frontend est un profit très significatif.** + +_Ainsi, vous n'aurez pas à expliquer à chaque nouveau membre de l'équipe ce que fait tel ou tel code, et pourquoi il a été ajouté - **tout sera expliqué par les tâches commerciales déjà reflétées dans le code.**_ + +> Ce que l'on appelle ["Langage des Affaires" en Domain Driven Design][ext-ubiq-lang] + +--- + +## Retour à la réalité + +Si les processus commerciaux sont compris et de bons noms sont donnés dès la phase de conception - _alors il n'est pas particulièrement problématique de transférer cette compréhension et cette logique dans le code._ + +**Cependant, en pratique**, les tâches et les fonctionnalités sont généralement développées de manière "trop" itérative et (ou) il n'y a pas de temps pour réfléchir à la conception. + +**Résultat**, la fonctionnalité a du sens aujourd'hui, et si vous étendez cette fonctionnalité dans un mois, vous pouvez réécrire le genre du projet. + +> *[[De la discussion][disc-src]] : Le développeur essaie de penser 2-3 étapes à l'avance, en tenant compte des souhaits futurs, mais ici il se heurte à sa propre expérience* +> +> _L'ingénieur expérimenté essaie généralement de voir 10 étapes à l'avance, et comprend où une fonctionnalité peut être divisée et combinée avec une autre_ +> +> _Mais parfois, il arrive qu'une tâche doive être confrontée à l'expérience, et il n'y a nulle part où prendre la compréhension de comment décomposer de manière judicieuse, avec les conséquences les moins malheureuses dans le futur._ + +## Le rôle de la méthodologie + +**La méthodologie aide à résoudre les problèmes des développeurs, afin qu'ils puissent plus facilement résoudre les problèmes des utilisateurs.** + +Il n'existe pas de solution aux problèmes des développeurs uniquement pour les développeurs. + +Mais pour que le développeur résolve ses tâches, **il faut comprendre les tâches des utilisateurs** - sinon cela ne fonctionnera pas. + +### Exigences méthodologiques + +Il devient clair qu'il faut identifier au moins deux exigences pour **Feature-Sliced Design** : + +1. La méthodologie doit indiquer **comment créer des fonctionnalités, des processus et des entités** + + * Ce qui signifie qu'elle doit expliquer clairement _comment diviser le code entre eux_, ce qui implique également que le nommage de ces entités doit être défini dans la spécification. + +2. La méthodologie doit aider l'architecture à **[s'adapter facilement aux exigences changeantes du projet][refs-arch--adaptability]** + +## Voir aussi + +* [(Post) Stimulation pour une formulation claire des tâches (+ discussion)][disc-src] + > _**L'article actuel** est une adaptation de cette discussion, vous pouvez lire la version complète sans coupures en suivant le lien_ +* [(Discussion) Comment découper la fonctionnalité et ce que c'est][tg-src] +* [(Article) "Comment mieux organiser vos applications"][ext-medium] + +[refs-arch--adaptability]: architecture#adaptability + +[ext-medium]: https://alexmngn.medium.com/how-to-better-organize-your-react-applications-2fd3ea1920f1 +[disc-src]: https://t.me/sergeysova/318 +[tg-src]: https://t.me/atomicdesign/18972 +[ext-ubiq-lang]: https://thedomaindrivendesign.io/developing-the-ubiquitous-language diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/about/understanding/signals.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/about/understanding/signals.mdx new file mode 100644 index 0000000000..970943afe3 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/about/understanding/signals.mdx @@ -0,0 +1,21 @@ +--- +sidebar_position: 5 +sidebar_class_name: sidebar-item--wip +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# Signaux de l'architecture + + + +> S'il y a une limitation du côté de l'architecture, il y a des raisons évidentes pour cela, ainsi que des conséquences si elles sont ignorées + +> La méthodologie et l'architecture envoient des signaux, et la manière dont vous y réagissez dépend des risques que vous êtes prêt à prendre et de ce qui convient le mieux à votre équipe) + +## Voir aussi + +- [(Fil de discussion) À propos des signaux de l'architecture et du flux de données](https://t.me/feature_sliced/2070) +- [(Fil de discussion) À propos de la nature fondamentale de l'architecture](https://t.me/feature_sliced/2492) +- [(Fil de discussion) À propos de la mise en évidence des points faibles](https://t.me/feature_sliced/3979) +- [(Fil de discussion) Comment comprendre que le modèle de données est gonflé](https://t.me/feature_sliced/4228) diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/branding.md b/i18n/fr/docusaurus-plugin-content-docs/current/branding.md new file mode 100644 index 0000000000..1756a80c3b --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/branding.md @@ -0,0 +1,81 @@ +# Branding Guidelines + +FSD's visual identity is based on its core-concepts: `Layered`, `Sliced self-contained parts`, `Parts & Compose`, `Segmented`. +But also we tend to design simple, pretty identity, which should convey the FSD philisophy and be easy to recognize. + +**Please, use FSD's identity "as-is", without changes but with our assets for your comfort.** This brand guide will help you to use FSD's identity correctly. + +:::caution Compatibility + +FSD had [another legacy identity](https://drive.google.com/drive/folders/11Y-3qZ_C9jOFoW2UbSp11YasOhw4yBdl?usp=sharing) before. Old design didn't represent core-concepts of methodology. Also it was created as pure draft, and should have been actualized. + +For a compatible and long-term use of the brand, we have been carefully rebranding for a year (2021-2022). **So that you can be sure when using identity of FSD 🍰** + +*But prefer namely actual identity, not old!* + +::: + +## Title + +- ✅ **Correct:** `Feature-Sliced Design`, `FSD` +- ❌ **Incorrect:** `Feature-Sliced`, `Feature Sliced`, `FeatureSliced`, `feature-sliced`, `feature sliced`, `FS` + +## Emojii + +The cake 🍰 image represents FSD core concepts quite well, so it has been chosen as our signature emoji + +> Example: *"🍰 Architectural design methodology for Frontend projects"* + +## Logo & Palette + +FSD has few variations of logo for different context, but it recommended to prefer **primary** + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ThemeLogo (Ctrl/Cmd + Click for download)Usage
primary
(#29BEDC, #517AED)
logo-primaryPreferred in most cases
flat
(#3193FF)
logo-flatFor one-color context
monochrome
(#FFF)
logo-monocrhomeFor grayscale context
square
(#3193FF)
logo-squareFor square boundaries
+ +## Banners & Schemes + +banner-primary +banner-monochrome + +## Social Preview + +Work in progress... + +## Presentation template + +Work in progress... + +## See also + +- [Discussion (github)](https://github.com/feature-sliced/documentation/discussions/399) +- [History of development with references (figma)](https://www.figma.com/file/RPphccpoeasVB0lMpZwPVR/FSD-Brand?node-id=0%3A1) +- [Rebranding demo](https://rebrand-sliced.netlify.app/en/) diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/get-started/cheatsheet.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/get-started/cheatsheet.mdx new file mode 100644 index 0000000000..be08f7302a --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/get-started/cheatsheet.mdx @@ -0,0 +1,34 @@ +--- +# sidebar_position: 3 +unlisted: true +--- + +# Fiche de décomposition + +Utilisez ceci comme référence rapide lorsque vous décidez comment décomposer votre interface utilisateur. Des versions PDF sont également disponibles ci-dessous, vous pouvez les imprimer et en garder une sous votre oreiller. + +## Choisir une couche + +[Télécharger PDF](/files/choosing-a-layer-en.pdf) + +![Définitions de toutes les couches et questions d'auto-évaluation](/img/choosing-a-layer-en.jpg) + +## Exemples + +### Tweet + +![tweet-décomposé-bordure-fondClair](/img/decompose-twitter.png) + +### GitHub + +![github-décomposé-bordure](/img/decompose-github.jpg) + +## Voir aussi +- [(Fil) Logique générale pour les fonctionnalités et les entités](https://t.me/feature_sliced/4262) +- [(Fil) Décomposition de la logique gonflée](https://t.me/feature_sliced/4210) +- [(Fil) À propos de la compréhension des zones de responsabilité lors de la décomposition](https://t.me/feature_sliced/4088) +- [(Fil) Décomposition du widget Product List](https://t.me/feature_sliced/3828) +- [(Article) Différentes approches pour la décomposition de la logique](https://www.pluralsight.com/guides/how-to-organize-your-react-+-redux-codebase) +- [(Fil) À propos de la différence entre les fonctionnalités et les entités](https://t.me/feature_sliced/3776) +- [(Fil) À propos de la différence entre les choses et les entités (2)](https://t.me/feature_sliced/3248) +- [(Fil) À propos de l'application des critères pour la décomposition](https://t.me/feature_sliced/3833) diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/get-started/faq.md b/i18n/fr/docusaurus-plugin-content-docs/current/get-started/faq.md new file mode 100644 index 0000000000..e023e54d3f --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/get-started/faq.md @@ -0,0 +1,68 @@ +--- +sidebar_position: 20 +pagination_next: guides/index +--- + +# FAQ + +:::info + +Vous pouvez poser vos questions dans notre [chat Telegram][telegram], [communauté Discord][discord], et [Discussions GitHub][github-discussions]. + +::: + +### Existe-t-il un kit d'outils ou un linter ? + +Il existe une configuration officielle ESLint — [@feature-sliced/eslint-config][eslint-config-official], et un plugin ESLint — [@conarti/eslint-plugin-feature-sliced][eslint-plugin-conarti], créé par Aleksandr Belous, un membre de la communauté. Vous êtes invités à contribuer à ces projets ou à créer les vôtres ! + +### Où stocker la mise en page/le modèle des pages ? + +Si vous avez besoin de modèles HTML simples, vous pouvez les conserver dans `shared/ui`. Si vous devez utiliser des couches supérieures à l'intérieur, plusieurs options s'offrent à vous : + +- Peut-être n'avez-vous pas besoin de mises en page du tout ? Si la mise en page est courte, il peut être plus raisonnable de dupliquer le code dans chaque page plutôt que d'essayer de l'abstraire. +- Si vous avez besoin de mises en page, vous pouvez les traiter comme des widgets ou des pages séparées, et les composer dans la configuration de votre routeur dans l'application. Le routage imbriqué est également une option. + +### Quelle est la différence entre une fonctionnalité et une entité ? + +Une _entité_ est un concept réel avec lequel votre application interagit. Une _fonctionnalité_ est une interaction qui apporte de la valeur à vos utilisateurs, la chose que les gens veulent faire avec vos entités. + +Pour plus d'informations, avec des exemples, consultez la page de référence sur les [slices][reference-entities]. + +### Puis-je intégrer des pages/fonctionnalités/entités les unes dans les autres ? + +Oui, mais cette intégration doit se faire dans des couches supérieures. Par exemple, à l'intérieur d'un widget, vous pouvez importer les deux fonctionnalités et insérer l'une dans l'autre en tant que props/enfants. + +Vous ne pouvez pas importer une fonctionnalité d'une autre fonctionnalité, cela est interdit par la [**règle d'importation sur les couches**][import-rule-layers]. + +### Qu'en est-il de l'Atomic Design ? + +La version actuelle de la méthodologie ne nécessite ni n'interdit l'utilisation de l'Atomic Design avec le Feature-Sliced Design. + +Par exemple, l'Atomic Design [peut être appliqué efficacement](https://t.me/feature_sliced/1653) pour le segment `ui` des modules. + +### Existe-t-il des ressources/articles/etc. utiles sur FSD ? + +Oui ! https://github.com/feature-sliced/awesome + +### Pourquoi ai-je besoin de Feature-Sliced Design ? + +Cela aide vous et votre équipe à avoir une vue d'ensemble rapide du projet en termes de ses composants principaux à valeur ajoutée. Une architecture standardisée permet de faciliter l'intégration des nouveaux membres et de résoudre les débats sur la structure du code. Consultez la page [motivation][motivation] pour en savoir plus sur la création de FSD. + +### Un développeur novice a-t-il besoin d'une architecture/méthodologie ? + +Plutôt oui que non + +*Habituellement, lorsqu'un projet est conçu et développé par une seule personne, tout se passe bien. Mais si des pauses dans le développement se produisent, ou si de nouveaux développeurs rejoignent l'équipe, des problèmes surgissent.* + +### Comment travailler avec le contexte d'autorisation ? + +Réponse [ici](/docs/guides/examples/auth) + +[import-rule-layers]: /docs/reference/layers#import-rule-on-layers +[reference-entities]: /docs/reference/layers#entities +[eslint-config-official]: https://github.com/feature-sliced/eslint-config +[eslint-plugin-conarti]: https://github.com/conarti/eslint-plugin-feature-sliced +[motivation]: /docs/about/motivation +[telegram]: https://t.me/feature_sliced +[discord]: https://discord.gg/S8MzWTUsmp +[github-discussions]: https://github.com/feature-sliced/documentation/discussions diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/get-started/index.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/get-started/index.mdx new file mode 100644 index 0000000000..2c06c6c1a3 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/get-started/index.mdx @@ -0,0 +1,38 @@ +--- +hide_table_of_contents: true +pagination_prev: intro +--- + +import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" +import { RocketOutlined, PlaySquareOutlined, QuestionCircleOutlined } from "@ant-design/icons"; + +# 🚀 Démarrer + +

+Bienvenue ! Cette section vous aide à découvrir l'application de Feature-Sliced Design et les bases de la méthodologie. Vous comprendrez également les avantages clés de la méthodologie et les raisons de sa création. +

+ + + + +{/* */} diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/get-started/overview.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/get-started/overview.mdx new file mode 100644 index 0000000000..1ee3137708 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/get-started/overview.mdx @@ -0,0 +1,137 @@ +--- +sidebar_position: 1 +--- + +# Aperçu + +**Feature-Sliced Design** (FSD) est une méthodologie architecturale pour la structure des applications front-end. En termes simples, c'est un ensemble de règles et de conventions sur l'organisation du code. Le principal objectif de cette méthodologie est de rendre le projet plus compréhensible et stable face aux exigences commerciales en constante évolution. + +Outre un ensemble de conventions, FSD est également un ensemble d'outils. Nous avons un [linter][ext-steiger] pour vérifier l'architecture de votre projet, des [générateurs de dossiers][ext-tools] via un CLI ou des IDE, ainsi qu'une riche bibliothèque [d'exemples][examples]. + +## Est-ce fait pour moi ? {#is-it-right-for-me} + +FSD peut être mis en œuvre dans des projets et des équipes de toutes tailles. Il est adapté à votre projet si : + +- Vous travaillez sur le **frontend** (UI sur web, mobile, bureau, etc.) +- Vous construisez une **application**, pas une bibliothèque + +Et c'est tout ! Il n'y a aucune restriction sur le langage de programmation, le framework UI ou le gestionnaire d'état que vous utilisez. Vous pouvez également adopter FSD de manière incrémentielle, l'utiliser dans des monorepos et l'étendre en divisant votre application en packages et en appliquant FSD individuellement dans chacun d'eux. + +Si vous avez déjà une architecture en place et que vous envisagez de passer à FSD, assurez-vous que l'architecture actuelle **pose problème** dans votre équipe. Par exemple, si votre projet est devenu trop grand et trop interconnecté pour implémenter efficacement de nouvelles fonctionnalités, ou si vous attendez beaucoup de nouveaux membres dans l'équipe. Si l'architecture actuelle fonctionne, il n'est peut-être pas nécessaire de la changer. Mais si vous décidez de migrer, consultez la section [Migration][migration] pour obtenir des conseils. + +## Exemple de base {#basic-example} + +Voici un projet simple qui implémente FSD : + +- `📁 app` +- `📁 pages` +- `📁 shared` + +Ces dossiers de niveau supérieur sont appelés _couches_. Voyons plus en détail : + +- `📂 app` + - `📁 routes` + - `📁 analytics` +- `📂 pages` + - `📁 home` + - `📂 article-reader` + - `📁 ui` + - `📁 api` + - `📁 settings` +- `📂 shared` + - `📁 ui` + - `📁 api` + +Les dossiers à l'intérieur de `📂 pages` sont appelés _tranches_. Ils divisent la couche par domaine (dans ce cas, par pages). + +Les dossiers à l'intérieur de `📂 app`, `📂 shared`, et `📂 pages/article-reader` sont appelés _segments_, et ils divisent les tranches (ou les couches) par objectif technique, c'est-à-dire à quoi sert le code. + +## Concepts {#concepts} + +Les couches, les tranches et les segments forment une hiérarchie comme suit : + +
+ ![Hiérarchie des concepts de FSD, décrite ci-dessous](/img/visual_schema.jpg) + +
+

Illustration ci-dessus : trois piliers, étiquetés de gauche à droite comme "Couches", "Tranches" et "Segments".

+

Le pilier "Couches" contient sept divisions disposées de haut en bas et étiquetées "app", "processes", "pages", "widgets", "features", "entities", et "shared". La division "processes" est barrée. La division "entities" est reliée au deuxième pilier "Tranches" de manière à indiquer que ce pilier est le contenu de "entities".

+

Le pilier "Tranches" contient trois divisions disposées de haut en bas et étiquetées "user", "post", et "comment". La division "post" est reliée au troisième pilier "Segments" de la même manière, ce qui montre que c'est le contenu de "post".

+

Le pilier "Segments" contient trois divisions, disposées de haut en bas et étiquetées "ui", "model", et "api".

+
+
+ +### Couches {#layers} + +Les couches sont normalisées dans tous les projets FSD. Vous n'êtes pas obligé d'utiliser toutes les couches, mais leurs noms sont importants. Il y en a actuellement sept (de haut en bas) : + +1. App\* — tout ce qui fait fonctionner l'application — routage, points d'entrée, styles globaux, fournisseurs. +2. Processes (obsolète) — scénarios complexes inter-pages. +3. Pages — pages complètes ou grandes parties de pages dans un routage imbriqué. +4. Widgets — gros morceaux autonomes de fonctionnalité ou d'UI, livrant généralement un cas d'utilisation complet. +5. Features — implémentations _réutilisées_ de fonctionnalités complètes du produit, c'est-à-dire des actions qui apportent de la valeur commerciale à l'utilisateur. +6. Entities — entités commerciales avec lesquelles le projet travaille, comme `user` ou `product`. +7. Shared\* — fonctionnalité réutilisable, surtout lorsqu'elle est détachée des spécificités du projet ou de l'entreprise, bien que ce ne soit pas nécessairement le cas. + +_\* — ces couches, App et Shared, contrairement aux autres, n'ont pas de tranches et sont composées directement de segments._ + +Le principe avec les couches est qu'un module d'une couche ne peut connaître et importer que des modules des couches strictement inférieures. + +### Tranches {#slices} + +Les tranches partitionnent le code par domaine commercial. Vous êtes libre de choisir n'importe quel nom pour elles et de créer autant de tranches que vous le souhaitez. Les tranches rendent votre codebase plus facile à naviguer en gardant les modules logiquement liés ensemble. + +Les tranches ne peuvent pas utiliser d'autres tranches sur la même couche, ce qui permet d'assurer une forte cohésion et un faible couplage. + +### Segments {#segments} + +Les tranches, ainsi que les couches App et Shared, sont composées de segments, et les segments regroupent votre code par son objectif. Les noms des segments ne sont pas contraints par la norme, mais il existe plusieurs noms conventionnels pour les objectifs les plus courants : + +- `ui` — tout ce qui est lié à l'affichage de l'UI : composants UI, formatage des dates, styles, etc. +- `api` — interactions avec le backend : fonctions de requêtes, types de données, mappeurs, etc. +- `model` — le modèle de données : schémas, interfaces, stores, et logique métier. +- `lib` — code de bibliothèque dont d'autres modules sur cette tranche ont besoin. +- `config` — fichiers de configuration et drapeaux de fonctionnalités. + +En général, ces segments suffisent pour la plupart des couches. Vous ne créeriez vos propres segments que dans Shared ou App, mais ce n'est pas une règle stricte. + +## Avantages {#advantages} + +- **Uniformité** + Étant donné que la structure est normalisée, les projets deviennent plus uniformes, ce qui facilite l'intégration des nouveaux membres dans l'équipe. + +- **Stabilité face aux changements et aux refactorisations** + Un module sur une couche ne peut pas utiliser d'autres modules sur la même couche, ni ceux des couches supérieures. + Cela vous permet d'effectuer des modifications isolées sans conséquences imprévues sur le reste de l'application. + +- **Réutilisation contrôlée de la logique** + Selon la couche, vous pouvez rendre le code très réutilisable ou très local. + Cela permet de maintenir un équilibre entre le principe **DRY** (Don't Repeat Yourself) et la praticité. + +- **Orientation vers les besoins commerciaux et des utilisateurs** + L'application est divisée en domaines commerciaux et l'utilisation du langage commercial est encouragée dans la dénomination, afin que vous puissiez effectuer un travail utile pour le produit sans avoir à comprendre en profondeur toutes les autres parties du projet. + +## Adoption incrémentale {#incremental-adoption} + +Si vous avez une base de code existante que vous souhaitez migrer vers FSD, nous suggérons la stratégie suivante. Nous l'avons trouvée utile lors de notre propre expérience de migration. + +1. Commencez lentement à structurer les modules des couches App et Shared, module par module, pour créer une base solide. + +2. Distribuez toute l'UI existante sur les Widgets et Pages en prenant de grandes lignes, même si elles ont des dépendances qui violent les règles de FSD. + +3. Commencez progressivement à résoudre les violations d'importation et à extraire les Entities et éventuellement même les Features. + +Il est conseillé de ne pas ajouter de nouvelles entités importantes pendant la refactorisation ou de ne refactoriser que certaines parties du projet. + +## Étapes suivantes {#next-steps} + +- **Vous voulez bien comprendre comment penser en FSD ?** Consultez le [Tutoriel][tutorial]. +- **Vous préférez apprendre par des exemples ?** Nous en avons beaucoup dans la section [Exemples][examples]. +- **Des questions ?** Venez sur notre [chat Telegram][ext-telegram] et obtenez de l'aide de la communauté. + +[tutorial]: /docs/get-started/tutorial +[examples]: /examples +[migration]: /docs/guides/migration/from-custom +[ext-steiger]: https://github.com/feature-sliced/steiger +[ext-tools]: https://github.com/feature-sliced/awesome?tab=readme-ov-file#tools +[ext-telegram]: https://t.me/feature_sliced diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/get-started/tutorial.md b/i18n/fr/docusaurus-plugin-content-docs/current/get-started/tutorial.md new file mode 100644 index 0000000000..295b78c8e8 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/get-started/tutorial.md @@ -0,0 +1,2189 @@ +--- +sidebar_position: 2 +--- +# Tutoriel + +## Partie 1. Sur le papier + +Ce tutoriel va examiner l'application Real World App, également connue sous le nom de Conduit. Conduit est un clone simple de [Medium](https://medium.com/) — il vous permet de lire et d'écrire des articles ainsi que de commenter les articles des autres. + +![Page d'accueil de Conduit](/img/tutorial/realworld-feed-anonymous.jpg) + +Il s'agit d'une application assez petite, donc nous allons la garder simple et éviter une décomposition excessive. Il est très probable que l'ensemble de l'application tienne dans seulement trois couches : **App**, **Pages**, et **Shared**. Si ce n'est pas le cas, nous introduirons des couches supplémentaires au fur et à mesure. Prêt ? + +### Commencez par lister les pages + +Si nous regardons la capture d'écran ci-dessus, nous pouvons supposer au moins les pages suivantes : + +- Accueil (fil d'articles) +- Connexion et inscription +- Lecteur d'articles +- Éditeur d'articles +- Vue du profil utilisateur +- Éditeur du profil utilisateur (paramètres de l'utilisateur) + + +Chacune de ces pages deviendra son propre *slice* dans la *couche* Pages. Rappelez-vous de l'aperçu, les slices sont simplement des dossiers à l'intérieur des couches, et les couches sont simplement des dossiers avec des noms prédéfinis comme `pages`. + +Ainsi, notre dossier Pages ressemblera à ceci : + + +``` +📂 pages/ + 📁 feed/ + 📁 sign-in/ + 📁 article-read/ + 📁 article-edit/ + 📁 profile/ + 📁 settings/ +``` + +La différence clé entre le Feature-Sliced Design et une structure de code non régulée est que les pages ne peuvent pas se référencer entre elles. Autrement dit, une page ne peut pas importer du code d'une autre page. Cela est dû à la règle d'importation sur les couches : + +Un module dans une slice ne peut importer d'autres slices que lorsqu'elles se trouvent sur des couches strictement inférieures. + +Dans ce cas, une page est une slice, donc les modules (fichiers) à l'intérieur de cette page ne peuvent se référer qu'au code des couches inférieures, et non de la même couche, Pages. + +### Vue détaillée du flux + +
+ ![Vue de l'utilisateur anonyme](/img/tutorial/realworld-feed-anonymous.jpg) +
+ _Vue de l'utilisateur anonyme_ +
+
+ +
+ ![Vue de l'utilisateur authentifié](/img/tutorial/realworld-feed-authenticated.jpg) +
+ _Vue de l'utilisateur authentifié_ +
+
+ + +Il y a trois zones dynamiques sur la page de flux : + +1. Les liens de connexion avec une indication si l'utilisateur est connecté +2. La liste des tags qui déclenche le filtrage dans le flux +3. Un/deux flux d'articles, chaque article avec un bouton "J'aime" + +Les liens de connexion font partie d'un en-tête qui est commun à toutes les pages, nous reviendrons dessus séparément. + +#### Liste des tags + +Pour construire la liste des tags, nous devons récupérer les tags disponibles, rendre chaque tag sous forme de puce, et stocker les tags sélectionnés dans un stockage côté client. Ces opérations appartiennent aux catégories "Interaction avec l'API", "Interface utilisateur" et "Stockage", respectivement. Dans le cadre de Feature-Sliced Design, le code est séparé par objectif à l'aide des *segments*. Les segments sont des dossiers dans les slices, et ils peuvent avoir des noms arbitraires décrivant leur objectif, mais certains objectifs sont tellement courants qu'il existe une convention pour certains noms de segments : + +- 📂 `api/` pour les interactions avec le backend +- 📂 `ui/` pour le code qui gère l'affichage et l'apparence +- 📂 `model/` pour le stockage et la logique métier +- 📂 `config/` pour les indicateurs de fonctionnalité, les variables d'environnement et autres formes de configuration + +Nous allons placer le code qui récupère les tags dans `api`, le composant de tag dans `ui`, et l'interaction avec le stockage dans `model`. + + +#### Articles + +En utilisant les mêmes principes de regroupement, nous pouvons décomposer le flux d'articles en trois segments identiques : + +- 📂 `api/` : récupérer les articles paginés avec le nombre de "J'aime" ; aimer un article +- 📂 `ui/` : + - liste d'onglets qui peut afficher un onglet supplémentaire si un tag est sélectionné + - article individuel + - pagination fonctionnelle +- 📂 `model/` : stockage côté client des articles actuellement chargés et de la page en cours (si nécessaire) + +### Réutilisation du code générique + +La plupart des pages ont des objectifs très différents, mais certaines choses restent les mêmes dans toute l'application — par exemple, l'UI kit qui respecte le langage de design, ou la convention côté backend que tout est fait avec une API REST utilisant la même méthode d'authentification. Puisque les slices sont censées être isolées, la réutilisation du code est facilitée par une couche inférieure, **Shared**. + +Shared diffère des autres couches dans le sens où elle contient des segments, et non des slices. De cette façon, la couche Shared peut être considérée comme un hybride entre une couche et une slice. + +En général, le code dans Shared n'est pas planifié à l'avance, mais plutôt extrait au fur et à mesure du développement, car c'est seulement en cours de développement qu'il devient clair quelles parties du code sont réellement partagées. Cependant, il est toujours utile de garder à l'esprit quel type de code appartient naturellement à Shared : + +- 📂 `ui/` — l'UI kit, pure apparence, pas de logique métier. Par exemple, les boutons, les boîtes de dialogue modales, les champs de formulaire. +- 📂 `api/` — des wrappers pratiques autour des primitives de requêtes (comme `fetch()` sur le Web) et, éventuellement, des fonctions pour déclencher des requêtes particulières selon la spécification du backend. +- 📂 `config/` — parsing des variables d'environnement +- 📂 `i18n/` — configuration du support des langues +- 📂 `router/` — primitives de routage et constantes de route + +Ce ne sont que quelques exemples de noms de segments dans Shared, mais vous pouvez en omettre certains ou créer les vôtres. La seule chose importante à retenir lorsque vous créez de nouveaux segments, c'est que les noms de segments doivent décrire **l'objectif (le pourquoi), pas l'essence (le quoi)**. Des noms comme « composants », « hooks », « modals » *ne doivent pas* être utilisés, car ils décrivent ce que ces fichiers sont, mais ne facilitent pas la navigation dans le code. Cela oblige les membres de l'équipe à fouiller dans chaque fichier de ces dossiers et garde également le code non lié à proximité, ce qui entraîne des zones larges de code affectées par le refactoring et rend ainsi la revue de code et les tests plus difficiles. + +### Définir une API publique stricte + +Dans le cadre de Feature-Sliced Design, le terme *API publique* fait référence à une slice ou un segment déclarant ce qui peut être importé par d'autres modules dans le projet. Par exemple, en JavaScript, cela peut être un fichier `index.js` réexportant des objets provenant d'autres fichiers dans la slice. Cela permet de refactoriser librement le code à l'intérieur d'une slice tant que le contrat avec l'extérieur (c'est-à-dire l'API publique) reste le même. + +Pour la couche Shared qui n'a pas de slices, il est généralement plus pratique de définir une API publique séparée pour chaque segment plutôt que de définir un seul index pour tout Shared. Cela permet de garder les imports de Shared naturellement organisés par objectif. Pour les autres couches qui ont des slices, l'inverse est vrai — il est généralement plus pratique de définir un index par slice et de laisser la slice décider de son propre ensemble de segments qui est inconnu de l'extérieur, car les autres couches ont généralement beaucoup moins d'exports. + +Nos slices/segments apparaîtront les uns aux autres comme suit : + + +``` +📂 pages/ + 📂 feed/ + 📄 index + 📂 sign-in/ + 📄 index + 📂 article-read/ + 📄 index + 📁 … +📂 shared/ + 📂 ui/ + 📄 index + 📂 api/ + 📄 index + 📁 … +``` +Ce qui se trouve à l'intérieur des dossiers comme `pages/feed` ou `shared/ui` n'est connu que de ces dossiers, et les autres fichiers ne doivent pas dépendre de la structure interne de ces dossiers. + +### Blocs réutilisés de grande taille dans l'UI + +Plus tôt, nous avons mentionné de revenir sur l'en-tête qui apparaît sur chaque page. Le reconstruire à partir de zéro sur chaque page serait peu pratique, donc il est tout à fait naturel de vouloir le réutiliser. Nous avons déjà Shared pour faciliter la réutilisation du code, cependant, il y a une mise en garde à propos de la mise en place de gros blocs d'UI dans Shared — la couche Shared ne doit pas connaître les couches situées au-dessus d'elle. + +Entre Shared et Pages, il y a trois autres couches : Entities, Features, et Widgets. Certains projets peuvent avoir des éléments dans ces couches qu'ils doivent utiliser dans un grand bloc réutilisable, ce qui signifie que nous ne pouvons pas placer ce bloc réutilisable dans Shared, sinon il importerait des couches supérieures, ce qui est interdit. C'est là qu'intervient la couche Widgets. Elle est située au-dessus de Shared, Entities et Features, donc elle peut utiliser toutes ces couches. + +Dans notre cas, l'en-tête est très simple — il s'agit d'un logo statique et d'une navigation de haut niveau. La navigation doit faire une requête à l'API pour déterminer si l'utilisateur est actuellement connecté ou non, mais cela peut être géré par un simple import du segment `api`. Par conséquent, nous conserverons notre en-tête dans Shared. + +### Analyse d'une page avec un formulaire + +Examinons également une page destinée à la modification, et non à la lecture. Par exemple, l'éditeur d'articles : + +![Conduit post editor](/img/tutorial/realworld-editor-authenticated.jpg) + +Cela semble trivial, mais comporte plusieurs aspects du développement d'application que nous n'avons pas encore explorés — validation des formulaires, états d'erreur, et persistance des données. + +Si nous devions créer cette page, nous prendrions quelques champs de saisie et boutons depuis Shared et assemblerions un formulaire dans le segment `ui` de cette page. Ensuite, dans le segment `api`, nous définirions une requête de mutation pour créer l'article sur le backend. + +Pour valider la requête avant de l'envoyer, nous aurions besoin d'un schéma de validation, et un bon endroit pour cela est le segment `model`, car il s'agit du modèle de données. Là, nous produirions des messages d'erreur et les afficherions en utilisant un autre composant dans le segment `ui`. + +Pour améliorer l'expérience utilisateur, nous pourrions également persister les entrées pour éviter toute perte de données accidentelle. Cela relève également du segment `model`. + +### Résumé + +Nous avons examiné plusieurs pages et esquissé une structure préliminaire pour notre application : + +1. Couche Shared + 1. `ui` contiendra notre kit UI réutilisable + 2. `api` contiendra nos interactions primitives avec le backend + 3. Le reste sera organisé au besoin +2. Couche Pages — chaque page est une slice distincte + 1. `ui` contiendra la page elle-même et toutes ses parties + 2. `api` contiendra des récupérations de données plus spécialisées, utilisant `shared/api` + 3. `model` pourrait contenir le stockage côté client des données que nous afficherons + +Passons à la construction ! + +## Partie 2. En code + +Maintenant que nous avons un plan, mettons-le en pratique. Nous utiliserons React et [Remix](https://remix.run). + +Un modèle est prêt pour ce projet, clonez-le depuis GitHub pour démarrer rapidement : [https://github.com/feature-sliced/tutorial-conduit/tree/clean](https://github.com/feature-sliced/tutorial-conduit/tree/clean). + +Installez les dépendances avec `npm install` et lancez le serveur de développement avec `npm run dev`. Ouvrez [http://localhost:3000](http://localhost:3000) et vous devriez voir une application vide. + +Run the following command to generate up-to-date API typings: + +```bash +npm run generate-api-types +``` + +This will create a file `shared/api/v1.d.ts`. We will use this file to create a typed API client in `shared/api/client.ts`: + +```tsx title="shared/api/client.ts" +import createClient from "openapi-fetch"; + +import { backendBaseUrl } from "shared/config"; +import type { paths } from "./v1"; + +export const { GET, POST, PUT, DELETE } = createClient({ baseUrl: backendBaseUrl }); +``` + +```tsx title="shared/api/index.ts" +export { GET, POST, PUT, DELETE } from "./client"; +``` + +### Real data in the feed + +We can now proceed to adding articles to the feed, fetched from the backend. Let’s begin by implementing an article preview component. + +Create `pages/feed/ui/ArticlePreview.tsx` with the following content: + +```tsx title="pages/feed/ui/ArticlePreview.tsx" +export function ArticlePreview({ article }) { /* TODO */ } +``` + +Since we’re writing in TypeScript, it would be nice to have a typed article object. If we explore the generated `v1.d.ts`, we can see that the article object is available through `components["schemas"]["Article"]`. So let’s create a file with our data models in Shared and export the models: + +```tsx title="shared/api/models.ts" +import type { components } from "./v1"; + +export type Article = components["schemas"]["Article"]; +``` + +```tsx title="shared/api/index.ts" +export { GET, POST, PUT, DELETE } from "./client"; + +export type { Article } from "./models"; +``` + +Now we can come back to the article preview component and fill the markup with data. Update the component with the following content: + +```tsx title="pages/feed/ui/ArticlePreview.tsx" +import { Link } from "@remix-run/react"; +import type { Article } from "shared/api"; + +interface ArticlePreviewProps { + article: Article; +} + +export function ArticlePreview({ article }: ArticlePreviewProps) { + return ( +
+
+ + + +
+ + {article.author.username} + + + {new Date(article.createdAt).toLocaleDateString(undefined, { + dateStyle: "long", + })} + +
+ +
+ +

{article.title}

+

{article.description}

+ Read more... +
    + {article.tagList.map((tag) => ( +
  • + {tag} +
  • + ))} +
+ +
+ ); +} +``` + +The like button doesn’t do anything for now, we will fix that when we get to the article reader page and implement the liking functionality. + +Now we can fetch the articles and render out a bunch of these cards. Fetching data in Remix is done with *loaders* — server-side functions that fetch exactly what a page needs. Loaders interact with the API on the page’s behalf, so we will put them in the `api` segment of a page: + +```tsx title="pages/feed/api/loader.ts" +import { json } from "@remix-run/node"; + +import { GET } from "shared/api"; + +export const loader = async () => { + const { data: articles, error, response } = await GET("/articles"); + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return json({ articles }); +}; +``` + +To connect it to the page, we need to export it with the name `loader` from the route file: + +```tsx title="pages/feed/index.ts" +export { FeedPage } from "./ui/FeedPage"; +export { loader } from "./api/loader"; +``` + +```tsx title="app/routes/_index.tsx" +import type { MetaFunction } from "@remix-run/node"; +import { FeedPage } from "pages/feed"; + +export { loader } from "pages/feed"; + +export const meta: MetaFunction = () => { + return [{ title: "Conduit" }]; +}; + +export default FeedPage; +``` + +And the final step is to render these cards in the feed. Update your `FeedPage` with the following code: + +```tsx title="pages/feed/ui/FeedPage.tsx" +import { useLoaderData } from "@remix-run/react"; + +import type { loader } from "../api/loader"; +import { ArticlePreview } from "./ArticlePreview"; + +export function FeedPage() { + const { articles } = useLoaderData(); + + return ( +
+
+
+

conduit

+

A place to share your knowledge.

+
+
+ +
+
+
+ {articles.articles.map((article) => ( + + ))} +
+
+
+
+ ); +} +``` + +### Filtering by tag + +Regarding the tags, our job is to fetch them from the backend and to store the currently selected tag. We already know how to do fetching — it’s another request from the loader. We will use a convenience function `promiseHash` from a package `remix-utils`, which is already installed. + +Update the loader file, `pages/feed/api/loader.ts`, with the following code: + +```tsx title="pages/feed/api/loader.ts" +import { json } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; + +import { GET } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return data as NonNullable; +} + +export const loader = async () => { + return json( + await promiseHash({ + articles: throwAnyErrors(GET("/articles")), + tags: throwAnyErrors(GET("/tags")), + }), + ); +}; +``` + +You might notice that we extracted the error handling into a generic function `throwAnyErrors`. It looks pretty useful, so we might want to reuse it later, but for now let’s just keep an eye on it. + +Now, to the list of tags. It needs to be interactive — clicking on a tag should make that tag selected. By Remix convention, we will use the URL search parameters as our storage for the selected tag. Let the browser take care of storage while we focus on more important things. + +Update `pages/feed/ui/FeedPage.tsx` with the following code: + +```tsx title="pages/feed/ui/FeedPage.tsx" +import { Form, useLoaderData } from "@remix-run/react"; +import { ExistingSearchParams } from "remix-utils/existing-search-params"; + +import type { loader } from "../api/loader"; +import { ArticlePreview } from "./ArticlePreview"; + +export function FeedPage() { + const { articles, tags } = useLoaderData(); + + return ( +
+
+
+

conduit

+

A place to share your knowledge.

+
+
+ +
+
+
+ {articles.articles.map((article) => ( + + ))} +
+ +
+
+

Popular Tags

+ +
+ +
+ {tags.tags.map((tag) => ( + + ))} +
+ +
+
+
+
+
+ ); +} +``` + +Then we need to use the `tag` search parameter in our loader. Change the `loader` function in `pages/feed/api/loader.ts` to the following: + +```tsx title="pages/feed/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; + +import { GET } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return data as NonNullable; +} + +export const loader = async ({ request }: LoaderFunctionArgs) => { + const url = new URL(request.url); + const selectedTag = url.searchParams.get("tag") ?? undefined; + + return json( + await promiseHash({ + articles: throwAnyErrors( + GET("/articles", { params: { query: { tag: selectedTag } } }), + ), + tags: throwAnyErrors(GET("/tags")), + }), + ); +}; +``` + +That’s it, no `model` segment necessary. Remix is pretty neat. + +### Pagination + +In a similar fashion, we can implement the pagination. Feel free to give it a shot yourself or just copy the code below. There’s no one to judge you anyway. + +```tsx title="pages/feed/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; + +import { GET } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return data as NonNullable; +} + +/** Amount of articles on one page. */ +export const LIMIT = 20; + +export const loader = async ({ request }: LoaderFunctionArgs) => { + const url = new URL(request.url); + const selectedTag = url.searchParams.get("tag") ?? undefined; + const page = parseInt(url.searchParams.get("page") ?? "", 10); + + return json( + await promiseHash({ + articles: throwAnyErrors( + GET("/articles", { + params: { + query: { + tag: selectedTag, + limit: LIMIT, + offset: !Number.isNaN(page) ? page * LIMIT : undefined, + }, + }, + }), + ), + tags: throwAnyErrors(GET("/tags")), + }), + ); +}; +``` + +```tsx title="pages/feed/ui/FeedPage.tsx" +import { Form, useLoaderData, useSearchParams } from "@remix-run/react"; +import { ExistingSearchParams } from "remix-utils/existing-search-params"; + +import { LIMIT, type loader } from "../api/loader"; +import { ArticlePreview } from "./ArticlePreview"; + +export function FeedPage() { + const [searchParams] = useSearchParams(); + const { articles, tags } = useLoaderData(); + const pageAmount = Math.ceil(articles.articlesCount / LIMIT); + const currentPage = parseInt(searchParams.get("page") ?? "1", 10); + + return ( +
+
+
+

conduit

+

A place to share your knowledge.

+
+
+ +
+
+
+ {articles.articles.map((article) => ( + + ))} + +
+ +
    + {Array(pageAmount) + .fill(null) + .map((_, index) => + index + 1 === currentPage ? ( +
  • + {index + 1} +
  • + ) : ( +
  • + +
  • + ), + )} +
+ +
+ +
+
+

Popular Tags

+ +
+ +
+ {tags.tags.map((tag) => ( + + ))} +
+ +
+
+
+
+
+ ); +} +``` + +So that’s also done. There’s also the tab list that can be similarly implemented, but let’s hold on to that until we implement authentication. Speaking of which! + +### Authentication + +Authentication involves two pages — one to login and another to register. They are mostly the same, so it makes sense to keep them in the same slice, `sign-in`, so that they can reuse code if needed. + +Create `RegisterPage.tsx` in the `ui` segment of `pages/sign-in` with the following content: + +```tsx title="pages/sign-in/ui/RegisterPage.tsx" +import { Form, Link, useActionData } from "@remix-run/react"; + +import type { register } from "../api/register"; + +export function RegisterPage() { + const registerData = useActionData(); + + return ( +
+
+
+
+

Sign up

+

+ Have an account? +

+ + {registerData?.error && ( +
    + {registerData.error.errors.body.map((error) => ( +
  • {error}
  • + ))} +
+ )} + +
+
+ +
+
+ +
+
+ +
+ +
+
+
+
+
+ ); +} +``` + +We have a broken import to fix now. It involves a new segment, so create that: + +```bash +npx fsd pages sign-in -s api +``` + +However, before we can implement the backend part of registering, we need some infrastructure code for Remix to handle sessions. That goes to Shared, in case any other page needs it. + +Put the following code in `shared/api/auth.server.ts`. This is highly Remix-specific, so don’t worry too much about it, just copy-paste: + +```tsx title="shared/api/auth.server.ts" +import { createCookieSessionStorage, redirect } from "@remix-run/node"; +import invariant from "tiny-invariant"; + +import type { User } from "./models"; + +invariant( + process.env.SESSION_SECRET, + "SESSION_SECRET must be set for authentication to work", +); + +const sessionStorage = createCookieSessionStorage<{ + user: User; +}>({ + cookie: { + name: "__session", + httpOnly: true, + path: "/", + sameSite: "lax", + secrets: [process.env.SESSION_SECRET], + secure: process.env.NODE_ENV === "production", + }, +}); + +export async function createUserSession({ + request, + user, + redirectTo, +}: { + request: Request; + user: User; + redirectTo: string; +}) { + const cookie = request.headers.get("Cookie"); + const session = await sessionStorage.getSession(cookie); + + session.set("user", user); + + return redirect(redirectTo, { + headers: { + "Set-Cookie": await sessionStorage.commitSession(session, { + maxAge: 60 * 60 * 24 * 7, // 7 days + }), + }, + }); +} + +export async function getUserFromSession(request: Request) { + const cookie = request.headers.get("Cookie"); + const session = await sessionStorage.getSession(cookie); + + return session.get("user") ?? null; +} + +export async function requireUser(request: Request) { + const user = await getUserFromSession(request); + + if (user === null) { + throw redirect("/login"); + } + + return user; +} +``` + +And also export the `User` model from the `models.ts` file right next to it: + +```tsx title="shared/api/models.ts" +import type { components } from "./v1"; + +export type Article = components["schemas"]["Article"]; +export type User = components["schemas"]["User"]; +``` + +Before this code can work, the `SESSION_SECRET` environment variable needs to be set. Create a file called `.env` in the root of the project, write `SESSION_SECRET=` and then mash some keys on your keyboard to create a long random string. You should get something like this: + +```bash title=".env" +SESSION_SECRET=dontyoudarecopypastethis +``` + +Finally, add some exports to the public API to make use of this code: + +```tsx title="shared/api/index.ts" +export { GET, POST, PUT, DELETE } from "./client"; + +export type { Article } from "./models"; + +export { createUserSession, getUserFromSession, requireUser } from "./auth.server"; +``` + +Now we can write the code that will talk to the RealWorld backend to actually do the registration. We will keep that in `pages/sign-in/api`. Create a file called `register.ts` and put the following code inside: + +```tsx title="pages/sign-in/api/register.ts" +import { json, type ActionFunctionArgs } from "@remix-run/node"; + +import { POST, createUserSession } from "shared/api"; + +export const register = async ({ request }: ActionFunctionArgs) => { + const formData = await request.formData(); + const username = formData.get("username")?.toString() ?? ""; + const email = formData.get("email")?.toString() ?? ""; + const password = formData.get("password")?.toString() ?? ""; + + const { data, error } = await POST("/users", { + body: { user: { email, password, username } }, + }); + + if (error) { + return json({ error }, { status: 400 }); + } else { + return createUserSession({ + request: request, + user: data.user, + redirectTo: "/", + }); + } +}; +``` + +```tsx title="pages/sign-in/index.ts" +export { RegisterPage } from './ui/RegisterPage'; +export { register } from './api/register'; +``` + +Almost done! Just need to connect the page and action to the `/register` route. Create `register.tsx` in `app/routes`: + +```tsx title="app/routes/register.tsx" +import { RegisterPage, register } from "pages/sign-in"; + +export { register as action }; + +export default RegisterPage; +``` + +Now if you go to [http://localhost:3000/register](http://localhost:3000/register), you should be able to create a user! The rest of the application won’t react to this yet, we’ll address that momentarily. + +In a very similar way, we can implement the login page. Give it a try or just grab the code and move on: + +```tsx title="pages/sign-in/api/sign-in.ts" +import { json, type ActionFunctionArgs } from "@remix-run/node"; + +import { POST, createUserSession } from "shared/api"; + +export const signIn = async ({ request }: ActionFunctionArgs) => { + const formData = await request.formData(); + const email = formData.get("email")?.toString() ?? ""; + const password = formData.get("password")?.toString() ?? ""; + + const { data, error } = await POST("/users/login", { + body: { user: { email, password } }, + }); + + if (error) { + return json({ error }, { status: 400 }); + } else { + return createUserSession({ + request: request, + user: data.user, + redirectTo: "/", + }); + } +}; +``` + +```tsx title="pages/sign-in/ui/SignInPage.tsx" +import { Form, Link, useActionData } from "@remix-run/react"; + +import type { signIn } from "../api/sign-in"; + +export function SignInPage() { + const signInData = useActionData(); + + return ( +
+
+
+
+

Sign in

+

+ Need an account? +

+ + {signInData?.error && ( +
    + {signInData.error.errors.body.map((error) => ( +
  • {error}
  • + ))} +
+ )} + +
+
+ +
+
+ +
+ +
+
+
+
+
+ ); +} +``` + +```tsx title="pages/sign-in/index.ts" +export { RegisterPage } from './ui/RegisterPage'; +export { register } from './api/register'; +export { SignInPage } from './ui/SignInPage'; +export { signIn } from './api/sign-in'; +``` + +```tsx title="app/routes/login.tsx" +import { SignInPage, signIn } from "pages/sign-in"; + +export { signIn as action }; + +export default SignInPage; +``` + +Now let’s give the users a way to actually get to these pages. + +### Header + +As we discussed in part 1, the app header is commonly placed either in Widgets or in Shared. We will put it in Shared because it’s very simple and all the business logic can be kept outside of it. Let’s create a place for it: + +```bash +npx fsd shared ui +``` + +Now create `shared/ui/Header.tsx` with the following contents: + +```tsx title="shared/ui/Header.tsx" +import { useContext } from "react"; +import { Link, useLocation } from "@remix-run/react"; + +import { CurrentUser } from "../api/currentUser"; + +export function Header() { + const currentUser = useContext(CurrentUser); + const { pathname } = useLocation(); + + return ( + + ); +} +``` + +Export this component from `shared/ui`: + +```tsx title="shared/ui/index.ts" +export { Header } from "./Header"; +``` + +In the header, we rely on the context that’s kept in `shared/api`. Create that as well: + +```tsx title="shared/api/currentUser.ts" +import { createContext } from "react"; + +import type { User } from "./models"; + +export const CurrentUser = createContext(null); +``` + +```tsx title="shared/api/index.ts" +export { GET, POST, PUT, DELETE } from "./client"; + +export type { Article } from "./models"; + +export { createUserSession, getUserFromSession, requireUser } from "./auth.server"; +export { CurrentUser } from "./currentUser"; +``` + +Now let’s add the header to the page. We want it to be on every single page, so it makes sense to simply add it to the root route and wrap the outlet (the place where the page will be rendered) with the `CurrentUser` context provider. This way our entire app and also the header has access to the current user object. We will also add a loader to actually obtain the current user object from cookies. Drop the following into `app/root.tsx`: + +```tsx title="app/root.tsx" +import { cssBundleHref } from "@remix-run/css-bundle"; +import type { LinksFunction, LoaderFunctionArgs } from "@remix-run/node"; +import { + Links, + LiveReload, + Meta, + Outlet, + Scripts, + ScrollRestoration, + useLoaderData, +} from "@remix-run/react"; + +import { Header } from "shared/ui"; +import { getUserFromSession, CurrentUser } from "shared/api"; + +export const links: LinksFunction = () => [ + ...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []), +]; + +export const loader = ({ request }: LoaderFunctionArgs) => + getUserFromSession(request); + +export default function App() { + const user = useLoaderData(); + + return ( + + + + + + + + + + + + + +
+ + + + + + + + ); +} +``` + +At this point, you should end up with the following on the home page: + +
+ ![The feed page of Conduit, including the header, the feed, and the tags. The tabs are still missing.](/img/tutorial/realworld-feed-without-tabs.jpg) + +
The feed page of Conduit, including the header, the feed, and the tags. The tabs are still missing.
+
+ +### Tabs + +Now that we can detect the authentication state, let’s also quickly implement the tabs and post likes to be done with the feed page. We need another form, but this page file is getting kind of large, so let’s move these forms into adjacent files. We will create `Tabs.tsx`, `PopularTags.tsx`, and `Pagination.tsx` with the following content: + +```tsx title="pages/feed/ui/Tabs.tsx" +import { useContext } from "react"; +import { Form, useSearchParams } from "@remix-run/react"; + +import { CurrentUser } from "shared/api"; + +export function Tabs() { + const [searchParams] = useSearchParams(); + const currentUser = useContext(CurrentUser); + + return ( +
+
+
    + {currentUser !== null && ( +
  • + +
  • + )} +
  • + +
  • + {searchParams.has("tag") && ( +
  • + + {searchParams.get("tag")} + +
  • + )} +
+
+
+ ); +} +``` + +```tsx title="pages/feed/ui/PopularTags.tsx" +import { Form, useLoaderData } from "@remix-run/react"; +import { ExistingSearchParams } from "remix-utils/existing-search-params"; + +import type { loader } from "../api/loader"; + +export function PopularTags() { + const { tags } = useLoaderData(); + + return ( +
+

Popular Tags

+ +
+ +
+ {tags.tags.map((tag) => ( + + ))} +
+ +
+ ); +} +``` + +```tsx title="pages/feed/ui/Pagination.tsx" +import { Form, useLoaderData, useSearchParams } from "@remix-run/react"; +import { ExistingSearchParams } from "remix-utils/existing-search-params"; + +import { LIMIT, type loader } from "../api/loader"; + +export function Pagination() { + const [searchParams] = useSearchParams(); + const { articles } = useLoaderData(); + const pageAmount = Math.ceil(articles.articlesCount / LIMIT); + const currentPage = parseInt(searchParams.get("page") ?? "1", 10); + + return ( +
+ +
    + {Array(pageAmount) + .fill(null) + .map((_, index) => + index + 1 === currentPage ? ( +
  • + {index + 1} +
  • + ) : ( +
  • + +
  • + ), + )} +
+ + ); +} +``` + +And now we can significantly simplify the feed page itself: + +```tsx title="pages/feed/ui/FeedPage.tsx" +import { useLoaderData } from "@remix-run/react"; + +import type { loader } from "../api/loader"; +import { ArticlePreview } from "./ArticlePreview"; +import { Tabs } from "./Tabs"; +import { PopularTags } from "./PopularTags"; +import { Pagination } from "./Pagination"; + +export function FeedPage() { + const { articles } = useLoaderData(); + + return ( +
+
+
+

conduit

+

A place to share your knowledge.

+
+
+ +
+
+
+ + + {articles.articles.map((article) => ( + + ))} + + +
+ +
+ +
+
+
+
+ ); +} +``` + +We also need to account for the new tab in the loader function: + +```tsx title="pages/feed/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; + +import { GET, requireUser } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + /* unchanged */ +} + +/** Amount of articles on one page. */ +export const LIMIT = 20; + +export const loader = async ({ request }: LoaderFunctionArgs) => { + const url = new URL(request.url); + const selectedTag = url.searchParams.get("tag") ?? undefined; + const page = parseInt(url.searchParams.get("page") ?? "", 10); + + if (url.searchParams.get("source") === "my-feed") { + const userSession = await requireUser(request); + + return json( + await promiseHash({ + articles: throwAnyErrors( + GET("/articles/feed", { + params: { + query: { + limit: LIMIT, + offset: !Number.isNaN(page) ? page * LIMIT : undefined, + }, + }, + headers: { Authorization: `Token ${userSession.token}` }, + }), + ), + tags: throwAnyErrors(GET("/tags")), + }), + ); + } + + return json( + await promiseHash({ + articles: throwAnyErrors( + GET("/articles", { + params: { + query: { + tag: selectedTag, + limit: LIMIT, + offset: !Number.isNaN(page) ? page * LIMIT : undefined, + }, + }, + }), + ), + tags: throwAnyErrors(GET("/tags")), + }), + ); +}; +``` + +Before we leave the feed page, let’s add some code that handles likes to posts. Change your `ArticlePreview.tsx` to the following: + +```tsx title="pages/feed/ui/ArticlePreview.tsx" +import { Form, Link } from "@remix-run/react"; +import type { Article } from "shared/api"; + +interface ArticlePreviewProps { + article: Article; +} + +export function ArticlePreview({ article }: ArticlePreviewProps) { + return ( +
+
+ + + +
+ + {article.author.username} + + + {new Date(article.createdAt).toLocaleDateString(undefined, { + dateStyle: "long", + })} + +
+
+ +
+
+ +

{article.title}

+

{article.description}

+ Read more... +
    + {article.tagList.map((tag) => ( +
  • + {tag} +
  • + ))} +
+ +
+ ); +} +``` + +This code will send a POST request to `/article/:slug` with `_action=favorite` to mark the article as favorite. It won’t work yet, but as we start working on the article reader, we will implement this too. + +And with that we are officially done with the feed! Yay! + +### Article reader + +First, we need data. Let’s create a loader: + +```bash +npx fsd pages article-read -s api +``` + +```tsx title="pages/article-read/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import invariant from "tiny-invariant"; +import type { FetchResponse } from "openapi-fetch"; +import { promiseHash } from "remix-utils/promise"; + +import { GET, getUserFromSession } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return data as NonNullable; +} + +export const loader = async ({ request, params }: LoaderFunctionArgs) => { + invariant(params.slug, "Expected a slug parameter"); + const currentUser = await getUserFromSession(request); + const authorization = currentUser + ? { Authorization: `Token ${currentUser.token}` } + : undefined; + + return json( + await promiseHash({ + article: throwAnyErrors( + GET("/articles/{slug}", { + params: { + path: { slug: params.slug }, + }, + headers: authorization, + }), + ), + comments: throwAnyErrors( + GET("/articles/{slug}/comments", { + params: { + path: { slug: params.slug }, + }, + headers: authorization, + }), + ), + }), + ); +}; +``` + +```tsx title="pages/article-read/index.ts" +export { loader } from "./api/loader"; +``` + +Now we can connect it to the route `/article/:slug` by creating the a route file called `article.$slug.tsx`: + +```tsx title="app/routes/article.$slug.tsx" +export { loader } from "pages/article-read"; +``` + +The page itself consists of three main blocks — the article header with actions (repeated twice), the article body, and the comments section. This is the markup for the page, it’s not particularly interesting: + +```tsx title="pages/article-read/ui/ArticleReadPage.tsx" +import { useLoaderData } from "@remix-run/react"; + +import type { loader } from "../api/loader"; +import { ArticleMeta } from "./ArticleMeta"; +import { Comments } from "./Comments"; + +export function ArticleReadPage() { + const { article } = useLoaderData(); + + return ( +
+
+
+

{article.article.title}

+ + +
+
+ +
+
+
+

{article.article.body}

+
    + {article.article.tagList.map((tag) => ( +
  • + {tag} +
  • + ))} +
+
+
+ +
+ +
+ +
+ +
+ +
+
+
+ ); +} +``` + +What’s more interesting is the `ArticleMeta` and `Comments`. They contain write operations such as liking an article, leaving a comment, etc. To get them to work, we first need to implement the backend part. Create `action.ts` in the `api` segment of the page: + +```tsx title="pages/article-read/api/action.ts" +import { redirect, type ActionFunctionArgs } from "@remix-run/node"; +import { namedAction } from "remix-utils/named-action"; +import { redirectBack } from "remix-utils/redirect-back"; +import invariant from "tiny-invariant"; + +import { DELETE, POST, requireUser } from "shared/api"; + +export const action = async ({ request, params }: ActionFunctionArgs) => { + const currentUser = await requireUser(request); + + const authorization = { Authorization: `Token ${currentUser.token}` }; + + const formData = await request.formData(); + + return namedAction(formData, { + async delete() { + invariant(params.slug, "Expected a slug parameter"); + await DELETE("/articles/{slug}", { + params: { path: { slug: params.slug } }, + headers: authorization, + }); + return redirect("/"); + }, + async favorite() { + invariant(params.slug, "Expected a slug parameter"); + await POST("/articles/{slug}/favorite", { + params: { path: { slug: params.slug } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + async unfavorite() { + invariant(params.slug, "Expected a slug parameter"); + await DELETE("/articles/{slug}/favorite", { + params: { path: { slug: params.slug } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + async createComment() { + invariant(params.slug, "Expected a slug parameter"); + const comment = formData.get("comment"); + invariant(typeof comment === "string", "Expected a comment parameter"); + await POST("/articles/{slug}/comments", { + params: { path: { slug: params.slug } }, + headers: { ...authorization, "Content-Type": "application/json" }, + body: { comment: { body: comment } }, + }); + return redirectBack(request, { fallback: "/" }); + }, + async deleteComment() { + invariant(params.slug, "Expected a slug parameter"); + const commentId = formData.get("id"); + invariant(typeof commentId === "string", "Expected an id parameter"); + const commentIdNumeric = parseInt(commentId, 10); + invariant( + !Number.isNaN(commentIdNumeric), + "Expected a numeric id parameter", + ); + await DELETE("/articles/{slug}/comments/{id}", { + params: { path: { slug: params.slug, id: commentIdNumeric } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + async followAuthor() { + const authorUsername = formData.get("username"); + invariant( + typeof authorUsername === "string", + "Expected a username parameter", + ); + await POST("/profiles/{username}/follow", { + params: { path: { username: authorUsername } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + async unfollowAuthor() { + const authorUsername = formData.get("username"); + invariant( + typeof authorUsername === "string", + "Expected a username parameter", + ); + await DELETE("/profiles/{username}/follow", { + params: { path: { username: authorUsername } }, + headers: authorization, + }); + return redirectBack(request, { fallback: "/" }); + }, + }); +}; +``` + +Export that from the slice and then from the route. While we’re at it, let’s also connect the page itself: + +```tsx title="pages/article-read/index.ts" +export { ArticleReadPage } from "./ui/ArticleReadPage"; +export { loader } from "./api/loader"; +export { action } from "./api/action"; +``` + +```tsx title="app/routes/article.$slug.tsx" +import { ArticleReadPage } from "pages/article-read"; + +export { loader, action } from "pages/article-read"; + +export default ArticleReadPage; +``` + +Now, even though we haven’t implemented the like button on the reader page yet, the like button in the feed will start working! That’s because it’s been sending “like” requests to this route. Give that a try. + +`ArticleMeta` and `Comments` are, again, a bunch of forms. We’ve done this before, let’s grab their code and move on: + +```tsx title="pages/article-read/ui/ArticleMeta.tsx" +import { Form, Link, useLoaderData } from "@remix-run/react"; +import { useContext } from "react"; + +import { CurrentUser } from "shared/api"; +import type { loader } from "../api/loader"; + +export function ArticleMeta() { + const currentUser = useContext(CurrentUser); + const { article } = useLoaderData(); + + return ( +
+
+ + + + +
+ + {article.article.author.username} + + {article.article.createdAt} +
+ + {article.article.author.username == currentUser?.username ? ( + <> + + Edit Article + +    + + + ) : ( + <> + + +    + + + )} +
+
+ ); +} +``` + +```tsx title="pages/article-read/ui/Comments.tsx" +import { useContext } from "react"; +import { Form, Link, useLoaderData } from "@remix-run/react"; + +import { CurrentUser } from "shared/api"; +import type { loader } from "../api/loader"; + +export function Comments() { + const { comments } = useLoaderData(); + const currentUser = useContext(CurrentUser); + + return ( +
+ {currentUser !== null ? ( +
+
+ +
+
+ + +
+
+ ) : ( +
+
+

+ Sign in +   or   + Sign up +   to add comments on this article. +

+
+
+ )} + + {comments.comments.map((comment) => ( +
+
+

{comment.body}

+
+ +
+ + + +   + + {comment.author.username} + + {comment.createdAt} + {comment.author.username === currentUser?.username && ( + +
+ + +
+
+ )} +
+
+ ))} +
+ ); +} +``` + +And with that our article reader is also complete! The buttons to follow the author, like a post, and leave a comment should now function as expected. + +
+ ![Article reader with functioning buttons to like and follow](/img/tutorial/realworld-article-reader.jpg) + +
Article reader with functioning buttons to like and follow
+
+ +### Article editor + +This is the last page that we will cover in this tutorial, and the most interesting part here is how we’re going to validate form data. + +The page itself, `article-edit/ui/ArticleEditPage.tsx`, will be quite simple, extra complexity stowed away into two other components: + +```tsx title="pages/article-edit/ui/ArticleEditPage.tsx" +import { Form, useLoaderData } from "@remix-run/react"; + +import type { loader } from "../api/loader"; +import { TagsInput } from "./TagsInput"; +import { FormErrors } from "./FormErrors"; + +export function ArticleEditPage() { + const article = useLoaderData(); + + return ( +
+
+
+
+ + +
+
+
+ +
+
+ +
+
+ +
+
+ +
+ + +
+
+
+
+
+
+ ); +} +``` + +This page gets the current article (unless we’re writing from scratch) and fills in the corresponding form fields. We’ve seen this before. The interesting part is `FormErrors`, because it will receive the validation result and display it to the user. Let’s take a look: + +```tsx title="pages/article-edit/ui/FormErrors.tsx" +import { useActionData } from "@remix-run/react"; +import type { action } from "../api/action"; + +export function FormErrors() { + const actionData = useActionData(); + + return actionData?.errors != null ? ( +
    + {actionData.errors.map((error) => ( +
  • {error}
  • + ))} +
+ ) : null; +} +``` + +Here we are assuming that our action will return the `errors` field, an array of human-readable error messages. We will get to the action shortly. + +Another component is the tags input. It’s just a plain input field with an additional preview of chosen tags. Not much to see here: + +```tsx title="pages/article-edit/ui/TagsInput.tsx" +import { useEffect, useRef, useState } from "react"; + +export function TagsInput({ + name, + defaultValue, +}: { + name: string; + defaultValue?: Array; +}) { + const [tagListState, setTagListState] = useState(defaultValue ?? []); + + function removeTag(tag: string): void { + const newTagList = tagListState.filter((t) => t !== tag); + setTagListState(newTagList); + } + + const tagsInput = useRef(null); + useEffect(() => { + tagsInput.current && (tagsInput.current.value = tagListState.join(",")); + }, [tagListState]); + + return ( + <> + + setTagListState(e.target.value.split(",").filter(Boolean)) + } + /> +
+ {tagListState.map((tag) => ( + + + [" ", "Enter"].includes(e.key) && removeTag(tag) + } + onClick={() => removeTag(tag)} + >{" "} + {tag} + + ))} +
+ + ); +} +``` + +Now, for the API part. The loader should look at the URL, and if it contains an article slug, that means we’re editing an existing article, and its data should be loaded. Otherwise, return nothing. Let’s create that loader: + +```ts title="pages/article-edit/api/loader.ts" +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import type { FetchResponse } from "openapi-fetch"; + +import { GET, requireUser } from "shared/api"; + +async function throwAnyErrors( + responsePromise: Promise>, +) { + const { data, error, response } = await responsePromise; + + if (error !== undefined) { + throw json(error, { status: response.status }); + } + + return data as NonNullable; +} + +export const loader = async ({ params, request }: LoaderFunctionArgs) => { + const currentUser = await requireUser(request); + + if (!params.slug) { + return { article: null }; + } + + return throwAnyErrors( + GET("/articles/{slug}", { + params: { path: { slug: params.slug } }, + headers: { Authorization: `Token ${currentUser.token}` }, + }), + ); +}; +``` + +The action will take the new field values, run them through our data schema, and if everything is correct, commit those changes to the backend, either by updating an existing article or creating a new one: + +```tsx title="pages/article-edit/api/action.ts" +import { json, redirect, type ActionFunctionArgs } from "@remix-run/node"; + +import { POST, PUT, requireUser } from "shared/api"; +import { parseAsArticle } from "../model/parseAsArticle"; + +export const action = async ({ request, params }: ActionFunctionArgs) => { + try { + const { body, description, title, tags } = parseAsArticle( + await request.formData(), + ); + const tagList = tags?.split(",") ?? []; + + const currentUser = await requireUser(request); + const payload = { + body: { + article: { + title, + description, + body, + tagList, + }, + }, + headers: { Authorization: `Token ${currentUser.token}` }, + }; + + const { data, error } = await (params.slug + ? PUT("/articles/{slug}", { + params: { path: { slug: params.slug } }, + ...payload, + }) + : POST("/articles", payload)); + + if (error) { + return json({ errors: error }, { status: 422 }); + } + + return redirect(`/article/${data.article.slug ?? ""}`); + } catch (errors) { + return json({ errors }, { status: 400 }); + } +}; +``` + +The schema doubles as a parsing function for `FormData`, which allows us to conveniently get the clean fields or just throw the errors to handle at the end. Here’s how that parsing function could look: + +```tsx title="pages/article-edit/model/parseAsArticle.ts" +export function parseAsArticle(data: FormData) { + const errors = []; + + const title = data.get("title"); + if (typeof title !== "string" || title === "") { + errors.push("Give this article a title"); + } + + const description = data.get("description"); + if (typeof description !== "string" || description === "") { + errors.push("Describe what this article is about"); + } + + const body = data.get("body"); + if (typeof body !== "string" || body === "") { + errors.push("Write the article itself"); + } + + const tags = data.get("tags"); + if (typeof tags !== "string") { + errors.push("The tags must be a string"); + } + + if (errors.length > 0) { + throw errors; + } + + return { title, description, body, tags: data.get("tags") ?? "" } as { + title: string; + description: string; + body: string; + tags: string; + }; +} +``` + +Arguably, it’s a bit lengthy and repetitive, but that’s the price we pay for human-readable errors. This could also be a Zod schema, for example, but then we would have to render error messages on the frontend, and this form is not worth the complication. + +One last step — connect the page, the loader, and the action to the routes. Since we neatly support both creation and editing, we can export the same thing from both `editor._index.tsx` and `editor.$slug.tsx`: + +```tsx title="pages/article-edit/index.ts" +export { ArticleEditPage } from "./ui/ArticleEditPage"; +export { loader } from "./api/loader"; +export { action } from "./api/action"; +``` + +```tsx title="app/routes/editor._index.tsx, app/routes/editor.$slug.tsx (same content)" +import { ArticleEditPage } from "pages/article-edit"; + +export { loader, action } from "pages/article-edit"; + +export default ArticleEditPage; +``` + +We’re done now! Log in and try creating a new article. Or “forget” to write the article and see the validation kick in. + +
+ ![The Conduit article editor, with the title field saying “New article” and the rest of the fields empty. Above the form there are two errors: “**Describe what this article is about” and “Write the article itself”.**](/img/tutorial/realworld-article-editor.jpg) + +
The Conduit article editor, with the title field saying “New article” and the rest of the fields empty. Above the form there are two errors: **“Describe what this article is about”** and **“Write the article itself”**.
+
+ +The profile and settings pages are very similar to the article reader and editor, they are left as an exercise for the reader, that’s you :) diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/_category_.yaml b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/_category_.yaml new file mode 100644 index 0000000000..6a3720e348 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/_category_.yaml @@ -0,0 +1,2 @@ +label: Examples +position: 1 diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/auth.md b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/auth.md new file mode 100644 index 0000000000..5a96d32275 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/auth.md @@ -0,0 +1,225 @@ +--- +sidebar_position: 1 +--- + +# Authentication + +Broadly, authentication consists of the following steps: + +1. Get the credentials from the user +1. Send them to the backend +1. Store the token to make authenticated requests + +## How to get credentials from the user + +We are assuming that your app is responsible for getting credentials. If you have authentication via OAuth, you can simply create a login page with a link to the OAuth provider's login page and skip to [step 3](#how-to-store-the-token-for-authenticated-requests). + +### Dedicated page for login + +Usually, websites have dedicated pages for login, where you enter your username and password. These pages are quite simple, so they don't require decomposition. Login and registration forms are quite similar in appearance, so they can even be grouped into one page. Create a slice for your login/registration page on the Pages layer: + +- 📂 pages + - 📂 login + - 📂 ui + - 📄 LoginPage.tsx (or your framework's component file format) + - 📄 RegisterPage.tsx + - 📄 index.ts + - other pages… + +Here we created two components and exported them both in the index file of the slice. These components will contain forms that are responsible for presenting the user with understandable controls to get their credentials. + +### Dialog for login + +If your app has a dialog for login that can be used on any page, consider making that dialog a widget. That way, you can still avoid too much decomposition, but have the freedom to reuse this dialog on any page. + +- 📂 widgets + - 📂 login-dialog + - 📂 ui + - 📄 LoginDialog.tsx + - 📄 index.ts + - other widgets… + +The rest of this guide is written for the dedicated page approach, but the same principles apply to the dialog widget. + +### Client-side validation + +Sometimes, especially for registration, it makes sense to perform client-side validation to let the user know quickly that they made a mistake. Validation can take place in the `model` segment of the login page. Use a schema validation library, for example, [Zod][ext-zod] for JS/TS, and expose that schema to the `ui` segment: + +```ts title="pages/login/model/registration-schema.ts" +import { z } from "zod"; + +export const registrationData = z.object({ + email: z.string().email(), + password: z.string().min(6), + confirmPassword: z.string(), +}).refine((data) => data.password === data.confirmPassword, { + message: "Passwords do not match", + path: ["confirmPassword"], +}); +``` + +Then, in the `ui` segment, you can use this schema to validate the user input: + +```tsx title="pages/login/ui/RegisterPage.tsx" +import { registrationData } from "../model/registration-schema"; + +function validate(formData: FormData) { + const data = Object.fromEntries(formData.entries()); + try { + registrationData.parse(data); + } catch (error) { + // TODO: Show error message to the user + } +} + +export function RegisterPage() { + return ( +
validate(new FormData(e.target))}> + + + + + + + + +
+ ) +} +``` + +## How to send credentials to the backend + +Create a function that makes a request to your backend's login endpoint. This function can either be called directly in the component code using a mutation library (e.g. TanStack Query), or it can be called as a side effect in a state manager. + +### Where to store the request function + +There are two places you can put this function: in `shared/api`, or in the `api` segment of the page. + +#### In `shared/api` + +This approach goes well with when you put all your API requests in `shared/api`, grouped by endpoint, for example. The file structure might look like this: + +- 📂 shared + - 📂 api + - 📂 endpoints + - 📄 login.ts + - other endpoint functions… + - 📄 client.ts + - 📄 index.ts + +The `📄 client.ts` file contains a wrapper around your request-making primitive (for example, `fetch()`). This wrapper would know about the base URL of your backend, set necessary headers, serialize data correctly, etc. + +```ts title="shared/api/endpoints/login.ts" +import { POST } from "../client"; + +export function login({ email, password }: { email: string, password: string }) { + return POST("/login", { email, password }); +} +``` + +```ts title="shared/api/index.ts" +export { login } from "./endpoints/login"; +``` + +#### In the `api` segment of the page + +If you don't keep all your requests in one place, consider stashing the login request in the `api` segment of the login page. + +- 📂 pages + - 📂 login + - 📂 api + - 📄 login.ts + - 📂 ui + - 📄 LoginPage.tsx + - 📄 index.ts + - other pages… + +```ts title="pages/login/api/login.ts" +import { POST } from "shared/api"; + +export function login({ email, password }: { email: string, password: string }) { + return POST("/login", { email, password }); +} +``` + +You don't have to export the `login()` function in the page's public API, because it's unlikely that any other place in the app will need this request. + +### Two-factor authentication + +If your app supports two-factor authentication (2FA), you might have to redirect to another page where a user can enter a one-time password. Usually your `POST /login` request would return the user object with a flag indicating that the user has 2FA enabled. If that flag is set, redirect the user to the 2FA page. + +Since this page is very related to logging in, you can also keep it in the same slice, `login` on the Pages layer. + +You would also need another request function, similar to `login()` that we created above. Place them together, either in Shared, or in the `api` segment of the `login` page. + +## How to store the token for authenticated requests {#how-to-store-the-token-for-authenticated-requests} + +Regardless of the authentication scheme you have, be it a simple login & password, OAuth, or two-factor authentication, at the end you will receive a token. This token should be stored so that subsequent requests can identify themselves. + +The ideal token storage for a web app is a **cookie** — it requires no manual token storage or handling. As such, cookie storage needs almost no consideration from the frontend architecture side. If your frontend framework has a server side (for example, [Remix][ext-remix]), then you should store the server-side cookie infrastructure in `shared/api`. There is an example in [the Authentication section of the tutorial][tutorial-authentication] of how to do that with Remix. + +Sometimes, however, cookie storage is not an option. In this case, you will have to store the token manually. Apart from storing the token, you may also need to set up logic for refreshing your token when it expires. With FSD, there are several places where you can store the token, as well as several ways to make it available for the rest of the app. + +### In Shared + +This approach plays well with an API client defined in `shared/api` because the token is freely available for other request functions that require authentication to succeed. You can make the API client hold state, either with a reactive store or simply a module-level variable, and update that state in your `login()`/`logout()` functions. + +Automatic token refresh can be implemented as a middleware in the API client — something that can execute every time you make any request. It can work like this: + +- Authenticate and store the access token as well as the refresh token +- Make any request that requires authentication +- If the request fails with a status code that indicates token expiration, and there is a token in the store, make a refresh request, store the new tokens, and retry the original request + +One of the drawbacks of this approach is that the logic of managing and refreshing the token doesn't have a dedicated place. This can be fine for some apps or teams, but if the token management logic is more complex, it may be preferable to separate responsibilities of making requests and managing tokens. You can do that by keeping your requests and API client in `shared/api`, but the token store and management logic in `shared/auth`. + +Another drawback of this approach is that if your backend returns an object of your current user's information along with the token, you have to store that somewhere or discard that information and request it again from an endpoint like `/me` or `/users/current`. + +### In Entities + +It's common for FSD projects to have an entity for a user and/or an entity for the current user. It can even be the same entity for both. + +:::note + +The **current user** is also sometimes called "viewer" or "me". This is to distinguish the single authenticated user, with permissions and private information, from a list of all users with publicly accessible information. + +::: + +To store the token in the User entity, create a reactive store in the `model` segment. That store can contain both the token and the user object. + +Since the API client is usually defined in `shared/api` or spreaded across the entities, the main challenge to this approach is making the token available to other requests that need it without breaking [the import rule on layers][import-rule-on-layers]: + +> A module in a slice can only import other slices when they are located on layers strictly below. + +There are several solutions to this challenge: + +1. **Pass the token manually every time you make a request** + This is the simplest solution, but it quickly becomes cumbersome, and if you don't have type safety, it's easy to forget. It's also not compatible with middlewares pattern for the API client in Shared. +1. **Expose the token to the entire app with a context or a global store like `localStorage`** + The key to retrieve the token will be kept in `shared/api` so that the API client can access it. The reactive store of the token will be exported from the User entity, and the context provider (if needed) will be set up on the App layer. This gives more freedom for designing the API client, however, this creates an implicit dependency on higher layers to provide context. When following this approach, consider providing helpful error messages if the context or `localStorage` are not set up correctly. +1. **Inject the token into the API client every time it changes** + If your store is reactive, you can create a subscription that will update the API client's token store every time the store in the entity changes. This is similar to the previous solution in that they both create an implicit dependency on higher layers, but this one is more imperative ("push"), while the previous one is more declarative ("pull"). + +Once you overcome the challenge of exposing the token that is stored in the entity's model, you can encode more business logic related to token management. For example, the `model` segment can contain logic to invalidate the token after a certain period of time, or to refresh the token when it expires. To actually make requests to the backend, use the `api` segment of the User entity or `shared/api`. + +### In Pages/Widgets (not recommended) + +It is discouraged to store app-wide state like an access token in pages or widgets. Avoid placing your token store in the `model` segment of the login page, instead choose from the first two solutions, Shared or Entities. + +## Logout and token invalidation + +Usually, apps don't have an entire page for logging out, but the logout functionality is still very important. It consists of an authenticated request to the backend and an update to the token store. + +If you store all your requests in `shared/api`, keep the logout request function there, close to the login function. Otherwise, consider keeping the logout request function next to the button that triggers it. For example, if you have a header widget that appears on every page and contains the logout link, put that request in the `api` segment of that widget. + +The update to the token store will have to be triggered from the place of the logout button, like a header widget. You can combine the request and the store update in the `model` segment of that widget. + +### Automatic logout + +Don't forget to build failsafes for when a request to log out fails, or a request to refresh a login token fails. In both of these cases, you should clear the token store. If you keep your token in Entities, this code can be placed in the `model` segment as it is pure business logic. If you keep your token in Shared, placing this logic in `shared/api` might bloat the segment and dilute its purpose. If you're noticing that your API segment contains two several unrelated things, consider splitting out the token management logic into another segment, for example, `shared/auth`. + +[tutorial-authentication]: /docs/get-started/tutorial#authentication +[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers +[ext-remix]: https://remix.run +[ext-zod]: https://zod.dev + diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/autocompleted.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/autocompleted.mdx new file mode 100644 index 0000000000..8639d1b09e --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/autocompleted.mdx @@ -0,0 +1,18 @@ +--- +sidebar_position: 5 +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# Autocomplete + + + + + +> About decomposition by layers + +## See also +- [(Discussion) About the application of the methodology for the selection with loaded dictionaries](https://github.com/feature-sliced/documentation/discussions/65#discussioncomment-480807) diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/browser-api.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/browser-api.mdx new file mode 100644 index 0000000000..957c651620 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/browser-api.mdx @@ -0,0 +1,14 @@ +--- +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# Browser API + + + +> About working with the Browser API: localStorage, audio Api, bluetooth API, etc. +> +> You can ask about the idea in more detail [@alex_novi](https://t.me/alex_novich) diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/cms.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/cms.mdx new file mode 100644 index 0000000000..82b7f031ad --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/cms.mdx @@ -0,0 +1,22 @@ +--- +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# CMS + + + +## Features may be different + +In some projects, all the functionality is concentrated in data from the server + +> https://github.com/feature-sliced/documentation/discussions/65#discussioncomment-480785 + +## How to work more correctly with CMS markup + +> https://t.me/feature_sliced/1557 + +> https://t.me/feature_sliced/1553 diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/feedback.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/feedback.mdx new file mode 100644 index 0000000000..8bab4fc7f6 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/feedback.mdx @@ -0,0 +1,12 @@ +--- +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# Feedback + + + +> Errors, Alerts, Notifications, ... diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/i18n.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/i18n.mdx new file mode 100644 index 0000000000..d2b543cee6 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/i18n.mdx @@ -0,0 +1,17 @@ +--- +sidebar_position: 6 +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# i18n + + + +## Where to place it? How to work with this? + +- https://t.me/feature_sliced/4425 +- https://t.me/feature_sliced/2325 +- https://t.me/feature_sliced/1867 diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/index.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/index.mdx new file mode 100644 index 0000000000..d41ddf713f --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/index.mdx @@ -0,0 +1,36 @@ +--- +hide_table_of_contents: true +--- + +# Examples + +

+Small practical examples of the methodology application +

+ +## Main + +import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" +import { UserSwitchOutlined, LayoutOutlined, FontSizeOutlined } from "@ant-design/icons"; + + + + diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/metric.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/metric.mdx new file mode 100644 index 0000000000..5587db6d1b --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/metric.mdx @@ -0,0 +1,12 @@ +--- +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# Metric + + + +> About ways to initialize metrics in the application diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/monorepo.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/monorepo.mdx new file mode 100644 index 0000000000..a9c7bb13fc --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/monorepo.mdx @@ -0,0 +1,18 @@ +--- +sidebar_position: 9 +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# Monorepositories + + + +> About applicability for mono repositories, about bff, about microapps + +## See also + +- [(Discussion) About mono repositories and plug-ins-packages](https://github.com/feature-sliced/documentation/discussions/50) +- [(Thread) About the application for a mono repository](https://t.me/feature_sliced/2412) diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md new file mode 100644 index 0000000000..e155d430d4 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md @@ -0,0 +1,102 @@ +--- +sidebar_position: 3 +--- + +# Page layouts + +This guide examines the abstraction of a _page layout_ — when several pages share the same overall structure, and differ only in the main content. + +:::info + +Is your question not covered by this guide? Post your question by leaving feedback on this article (blue button on the right) and we will consider expanding this guide! + +::: + +## Simple layout + +The simplest layout can be seen on this page. It has a header with site navigation, two sidebars, and a footer with external links. There is no complicated business logic, and the only dynamic parts are sidebars and the switchers on the right side of the header. Such a layout can be placed entirely in `shared/ui` or in `app/layouts`, with props filling in the content for the sidebars: + +```tsx title="shared/ui/layout/Layout.tsx" +import { Link, Outlet } from "react-router-dom"; +import { useThemeSwitcher } from "./useThemeSwitcher"; + +export function Layout({ siblingPages, headings }) { + const [theme, toggleTheme] = useThemeSwitcher(); + + return ( +
+
+ + +
+
+ + {/* This is where the main content goes */} + +
+
+
    +
  • GitHub
  • +
  • Twitter
  • +
+
+
+ ); +} +``` + +```ts title="shared/ui/layout/useThemeSwitcher.ts" +export function useThemeSwitcher() { + const [theme, setTheme] = useState("light"); + + function toggleTheme() { + setTheme(theme === "light" ? "dark" : "light"); + } + + useEffect(() => { + document.body.classList.remove("light", "dark"); + document.body.classList.add(theme); + }, [theme]); + + return [theme, toggleTheme] as const; +} +``` + +The code of sidebars is left as an exercise for the reader 😉. + +## Using widgets in the layout + +Sometimes you want to include certain business logic in the layout, especially if you're using deeply nested routes with a router like [React Router][ext-react-router]. Then you can't store the layout in Shared or in Widgets due to [the import rule on layers][import-rule-on-layers]: + +> A module in a slice can only import other slices when they are located on layers strictly below. + +Before we discuss solutions, we need to discuss if it's even a problem in the first place. Do you _really need_ that layout, and if so, does it _really need_ to be a Widget? If the block of business logic in question is reused on 2-3 pages, and the layout is simply a small wrapper for that widget, consider one of these two options: + +1. **Write the layout inline on the App layer, where you configure the routing** + This is great for routers that support nesting, because you can group certain routes and apply the layout only to them. + +2. **Just copy-paste it** + The urge to abstract code is often very overrated. It is especially the case for layouts, which rarely change. At some point, if one of these pages will need to change, you can simply do the change without needlessly affecting other pages. If you're worried that someone might forget to update the other pages, you can always leave a comment that describes the relationship between the pages. + +If none of the above are applicable, there are two solutions to include a widget in the layout: + +1. **Use render props or slots** + Most frameworks allow you to pass a piece of UI externally. In React, it's called [render props][ext-render-props], in Vue it's called [slots][ext-vue-slots]. +2. **Move the layout to the App layer** + You can also store your layout on the App layer, for example, in `app/layouts`, and compose any widgets you want. + +## Further reading + +- There's an example of how to build a layout with authentication with React and Remix (equivalent to React Router) in the [tutorial][tutorial]. + +[tutorial]: /docs/get-started/tutorial +[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers +[ext-react-router]: https://reactrouter.com/ +[ext-render-props]: https://www.patterns.dev/react/render-props-pattern/ +[ext-vue-slots]: https://vuejs.org/guide/components/slots diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/platforms.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/platforms.mdx new file mode 100644 index 0000000000..2a5212f2a1 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/platforms.mdx @@ -0,0 +1,12 @@ +--- +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# Desktop/Touch platforms + + + +> About the application of the methodology for desktop/touch diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/ssr.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/ssr.mdx new file mode 100644 index 0000000000..43eb82eabd --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/ssr.mdx @@ -0,0 +1,12 @@ +--- +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# SSR + + + +> About the implementation of SSR using the methodology diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/theme.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/theme.mdx new file mode 100644 index 0000000000..cf590be8e5 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/theme.mdx @@ -0,0 +1,19 @@ +--- +sidebar_position: 4 +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# Theme + + + +## Where should I put my work with the theme and palette? + +> https://t.me/feature_sliced/4410 + +## Discussion about the location of the theme, i18n logic + +> https://youtu.be/b_nBvHWqxP8?t=133 diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/types.md b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/types.md new file mode 100644 index 0000000000..cd6390be4b --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/types.md @@ -0,0 +1,440 @@ +--- +sidebar_position: 2 +--- + +# Types + +This guide concerns data types from typed languages like TypeScript and describes where they fit within FSD. + +:::info + +Is your question not covered by this guide? Post your question by leaving feedback on this article (blue button on the right) and we will consider expanding this guide! + +::: + +## Utility types + +Utility types are types that don't have much meaning on their own and are usually used with other types. For example: + +
+ +```ts +type ArrayValues = T[number]; +``` + +
+ Source: https://github.com/sindresorhus/type-fest/blob/main/source/array-values.d.ts +
+ +
+ +To make utility types available across your project, either install a library like [`type-fest`][ext-type-fest], or create your own library in `shared/lib`. Make sure to clearly indicate what new types _should_ be added to this library, and what types _don't belong_ there. For example, call it `shared/lib/utility-types` and add a README inside that describes what is a utility type in your team. + +Don't overestimate the potential reusability of a utility type. Just because it can be reused, doesn't mean it will be, and as such, not every utility type needs to be in Shared. Some utility types are fine right next to where they are needed: + +- 📂 pages + - 📂 home + - 📂 api + - 📄 ArrayValues.ts (utility type) + - 📄 getMemoryUsageMetrics.ts (the code that uses the utility type) + +:::warning + +Resist the temptation to create a `shared/types` folder, or to add a `types` segment to your slices. The category "types" is similar to the category "components" or "hooks" in that it describes what the contents are, not what they are for. Segments should describe the purpose of the code, not the essence. + +::: + +## Business entities and their cross-references + +Among the most important types in an app are the types of business entities, i.e. the real-world things that your app works with. For example, in a music streaming app, you might have business entities _Song_, _Album_, etc. + +Business entities often come from the backend, so the first step is to type the backend responses. It's convenient to have a function to make a request to every endpoint, and to type the response of this function. For extra type safety, you may want to run the response through a schema validation library like [Zod][ext-zod]. + +For example, if you keep all your requests in Shared, you could do it like this: + +```ts title="shared/api/songs.ts" +import type { Artist } from "./artists"; + +interface Song { + id: number; + title: string; + artists: Array; +} + +export function listSongs() { + return fetch('/api/songs').then((res) => res.json() as Promise>); +} +``` + +You might notice that the `Song` type references a different entity, `Artist`. This is a benefit of storing your requests in Shared — real-world types are often intertwined. If we kept this function in `entities/song/api`, we wouldn't be able to simply import `Artist` from `entities/artist`, because FSD restricts cross-imports between slices with [the import rule on layers][import-rule-on-layers]: + +> A module in a slice can only import other slices when they are located on layers strictly below. + +There are two ways to deal with this issue: + +1. **Parametrize your types** + You can make your types accept type arguments as slots for connections with other entities, and even impose constraints on those slots. For example: + + ```ts title="entities/song/model/song.ts" + interface Song { + id: number; + title: string; + artists: Array; + } + ``` + + This works better for some types than others. A simple type like `Cart = { items: Array }` can easily be made to work with any type of product. More connected types, like `Country` and `City`, may not be as easy to separate. + +2. **Cross-import (but do it right)** + To make cross-imports between entities in FSD, you can use a special public API specifically for each slice that will be cross-importing. For example, if we have entities `song`, `artist`, and `playlist`, and the latter two need to reference `song`, we can make two special public APIs for both of them in the `song` entity with the `@x` notation: + + - 📂 entities + - 📂 song + - 📂 @x + - 📄 artist.ts (a public API for the `artist` entity to import from) + - 📄 playlist.ts (a public API for the `playlist` entity to import from) + - 📄 index.ts (regular public API) + + The contents of a file `📄 entities/song/@x/artist.ts` are similar to `📄 entities/song/index.ts`: + + ```ts title="entities/song/@x/artist.ts" + export type { Song } from "../model/song.ts"; + ``` + + Then the `📄 entities/artist/model/artist.ts` can import `Song` like this: + + ```ts title="entities/artist/model/artist.ts" + import type { Song } from "entities/song/@x/artist"; + + export interface Artist { + name: string; + songs: Array; + } + ``` + + By making explicit connections between entities, we stay on top of inter-dependencies and maintain a decent level of domain separation. + +## Data transfer objects and mappers {#data-transfer-objects-and-mappers} + +Data transfer objects, or DTOs, is a term that describes the shape of data that comes from the backend. Sometimes, the DTO is fine to use as is, but sometimes it's inconvenient for the frontend. That's where mappers come in — they transform a DTO into a more convenient shape. + +### Where to put DTOs + +If you have backend types in a separate package (for example, if you share code between the frontend and the backend), then just import your DTOs from there and you're done! If you don't share code between the backend and frontend, then you need to keep DTOs somewhere in your frontend codebase, and we will explore this case below. + +If you have your request functions in `shared/api`, that's where the DTOs should be, right next to the function that uses them: + +```ts title="shared/api/songs.ts" +import type { ArtistDTO } from "./artists"; + +interface SongDTO { + id: number; + title: string; + artist_ids: Array; +} + +export function listSongs() { + return fetch('/api/songs').then((res) => res.json() as Promise>); +} +``` + +As mentioned in the previous section, storing your requests and DTOs in Shared comes with the benefit of being able to reference other DTOs. + +### Where to put mappers + +Mappers are functions that accept a DTO for transformation, and as such, they should be located near the definition of the DTO. In practice this means that if your requests and DTOs are defined in `shared/api`, then the mappers should go there as well: + +```ts title="shared/api/songs.ts" +import type { ArtistDTO } from "./artists"; + +interface SongDTO { + id: number; + title: string; + disc_no: number; + artist_ids: Array; +} + +interface Song { + id: string; + title: string; + /** The full title of the song, including the disc number. */ + fullTitle: string; + artistIds: Array; +} + +function adaptSongDTO(dto: SongDTO): Song { + return { + id: String(dto.id), + title: dto.title, + fullTitle: `${dto.disc_no} / ${dto.title}`, + artistIds: dto.artist_ids.map(String), + }; +} + +export function listSongs() { + return fetch('/api/songs').then(async (res) => (await res.json()).map(adaptSongDTO)); +} +``` + +If your requests and stores are defined in entity slices, then all this code would go there, keeping in mind the limitations of cross-imports between slices: + +```ts title="entities/song/api/dto.ts" +import type { ArtistDTO } from "entities/artist/@x/song"; + +export interface SongDTO { + id: number; + title: string; + disc_no: number; + artist_ids: Array; +} +``` + +```ts title="entities/song/api/mapper.ts" +import type { SongDTO } from "./dto"; + +export interface Song { + id: string; + title: string; + /** The full title of the song, including the disc number. */ + fullTitle: string; + artistIds: Array; +} + +export function adaptSongDTO(dto: SongDTO): Song { + return { + id: String(dto.id), + title: dto.title, + fullTitle: `${dto.disc_no} / ${dto.title}`, + artistIds: dto.artist_ids.map(String), + }; +} +``` + +```ts title="entities/song/api/listSongs.ts" +import { adaptSongDTO } from "./mapper"; + +export function listSongs() { + return fetch('/api/songs').then(async (res) => (await res.json()).map(adaptSongDTO)); +} +``` + +```ts title="entities/song/model/songs.ts" +import { createSlice, createEntityAdapter } from "@reduxjs/toolkit"; + +import { listSongs } from "../api/listSongs"; + +export const fetchSongs = createAsyncThunk('songs/fetchSongs', listSongs); + +const songAdapter = createEntityAdapter(); +const songsSlice = createSlice({ + name: "songs", + initialState: songAdapter.getInitialState(), + reducers: {}, + extraReducers: (builder) => { + builder.addCase(fetchSongs.fulfilled, (state, action) => { + songAdapter.upsertMany(state, action.payload); + }) + }, +}); +``` + +### How to deal with nested DTOs + +The most problematic part is when a response from the backend contains several entities. For example, if the song included not just the authors' IDs, but the entire author objects. In this case, it is impossible for entities not to know about each other (unless we want to discard the data or have a firm conversation with the backend team). Instead of coming up with solutions for indirect connections between slices (such as a common middleware that would dispatch actions to other slices), prefer explicit cross-imports with the `@x` notation. Here is how we can implement it with Redux Toolkit: + +```ts title="entities/song/model/songs.ts" +import { + createSlice, + createEntityAdapter, + createAsyncThunk, + createSelector, +} from '@reduxjs/toolkit' +import { normalize, schema } from 'normalizr' + +import { getSong } from "../api/getSong"; + +// Define normalizr entity schemas +export const artistEntity = new schema.Entity('artists') +export const songEntity = new schema.Entity('songs', { + artists: [artistEntity], +}) + +const songAdapter = createEntityAdapter() + +export const fetchSong = createAsyncThunk( + 'songs/fetchSong', + async (id: string) => { + const data = await getSong(id) + // Normalize the data so reducers can load a predictable payload, like: + // `action.payload = { songs: {}, artists: {} }` + const normalized = normalize(data, songEntity) + return normalized.entities + } +) + +export const slice = createSlice({ + name: 'songs', + initialState: songAdapter.getInitialState(), + reducers: {}, + extraReducers: (builder) => { + builder.addCase(fetchSong.fulfilled, (state, action) => { + songAdapter.upsertMany(state, action.payload.songs) + }) + }, +}) + +const reducer = slice.reducer +export default reducer +``` + +```ts title="entities/song/@x/artist.ts" +export { fetchSong } from "../model/songs"; +``` + +```ts title="entities/artist/model/artists.ts" +import { createSlice, createEntityAdapter } from '@reduxjs/toolkit' + +import { fetchSong } from 'entities/song/@x/artist' + +const artistAdapter = createEntityAdapter() + +export const slice = createSlice({ + name: 'users', + initialState: artistAdapter.getInitialState(), + reducers: {}, + extraReducers: (builder) => { + builder.addCase(fetchSong.fulfilled, (state, action) => { + // And handle the same fetch result by inserting the artists here + usersAdapter.upsertMany(state, action.payload.users) + }) + }, +}) + +const reducer = slice.reducer +export default reducer +``` + +This slightly limits the benefits of slice isolation, but it accurately represents a connection between these two entities that we have no control over. If these entities are to ever be refactored, they have to be refactored together. + +## Global types and Redux + +Global types are types that will be used across the whole application. There are two kinds of global types, based on what they need to know about: +1. Generic types that don't have any application specifics +2. Types that need to know about the whole application + +The first case is simple to resolve — place your types in Shared, in an appropriate segment. For example, if you have an interface for a global variable for analytics, you can put it in `shared/analytics`. + +:::warning + +Avoid creating the `shared/types` folder. It groups unrelated things based only on the property of "being a type", and that property is usually not useful when searching for code in a project. + +::: + +The second case is commonly encountered in projects with Redux without RTK. Your final store type is only available once you add all the reducers together, but this store type needs to be available to selectors that you use across the app. For example, here's your typical store definition: + +```ts title="app/store/index.ts" +import { combineReducers, rootReducer } from "redux"; + +import { songReducer } from "entities/song"; +import { artistReducer } from "entities/artist"; + +const rootReducer = combineReducers(songReducer, artistReducer); + +const store = createStore(rootReducer); + +type RootState = ReturnType; +type AppDispatch = typeof store.dispatch; +``` + +It would be nice to have typed Redux hooks `useAppDispatch` and `useAppSelector` in `shared/store`, but they cannot import `RootState` and `AppDispatch` from the App layer due to the [import rule on layers][import-rule-on-layers]: + +> A module in a slice can only import other slices when they are located on layers strictly below. + +The recommended solution in this case is to create an implicit dependency between layers Shared and App. These two types, `RootState` and `AppDispatch` are unlikely to change, and they will be familiar to Redux developers, so we don't have to worry about them as much. + +In TypeScript, you can do it by declaring the types as global like this: + +```ts title="app/store/index.ts" +/* same content as in the code block before… */ + +declare type RootState = ReturnType; +declare type AppDispatch = typeof store.dispatch; +``` + +```ts title="shared/store/index.ts" +import { useDispatch, useSelector, type TypedUseSelectorHook } from "react-redux"; + +export const useAppDispatch = useDispatch.withTypes() +export const useAppSelector: TypedUseSelectorHook = useSelector; +``` + +## Enums + +The general rule with enums is that they should be defined **as close to the usage locations as possible**. When an enum represents values specific to a single feature, it should be defined in that same feature. + +The choice of segment should be dictated by usage locations as well. If your enum contains, for example, positions of a toast on the screen, it should be placed in the `ui` segment. If it represents the loading state of a backend operation, it should be placed in the `api` segment. + +Some enums are truly common across the whole project, like general backend response statuses or design system tokens. In this case, you can place them in Shared, and choose the segment based on what the enum represents (`api` for response statuses, `ui` for design tokens, etc.). + +## Type validation schemas and Zod + +If you want to validate that your data conforms to a certain shape or constraints, you can define a validation schema. In TypeScript, a popular library for this job is [Zod][ext-zod]. Validation schemas should also be colocated with the code that uses them, as much as possible. + +Validation schemas are similar to mappers (as discussed in the [Data transfer objects and mappers](#data-transfer-objects-and-mappers) section) in the sense that they take a data transfer object and parse it, producing an error if the parsing fails. + +One of the most common cases of validation is for the data that comes from the backend. Typically, you want to fail the request when the data doesn't match the schema, so it makes sense to put the schema in the same place as the request function, which is usually the `api` segment. + +If your data comes through user input, like a form, the validation should happen as the data is being entered. You can place your schema in the `ui` segment, next to the form component, or in the `model` segment, if the `ui` segment is too crowded. + +## Typings of component props and context + +In general, it's best to keep the props or context interface in the same file as the component or context that uses them. If you have a framework with single-file components, like Vue or Svelte, and you can't define the props interface in the same file, or you want to share that interface between several components, create a separate file in the same folder, typically, the `ui` segment. + +Here's an example with JSX (React or Solid): + +```ts title="pages/home/ui/RecentActions.tsx" +interface RecentActionsProps { + actions: Array<{ id: string; text: string }>; +} + +export function RecentActions({ actions }: RecentActionsProps) { + /* … */ +} +``` + +And here's an example with the interface stored in a separate file for Vue: + +```ts title="pages/home/ui/RecentActionsProps.ts" +export interface RecentActionsProps { + actions: Array<{ id: string; text: string }>; +} +``` + +```html title="pages/home/ui/RecentActions.vue" + +``` + +## Ambient declaration files (`*.d.ts`) + +Some packages, for example, [Vite][ext-vite] or [ts-reset][ext-ts-reset], require ambient declaration files to work across your app. Usually, they aren't large or complicated, so they often don't require any architecting, it's fine to just throw them in the `src/` folder. To keep the `src` more organized, you can keep them on the App layer, in `app/ambient/`. + +Other packages simply don't have typings, and you might want to declare them as untyped or even write your own typings for them. A good place for those typings would be `shared/lib`, in a folder like `shared/lib/untyped-packages`. Create a `%LIBRARY_NAME%.d.ts` file there and declare the types you need: + +```ts title="shared/lib/untyped-packages/use-react-screenshot.d.ts" +// This library doesn't have typings, and we didn't want to bother writing our own. +declare module "use-react-screenshot"; +``` + +## Auto-generation of types + +It's common to generate types from external sources, for example, generating backend types from an OpenAPI schema. In this case, create a dedicated place in your codebase for these types, like `shared/api/openapi`. Ideally, you should also include a README in that folder that describes what these files are, how to regenerate them, etc. + +[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers +[ext-type-fest]: https://github.com/sindresorhus/type-fest +[ext-zod]: https://zod.dev +[ext-vite]: https://vitejs.dev +[ext-ts-reset]: https://www.totaltypescript.com/ts-reset diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/white-labels.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/white-labels.mdx new file mode 100644 index 0000000000..855f00eb72 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/examples/white-labels.mdx @@ -0,0 +1,18 @@ +--- +sidebar_position: 8 +sidebar_class_name: sidebar-item--wip +unlisted: true +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# White Labels + + + +> Figma, brand uikit, templates, adaptability to brands + +## See also + +- [(Thread) About the application for white-labels (branded) projects](https://t.me/feature_sliced/1543) +- [(Presentation) About white-labels apps and design](http://yadi.sk/i/5IdhzsWrpO3v4Q) diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/index.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/guides/index.mdx new file mode 100644 index 0000000000..319ebbf93f --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/index.mdx @@ -0,0 +1,46 @@ +--- +hide_table_of_contents: true +pagination_prev: get-started/index +--- + +# 🎯 Guides + +PRACTICE-ORIENTED + +

+Practical guides and examples on the use of Feature-Sliced Design. There is also describe migration guides and a handbook of harmful practices. It is most useful when you are trying to implement something specific or want to look at the methodology "in battle" +

+ +## Main + +import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" +import { ToolOutlined, ImportOutlined, BugOutlined, FunctionOutlined } from "@ant-design/icons"; + + + + + + diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/issues/_category_.yaml b/i18n/fr/docusaurus-plugin-content-docs/current/guides/issues/_category_.yaml new file mode 100644 index 0000000000..19189179c1 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/issues/_category_.yaml @@ -0,0 +1,2 @@ +label: Code smells & Issues +position: 4 diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx new file mode 100644 index 0000000000..033b68930d --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx @@ -0,0 +1,21 @@ +--- +sidebar_position: 4 +sidebar_class_name: sidebar-item--wip +pagination_next: reference/index +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# Cross-imports + + + +> Cross-imports appear when the layer or abstraction begins to take too much responsibility than it should. That is why the methodology identifies new layers that allow you to uncouple these cross-imports + +## See also + +- [(Thread) About the supposed inevitability of cross-ports](https://t.me/feature_sliced/4515) +- [(Thread) About resolving cross-ports in entities](https://t.me/feature_sliced/3678) +- [(Thread) About cross-imports and responsibility](https://t.me/feature_sliced/3287) +- [(Thread) About imports between segments](https://t.me/feature_sliced/4021) +- [(Thread) About cross-imports inside shared](https://t.me/feature_sliced/3618) diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/issues/desegmented.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/guides/issues/desegmented.mdx new file mode 100644 index 0000000000..1ba2b6e2e9 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/issues/desegmented.mdx @@ -0,0 +1,97 @@ +--- +sidebar_position: 2 +sidebar_class_name: sidebar-item--wip +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# Desegemented + + + +## Situation + +Very often, there is a situation on projects when modules related to a specific domain from the subject area are unnecessarily desegmented and scattered around the project + +```sh +├── components/ +| ├── DeliveryCard +| ├── DeliveryChoice +| ├── RegionSelect +| ├── UserAvatar +├── actions/ +| ├── delivery.js +| ├── region.js +| ├── user.js +├── epics/ +| ├── delivery.js +| ├── region.js +| ├── user.js +├── constants/ +| ├── delivery.js +| ├── region.js +| ├── user.js +├── helpers/ +| ├── delivery.js +| ├── region.js +| ├── user.js +├── entities/ +| ├── delivery/ +| | ├── getters.js +| | ├── selectors.js +| ├── region/ +| ├── user/ +``` + +## Problem + +The problem manifests itself at least in violation of the principle of * * High Cohesion** and excessive stretching * * of the axis of changes** + +## If you ignore it + +- If necessary, touch on the logic, for example, delivery - we will have to keep in mind that it lies in several places and touch on several places in the code-which unnecessarily stretches our * * Axis of changes** +- If we need to study the logic of the user, we will have to go through the whole project to study in detail * * actions, epics, constants, entities, components** - instead of it lying in one place +- Implicit connections and the uncontrollability of a growing subject area +- With this approach, the eye is very often blurred and you may not notice how we "create constants for the sake of constants", creating a dump in the corresponding project directory + +## Solution + +Place all modules related to a specific domain/user case - directly next to each other + +So that when studying a particular module, all its components lie side by side, and are not scattered around the project + +> It also increases the discoverability and clarity of the code base and the relationships between modules + +```diff +- ├── components/ +- | ├── DeliveryCard +- | ├── DeliveryChoice +- | ├── RegionSelect +- | ├── UserAvatar +- ├── actions/ +- | ├── delivery.js +- | ├── region.js +- | ├── user.js +- ├── epics/{...} +- ├── constants/{...} +- ├── helpers/{...} + ├── entities/ + | ├── delivery/ ++ | | ├── ui/ # ~ components/ ++ | | | ├── card.js ++ | | | ├── choice.js ++ | | ├── model/ ++ | | | ├── actions.js ++ | | | ├── constants.js ++ | | | ├── epics.js ++ | | | ├── getters.js ++ | | | ├── selectors.js ++ | | ├── lib/ # ~ helpers + | ├── region/ + | ├── user/ +``` + +## See also + +* [(Article) About Low Coupling and High Cohesion clearly](https://enterprisecraftsmanship.com/posts/cohesion-coupling-difference/) +* [(Article) Low Coupling and High Cohesion. The Law of Demeter](https://medium.com/german-gorelkin/low-coupling-high-cohesion-d36369fb1be9) diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/issues/routes.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/guides/issues/routes.mdx new file mode 100644 index 0000000000..9547356014 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/issues/routes.mdx @@ -0,0 +1,46 @@ +--- +sidebar_position: 3 +sidebar_class_name: sidebar-item--wip +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# Routing + + + +## Situation + +Urls to pages are hardcoded in the layers below pages + +```tsx title="entities/post/card" + + + + ... + +``` + +## Problem + +Urls are not concentrated in the page layer, where they belong according to the scope of responsibility + +## If you ignore it + +Then, when changing urls, you will have to keep in mind that these urls (and the logic of urls/redirects) can be in all layers except pages + +And it also means that now even a simple product card takes part of the responsibility from the pages, which smears the logic of the project + +## Solution + +Determine how to work with urls/redirects from the page level and above + +Transfer to the layers below via composition/props/factories + +## See also + +- [(Thread) What if I "sew up" routing in entities/features/widgets](https://t.me/feature_sliced/4389) +- [(Thread) Why does it smear the logic of routes only in pages](https://t.me/feature_sliced/3756) diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/migration/_category_.yaml b/i18n/fr/docusaurus-plugin-content-docs/current/guides/migration/_category_.yaml new file mode 100644 index 0000000000..fee60d9e41 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/migration/_category_.yaml @@ -0,0 +1,2 @@ +label: Migration +position: 2 diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/migration/from-custom.md b/i18n/fr/docusaurus-plugin-content-docs/current/guides/migration/from-custom.md new file mode 100644 index 0000000000..04eb9b40cc --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/migration/from-custom.md @@ -0,0 +1,313 @@ +--- +sidebar_position: 3 +sidebar_label: From a custom architecture +--- + +# Migration from a custom architecture + +This guide describes an approach that might be helpful when migrating from a custom self-made architecture to Feature-Sliced Design. + +Here is the folder structure of a typical custom architecture. We will be using it as an example in this guide. +Click on the blue arrow to open the folder. + +
+ 📁 src +
    +
  • +
    + 📁 actions +
      +
    • 📁 product
    • +
    • 📁 order
    • +
    +
    +
  • +
  • 📁 api
  • +
  • 📁 components
  • +
  • 📁 containers
  • +
  • 📁 constants
  • +
  • 📁 i18n
  • +
  • 📁 modules
  • +
  • 📁 helpers
  • +
  • +
    + 📁 routes +
      +
    • 📁 products.jsx
    • +
    • 📄 products.[id].jsx
    • +
    +
    +
  • +
  • 📁 utils
  • +
  • 📁 reducers
  • +
  • 📁 selectors
  • +
  • 📁 styles
  • +
  • 📄 App.jsx
  • +
  • 📄 index.js
  • +
+
+ +## Before you start {#before-you-start} + +The most important question to ask your team when considering to switch to Feature-Sliced Design is — _do you really need it?_ We love Feature-Sliced Design, but even we recognize that some projects are perfectly fine without it. + +Here are some reasons to consider making the switch: + +1. New team members are complaining that it's hard to get to a productive level +2. Making modifications to one part of the code **often** causes another unrelated part to break +3. Adding new functionality is difficult due to the sheer amount of things you need to think about + +**Avoid switching to FSD against the will of your teammates**, even if you are the lead. +First, convince your teammates that the benefits outweigh the cost of migration and the cost of learning a new architecture instead of the established one. + +Also keep in mind that any kind of architectural changes are not immediately observable to the management. Make sure they are on board with the switch before starting and explain to them why it might benefit the project. + +:::tip + +If you need help convincing the project manager that FSD is beneficial, consider some of these points: +1. Migration to FSD can happen incrementally, so it will not halt the development of new features +2. A good architecture can significantly decrease the time that a new developer needs to get productive +3. FSD is a documented architecture, so the team doesn't have to continuously spend time on maintaining their own documentation + +::: + +--- + +If you made the decision to start migrating, then the first thing you want to do is to set up an alias for `📁 src`. It will be helpful later to refer to top-level folders. We will consider `@` as an alias for `./src` for the rest of this guide. + +## Step 1. Divide the code by pages {#divide-code-by-pages} + +Most custom architectures already have a division by pages, however small or large in logic. If you already have `📁 pages`, you may skip this step. + +If you only have `📁 routes`, create `📁 pages` and try to move as much component code from `📁 routes` as possible. Ideally, you would have a tiny route and a larger page. As you're moving code, create a folder for each page and add an index file: + +:::note + +For now, it's okay if your pages reference each other. You can tackle that later, but for now, focus on establishing a prominent division by pages. + +::: + +Route file: + +```js title="src/routes/products.[id].js" +export { ProductPage as default } from "@/pages/product" +``` + +Page index file: + +```js title="src/pages/product/index.js" +export { ProductPage } from "./ProductPage.jsx" +``` + +Page component file: + +```jsx title="src/pages/product/ProductPage.jsx" +export function ProductPage(props) { + return
; +} +``` + +## Step 2. Separate everything else from the pages {#separate-everything-else-from-pages} + +Create a folder `📁 src/shared` and move everything that doesn't import from `📁 pages` or `📁 routes` there. Create a folder `📁 src/app` and move everything that does import the pages or routes there, including the routes themselves. + +Remember that the Shared layer doesn't have slices, so it's fine if segments import from each other. + +You should end up with a file structure like this: + +
+ 📁 src +
    +
  • +
    + 📁 app +
      +
    • +
      + 📁 routes +
        +
      • 📄 products.jsx
      • +
      • 📄 products.[id].jsx
      • +
      +
      +
    • +
    • 📄 App.jsx
    • +
    • 📄 index.js
    • +
    +
    +
  • +
  • +
    + 📁 pages +
      +
    • +
      + 📁 product +
        +
      • +
        + 📁 ui +
          +
        • 📄 ProductPage.jsx
        • +
        +
        +
      • +
      • 📄 index.js
      • +
      +
      +
    • +
    • 📁 catalog
    • +
    +
    +
  • +
  • +
    + 📁 shared +
      +
    • 📁 actions
    • +
    • 📁 api
    • +
    • 📁 components
    • +
    • 📁 containers
    • +
    • 📁 constants
    • +
    • 📁 i18n
    • +
    • 📁 modules
    • +
    • 📁 helpers
    • +
    • 📁 utils
    • +
    • 📁 reducers
    • +
    • 📁 selectors
    • +
    • 📁 styles
    • +
    +
    +
  • +
+
+ +## Step 3. Tackle cross-imports between pages {#tackle-cross-imports-between-pages} + + + + +Find all instances where one page is importing from the other and do one of the two things: + +1. Copy-paste the imported code into the depending page to remove the dependency +2. Move the code to a proper segment in Shared: + - if it's a part of the UI kit, move it to `📁 shared/ui`; + - if it's a configuration constant, move it to `📁 shared/config`; + - if it's a backend interaction, move it to `📁 shared/api`. + +:::note + +**Copy-pasting isn't architecturally wrong**, in fact, sometimes it may be more correct to duplicate than to abstract into a new reusable module. The reason is that sometimes the shared parts of pages start drifting apart, and you don't want dependencies getting in your way in these cases. + +However, there is still sense in the DRY ("don't repeat yourself") principle, so make sure you're not copy-pasting business logic. Otherwise you will need to remember to fix bugs in several places at once. + +::: + +## Step 4. Unpack the Shared layer {#unpack-shared-layer} + +You might have a lot of stuff in the Shared layer on this step, and you generally want to avoid that. The reason is that the Shared layer may be a dependency for any other layer in your codebase, so making changes to that code is automatically more prone to unintended consequences. + +Find all the objects that are only used on one page and move it to the slice of that page. And yes, _that applies to actions, reducers, and selectors, too_. There is no benefit in grouping all actions together, but there is benefit in colocating relevant actions close to their usage. + +You should end up with a file structure like this: + +
+ 📁 src +
    +
  • 📁 app (unchanged)
  • +
  • +
    + 📁 pages +
      +
    • +
      + 📁 product +
        +
      • 📁 actions
      • +
      • 📁 reducers
      • +
      • 📁 selectors
      • +
      • +
        + 📁 ui +
          +
        • 📄 Component.jsx
        • +
        • 📄 Container.jsx
        • +
        • 📄 ProductPage.jsx
        • +
        +
        +
      • +
      • 📄 index.js
      • +
      +
      +
    • +
    • 📁 catalog
    • +
    +
    +
  • +
  • +
    + 📁 shared (only objects that are reused) +
      +
    • 📁 actions
    • +
    • 📁 api
    • +
    • 📁 components
    • +
    • 📁 containers
    • +
    • 📁 constants
    • +
    • 📁 i18n
    • +
    • 📁 modules
    • +
    • 📁 helpers
    • +
    • 📁 utils
    • +
    • 📁 reducers
    • +
    • 📁 selectors
    • +
    • 📁 styles
    • +
    +
    +
  • +
+
+ +## Step 5. Organize code by technical purpose {#organize-by-technical-purpose} + +In FSD, division by technical purpose is done with _segments_. There are a few common ones: + +- `ui` — everything related to UI display: UI components, date formatters, styles, etc. +- `api` — backend interactions: request functions, data types, mappers, etc. +- `model` — the data model: schemas, interfaces, stores, and business logic. +- `lib` — library code that other modules on this slice need. +- `config` — configuration files and feature flags. + +You can create your own segments, too, if you need. Make sure not to create segments that group code by what it is, like `components`, `actions`, `types`, `utils`. Instead, group the code by what it's for. + +Reorganize your pages to separate code by segments. You should already have a `ui` segment, now it's time to create other segments, like `model` for your actions, reducers, and selectors, or `api` for your thunks and mutations. + +Also reorganize the Shared layer to remove these folders: +- `📁 components`, `📁 containers` — most of it should become `📁 shared/ui`; +- `📁 helpers`, `📁 utils` — if there are some reused helpers left, group them together by function, like dates or type conversions, and move theses groups to `📁 shared/lib`; +- `📁 constants` — again, group by function and move to `📁 shared/config`. + +## Optional steps {#optional-steps} + +### Step 6. Form entities/features from Redux slices that are used on several pages {#form-entities-features-from-redux} + +Usually, these reused Redux slices will describe something relevant to the business, for example, products or users, so these can be moved to the Entities layer, one entity per one folder. If the Redux slice is related to an action that your users want to do in your app, like comments, then you can move it to the Features layer. + +Entities and features are meant to be independent from each other. If your business domain contains inherent connections between entities, refer to the [guide on business entities][business-entities-cross-relations] for advice on how to organize these connections. + +The API functions related to these slices can stay in `📁 shared/api`. + +### Step 7. Refactor your modules {#refactor-your-modules} + +The `📁 modules` folder is commonly used for business logic, so it's already pretty similar in nature to the Features layer from FSD. Some modules might also be describe large chunks of the UI, like an app header. In that case, you should migrate them to the Widgets layer. + +### Step 8. Form a clean UI foundation in `shared/ui` {#form-clean-ui-foundation} + +`📁 shared/ui` should ideally contain a set of UI elements that don't have any business logic encoded in them. They should also be highly reusable. + +Refactor the UI components that used to be in `📁 components` and `📁 containers` to separate out the business logic. Move that business logic to the higher layers. If it's not used in too many places, you could even consider copy-pasting. + +## See also {#see-also} + +- [(Talk in Russian) Ilya Klimov — Крысиные бега бесконечного рефакторинга: как не дать техническому долгу убить мотивацию и продукт](https://youtu.be/aOiJ3k2UvO4) + +[ext-steiger]: https://github.com/feature-sliced/steiger +[business-entities-cross-relations]: /docs/guides/examples/types#business-entities-and-their-cross-references diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md b/i18n/fr/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md new file mode 100644 index 0000000000..241a964b12 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md @@ -0,0 +1,171 @@ +--- +sidebar_position: 4 +--- + +# Migration from v1 + +## Why v2? + +The original concept of **feature-slices** [was announced][ext-tg-spb] in 2018. + +Since then, many transformations of the methodology have taken place, but at the same time **[the basic principles were preserved][ext-v1]**: + +- Using a *standardized* frontend project structure +- Splitting the application in the first place-according to *business logic* +- Use of *isolated features* to prevent implicit side effects and cyclic dependencies +- Using the *Public API* with a ban on climbing "into the insides" of the module + +At the same time, in the previous version of the methodology, there were still **weak points** that + +- Sometimes it leads to boilerplate code +- Sometimes it leads to excessive complication of the code base and non-obvious rules between abstractions +- Sometimes it leads to implicit architectural solutions, which prevented the project from being pulled up and new people from onboarding + +The new version of the methodology ([v2][ext-v2]) is designed **to eliminate these shortcomings, while preserving the existing advantages** of the approach. + +Since 2018, [has also developed][ext-fdd-issues] another similar methodology - [**feature-driven**][ext-fdd], which was first announced by [Oleg Isonen][ext-kof]. + +After merging of the two approaches, we have **improved and refined existing practices** - towards greater flexibility, clarity and efficiency in application. + +> As a result, this has even affected the name of the methodology - *"feature-slice**d**"* + +## Why does it make sense to migrate the project to v2? + +> `WIP:` The current version of the methodology is under development and some details *may change* + +#### 🔍 More transparent and simple architecture + +The methodology (v2) offers **more intuitive and more common abstractions and ways of separating logic among developers.** + +All this has an extremely positive effect on attracting new people, as well as studying the current state of the project, and distributing the business logic of the application. + +#### 📦 More flexible and honest modularity + +The methodology (v2) allows **to distribute logic in a more flexible way:** + +- With the ability to refactor isolated parts from scratch +- With the ability to rely on the same abstractions, but without unnecessary interweaving of dependencies +- With simpler requirements for the location of the new module *(layer => slice => segment)* + +#### 🚀 More specifications, plans, community + +At the moment, the `core-team` is actively working on the latest (v2) version of the methodology + +So it is for her: + +- there will be more described cases / problems +- there will be more guides on the application +- there will be more real examples +- in general, there will be more documentation for onboarding new people and studying the concepts of the methodology +- the toolkit will be developed in the future to comply with the concepts and conventions on architecture + +> Of course, there will be user support for the first version as well - but the latest version is still a priority for us +> +> In the future, with the next major updates, you will still have access to the current version (v2) of the methodology, **without risks for your teams and projects** + +## Changelog + +### `BREAKING` Layers + +Now the methodology assumes explicit allocation of layers at the top level + +- `/app` > `/processes` > **`/pages`** > **`/features`** > `/entities` > `/shared` +- *That is, not everything is now treated as features/pages* +- This approach allows you to [explicitly set rules for layers][ext-tg-v2-draft]: +- The **higher the layer** of the module is located , the more **context** it has + + *(in other words-each module of the layer - can import only the modules of the underlying layers, but not higher)* + +- The **lower the layer of the** module is located , the more **danger and responsibility** to make changes to it + + *(because it is usually the underlying layers that are more overused)* + +### `BREAKING` Shared + +The infrastructure abstractions `/ui`, `/lib`, `/api`, which used to lie in the src root of the project, are now separated by a separate directory `/src/shared` + +- `shared/ui` - Still the same general uikit of the application (optional) + - *At the same time, no one forbids using `Atomic Design` here as before* +- `shared/lib` - A set of auxiliary libraries for implementing logic + - *Still - without a dump of helpers* +- `shared/api` - A common entry point for accessing the API + - *Can also be registered locally in each feature / page - but it is not recommended* +- As before - there should be no explicit binding to business logic in `shared` + - *If necessary, you need to take this relationship to the `entities` level or even higher* + +### `NEW` Entities, Processes + +In v2 **, other new abstractions** have been added to eliminate the problems of logic complexity and high coupling. + +- `/entities` - layer **business entities** containing slices that are related directly to the business models or synthetic entities required only on frontend + - *Examples: `user`, `i18n`, `order`, `blog`* +- `/processes` - layer **business processes**, penetrating app + - **The layer is optional**, it is usually recommended to use it when *the logic grows and begins to blur in several pages* + - *Examples: `payment`, `auth`, `quick-tour`* + +### `BREAKING` Abstractions & Naming + +Now specific abstractions and [clear recommendations for naming them][refs-adaptability]are defined + +[disc-process]: https://github.com/feature-sliced/documentation/discussions/20 +[disc-features]: https://github.com/feature-sliced/documentation/discussions/23 +[disc-entities]: https://github.com/feature-sliced/documentation/discussions/18#discussioncomment-422649 +[disc-shared]: https://github.com/feature-sliced/documentation/discussions/31#discussioncomment-453020 + +[disc-ui]: https://github.com/feature-sliced/documentation/discussions/31#discussioncomment-453132 +[disc-model]: https://github.com/feature-sliced/documentation/discussions/31#discussioncomment-472645 +[disc-api]: https://github.com/feature-sliced/documentation/discussions/66 + +#### Layers + +- `/app` — **application initialization layer** + - *Previous versions: `app`, `core`,`init`, `src/index` (and this happens)* +- `/processes` — [**business process layer**][disc-process] + - *Previous versions: `processes`, `flows`, `workflows`* +- `/pages` — **application page layer** + - *Previous versions: `pages`, `screens`, `views`, `layouts`, `components`, `containers`* +- `/features` — [**functionality parts layer**][disc-features] + - *Previous versions: `features`, `components`, `containers`* +- `/entities` — [**business entity layer**][disc-entities] + - *Previous versions: `entities`, `models`, `shared`* +- `/shared` — [**layer of reused infrastructure code**][disc-shared] 🔥 + - *Previous versions: `shared`, `common`, `lib`* + +#### Segments + +- `/ui` — [**UI segment**][disc-ui] 🔥 + - *Previous versions: `ui`, `components`, `view`* +- `/model` — [**BL-segment**][disc-model] 🔥 + - *Previous versions: `model`, `store`, `state`, `services`, `controller`* +- `/lib` — segment **of auxiliary code** + - *Previous versions: `lib`, `libs`, `utils`, `helpers`* +- `/api` — [**API segment**][disc-api] + - *Previous versions: `api`, `service`, `requests`, `queries`* +- `/config` — **application configuration segment** + - *Previous versions: `config`, `env`, `get-env`* + +### `REFINED` Low coupling + +Now it is much easier to [observe the principle of low coupling][refs-low-coupling] between modules, thanks to the new layers. + +*At the same time, it is still recommended to avoid as much as possible cases where it is extremely difficult to "uncouple" modules* + +## See also + +- [Notes from the report "React SPB Meetup #1"][ext-tg-spb] +- [React Berlin Talk - Oleg Isonen "Feature Driven Architecture"][ext-kof-fdd] +- [Comparison with v1 (community-chat)](https://t.me/feature_sliced/493) +- [New ideas v2 with explanations (atomicdesign-chat)][ext-tg-v2-draft] +- [Discussion of abstractions and naming for the new version of the methodology (v2)](https://github.com/feature-sliced/documentation/discussions/31) + +[refs-low-coupling]: /docs/reference/isolation/coupling-cohesion +[refs-adaptability]: /docs/about/understanding/naming + +[ext-v1]: https://feature-sliced.github.io/featureslices.dev/v1.0.html +[ext-tg-spb]: https://t.me/feature_slices +[ext-fdd]: https://github.com/feature-sliced/documentation/tree/rc/feature-driven +[ext-fdd-issues]: https://github.com/kof/feature-driven-architecture/issues +[ext-v2]: https://github.com/feature-sliced/documentation +[ext-kof]: https://github.com/kof +[ext-kof-fdd]: https://www.youtube.com/watch?v=BWAeYuWFHhs +[ext-tg-v2-draft]: https://t.me/atomicdesign/18708 diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/tech/_category_.yaml b/i18n/fr/docusaurus-plugin-content-docs/current/guides/tech/_category_.yaml new file mode 100644 index 0000000000..22eec90063 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/tech/_category_.yaml @@ -0,0 +1,2 @@ +label: Tech +position: 3 diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx new file mode 100644 index 0000000000..518fdf02be --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx @@ -0,0 +1,117 @@ +--- +sidebar_position: 10 +--- +# Usage with NextJS + +It is possible to implement FSD in a NextJS project, but conflicts arise due to differences between the requirements for the NextJS project structure and the principles of FSD in two points:  + +- Routing files in the `pages` layer +- Conflict or absence of the `app` layer in NextJS + +## Conflict between FSD and NextJS on `pages` layer {#pages-conflict} + + +NextJS suggests using the `pages` folder to define application routes. NextJS expects files in the `pages` folder to match URLs. +This routing mechanism **does not correspond** to the FSD concept, since it is not possible to maintain a flat slice structure in such a routing mechanism. + +### Moving the `pages` folder of NextJS to the root folder of the project (recommended) + +The approach is to move the `pages` NextJS folder to the root folder of the project and import the FSD pages into the `pages` NextJS folder. This saves +the FSD project structure inside the `src` folder. + +```sh +├── pages # NextJS pages folder +├── src +│ ├── app +│ ├── entities +│ ├── features +│ ├── pages # FSD pages folder +│ ├── shared +│ ├── widgets +``` + +### Renaming the `pages` layer within the FSD structure + +Another way to solve the problem is to rename the `pages` layer in the FSD structure to avoid conflicts with the NextJS `pages` folder. +You can rename the `pages` layer in FSD to `views`. +In that way, the structure of the project in the `src` folder is preserved without contradiction with the requirements of NextJS. + +```sh +├── app +├── entities +├── features +├── pages # NextJS pages folder +├── views # Renamed FSD pages folder +├── shared +├── widgets +``` + +Keep in mind that it's highly recommended to document this rename prominently in your project's README or internal documentation. This rename is a part of your ["project knowledge"][project-knowledge]. + +## The absence of the `app` folder in NextJS {#app-absence} + +In NextJS below version 13, there is no explicit `app` folder, instead NextJS provides the `_app.tsx` file, +which plays the role of a wrapping component for all project pages. + +### Importing app functionality to `pages/_app.tsx` file + +To solve the problem of missing the `app` folder in the NextJS structure, you can create an `App` component inside the `app` layer and import the `App` component into `pages/_app.tsx` so that NextJS can work with it. +For example: + +```tsx +// app/providers/index.tsx + +const App = ({ Component, pageProps }: AppProps) => { + return ( + + + + + + + + ); +}; + +export default App; +``` +Then you can import the `App` component and global project styles into `pages/_app.tsx` as follows: + +```tsx +// pages/_app.tsx + +import 'app/styles/index.scss' + +export { default } from 'app/providers'; +``` + +## Dealing with App Router {#app-router} + +App Router has become stable in NextJS version 13.4. App Router allows you to use the `app` folder for routing instead of the `pages` folder. +To comply with the principles of FSD, you should treat the NextJS `app` folder in the same way as recommended +to resolve a conflict with the NextJS `pages` folder. + +The approach is to move the NextJS `app` folder to the root folder of the project and import the FSD pages into the NextJS `app` folder. This saves +the FSD project structure inside the `src` folder. You should still also add the `pages` folder to the root, because the App router is compatible with the Pages router. + +``` +├── app # NextJS app folder +├── pages # Stub NextJS pages folder +│ ├── README.md # Description of why this folder exists +├── src +│ ├── app # FSD app folder +│ ├── entities +│ ├── features +│ ├── pages # FSD app folder +│ ├── shared +│ ├── widgets +``` + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)][ext-app-router-stackblitz] + +## See also {#see-also} + +- [(Thread) About the pages directory in NextJS](https://t.me/feature_sliced/3623) + +[project-knowledge]: /docs/about/understanding/knowledge-types +[ext-app-router-stackblitz]: https://stackblitz.com/edit/stackblitz-starters-aiez55?file=README.md diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/tech/with-nuxtjs.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/guides/tech/with-nuxtjs.mdx new file mode 100644 index 0000000000..929bdcaf51 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/tech/with-nuxtjs.mdx @@ -0,0 +1,179 @@ +--- +sidebar_position: 10 +--- +# Usage with NuxtJS + +It is possible to implement FSD in a NuxtJS project, but conflicts arise due to the differences between NuxtJS project structure requirements and FSD principles: + +- Initially, NuxtJS offers a project file structure without a `src` folder, i.e. in the root of the project. +- The file routing is in the `pages` folder, while in FSD this folder is reserved for the flat slice structure. + + +## Adding an alias for the `src` directory + +Add an `alias` object to your config: +```ts title="nuxt.config.ts" +export default defineNuxtConfig({ + devtools: { enabled: true }, // Not FSD related, enabled at project startup + alias: { + "@": '../src' + }, +}) +``` +## Choose how to configure the router + +In NuxtJS, there are two ways to customize the routing - using a config and using a file structure. +In the case of file-based routing, you will create index.vue files in folders inside the app/routes directory, and in the case of configure, you will configure the routers in the `router.options.ts` file. + + +### Routing using config + +In the `app` layer, create a `router.options.ts` file, and export a config object from it: +```ts title="app/router.options.ts" +import type { RouterConfig } from '@nuxt/schema'; + +export default { + routes: (_routes) => [], +}; + +``` + +To add a `Home` page to your project, you need to do the following steps: +- Add a page slice inside the `pages` layer +- Add the appropriate route to the `app/router.config.ts` config + + +To create a page slice, let's use the [CLI](https://github.com/feature-sliced/cli): + +```shell +fsd pages home +``` + +Create a ``home-page.vue`` file inside the ui segment, access it using the Public API + +```ts title="src/pages/home/index.ts" +export { default as HomePage } from './ui/home-page'; +``` + +Thus, the file structure will look like this: +```sh +|── src +│ ├── app +│ │ ├── router.config.ts +│ ├── pages +│ │ ├── home +│ │ │ ├── ui +│ │ │ │ ├── home-page.vue +│ │ │ ├── index.ts +``` +Finally, let's add a route to the config: + +```ts title="app/router.config.ts" +import type { RouterConfig } from '@nuxt/schema' + +export default { + routes: (_routes) => [ + { + name: 'home', + path: '/', + component: () => import('@/pages/home.vue').then(r => r.default || r) + } + ], +} +``` + +### File Routing + +First of all, create a `src` directory in the root of your project, and create app and pages layers inside this directory and a routes folder inside the app layer. +Thus, your file structure should look like this: + +```sh +├── src +│ ├── app +│ │ ├── routes +│ ├── pages # Pages folder, related to FSD +``` + +In order for NuxtJS to use the routes folder inside the `app` layer for file routing, you need to modify `nuxt.config.ts` as follows: +```ts title="nuxt.config.ts" +export default defineNuxtConfig({ + devtools: { enabled: true }, // Not FSD related, enabled at project startup + alias: { + "@": '../src' + }, + dir: { + pages: './src/app/routes' + } +}) +``` + +Now, you can create routes for pages within `app` and connect pages from `pages` to them. + +For example, to add a `Home` page to your project, you need to do the following steps: +- Add a page slice inside the `pages` layer +- Add the corresponding route inside the `app` layer +- Connect the page from the slice with the route + +To create a page slice, let's use the [CLI](https://github.com/feature-sliced/cli): + +```shell +fsd pages home +``` + +Create a ``home-page.vue`` file inside the ui segment, access it using the Public API + +```ts title="src/pages/home/index.ts" +export { default as HomePage } from './ui/home-page'; +``` + +Create a route for this page inside the `app` layer: + +```sh + +├── src +│ ├── app +│ │ ├── routes +│ │ │ ├── index.vue +│ ├── pages +│ │ ├── home +│ │ │ ├── ui +│ │ │ │ ├── home-page.vue +│ │ │ ├── index.ts +``` + +Add your page component inside the `index.vue` file: + +```html title="src/app/routes/index.vue" + + + +``` + +## What to do with `layouts`? + +You can place layouts inside the `app` layer, to do this you need to modify the config as follows: + +```ts title="nuxt.config.ts" +export default defineNuxtConfig({ + devtools: { enabled: true }, // Not related to FSD, enabled at project startup + alias: { + "@": '../src' + }, + dir: { + pages: './src/app/routes', + layouts: './src/app/layouts' + } +}) +``` + + +## See also + +- [Documentation on changing directory config in NuxtJS](https://nuxt.com/docs/api/nuxt-config#dir) +- [Documentation on changing router config in NuxtJS](https://nuxt.com/docs/guide/recipes/custom-routing#router-config) +- [Documentation on changing aliases in NuxtJS](https://nuxt.com/docs/api/nuxt-config#alias) + diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx new file mode 100644 index 0000000000..1bb64f57f3 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx @@ -0,0 +1,435 @@ +--- +sidebar_position: 10 +--- +# Usage with React Query + +## The problem of “where to put the keys” + +### Solution — break down by entities + +If the project already has a division into entities, and each request corresponds to a single entity, +the purest division will be by entity. In this case, we suggest using the following structure: +```sh +└── src/ # + ├── app/ # + | ... # + ├── pages/ # + | ... # + ├── entities/ # + | ├── {entity}/ # + | ... └── api/ # + | ├── `{entity}.query` # Query-factory where are the keys and functions + | ├── `get-{entity}` # Entity getter function + | ├── `create-{entity}` # Entity creation function + | ├── `update-{entity}` # Entity update function + | ├── `delete-{entity}` # Entity delete function + | ... # + | # + ├── features/ # + | ... # + ├── widgets/ # + | ... # + └── shared/ # + ... # +``` + +If there are connections between the entities (for example, the Country entity has a field-list of City entities), +then you can use an [experimental approach to organized cross-imports +via @x-notation](https://github.com/feature-sliced/documentation/discussions/390#discussioncomment-5570073) or consider the alternative solution below. + +### Alternative solution — keep it in shared + +In cases where entity separation is not appropriate, the following structure can be considered: + +```sh +└── src/ # + ... # + └── shared/ # + ├── api/ # + ... ├── `queries` # Query-factories + | ├── `document.ts` # + | ├── `background-jobs.ts` # + | ... # + └── index.ts # +``` + +Then in `@/shared/api/index.ts`: + +```ts title="@/shared/api/index.ts" +export { documentQueries } from "./queries/document"; +``` + +## The problem of “Where to insert mutations?” + +It is not recommended to mix mutations with queries. There are two options: + +### 1. Define a custom hook in the `api` segment near the place of use + +```tsx title="@/features/update-post/api/use-update-title.ts" +export const useUpdateTitle = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ id, newTitle }) => + apiClient + .patch(`/posts/${id}`, { title: newTitle }) + .then((data) => console.log(data)), + + onSuccess: (newPost) => { + queryClient.setQueryData(postsQueries.ids(id), newPost); + }, + }); +}; +``` + +### 2. Define a mutation function somewhere else (Shared or Entities) and use `useMutation` directly in the component + +```tsx +const { mutateAsync, isPending } = useMutation({ + mutationFn: postApi.createPost, +}); +``` + +```tsx title="@/pages/post-create/ui/post-create-page.tsx" +export const CreatePost = () => { + const { classes } = useStyles(); + const [title, setTitle] = useState(""); + + const { mutate, isPending } = useMutation({ + mutationFn: postApi.createPost, + }); + + const handleChange = (e: ChangeEvent) => + setTitle(e.target.value); + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + mutate({ title, userId: DEFAULT_USER_ID }); + }; + + return ( +
+ + + Create + + + ); +}; +``` + +## Organization of requests + +### Query factory + +A query factory is an object where the key values are functions that return a list of query keys. Here's how to use it: + +```ts +const keyFactory = { + all: () => ["entity"], + lists: () => [...postQueries.all(), "list"], +}; +``` + +:::info +`queryOptions` is a built-in utility in react-query@v5 (optional) + +```ts +queryOptions({ + queryKey, + ...options, +}); +``` + +For greater type safety, further compatibility with future versions of react-query, and easy access to functions and query keys, +you can use the built-in queryOptions function from “@tanstack/react-query” +[(More details here)](https://tkdodo.eu/blog/the-query-options-api#queryoptions). +::: + +### 1. Creating a Query Factory + +```tsx title="@/entities/post/api/post.queries.ts" +import { keepPreviousData, queryOptions } from "@tanstack/react-query"; +import { getPosts } from "./get-posts"; +import { getDetailPost } from "./get-detail-post"; +import { PostDetailQuery } from "./query/post.query"; + +export const postQueries = { + all: () => ["posts"], + + lists: () => [...postQueries.all(), "list"], + list: (page: number, limit: number) => + queryOptions({ + queryKey: [...postQueries.lists(), page, limit], + queryFn: () => getPosts(page, limit), + placeholderData: keepPreviousData, + }), + + details: () => [...postQueries.all(), "detail"], + detail: (query?: PostDetailQuery) => + queryOptions({ + queryKey: [...postQueries.details(), query?.id], + queryFn: () => getDetailPost({ id: query?.id }), + staleTime: 5000, + }), +}; +``` + +### 2. Using Query Factory in application code +```tsx +import { useParams } from "react-router-dom"; +import { postApi } from "@/entities/post"; +import { useQuery } from "@tanstack/react-query"; + +type Params = { + postId: string; +}; + +export const PostPage = () => { + const { postId } = useParams(); + const id = parseInt(postId || ""); + const { + data: post, + error, + isLoading, + isError, + } = useQuery(postApi.postQueries.detail({ id })); + + if (isLoading) { + return
Loading...
; + } + + if (isError || !post) { + return <>{error?.message}; + } + + return ( +
+

Post id: {post.id}

+
+

{post.title}

+
+

{post.body}

+
+
+
Owner: {post.userId}
+
+ ); +}; +``` + +### Benefits of using a Query Factory +- **Request structuring:** A factory allows you to organize all API requests in one place, making your code more readable and maintainable. +- **Convenient access to queries and keys:** The factory provides convenient methods for accessing different types of queries and their keys. +- **Query Refetching Ability:** The factory allows easy refetching without the need to change query keys in different parts of the application. + +## Pagination + +In this section, we'll look at an example of the `getPosts` function, which makes an API request to retrieve post entities using pagination. + +### 1. Creating a function `getPosts` +The getPosts function is located in the `get-posts.ts` file, which is located in the `api` segment + +```tsx title="@/pages/post-feed/api/get-posts.ts" +import { apiClient } from "@/shared/api/base"; + +import { PostWithPaginationDto } from "./dto/post-with-pagination.dto"; +import { PostQuery } from "./query/post.query"; +import { mapPost } from "./mapper/map-post"; +import { PostWithPagination } from "../model/post-with-pagination"; + +const calculatePostPage = (totalCount: number, limit: number) => + Math.floor(totalCount / limit); + +export const getPosts = async ( + page: number, + limit: number, +): Promise => { + const skip = page * limit; + const query: PostQuery = { skip, limit }; + const result = await apiClient.get("/posts", query); + + return { + posts: result.posts.map((post) => mapPost(post)), + limit: result.limit, + skip: result.skip, + total: result.total, + totalPages: calculatePostPage(result.total, limit), + }; +}; +``` + +### 2. Query factory for pagination +The `postQueries` query factory defines various query options for working with posts, +including requesting a list of posts with a specific page and limit. + +```tsx +import { keepPreviousData, queryOptions } from "@tanstack/react-query"; +import { getPosts } from "./get-posts"; + +export const postQueries = { + all: () => ["posts"], + lists: () => [...postQueries.all(), "list"], + list: (page: number, limit: number) => + queryOptions({ + queryKey: [...postQueries.lists(), page, limit], + queryFn: () => getPosts(page, limit), + placeholderData: keepPreviousData, + }), +}; +``` + + +### 3. Use in application code + +```tsx title="@/pages/home/ui/index.tsx" +export const HomePage = () => { + const itemsOnScreen = DEFAULT_ITEMS_ON_SCREEN; + const [page, setPage] = usePageParam(DEFAULT_PAGE); + const { data, isFetching, isLoading } = useQuery( + postApi.postQueries.list(page, itemsOnScreen), + ); + return ( + <> + setPage(page)} + page={page} + count={data?.totalPages} + variant="outlined" + color="primary" + /> + + + ); +}; +``` +:::note +The example is simplified, the full version is available on [GitHub](https://github.com/ruslan4432013/fsd-react-query-example) +::: + +## `QueryProvider` for managing queries +In this guide, we will look at how to organize a `QueryProvider`. + +### 1. Creating a `QueryProvider` +The file `query-provider.tsx` is located at the path `@/app/providers/query-provider.tsx`. + +```tsx title="@/app/providers/query-provider.tsx" +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { ReactNode } from "react"; + +type Props = { + children: ReactNode; + client: QueryClient; +}; + +export const QueryProvider = ({ client, children }: Props) => { + return ( + + {children} + + + ); +}; +``` + +### 2. Creating a `QueryClient` +`QueryClient` is an instance used to manage API requests. +The `query-client.ts` file is located at `@/shared/api/query-client.ts`. +`QueryClient` is created with certain settings for query caching. + +```tsx title="@/shared/api/query-client.ts" +import { QueryClient } from "@tanstack/react-query"; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5 * 60 * 1000, + gcTime: 5 * 60 * 1000, + }, + }, +}); +``` + +## Code generation + +There are tools that can generate API code for you, but they are less flexible than the manual approach described above. +If your Swagger file is well-structured, +and you're using one of these tools, it might make sense to generate all the code in the `@/shared/api` directory. + + +## Additional advice for organizing RQ +### API Client + +Using a custom API client class in the shared layer, +you can standardize the configuration and work with the API in the project. +This allows you to manage logging, +headers and data exchange format (such as JSON or XML) from one place. +This approach makes it easier to maintain and develop the project because it simplifies changes and updates to interactions with the API. + +```tsx title="@/shared/api/api-client.ts" +import { API_URL } from "@/shared/config"; + +export class ApiClient { + private baseUrl: string; + + constructor(url: string) { + this.baseUrl = url; + } + + async handleResponse(response: Response): Promise { + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + try { + return await response.json(); + } catch (error) { + throw new Error("Error parsing JSON response"); + } + } + + public async get( + endpoint: string, + queryParams?: Record, + ): Promise { + const url = new URL(endpoint, this.baseUrl); + + if (queryParams) { + Object.entries(queryParams).forEach(([key, value]) => { + url.searchParams.append(key, value.toString()); + }); + } + const response = await fetch(url.toString(), { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + return this.handleResponse(response); + } + + public async post>( + endpoint: string, + body: TData, + ): Promise { + const response = await fetch(`${this.baseUrl}${endpoint}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + return this.handleResponse(response); + } +} + +export const apiClient = new ApiClient(API_URL); +``` + +## See also {#see-also} + +- [(GitHub) Sample Project](https://github.com/ruslan4432013/fsd-react-query-example) +- [(CodeSandbox) Sample Project](https://codesandbox.io/p/github/ruslan4432013/fsd-react-query-example/main) +- [About the query factory](https://tkdodo.eu/blog/the-query-options-api) diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx new file mode 100644 index 0000000000..886b2381ee --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx @@ -0,0 +1,100 @@ +--- +sidebar_position: 10 +--- +# Usage with SvelteKit + +It is possible to implement FSD in a SvelteKit project, but conflicts arise due to the differences between the structure requirements of a SvelteKit project and the principles of FSD: + +- Initially, SvelteKit offers a file structure inside the `src/routes` folder, while in FSD the routing must be part of the `app` layer. +- SvelteKit suggests putting everything not related to routing in the `src/lib` folder. + + +## Let's set up the config + +```ts title="svelte.config.ts" +import adapter from '@sveltejs/adapter-auto'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config}*/ +const config = { + preprocess: [vitePreprocess()], + kit: { + adapter: adapter(), + files: { + routes: 'src/app/routes', // move routing inside the app layer + lib: 'src', + appTemplate: 'src/app/index.html', // Move the application entry point inside the app layer + assets: 'public' + }, + alias: { + '@/*': 'src/*' // Create an alias for the src directory + } + } +}; +export default config; +``` + +## Move file routing to `src/app`. + +Let's create an app layer, move the app's entry point `index.html` into it, and create a routes folder. +Thus, your file structure should look like this: + +```sh +├── src +│ ├── app +│ │ ├── index.html +│ │ ├── routes +│ ├── pages # FSD Pages folder +``` + +Now, you can create routes for pages within `app` and connect pages from `pages` to them. + +For example, to add a home page to your project, you need to do the following steps: +- Add a page slice inside the `pages` layer +- Add the corresponding rooute to the `routes` folder from the `app` layer +- Align the page from the slice with the rooute + +To create a page slice, let's use the [CLI](https://github.com/feature-sliced/cli): + +```shell +fsd pages home +``` + +Create a ``home-page.svelte`` file inside the ui segment, access it using the Public API + +```ts title="src/pages/home/index.ts" +export { default as HomePage } from './ui/home-page.svelte'; +``` + +Create a route for this page inside the `app` layer: + +```sh + +├── src +│ ├── app +│ │ ├── routes +│ │ │ ├── +page.svelte +│ │ ├── index.html +│ ├── pages +│ │ ├── home +│ │ │ ├── ui +│ │ │ │ ├── home-page.svelte +│ │ │ ├── index.ts +``` + +Add your page component inside the `+page.svelte` file: + +```html title="src/app/routes/+page.svelte" + + + + +``` + +## See also. + +- [Documentation on changing directory config in SvelteKit](https://kit.svelte.dev/docs/configuration#files) + + diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/intro.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/intro.mdx new file mode 100644 index 0000000000..8fb3972fa7 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/intro.mdx @@ -0,0 +1,69 @@ +--- +sidebar_position: 1 +slug: / +pagination_next: get-started/index +--- + +# Documentation + +![feature-sliced-banner](/img/banner.jpg) + +**Feature-Sliced Design** (FSD) is an architectural methodology for scaffolding front-end applications. Simply put, it's a compilation of rules and conventions on organizing code. The main purpose of this methodology is to make the project more understandable and structured in the face of ever-changing business requirements. + +import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" +import { RocketOutlined, ThunderboltOutlined, FundViewOutlined } from "@ant-design/icons"; +import Link from "@docusaurus/Link"; + + + + + +
+ + + + + + + diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/reference/index.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/reference/index.mdx new file mode 100644 index 0000000000..f1f31cd9d9 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/reference/index.mdx @@ -0,0 +1,39 @@ +--- +sidebar_position: 0 +hide_table_of_contents: true +pagination_prev: guides/index +--- + +import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx" +import { ApiOutlined, GroupOutlined, AppstoreOutlined, NodeIndexOutlined } from "@ant-design/icons"; + +# 📚 Reference + +

+A detailed description of the key concepts of Feature-Sliced Design. +

+ + + + + diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/reference/isolation/_category_.yaml b/i18n/fr/docusaurus-plugin-content-docs/current/reference/isolation/_category_.yaml new file mode 100644 index 0000000000..df39e2f2b1 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/reference/isolation/_category_.yaml @@ -0,0 +1 @@ +label: Isolation diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/reference/isolation/coupling-cohesion.md b/i18n/fr/docusaurus-plugin-content-docs/current/reference/isolation/coupling-cohesion.md new file mode 100644 index 0000000000..9ca0e6cd8c --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/reference/isolation/coupling-cohesion.md @@ -0,0 +1,149 @@ +--- +sidebar_position: 1 +--- + +# Low Coupling & High Cohesion + +Application modules should be designed according to **high cohesion** (should solve one specific task) and **low coupling** (independent of other modules) principles. + +
+ + +
+ Image inspired by https://enterprisecraftsmanship.com/posts/cohesion-coupling-difference/ +
+
+ +Within the methodology, this is achieved through: + +* Splitting the application into layers and slices that implement specific functionality +* Providing a [public access interface][refs-public-api] for each module +* Setting up restrictions for [modules interactions][refs-isolation] - each module can depend only on the modules below it, but not on modules from the same or higher layer + +## Components composition (UI level) + +The majority of modern UI frameworks and libraries provide a component model in which each component can have its own properties, state, child components, and even slots. + +This model allows you to design an interface as a **composition of various components that are not directly related to each other** and, thereby, achieve **low coupling** of the interface components. + +### Example + +Let's consider such a composition using the example of a **list with a header:** + +#### Laying the extensibility + +List component will not itself define the look and structure of the header components and list elements, instead it will accept them as parameters + +```tsx +interface ListProps { + Header: React.ReactNode; + Items: React.ReactNode; +} + +const List: Component = ({ Header, Items }) => ( +
+ {Header} +
    + {Items} +
+
+) + +``` + +#### Using the composition + +This allows you to **reuse and independently change** components with different Header and list Items. Header and Items components can have both their own local state and their binding to the general state of the application - the List component will not know anything about it, and therefore will not depend on it + +```tsx +} Items={} /> + +} /> + +} Items={} /> + +``` + +## Layer composition (APP level) + +The methodology suggests putting the functionality that is valuable for the user into **features slice**, and the logic related to business entities - into **entities**. Both features and entities **should be designed as modules with high cohesion**, i.e. aimed at solving **one specific task** or related to **one specific entity.** + +All interactions between such modules, similar to the UI components from the example above, should be coordinated via a **modules composition**. + +### Example + +Let's use an example of a chat application with the following features: + +* user can open a contact list and select a friend +* user can open a conversation with a selected friend + +According to methodology principles, it can be represented as: + +Entities + +* User (contains user's state) +* Contact (state of the contact list, utilities for working with an individual contact) +* Chat (the state of the current chat and utilies for it) + +Features + +* Form for sending a message +* Chat selection menu + +#### Let's tie it all together + +The application, to begin with, will have one page, and the interface will be slightly modified from the first example + +```tsx title="page/main/ui.tsx" +} + Items={} + Footer={} +/> +``` + +#### Data model + +The page data model will be organized as a **composition of features and entities**. In this example, the features will be implemented as factories and they will access the interface of entities through the parameters of these factories. + +> However, the implementation using factory is optional - the feature may directly depend on the lower layers. + +```ts title="pages/main/model.ts" +import { userModel } from "entitites/user" +import { conversationModel } from "entities/conversation" +import { contactModel } from "entities/contact" + +import { createMessageInput } from "features/message-input" +import { createConversationSwitch } from "features/conversation-switch" + +import { beautifiy } from "shared/lib/beautify-text" + +export const { allConversations, setConversation } = createConversationSwitch({ + contacts: contactModel.allContacts, + setConversation: conversationModel.setConversation, + currentConversation: conversationModel.conversation, + currentUser: userModel.currentUser +}) + +export const { sendMessage, attachFile } = createMessageInput({ + author: userModel.currentUser + send: conversationModel.sendMessage, + formatMessage: beautify +}) +``` + +## Summary + +1. Modules must have **high cohesion** (have one responsibility, solve one specific task) and provide a [**public interface**][refs-public-api] access +2. **Low coupling** is achieved through the composition of elements - UI components, features and entities +3. To reduce entanglement, modules **should interact with each other only through a public interfaces** - this makes modules independent of each other's internal implementation + +## See also + +* [(Article) Low Coupling and High Cohesion in details](https://enterprisecraftsmanship.com/posts/cohesion-coupling-difference/) + * *The diagram at the beginning is inspired by this article* +* [(Article) Low Coupling and High Cohesion. The Law of Demeter](https://medium.com/german-gorelkin/low-coupling-high-cohesion-d36369fb1be9) +* [(Presentation) On design principles (including Low Coupling & High Cohesion)](https://www.slideshare.net/cristalngo/software-design-principles-57388843) + +[refs-public-api]: /docs/reference/public-api +[refs-isolation]: /docs/reference/isolation diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/reference/isolation/decouple-entities.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/reference/isolation/decouple-entities.mdx new file mode 100644 index 0000000000..6b8d757d7a --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/reference/isolation/decouple-entities.mdx @@ -0,0 +1,21 @@ +--- +sidebar_position: 2 +sidebar_class_name: sidebar-item--wip +--- + +import WIP from '@site/src/shared/ui/wip/tmpl.mdx' + +# Decouple entities + + + +> About cross-imports of types, adapters and about how to explicitly build connections between entities + +> Also about mythical absolutely-decoupled entities + +## See also + +- [(Thread) Memo about decomposition by entities and building explicit links between them](https://t.me/feature_sliced/3633) +- [(Thread) Example of decomposition for "connected entities" (users/pets/friends)](https://t.me/feature_sliced/3316) +- [(Thread) About cross-imports of types/adapters in entities](https://t.me/feature_sliced/4276) +- [(Thread) About the boundaries of entities and features](https://t.me/feature_sliced/4521) diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/reference/isolation/index.md b/i18n/fr/docusaurus-plugin-content-docs/current/reference/isolation/index.md new file mode 100644 index 0000000000..42756834fd --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/reference/isolation/index.md @@ -0,0 +1,71 @@ +# Isolation of modules + +Within the framework of the methodology, all modules are distributed by scopes of responsibility (layer, slice, segment) + +The layers, in turn, are organized vertically: + +- "At the bottom" are the reused modules (ui-kit, internal libraries of the project), as the most abstract +- And as you move "up", more specific modules are located. + +Regardless of whether it belongs to any slice, each module [**is required to provide a public access interface**][refs-public-api]. + +## Requirements + +The interaction of each module with the rest of the application is designed taking into account a number of requirements: + +1. **Low coupling** with other modules + - *A change in one module should have a weak and predictable effect on others* + +1. **High cohesion** - the responsibilities of each module are "focused" on one task + + - *If the module has too many responsibilities (starts "doing too much") - this should be noticed as soon as possible* +1. **Absence of cyclic dependencies** on the scale of the entire application + + - *Often lead to unexpected, undesirable behavior, it is better to avoid them altogether* + +## Rule + +To meet these requirements, within the framework of the methodology, it is necessary to observe the basic rule: + +:::info Important + +A module can depend only on "underlying" modules, but not on modules from the same or higher layer + +::: + +- `features/auth` **cannot** depend on `features/filters` **and vice versa** +- `features/auth` **may** depend on `shared/ui/button`, **but not vice versa** + +Following this rule allows you to keep dependencies **"unidirectional"** - which automatically **eliminates cyclic imports** and significantly **simplifies tracking dependencies** between modules in the application. + +## Identifying problems + + +Violation of this rule is a signal of problems: + +1. The module has **import from another module** from its own layer + + - Perhaps the module was **unnecessarily fragmented** or has **unnecessary responsibility.** + - You should **combine** it with the imported module or **move it (partially or completely) to the layer below** or transfer the logic of relationships to modules on higher layers. + +1. The module **is imported by many modules** from its own layer + + - Perhaps the module has **extra responsibility.** + - You should **move it (partially or entirely) to the layer below**, or transfer the logic of connections to modules on higher layers. + +1. The module **has imports from many modules** from its own layer + + - Perhaps the module belongs to **another scope of responsibility.** + - You should **move it (partially or completely) to the layer above**. + +## See also + +- [(Guide) About achieving low coupling][refs-low-coupling] +- [(Discussion) Coupled entities](https://github.com/feature-sliced/documentation/discussions/49) +- [(Discussion) About cross-imports and analysis зависимостей](https://github.com/feature-sliced/documentation/discussions/65#discussioncomment-480822) +- [**GRASP** Patterns](https://en.wikipedia.org/wiki/GRASP_(object-oriented_design)) + +[refs-public-api]: /docs/reference/public-api +[refs-low-coupling]: /docs/reference/isolation/coupling-cohesion diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/reference/layers.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/reference/layers.mdx new file mode 100644 index 0000000000..8782c9d45f --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/reference/layers.mdx @@ -0,0 +1,185 @@ +--- +sidebar_position: 1 +pagination_next: reference/slices-segments +--- + +# Layers + +Layers are the first level of organisational hierarchy in Feature-Sliced Design. Their purpose is to separate code based on how much responsibility it needs and how many other modules in the app it depends on. + +:::note + +On this page, a _module_ refers to an internal module in the application — a file or directory with an index file. Not to be confused with npm packages. + +::: + +Every layer carries special semantic meaning to help you determine how much responsibility you should allocate to a module in your code. The names and meanings of layers are standardized across all projects built with Feature-Sliced Design. + +There are **7 layers** in total, arranged from most responsibility and dependency to least: + +A file system tree, with a single root folder called src and then seven subfolders: app, processes, pages, widgets, features, entities, shared. The processes folder is slightly faded out. +A file system tree, with a single root folder called src and then seven subfolders: app, processes, pages, widgets, features, entities, shared. The processes folder is slightly faded out. + +1. App +2. Processes (deprecated) +3. Pages +4. Widgets +5. Features +6. Entities +7. Shared + +You don't have to use every layer in your project — only add them if you think it brings value to your project. + +## Import rule on layers + +Layers are made up of _slices_ — highly cohesive groups of modules. Feature-Sliced Design promotes low coupling, which is why dependencies between slices are regulated by **the import rule on layers**: + +> _A module in a slice can only import other slices when they are located on layers strictly below._ + +For example, in `~/features/aaa`, `aaa` is the slice, so a file `~/features/aaa/api/request.ts` cannot import code from any module in `~/features/bbb`, but can import code from `~/entities` and `~/shared`, as well as any sibling code from `~/features/aaa`. + +## Layer definitions + +### Shared + +Isolated modules, components and abstractions that are detached from the specifics of the project or business. +Warning: not to be treated like [a utility dump][ext--sova-utility-dump]! + +This layer, unlike others, does not consist of slices, and instead consists of segments directly. + +**Content examples**: + +* UI kit +* API client +* Code working with browser APIs + +### Entities + +Concepts from the real world that form together the essence of the project. Commonly, these are the terms that the business uses to describe the product. + +Each slice in this layer contains static UI elements, data stores and CRUD operations. + +**Slice examples**: + + + +
For a social network For a Git frontend (e.g., GitHub)
    +
  • User
  • +
  • Post
  • +
  • Group
  • +
    +
  • Repository
  • +
  • File
  • +
  • Commit
  • +
+ + +:::tip + +You may notice in the example of a Git frontend that a _repository_ contains _files_. This makes the repository a higher-level entity which has other entities nested inside. That is a common situation with entities, and sometimes it's hard to manage such higher-level entities without breaking the import rule on layers. + +Here are a few suggestions to overcome this issue: +* The UI of entities should contain slots for places where the lower-level entities are to be inserted +* The business logic related to entity interaction should be placed in features (most of the time) +* The typings of database entities can be extracted to the Shared layer below, next to the API client + +::: + +### Features + +Actions that a user can make in the application to interact with the business entities to achieve a valuable outcome. This also includes actions that the app makes on behalf of the user to produce value for them. + +Each slice in this layer can contain _interactive_ UI elements, internal state and API calls that enable value-producing actions. + +**Slice examples**: + + + +
For a social network For a Git frontend (e.g., GitHub) Actions on behalf of users
    +
  • Authenticate
  • +
  • Create a post
  • +
  • Join a group
  • +
    +
  • Edit a file
  • +
  • Leave a comment
  • +
  • Merge branches
  • +
    +
  • Detect dark mode
  • +
  • Perform background computation
  • +
  • User-Agent-based actions
  • +
+ +### Widgets + +Self-sufficient UI blocks that emerged from the composition of lower-level units like entities and features. + +This layer provides a way to fill in the slots left in the UI of Entities with other Entities and interactive elements from Features. Therefore, it is common not to have business logic on this layer, instead keeping it in Features. Each slice in this layer contains ready-to-use UI components and sometimes non-business logic such as gestures, keyboard interaction, etc. + +Sometimes, however, it is more convenient to have business logic on this layer. Usually it happens when the widget is quite rich in interactivity (e.g., interactive data tables) and the business logic inside them is not used in other places. + +**Slice examples**: + + + +
For a social network For a Git frontend (e.g., GitHub)
    +
  • Post card
  • +
  • User profile header (with actions)
  • +
    +
  • List of files in a repository (with actions)
  • +
  • Comment in a thread
  • +
  • Repository card
  • +
+ +:::tip + +If you're using a nested routing system (e.g. the router of [Remix][ext--remix]), it may be helpful to use the Widgets layer in the same way as a flat routing system would use the Pages layer — to create complete interface blocks, complete with related data fetching, loading states, and error boundaries. In the same way, you can store page layouts on this layer. + +::: + +### Pages + +Complete pages for a page-based application (like a website) or screens/activities for screen-based applications (like mobile apps). + +This layer is similar to Widgets in its compositional nature, albeit on a larger scale. Each slice in this layer contains UI components that are ready to be plugged into a router and sometimes data-fetching logic and error handling. + +**Slice examples**: + + + +
For a social network For a Git frontend (e.g., GitHub)
    +
  • News feed
  • +
  • Community page
  • +
  • User's public profile
  • +
    +
  • Repository page
  • +
  • User's repositories
  • +
  • Branches in a repository
  • +
+ +### Processes + +:::caution + +This layer has been deprecated. The current version of the spec recommends avoiding it and moving its contents to `features` and `app` instead. + +::: + +Escape hatches for multi-page interactions. + +This layer is deliberately left undefined. Most applications should not use this layer, and keep router-level and server-level logic on the App layer. Consider using this layer only when the App layer grows large enough to become unmaintainable and needs unloading. + +### App + +All kinds of app-wide matters, both in the technical sense (e.g., context providers) and in the business sense (e.g., analytics). + +This layer usually doesn't contain slices, like Shared, instead having segments directly. + +**Content examples**: + +* Styles +* Routing +* Store and other context providers +* Analytics initialization + +[ext--remix]: https://remix.run +[ext--sova-utility-dump]: https://dev.to/sergeysova/why-utils-helpers-is-a-dump-45fo diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/reference/public-api.md b/i18n/fr/docusaurus-plugin-content-docs/current/reference/public-api.md new file mode 100644 index 0000000000..9a6ef1fc9a --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/reference/public-api.md @@ -0,0 +1,217 @@ +--- +sidebar_position: 3 +pagination_next: about/index +--- + +# Public API + +Each entity of the methodology is designed as a **user-friendly and integrable module.** + +## Goals + +The convenience of using and integrating the module is achieved through the fulfillment of *a number of goals*: + +1. The application must be **protected from changes** to the internal structure of individual modules +1. The processing of the internal structure of the module **should not affect** other modules +1. Significant changes in the behavior of the module should be **easily detectable** + > **Significant changes in the behavior of the module** - changes that break the expectations of the user entities of the module. + +These goals can be achieved by introducing a public interface (Public API), which is a single access point to the module's capabilities and defines the "contract" of the module's interaction with the outside world. + +:::info Important + +The entity structure must have a single entry point that provides a public interface + +::: + +```sh +└── features/               #  + ├── auth-form / # Internal structure of the feature + | ├── ui/        # + | ├── model/     # + | ├── {...}/     # + ├── index.ts # Entrypoint features with its public API +``` + +```ts title="**/**/index.ts" +export { Form as AuthForm } from "./ui" +export * as authFormModel from "./model" +``` + +## Requirements for the public API + +Meeting these requirements allows you to reduce interaction with the module to **the implementation of a public interface-contract** and, thereby, achieve reliability and ease of use of the module. + +### 1. Access Control + +The public API must **control access** to the contents of the module + +- Other parts of the application can use **only those module entities that are presented in the public interface** +- The internal part of the module outside the public interface **is accessible only to the module itself**. + +#### Examples + +##### Suspension from private imports + +- **Bad**: There is a direct access to the internal parts of the module, bypassing the public access interface - it is dangerous, especially when refactoring the module + + ```diff + - import { Form } from "features/auth-form/components/view/form" + ``` + +- **Good:** The API exports only what is necessary and allowed in advance, the module developer now needs to think only about not breaking the Public API when refactoring + + ```diff + + import { AuthForm } from "features/auth-form" + ``` + +### 2. Sustainability for changes + +The public API should be sustainable for changes inside the module + +- Breaking changes in the behavior of the module are reflected in the change of the Public API + +#### Examples + +##### Abstracting from the implementation + +Changing the internal structure should not lead to a change in the Public API + +- **Bad:** moving or renaming this component inside the feature will lead to the need to refactor imports in all places where the component is used. + + ```diff + - import { Form } from "features/auth-form/ui/form" + ``` + +- **Good:** the interface of the feature does not display its internal structure, external "users" of the feature will not suffer from moving or renaming the component inside the feature + + ```diff + + import { AuthForm } from "features/auth-form" + ``` + +### 3. Integrability + +The public API should facilitate **easy and flexible integration** + +- Should be convenient for use by the rest of the application, in particular, to solve the problem of name collisions + +#### Examples + +##### Name collision + +- **Bad:** there will be a name collision + + ```ts title="features/auth-form/index.ts" + export { Form } from "./ui" + export * as model from "./model" + ``` + + ```ts title="features/post-form/index.ts" + export { Form } from "./ui" + export * as model from "./model" + ``` + + ```diff + - import { Form, model } from "features/auth-form" + - import { Form, model } from "features/post-form" + ``` + +- **Good:** the collision is solved at the interface level + + ```ts title="features/auth-form/index.ts" + export { Form as AuthForm } from "./ui" + export * as authFormModel from "./model" + ``` + + ```ts title="features/post-form/index.ts" + export { Form as PostForm } from "./ui" + export * as postFormModel from "./model" + ``` + + ```diff + + import { AuthForm, authFormModel } from "features/auth-form" + + import { PostForm, postFormModel } from "features/post-form" + ``` + +##### Flexible use + +- **Bad:** it is inconvenient to write, it is inconvenient to read, the" user " of the feature suffers + + ```diff + - import { storeActionUpdateUserDetails } from "features/auth-form" + - dispatch(storeActionUpdateUserDetails(...)) + ``` + +- **Good:** the "user" of the feature gets access to the necessary things iteratively and flexibly + + ```diff + + import { authFormModel } from "features/auth-form" + + dispatch(authFormModel.effects.updateUserDetails(...)) // redux + + authFormModel.updateUserDetailsFx(...) // effector + ``` + +##### Resolution of collisions + +Name collisions should be resolved at the level of the public interface, not the implementation + +- **Bad:** name collisions are resolved at the implementation level + + ```ts title="features/auth-form/index.ts" + export { AuthForm } from "./ui" + export { authFormActions, authFormReducer } from "model" + ``` + + ```ts title="features/post-form/index.ts" + export { PostForm } from "./ui" + export { postFormActions, postFormReducer } from "model" + ``` + +- **Good:** name collisions are resolved at the interface level + + ```ts title="features/auth-form/model.ts" + export { actions, reducer } + ``` + + ```ts title="features/auth-form/index.ts" + export { Form as AuthForm } from "./ui" + export * as authFormModel from "./model" + ``` + + ```ts title="features/post-form/model.ts" + export { actions, reducer } + ``` + + ```ts title="features/post-form/index.ts" + export { Form as PostForm } from "./ui" + export * as postFormModel from "./model" + ``` + +## About re-exports + +In JavaScript, the public interface of a module is created by re-exporting entities from inside the module in an `index` file: + +```ts title="**/**/index.ts" +export { Form as AuthForm } from "./ui" +export * as authModel from "./model" +``` + +### Disadvantages + +- In most popular bundlers, due to re-exports, **the code-splitting works worse**, because [tree-shaking](https://webpack.js.org/guides/tree-shaking/) with this approach, it is safe to discard only the entire module, but not part of it. + > For example, importing `authModel` into the page model will cause the `AuthForm` component to get into the chunk of this page, even if this component is not used there. + +- As a result, initialization of the chunk becomes more expensive, because the browser must process all the modules in it, including those that got into the bundle "for the company" + +### Possible solutions + +- `webpack` allows you to mark re-export files as [**side effects free**](https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free) - this allows `webpack` to use more aggressive optimizations when working with such a file + +## See also + +- [(Discussion) Public Abstraction API][disc-src] +- [Principles **SOLID**][ext-solid] +- [Patterns **GRASP**][ext-grasp] + +[disc-src]: https://github.com/feature-sliced/documentation/discussions/41 +[ext-solid]: https://ru.wikipedia.org/wiki/SOLID +[ext-grasp]: https://ru.wikipedia.org/wiki/GRASP diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx b/i18n/fr/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx new file mode 100644 index 0000000000..01aeb65e24 --- /dev/null +++ b/i18n/fr/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx @@ -0,0 +1,57 @@ +--- +title: Slices and segments +sidebar_position: 2 +pagination_next: reference/public-api +--- + +# Slices and segments + +## Slices + +Slices are the second level in the organizational hierarchy of Feature-Sliced Design. Their main purpose is to group code by its meaning for the product, business or just the application. + +The names of slices are not standardized because they are directly determined by the business domain of your application. For example, a photo gallery might have slices `photo`, `create-album`, `gallery-page`. A social network would require different slices, for example, `post`, `add-user-to-friends`, `news-feed`. + +Closely related slices can be structurally grouped in a directory, but they should exercise the same isolation rules as other slices — there should be **no code sharing** in that directory. + +![Features "compose", "like" and "delete" grouped in a directory "post". In that directory there is also a file "some-shared-code.ts" that is crossed out to imply that it's not allowed.](/img/graphic-nested-slices.svg) + +The layers Shared and App don't contain slices. That is because Shared should contain no business logic at all, hence has no meaning for the product, and App should contain only code that concerns the entire application, so no splitting is necessary. + +### Public API rule on slices + +Inside a slice, the code could be organized very liberally, and that doesn't pose any issues as long as the slice provides a good public API. This is enforced with the **public API rule on slices**: + +> _Every slice (and segment on layers that don't have slices) must contain a public API definition._ +> +> _Modules outside of this slice/segment can only reference the public API, not the internal file structure of the slice/segment._ + +Read more about the rationale of public APIs and the best practices on creating one in the [Public API reference][ref--public-api]. + +## Segments + +Segments are the third and final level in the organizational hierarchy, and their purpose is to group code by its technical nature. + +There a few standardized segment names: +* `ui` — UI components, data formatting functions +* `model` — business logic and data storage, as well as functions to manipulate this data +* `lib` — auxiliary and infrastructural code +* `api` — communication with external APIs, backend API methods + +Custom segments are permitted, but should be created sparingly. The most common places for custom segments are the App layer and the Shared layer, where slices don't make sense. + +### Examples + +| Layer | `ui` | `model` | `lib` | `api` | +| :------- | :----------- | :----------- | :----------- | :----------- | +| Shared | UI kit | Usually not used | Utility modules of several related files.
If you need to use individual helpers, consider using utility libraries such as [`lodash-es`][ext--lodash]. | Rudimentary API client with additional features like authentication or caching. | +| Entities | Skeleton of a business entity with slots for interactive elements | Data storage of instances of this entity as well as functions for manipulating that data.
This segment is most fit for storing server-side data. If you use [TanStack Query][ext--tanstack-query] or other methods of implicit storage, you may choose to omit this segment. | Functions for manipulating instances of this entity that aren't related to storage | API methods using the API client from Shared for easy communication with the backend | +| Features | Interactive elements that enable users to use this feature | Business logic and infrastructure data storage, if needed (e.g., current app theme). This is the code that actually produces value for the user. | Infrastructural code that helps to concisely describe the business logic in the `model` segment | API methods that represent this feature on the backend.
May compose API methods from Entities. | +| Widgets | Composition of Entities and Features into self-contained UI blocks.
Can also contain error boundaries and loading states. | Infrastructure data storage, if needed | Non-business interactions (e.g., gestures) and other necessary code for the block to function on a page | Usually not used, but can contain data loaders in nested routing contexts (e.g., [Remix][ext--remix]) | +| Pages | Composition of Entities, Features and Widgets into complete pages.
Can also contain error boundaries and loading states. | Usually not used | Non-business interactions (e.g., gestures) and other necessary code for the page to deliver a complete user experience | Data loaders for SSR-oriented frameworks | + +[ref--public-api]: /docs/reference/public-api + +[ext--lodash]: https://www.npmjs.com/package/lodash-es +[ext--tanstack-query]: https://tanstack.com/query/latest +[ext--remix]: https://remix.run diff --git a/i18n/fr/docusaurus-theme-classic/footer.json b/i18n/fr/docusaurus-theme-classic/footer.json new file mode 100644 index 0000000000..ccc324af6e --- /dev/null +++ b/i18n/fr/docusaurus-theme-classic/footer.json @@ -0,0 +1,66 @@ +{ + "link.title.Specs": { + "message": "Spécifications", + "description": "The title of the footer links column with title=Specs in the footer" + }, + "link.title.Community": { + "message": "Communauté", + "description": "The title of the footer links column with title=Community in the footer" + }, + "link.title.More": { + "message": "Plus", + "description": "The title of the footer links column with title=More in the footer" + }, + "link.item.label.Documentation": { + "message": "Documentation", + "description": "The label of footer link with label=Documentation linking to /docs" + }, + "link.item.label.Discussions": { + "message": "Discussions", + "description": "The label of footer link with label=Discussions linking to https://github.com/feature-sliced/documentation/discussions" + }, + "link.item.label.Community": { + "message": "Communauté", + "description": "The label of the footer link with label=Community linking to /community" + }, + "link.item.label.Help": { + "message": "Aide", + "description": "The label of the footer link with label=Help linking to /nav" + }, + "link.item.label.License": { + "message": "Licence", + "description": "The label of footer link with label=License linking to LICENSE" + }, + "link.item.label.Contribution Guide (RU)": { + "message": "Guide de Contribution (RU)", + "description": "The label of footer link with label=Contribution Guide (RU) linking to CONTRIBUTING.md" + }, + "link.item.label.Discord": { + "message": "Discord", + "description": "The label of footer link with label=Discord linking to https://discord.com/invite/S8MzWTUsmp" + }, + "link.item.label.Telegram": { + "message": "Telegram", + "description": "The label of footer link with label=Telegram linking to https://t.me/feature_sliced" + }, + "link.item.label.Twitter": { + "message": "Twitter", + "description": "The label of footer link with label=Twitter linking to https://twitter.com/feature_sliced" + }, + "link.item.label.Open Collective": { + "message": "Collectif ouvert", + "description": "The label of footer link with label=Open Collective linking to https://opencollective.com/feature-sliced" + }, + "link.item.label.YouTube": { + "message": "YouTube", + "description": "The label of footer link with label=YouTube linking to https://www.youtube.com/c/FeatureSlicedDesign" + }, + "link.item.label.GitHub": { + "message": "GitHub", + "description": "The label of footer link with label=GitHub linking to https://github.com/feature-sliced" + }, + "copyright": { + "message": "Droit d'auteur © 2018-2023 Feature-Sliced Design", + "description": "The footer copyright" + } +}