Template for Monorepo with PNPm, TypeScript, ESLint, Prettier, and TurboRepo.
- 1. Initialization
- 2. Package organization: "apps" vs "libs"
- 3. VSCode integration
- 4. Github workflows
- 5. Scripts
- 6. Troubleshooting
git clone https://github.com/cahnory/pnpm-monorepo.git <your-project>
cd <your-project>
pnpm install
In our monorepo, we have two main directories for organizing packages: "apps" and "libs". It's important to note that the packages located in the "apps" directory are not intended to be imported by other packages within the monorepo. These packages typically represent standalone applications or executables.
On the other hand, the packages residing in the "libs" directory are specifically designed to be imported and used by other packages. They provide reusable components, libraries, or modules that facilitate code sharing and maintainability across different parts of the monorepo.
To add a new app:
-
duplicate one of the sample app package found in the "apps" directory.
-
Rename the duplicated package to your desired new package name.
-
Update the "name" property in the package.json file to match the new package name.
{ "name": "@pnpm-monorepo/<new-app-name>" }
To add a new lib:
-
duplicate one of the sample lib package found in the "libs" directory.
-
Rename the duplicated package to your desired new package name.
-
Update the "name" property in the package.json file to match the new package name.
{ "name": "@pnpm-monorepo/<new-package-name>" }
The monorepo includes a VSCode configuration file that optimizes code formatting and enables automatic formatting upon saving files. Additionally, a curated list of recommended extensions is provided, including tools and language-specific extensions essential for code formatting and linting. By utilizing this configuration and installing the recommended extensions, developers can ensure consistent code style and enhance productivity within the monorepo.
To view the recommended extensions for the monorepo, paste workbench.extensions.action.showRecommendedExtensions in the command launcher (F1). This will open the Recommended Extensions view, where you can see the curated list of extensions. From there, you can easily install any missing extensions.
Each package contains a symbolic link ./vscode
that points to the root-level ./vscode
directory within the monorepo. This ensures that even if you open an individual package directly in VSCode (as opposed to opening the entire monorepo), you'll have a consistent VSCode configuration. This approach provides a uniform development environment across packages, reducing potential configuration discrepancies.
Some files are marked as read-only using files.readonlyInclude setting, preventing inadvertent modifications.
By default, the configuration includes patterns targeting files and directories commonly generated during development, like "node_modules" and "build" directories for both apps and libraries:
"files.readonlyInclude": {
"**/node_modules/**": true,
"**/apps/*/build/**": true,
"**/libs/*/build/**": true
}
Extend this list to safeguard additional files as needed.
The Continuous integration workflow consists of two primary jobs. The first, "Static Analysis," focuses on the static analysis of the code, checking various aspects such as typing, linting, formatting, and commit name consistency. This step ensures code quality independent of the OS and Node.js version.
The second job, "Runtime Compatibility," tests the project's compatibility across different environments. It runs on multiple Node.js versions on Ubuntu, a common deployment platform, and also includes MacOS and Windows. The goal is to ensure the project operates correctly during installation, building, and testing across various platforms, providing a consistent and reliable development experience for developers.
The build script is used to compile the source code of the package. It generates the necessary output files, such as transpiled JavaScript files or bundled assets. Running this script ensures that the package is built and ready for deployment or usage.
The dev script is helpful during the development phase. It allows developers to watch the package's source files for changes and automatically triggers a rebuild whenever a file is modified. This feature provides a convenient workflow by keeping the package up to date during development.
The lint script is responsible for running various linters associated with the package. It divides its functionality into three sub-scripts: lint:format, lint:semantic, and lint:types.
The lint:format script checks that the package's source code adheres to the prettier config defined for the monorepo. It ensures consistent code style throughout the package.
The lint:semantic script checks that the package's source code adheres to the eslint rules defined for the monorepo. It enforces best practices, and maintains code quality throughout the package.
The lint:types script verifies that the package's source code is free from TypeScript type errors. It performs static type checking, ensuring type safety within the package. Running this script helps catch potential type-related issues before runtime.
The lint:fix script attempts to fix all linters issues.
The lint:fix:format script attempts to automatically corrects any formatting/stylistic issues found in the package's source code, ensuring that it adheres to the prettier config defined for the monorepo.
The lint:fix:semantic script attempts to automatically fixes any code issues that do not adhere to the eslint rules defined for the monorepo.
The test script is responsible for running various tests associated with the package. It validates the functionality and quality of the package.
The test:unit script runs the package's unit tests. It validates the behavior of individual components, functions, or modules within the package. This script ensures that the package's internal units are functioning as expected.
The monorepo also includes root-level scripts with similar functionality as their package-level counterparts. The key distinction is that these scripts can be executed across all packages within the monorepo simultaneously, providing a unified and efficient workflow. Running the root-level scripts allows you to perform the respective actions (build, dev, test) on multiple packages within the monorepo in a single operation. This saves time and ensures consistency across the entire codebase.
Run the build script for all packages within the monorepo.
Commt using commitizen. Get instant feedback on your commit message formatting and be prompted for required fields
Run the dev script for all packages within the monorepo.
Run the lint script on all packages within the monorepo followed by the root scripts lint:commits and lint:rebase.
Run the lint:format script for all packages within the monorepo.
Run the lint:semantic script for all packages within the monorepo.
Run the lint:types script for all packages within the monorepo.
The "lint:commits" script verifies the commit messages of the commits added by the current branch using the commitlint library. It performs linting or checks against predefined rules, specified in the commitlint configuration, to ensure that the commit messages follow specific guidelines or standards. This helps maintain consistency and clarity in the commit history of the project.
You can learn more about commitlint by visiting the official commitlint documentation.
The test:rebase script examines whether a rebase with the remote default branch is necessary. It analyzes the current branch's changes and compares them with the default branch in the remote repository. This check helps identify if the current branch is outdated or needs to be updated to incorporate the latest changes from the default branch. Performing the necessary rebase ensures that the branch remains up to date and avoids potential conflicts during future merges or pull requests.
Run the lint:fix script on all packages within the monorepo.
Run the lint:fix:format script for all packages within the monorepo.
Run the lint:fix:semantic script for all packages within the monorepo.
Run the test script on all packages within the monorepo.
Run the test:unit script for all packages within the monorepo.
If you encounter errors in your IDE or if your project is not functioning properly, it is likely that an event such as a branch change, stash application, pull, or repository reset has occurred. In such cases, the recommended first step is to execute the pnpm prepare command:
pnpm run prepare
The pnpm prepare command is designed to reinstall project dependencies and perform any necessary build or configuration tasks. This ensures that your project is in sync with any potential modifications that have taken place.
When trying to import a package from the repository, if you encounter the error message:
Unable to resolve path to module "@pnpm-monorepo/<module-name>". eslint("import/no-unresolved")
it is an indication that the package may need to be built or rebuilt. Execute the pnpm prepare command:
pnpm run prepare
This could happen because an event such as a branch change, stash application, pull, or repository reset has occurred or if you're making modification to source files without runing the dev script.