Co-authored-by: Michał Kurczewski <michalkurczewski94@gmail.com> Co-authored-by: Michał Kurczewski <michal@kurczewski.dev> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
9.4 KiB
Feature development workflow
This document explains the feature development workflow for Mudita Center. It is intended to help contributors understand the process of adding new features and acknowledge the best practices to follow.
Base structure
The repo is built in a monorepo structure using Nx as a build tool.
The main application code is located in the apps directory, while shared libraries
and components are located in the libs directory.
Other directories (/patches, /resources, /scripts) are used for development and build purposes,
containing scripts not directly related to the application code.
Apps
This is where different applications are located along with their specific configurations.
They should be kept minimal, delegating most of the logic to the libraries in the libs directory.
apps/app- the main Electron application code and Electron-specific configurations,apps/app-e2e- the directory where end-to-end tests are located,apps/web- initial React setup and Storybook configuration.
Libraries
This is the core of the Mudita Center codebase where most of the development happens. Each library corresponds to a specific feature or a set of related features.
Each library is an Nx-generated React library with its own isolated environment, including Jest for unit testing, ESLint for linting, and TypeScript for type checking.
Generating a new library can be done by simply running a Nx generator command:
nx g @nx/react:library --directory=libs/<lib_name>/<directory> --name=<lib_name>/<directory> --unitTestRunner=jest --tags=<lib_tag> --no-interactive
This will generate a new library in the libs/<lib_name>/<directory> directory and the import
declaration for it will be as following:
import { something } from "<lib_name>/<directory>"
You can read more about Nx generators in the official documentation, however, before proceeding to create a new library, please read this entire article to understand the Mudita Center library structure and conventions.
Library mental model
A library is a building block of the Mudita Center codebase. It encapsulates specific features, UI components, or utility functions, promoting code reusability and maintainability.
Nx helps to maintain clear boundaries between different libraries, enforcing dependency rules and ensuring that libraries do not inadvertently depend on each other in an undesired way.
This is done mostly via tags assigned to each library (but developer's caution is still required).
Tags are used to define the purpose and scope of each library, and to enforce architectural rules.
For example, a ui library should not depend on a feature library, while the opposite is allowed.
You can read more about the general idea of tags and rules in the Nx documentation.
Rules
Tag rules are defined in the eslint configuration under the
@nrwl/nx/enforce-module-boundaries section. For better understanding, here is a breakdown
of all the tags used in the Mudita Center codebase:
-
type:modelsLibraries of this type should contain only data models, types, and interfaces. This helps to keep the data structures consistent across the codebase and allows other libraries to depend on them without the risk of circular dependencies.
They can depend only on other
type:modelslibraries. -
type:utilsLibraries of this type should contain utility functions and helpers that can be used across different libraries within the same domain. These libraries should not contain any UI components or business logic.
They can depend only on
type:modelsand othertype:utilslibraries. -
type:uiLibraries of this type should contain reusable UI components that can be used across different features, preferably within the same domain. These libraries should not contain any business logic.
They can depend on
type:models,type:utils, and othertype:uilibraries. -
process:rendererLibraries of this type are intended to be used only in the renderer process of the Electron app. They can contain UI components, business logic, and other code that runs in the renderer process.
They can depend on any library except
process:mainlibraries. -
process:mainLibraries of this type are intended to be used only in the main process of the Electron app. They can contain business logic, services, and other code that runs in the main process.
They can depend only on
type:models,type:utils, and otherprocess:mainlibraries.
Creating a new library
The mentioned Nx generator command can be used to create any kind of library:
nx g @nx/react:library --directory=libs/<lib_name>/<lib_type> --name=<lib_name>/<lib_type> --unitTestRunner=jest --tags=<lib_tag> --no-interactive
# or
nx g @nx/react:library --directory=libs/<lib_subdirectory>/<lib_name>/<lib_type> --name=<lib_subdirectory>/<lib_name>/<lib_type> --unitTestRunner=jest --tags=<lib_tag> --no-interactive
Note: you can apply a --dry-run flag at the end of the command to see what files would
be created without actually creating them.
However, for convenience, a custom npm script is provided to simplify the process of creating most common library types used in the Mudita Center codebase:
npm run lib:create n=<lib_name> t=<lib_type> d=<lib_subdirectory>
Where:
-
<lib_name>is the name of the library (domain) to be created, -
<lib_type>is the type of the library to be created (see Available library types section), -
<lib_subdirectory>(optional) is the subdirectory insidelibs/where the library should be created.If not provided, the library will be created directly inside the
libs/directory.When provided, omit the
libs/prefix as it will be automatically added by the script. This ensures that all libraries are always created inside thelibs/directory.
Note: You can use either the short (n, t, d) or the long (name, type, directory)
names for the parameters.
Available library types
The following library types are available via the npm run lib:create script:
models- same astype:modelsdescribed in Rules,utils- same astype:utilsdescribed in Rules,ui- same astype:uidescribed in Rules,routes- a special type of library that should containreact-routerroute definitions and related components; it is tagged asprocess:renderer,feature- a special type of library that combines other libraries to create a complete feature, often utilizing business logic; it is tagged asprocess:renderer,renderer- a kind of library that is mostly used for exposing Electron's context bridge to the renderer process; it is tagged asprocess:renderer, but can also contain other renderer process code that doesn't fit into other library types,main- a kind of library that is intended to contain main process code, mostly services and their initialization; it is tagged asprocess:main.
These are the most common library types used in the Mudita Center codebase so please try to
fit your new library into one of these types before deciding to use the nx g command directly.
Example
As an example, the following commands will create three different libraries, but within the same auth domain:
npm run lib:create n=auth t=feature
npm run lib:create n=auth type=ui
npm run lib:create name=auth type=utils
This will result in creating the following directory structure:
libs/
`-- auth/
|
|-- feature/
| |-- src/
| | |-- lib/
| | `-- index.ts
| |-- jest.config.ts
| |-- tsconfig.lib.json
| `-- ...
|
|-- ui/
| |-- src/
| | |-- lib/
| | `-- index.ts
| |-- jest.config.ts
| |-- tsconfig.lib.json
| `-- ...
|
`-- utils/
|-- src/
| |-- lib/
| `-- index.ts
|-- jest.config.ts
|-- tsconfig.lib.json
`-- ...
It will also update the tsconfig.base.json file to include path mappings for the newly created libraries.
This way, you can easily import the libraries elsewhere* in the codebase like this:
import { SomeFeature } from "auth/feature"
import { SomeComponent } from "auth/ui"
import { someUtil } from "auth/utils"
* Respecting the dependency rules described in the Rules section.
From now on, you can start writing a code inside the src/lib/ directory of a given library
and eventually export public API via the src/index.ts file of that library.
Linting and testing
As each library comes with its own ESLint and Jest configurations, you can run linting and testing for a specific library by using the following commands:
# lint
nx lint <lib_name>/<lib_type>
# typecheck
nx typecheck <lib_name>/<lib_type>
# jest
nx test <lib_name>/<lib_type>
Example
Continuing with the previous example of the auth libraries, you can run linting, type checking, and testing for the auth/ui library like this:
nx lint auth/ui
nx typecheck auth/ui
nx test auth/ui
Or you can run these commands for all auth libraries at once by using the following command:
nx run-many --target=lint,typecheck,test --projects=auth/feature,auth/ui,auth/utils