diff --git a/docs/contributing/creating-a-lib.md b/docs/contributing/creating-a-lib.md new file mode 100644 index 00000000..d15876da --- /dev/null +++ b/docs/contributing/creating-a-lib.md @@ -0,0 +1,232 @@ +# Creating a New Library + +This guide explains how to create a new library in the Bitwarden monorepo using Nx. + +## Prerequisites + +- Node.js and npm installed +- Bitwarden monorepo cloned and dependencies installed +- The Nx cli installed, or accessible over npx + +## Creating a Library with Nx + +We use the `@nx/js` plugin to generate new libraries. Follow these steps to create a new library: + +1. From the root of the monorepo, run the generator command: + + ```bash + nx g @nx/js:lib my-new-lib --directory=libs/my-new-lib + ``` + + Replace `my-new-lib` with the name of your library. + +2. The generator will ask you questions about the library configuration. Here are recommended + settings: + + - **Which bundler would you like to use?** Select `none` (we use our own bundling setup) + - **Which linter would you like to use?** + - **Which unit test runner would you like to use?** + +3. Once generated, the library will have this structure: + +``` +libs/ + my-new-lib/ + src/ + index.ts + my-new-lib.ts + tsconfig.json + project.json + package.json + jest.config.js +``` + +4. Update the `package.json` with the appropriate Bitwarden naming convention: + +```json +{ + "name": "@bitwarden/my-new-lib", + "version": "0.0.0" +} +``` + +5. Update the library's path in the root `tsconfig.base.json` file to make it available to other + projects: + +```json +{ + "compilerOptions": { + "paths": { + "@bitwarden/my-new-lib": ["libs/my-new-lib/src"] + } + } +} +``` + +## Building and Testing the Library + +- Build the library: `nx build my-new-lib` +- Test the library: `nx test my-new-lib` +- Lint the library: `nx lint my-new-lib` + +## Importing Your Library in Other Projects + +Once your library is created and built, you can import it in other projects within the monorepo: + +```ts +import { someFunction } from "@bitwarden/my-new-lib"; +``` + +## Best Practices + +- Follow the Bitwarden code style guidelines +- Document your library's public API +- Write unit tests for your library's functionality +- If your library has dependencies on other libraries, make sure to add them to the `project.json` + file + +## Designing a Library + +There are a few ways you and your team may want to design a library, there are a lot of factors to +take into account like clean organization, ease of onboarding new members, simplicity of moving +ownership to a new team, and optimizing for module size. Below are a few ways you might want to +design a library. + +### Option 1: Feature-based libraries + +One strategy to employ is a feature-scoped library. + +The import for such a library would be `@bitwarden/[feature]`. For example the `global-state` +feature would be imported with `@bitwarden/global-state`. + +If the feature has both a UI component and needs to be used in the CLI it would probably result in a +`@bitwarden/[feature]-ui` or `@bitwarden/[feature]-angular` library as well. + +:::note + +With more things being added to the SDK and the CLI eventually being written directly in Rust there +will become less and less need to have a package with an Angular dependency and without it. + +::: + +Pros + +- You'll have smaller libraries that have minimal dependencies, making them easier for another team + to depend on without a circular reference. +- If your team is ever split or a feature you own is moved to another team this can likely be done + with just an update to the GitHub `CODEOWNERS` file. +- You'll have a clearer dependency graph. +- Your modules will be smaller. + +Cons + +- YOu have to spend the time to think about and define what a feature is. +- You have to create libraries somewhat often. +- You MAY need "glue" libraries still. +- It is not as clear who owns what feature from looking at library names. + +:::info Glue libraries + +A "Glue" library is a library that might not exist other than the need for two teams to collaborate +on a cross cutting feature. The glue library might exist to hold an interface that team B is +expected to implement but team A will be responsible for consuming. This helps glue 2 features +together while still allowing for team A to consume other things that exist in team B's library but +still avoid a circular dependency. + +::: + +### Option 2: Team-based libraries + +Another strategy would be to have a library for the vast majority of your teams code in a single +package. + +There are many ways you may choose to design the library, but if it's one library you will need to +be dependent on everything that makes all your features tick. + +**If all teams go this route it will be impossible to only have these team libraries**. Why? Because +the team grouping is very likely to result in circular dependencies. For example, if team A depends +on team B's library then team B cannot depend on anything in team A's library. If team B did need +something they would need to request that team A move it "downstream". Team A would need to move +their code and come up with a name for it. If they don't want to also re-export those pieces of code +they will need to update the import for every other place that code of used. You may also have to +deal with the code now being separated from similar code or you may decide to move that code too. + +Pros + +- You have fewer libraries to maintain. +- All your team’s code is in one place. + +Cons + +- You'll need to move code ad-hoc more often to make glue libraries, and each time try to think + about how to design the package abstraction. +- If your team splits you will need to move a lot more code. +- You’ll have larger modules. + +### Option 3: Type-based libraries + +You can also split libraries based on the primary kind of file that it holds. For example, you could +have a library holding all `type`’s, one for `abstractions`, and on more `services`. Since one +library for all type’s would be mean having a library that has multiple owners it would be highly +discouraged and therefore this would likely be split by team as well, resulting in packages like +`@bitwarden/platform-types`; this library strategy is really a subset of the team-based one. + +Pros + +- You’ll be less likely to have circular dependencies within your team’s code, since generally Types + < Abstractions < Services where < means lower level. +- It’s most similar to the organization strategy we’ve had for a while. + +Cons + +- There is no guarantee that all the types for a given team are lower level than all the types of + another team that they need to depend on. Circular dependencies can still happen amongst teams. +- It’s also possible for a type to need to depend on an abstraction, +- We are generally discouraging teams from making abstractions unless needed (link). + +### Option 4: Feature/type hybrid + +Another strategy could be to split libraries by the kind of item in feature-scoped libraries. + +Pros + +- Lowest chance of circular dependencies showing up later. +- Pretty easy to move ownership. + +Cons + +- The most libraries to maintain. +- Consumers will likely have to import multiple modules in order to use your feature. + +### Platform Recommendation + +Given the options available with Nx and the pros and cons of each, Platform is planning on using +[feature-based libraries](#option-1-feature-based-libraries) and we recommend other teams do as +well. + +We understand that we might have a domain that is a little easier to split into features, but we +believe that the work is worthwhile for teams to do. + +There will be some instances that our libraries may only contain abstractions and very simple types +which would then resemble the type-based approach. We will be forced to do this because we have some +things like storage where it’s only really useful which the implementations made in the individual +apps. + +Example Platform feature libraries (all of these would be imported like `@bitwarden/[feature]`: + +- `storage-core` +- `user-state` +- `global-state` +- `state` (will have its own code but also a meta package re-exporting `user-state` and + `global-state`) +- `clipboard` +- `messaging` +- `ipc` +- `config` +- `http` +- `i18n` +- `environments` +- `server-notifications` +- `sync` + +Hopefully these will give you some ideas for how to split up your own features. diff --git a/docs/contributing/development-tooling/index.md b/docs/contributing/development-tooling/index.md new file mode 100644 index 00000000..8af3f3ae --- /dev/null +++ b/docs/contributing/development-tooling/index.md @@ -0,0 +1 @@ +# Development Tooling diff --git a/docs/contributing/development-tooling/nx/generators/basic-lib-generator.md b/docs/contributing/development-tooling/nx/generators/basic-lib-generator.md new file mode 100644 index 00000000..1671d7b1 --- /dev/null +++ b/docs/contributing/development-tooling/nx/generators/basic-lib-generator.md @@ -0,0 +1,248 @@ +--- +title: Basic Library Generator +--- + +# Using the basic-lib Generator + +The `basic-lib` generator creates a new library with Bitwarden's standard configuration and +structure. It sets up all the necessary files, configurations, and hooks with global configuration +files. + +## Command Syntax + +You can use the basic lib generator by running this command: + +```bash +npx nx g @bitwarden/nx-plugin:basic-lib +``` + +## Available Options + +All fields are required, but do not need to be supplied as CLI flags. Generator users will be asked +interactively for each of these if they are not supplied as CLI flags. + +| Option | Description | Required | Default | +| --------------- | ----------------------------------------------- | -------- | ------- | +| `--name` | The name of the library | Yes | - | +| `--description` | A brief description of the library | Yes | none | +| `--team` | The team responsible for the library | Yes | none | +| `--directory` | The directory where the library will be created | Yes | "libs" | + +## Step-by-Step Example + +Let's create a new utility library called "password-insulter": + +1. Open your terminal and navigate to the root of the Bitwarden clients repository +2. Run the generator command: + ```bash + nx g @bitwarden/nx-plugin:basic-lib --name=password-insulter --description="Like the password strength meter, but more judgemental" --team=tools + ``` +3. The generator will create the library structure and update necessary configuration files +4. The new library is now ready to use + +## What Gets Generated + +The generator creates the following: + +- **Library Structure**: + + - `libs/password-insulter/` + - `src/` + - `index.ts` - Main entry point + - `README.md` - With the provided description + - `package.json` - Very minimal + - `tsconfig.json` - TypeScript configuration + - `tsconfig.lib.json` - Library-specific TypeScript configuration + - `tsconfig.spec.json` - Test-specific TypeScript configuration + - `jest.config.js` - Test configuration + - `.eslintrc.json` - Linting rules + +- **Configuration Updates**: + +The generator then updates `tsconfig.base.json` to reference your new library, updates CODEOWNERS to +assign the team to the new folder, and runs `npm i` to link everything up. + +## Post-Generation Next Steps + +After generating your library: + +1. Review the generated README.md and update it with more detailed information if needed +2. Implement your library code in the `src/` directory, using whatever subfolder structure you + prefer +3. Export public APIs through the `src/index.ts` file +4. Write tests for your library +5. Build your library with `npx nx build password-insulter` +6. Lint your library with `npx nx lint password-insulter` +7. Test your library with `npx nx test password-insulter` + +## Troubleshooting Common Issues + +### Issue: Generator fails with path errors + +**Solution**: Ensure you're running the command from the root of the repository. + +### Issue: TypeScript path mapping not working + +**Solution**: Run `nx reset` to clear the Nx cache, then try importing from your library again. + +## Extending the Generated Code + +The generated library provides a basic structure that you can extend: + +- Add additional directories for specific features +- Create subdirectories in `src/` for better organization +- Modify the Jest configuration for specialized testing needs + +## Designing a Library + +There are a few ways you and your team may want to design a library, there are a lot of factors to +take into account like clean organization, ease of onboarding new members, simplicity of moving +ownership to a new team, and optimizing for module size. Below are a few ways you might want to +design a library. + +### Option 1: Feature-based libraries + +One strategy to employ is a feature-scoped library. + +The import for such a library would be `@bitwarden/[feature]`. For example the `global-state` +feature would be imported with `@bitwarden/global-state`. + +If the feature has both a UI component and needs to be used in the CLI it would probably result in a +`@bitwarden/[feature]-ui` or `@bitwarden/[feature]-angular` library as well. + +:::note + +With more things being added to the SDK and the CLI eventually being written directly in Rust there +will become less and less need to have a package with an Angular dependency and without it. + +::: + +Pros + +- You'll have smaller libraries that have minimal dependencies, making them easier for another team + to depend on without a circular reference. +- If your team is ever split or a feature you own is moved to another team this can likely be done + with just an update to the GitHub `CODEOWNERS` file. +- You'll have a clearer dependency graph. +- Your modules will be smaller. + +Cons + +- YOu have to spend the time to think about and define what a feature is. +- You have to create libraries somewhat often. +- You MAY need "glue" libraries still. +- It is not as clear who owns what feature from looking at library names. + +:::info Glue libraries + +A "Glue" library is a library that might not exist other than the need for two teams to collaborate +on a cross cutting feature. The glue library might exist to hold an interface that team B is +expected to implement but team A will be responsible for consuming. This helps glue 2 features +together while still allowing for team A to consume other things that exist in team B's library but +still avoid a circular dependency. + +::: + +### Option 2: Team-based libraries + +Another strategy would be to have a library for the vast majority of your teams code in a single +package. + +There are many ways you may choose to design the library, but if it's one library you will need to +be dependent on everything that makes all your features tick. + +**If all teams go this route it will be impossible to only have these team libraries**. Why? Because +the team grouping is very likely to result in circular dependencies. For example, if team A depends +on team B's library then team B cannot depend on anything in team A's library. If team B did need +something they would need to request that team A move it "downstream". Team A would need to move +their code and come up with a name for it. If they don't want to also re-export those pieces of code +they will need to update the import for every other place that code of used. You may also have to +deal with the code now being separated from similar code or you may decide to move that code too. + +Pros + +- You have fewer libraries to maintain. +- All your team’s code is in one place. + +Cons + +- You'll need to move code ad-hoc more often to make glue libraries, and each time try to think + about how to design the package abstraction. +- If your team splits you will need to move a lot more code. +- You’ll have larger modules. + +### Option 3: Type-based libraries + +You can also split libraries based on the primary kind of file that it holds. For example, you could +have a library holding all `type`’s, one for `abstractions`, and on more `services`. Since one +library for all type’s would be mean having a library that has multiple owners it would be highly +discouraged and therefore this would likely be split by team as well, resulting in packages like +`@bitwarden/platform-types`; this library strategy is really a subset of the team-based one. + +Pros + +- You’ll be less likely to have circular dependencies within your team’s code, since generally Types + < Abstractions < Services where < means lower level. +- It’s most similar to the organization strategy we’ve had for a while. + +Cons + +- There is no guarantee that all the types for a given team are lower level than all the types of + another team that they need to depend on. Circular dependencies can still happen amongst teams. +- It’s also possible for a type to need to depend on an abstraction, +- We are generally discouraging teams from making abstractions unless needed (link). + +### Option 4: Feature/type hybrid + +Another strategy could be to split libraries by the kind of item in feature-scoped libraries. + +Pros + +- Lowest chance of circular dependencies showing up later. +- Pretty easy to move ownership. + +Cons + +- The most libraries to maintain. +- Consumers will likely have to import multiple modules in order to use your feature. + +### Platform Recommendation + +Given the options available with Nx and the pros and cons of each, Platform is planning on using +[feature-based libraries](#option-1-feature-based-libraries) and we recommend other teams do as +well. + +We understand that we might have a domain that is a little easier to split into features, but we +believe that the work is worthwhile for teams to do. + +There will be some instances that our libraries may only contain abstractions and very simple types +which would then resemble the type-based approach. We will be forced to do this because we have some +things like storage where it’s only really useful which the implementations made in the individual +apps. + +Example Platform feature libraries (all of these would be imported like `@bitwarden/[feature]`: + +- `storage-core` +- `user-state` +- `global-state` +- `state` (will have its own code but also a meta package re-exporting `user-state` and + `global-state`) +- `clipboard` +- `messaging` +- `ipc` +- `config` +- `http` +- `i18n` +- `environments` +- `server-notifications` +- `sync` + +Hopefully these will give you some ideas for how to split up your own features. + +## Further Learning + +For more information about Nx libraries and generators: + +- [Nx Library Generation](https://nx.dev/plugin-features/create-libraries) +- [Nx Library Types](https://nx.dev/more-concepts/library-types) +- [Nx Project Configuration](https://nx.dev/reference/project-configuration) diff --git a/docs/contributing/development-tooling/nx/generators/bitwarden-nx-plugin.md b/docs/contributing/development-tooling/nx/generators/bitwarden-nx-plugin.md new file mode 100644 index 00000000..c4d42957 --- /dev/null +++ b/docs/contributing/development-tooling/nx/generators/bitwarden-nx-plugin.md @@ -0,0 +1,54 @@ +--- +title: Bitwarden Nx Plugin +--- + +# @bitwarden/nx-plugin + +The `@bitwarden/nx-plugin` is a custom Nx plugin developed specifically for Bitwarden projects. It +provides generators tailored to Bitwarden's architecture and coding standards. + +## Overview + +This plugin extends Nx's capabilities with Bitwarden-specific generators that help maintain +consistency across the codebase. It automates the creation of libraries, components, and other +project elements according to Bitwarden's established patterns. + +## How It Fits Into the Project Architecture + +The `@bitwarden/nx-plugin` is designed to: + +1. Enforce Bitwarden's architectural decisions and code organization +2. Streamline the creation of new libraries and components +3. Ensure consistent configuration across the project +4. Automate updates to project metadata and configuration files +5. Reduce the learning curve for new contributors + +By using this plugin, we maintain a consistent approach to code organization and structure across +the entire project. + +## Installation and Setup + +The plugin is included as a development dependency in the project. If you're working with a fresh +clone of the repository, it will be installed when you run: + +```bash +npm install +``` + +No additional setup is required to use the generators provided by the plugin. + +## Available Generators + +The plugin currently includes the following generators: + +- `basic-lib`: Creates a new library with standard configuration and structure + +Additional generators may be added in the future to support other common patterns in the Bitwarden +codebase. + +## Further Learning + +To learn more about Nx plugins and how they work: + +- [Nx Plugin Development](https://nx.dev/extending-nx/creating-nx-plugins) +- [Nx Plugins Overview](https://nx.dev/extending-nx/intro) diff --git a/docs/contributing/development-tooling/nx/generators/index.md b/docs/contributing/development-tooling/nx/generators/index.md new file mode 100644 index 00000000..79df4847 --- /dev/null +++ b/docs/contributing/development-tooling/nx/generators/index.md @@ -0,0 +1,64 @@ +--- +title: Nx Generators +--- + +# Nx Generators + +Nx generators are powerful tools that automate the creation of code, configuration, and other files +in your project. They help maintain consistency across your codebase and reduce the manual effort +required to scaffold new components, libraries, or applications. + +## What are Nx Generators? + +Nx generators (previously known as schematics) are code generation tools that follow templates to +create or modify files in your project. They can: + +- Create new files from templates +- Modify existing files +- Update configuration files +- Ensure consistent project structure +- Automate repetitive tasks + +Generators can be run using the Nx CLI with the `nx generate` command (or the shorthand `nx g`). + +## When to Use Generators + +Use generators when: + +- Creating new libraries, components, or features that follow a standard pattern +- You want to ensure consistency across similar parts of your application +- You need to automate repetitive setup tasks +- You want to reduce the chance of human error in project setup + +## Why Use Generators Instead of Manual Creation + +- **Consistency**: Generators ensure that all generated code follows the same patterns and + conventions +- **Efficiency**: Save time by automating repetitive tasks +- **Reduced Errors**: Minimize human error in project setup +- **Maintainability**: Easier to maintain code that follows consistent patterns +- **Onboarding**: Help new team members create code that follows project standards + +## How Bitwarden Uses Generators + +Platform maintains a Nx plugin (`@bitwarden/nx-plugin`) with custom generators for our monorepo. We +may at times also use stock generators from dependencies such as Nx's own generator for generating +nx plugins (a generator generator generator, if you will). + +## Creating A Nx Generator + +It may be useful at some point to create a new Nx generator. The platform team maintains a nx plugin +in `libs/nx-plugin` that has a generators folder. If you need to create a new generator please do so +by following these steps. + +1. Run + `npx nx generate @nx/plugin:generator libs/nx-plugin/src/generators/your-generator-name-here}`. + This will create a basic generator structure for you to get started with. + +## Further Learning + +For more information about Nx generators, check out these resources: + +- [Nx Generators Documentation](https://nx.dev/plugin-features/use-code-generators) +- [Creating Custom Generators](https://nx.dev/recipes/generators/creating-files) +- [Nx Generator Examples](https://nx.dev/plugin-features/use-code-generators#examples) diff --git a/docs/contributing/development-tooling/nx/index.md b/docs/contributing/development-tooling/nx/index.md new file mode 100644 index 00000000..0bb90968 --- /dev/null +++ b/docs/contributing/development-tooling/nx/index.md @@ -0,0 +1,171 @@ +# Nx + +Nx is a powerful open-source build system designed specifically for monorepo development. It +provides tools and techniques for enhancing developer productivity, optimizing CI performance, and +maintaining code quality in complex JavaScript/TypeScript codebases that contain multiple +applications and libraries within a single repository. + +## Why We're Using Nx + +We use Nx in the Bitwarden clients monorepo to improve our development workflow and build +efficiency. Key advantages include: + +### 1. Unified Commands + +Instead of navigating to specific directories and running individual build commands, you can now +execute commands for any project from the repository root: + +```bash +# Old way +cd apps/web +npm run build +``` + +```bash +# New way with Nx +nx build web +nx serve desktop +nx test common +``` + +### 2. Build Caching + +Nx automatically caches build results. If code and dependencies haven't changed, subsequent builds +are significantly faster, often restoring results from cache instantly. This saves considerable +build time and memory. + +### 3. Intelligent Dependency Management + +Nx understands the dependencies between projects. Running a build for one project automatically +builds its dependencies first if needed: + +You can visualize these dependencies with: + +```bash +nx graph +``` + +### 4. Configuration-Based Projects + +Each app and library can be defined as a project with a `project.json` file that specifies its build +configurations, targets, and other settings. This replaces many scripts previously defined in +individual `package.json` files. + +### 5. Code Generation + +Nx ships with powerful tools for automating the creation of things like libraries. + +## Key Nx Terminology + +- _nx.json_: The primary configuration file for the Nx workspace, located at the root. Defines + global settings, plugins, workspace layout, and target defaults. + +- _project.json_: A configuration file in each project's directory that defines the targets (tasks) + for that specific project and the executors used to run them. + +- _Target_: A specific task that can be performed on a project, like `build`, `serve`, `lint`, or + `test`. Run with `nx `. + +- _Executor_: The code responsible for performing a target's action, typically provided by Nx + plugins (e.g., `@nx/webpack:webpack` for running Webpack builds). + +- _Configuration_: A named set of options for running a target in different modes, accessed via the + `--configuration` flag (e.g., `nx build browser --configuration=chrome-mv3`). + +## Using Nx + +To use the Nx cli you have two options: + +1. Install nx globally +2. Use npx + +## Common Commands + +### Building Projects + +```bash +# Build a project with default configuration +nx build + +# Build with specific configuration +nx build browser --configuration=chrome-mv3 +nx build web --configuration=bit + +# Build all projects (rarely needed) +nx run-many --target=build --all +``` + +### Serving Projects for Development + +```bash +# Start development server +nx serve + +# Serve with specific configuration +nx serve browser --configuration=firefox-mv2-dev +``` + +### Testing + +```bash +# Run tests for a project +nx test + +# Run tests only for projects affected by your changes +nx affected --target=test +``` + +### Other Useful Commands + +```bash +# Analyze dependencies between projects +nx graph + +# Run a task for all projects affected by your changes +nx affected --target= + +# Show what commands would be run, but don't execute them +nx affected --target=build --dry-run +``` + +## Understanding the Cache + +Nx stores its cache in the `.nx/cache` directory at the root of the repository. This includes: + +- Terminal outputs +- Build artifacts +- Metadata about the task execution + +The cache is created based on the inputs defined for each target. If none of the inputs have +changed, Nx will restore the previous output. + +## Contributing Guidelines + +When contributing to Bitwarden with Nx: + +1. _Use Nx commands_ from the repository root instead of navigating to individual project + directories. +2. _Respect project boundaries_ - imports between projects should follow the established dependency + graph. +3. _When adding new dependencies_ between projects, ensure they're reflected in imports in the code. +4. _For new scripts or build steps_, add them to the appropriate project's `project.json` file + rather than to individual `package.json` files. +5. _Test affected projects_ before submitting a PR: + ```bash + nx affected --target=test + nx affected --target=lint + ``` + +## Getting Help + +If you're having issues with Nx: + +1. Check the [Nx documentation](https://nx.dev/getting-started/intro) +2. Run `nx --help` or `nx --help` for command-specific help +3. Reach out to the Platform team for assistance with Nx-specific problems + +## References + +- [Nx Documentation](https://nx.dev/getting-started/intro) +- [Nx Project Configuration Reference](https://nx.dev/reference/project-configuration) +- [Nx Cache Documentation](https://nx.dev/concepts/how-caching-works)