Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d99164a6ab | |||
| 67255189df | |||
| d02b30664f | |||
| eb5c1d429c | |||
| f921c189c6 | |||
| 6270a4b46f | |||
| 2b5d4f1989 | |||
| bf0f9fda4e | |||
| c97d54b8e1 | |||
| e1fbe37b59 | |||
| 37b022f00d | |||
| d3553867e0 | |||
| 985e094a6d | |||
| 2e6bb3ef78 | |||
| fcb1a3b662 | |||
| efbf3a3caf | |||
| 7dd2b3493e | |||
| 0fbfabfa91 | |||
| b05ef8cc0d | |||
| bcf2d5f4a1 | |||
| b6d69301ef | |||
| a963fa9b57 | |||
| 00b0b38b55 | |||
| 117f756ee3 | |||
| a899385731 | |||
| 1d8508e37b | |||
| d887446657 | |||
| 7bc8269e2d | |||
| dca604846b | |||
| 5039ad4e5d | |||
| 70e10dd797 | |||
| 311204a8b4 | |||
| bd35bfe4a5 | |||
| ed3c5cba6e | |||
| 099ee419fa | |||
| 4aa1412099 | |||
| 3737d8014f | |||
| 4667cd6c54 | |||
| 55fd3bf27d | |||
| 537099a658 | |||
| 1bee6c7ffc | |||
| 721d580a7a | |||
| bb363d6eaa | |||
| 8d7505b063 | |||
| 0fd1e3601f | |||
| ba48f0a54e | |||
| 28de4454e7 | |||
| 7133996d41 | |||
| 4c359b1567 | |||
| fa7968d1d1 | |||
| f6f8536064 | |||
| 1c3c90aa1a | |||
| de3060bb80 | |||
| ca1988d059 | |||
| dc41bfe2ec | |||
| a24e16f4ef | |||
| 3c928f34a7 | |||
| 27444bdf05 | |||
| 06e7bb8a12 | |||
| 064bc422a2 | |||
| 843c06f404 | |||
| 206a47e8ff | |||
| abfe5d9804 | |||
| 3ccec47072 | |||
| 7fa59941df | |||
| ef63f29275 | |||
| ed40a514c8 | |||
| b8752db9f1 | |||
| 1b736ac50b | |||
| e74dee0086 | |||
| c57ff37dc4 | |||
| fae2455f22 | |||
| 5c51d92f30 | |||
| 08f385720b | |||
| c9b14a2a3f | |||
| 083809533d | |||
| 84617b0ccb | |||
| 6f9f461c66 | |||
| 615fd172a5 | |||
| 707b732860 | |||
| 33e350b5c2 | |||
| 8d383cef8f | |||
| 039c9640a1 | |||
| 6bed247fff | |||
| 14e8dccbce | |||
| fcab3c8e6a | |||
| f89d6b309e | |||
| d98e6e4e84 | |||
| f7e796a3d6 | |||
| 51bebd9745 | |||
| fe17c23333 | |||
| 59b30ef094 |
@@ -12,17 +12,13 @@ launch-templates:
|
||||
NX_NATIVE_LOGGING: 'nx::native::db'
|
||||
init-steps:
|
||||
- name: Checkout
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/checkout/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/checkout/main.yaml'
|
||||
- name: Cache restore
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/cache/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/cache/main.yaml'
|
||||
inputs:
|
||||
key: 'pnpm-lock.yaml'
|
||||
paths: |
|
||||
node_modules
|
||||
~/.cache/Cypress
|
||||
~/.cache/ms-playwright
|
||||
~/.pnpm-store
|
||||
base_branch: 'master'
|
||||
paths: .pnpm-store
|
||||
base-branch: 'master'
|
||||
- name: Install e2e deps
|
||||
script: |
|
||||
sudo apt-get update
|
||||
@@ -69,17 +65,13 @@ launch-templates:
|
||||
NX_NATIVE_LOGGING: 'nx::native::db'
|
||||
init-steps:
|
||||
- name: Checkout
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/checkout/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/checkout/main.yaml'
|
||||
- name: Cache restore
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/cache/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/cache/main.yaml'
|
||||
inputs:
|
||||
key: 'pnpm-lock.yaml'
|
||||
paths: |
|
||||
node_modules
|
||||
~/.cache/Cypress
|
||||
~/.cache/ms-playwright
|
||||
~/.pnpm-store
|
||||
base_branch: 'master'
|
||||
paths: .pnpm-store
|
||||
base-branch: 'master'
|
||||
- name: Install e2e deps
|
||||
script: |
|
||||
sudo apt-get update
|
||||
|
||||
@@ -547,9 +547,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "fs4"
|
||||
version = "0.10.0"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec6fcfb3c0c1d71612528825042261419d5dade9678c39a781e05b63677d9b32"
|
||||
checksum = "c29c30684418547d476f0b48e84f4821639119c483b1eccd566c8cd0cd05f521"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2017-2024 Narwhal Technologies Inc.
|
||||
Copyright (c) 2017-2025 Narwhal Technologies Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
"description": "Nx plugin add vitepress project to your workspace",
|
||||
"url": "https://github.com/Ahryman40k/nx-vitepress/tree/main/packages/nx-vitepress"
|
||||
},
|
||||
{
|
||||
"name": "@klerick/nx-angular-mf",
|
||||
"description": "Custom Angular Builder for Microfrontend Architecture",
|
||||
"url": "https://github.com/klerick/nx-angular-mf"
|
||||
},
|
||||
{
|
||||
"name": "@nightwatch/nx",
|
||||
"description": "The NightwatchJS plugin allows your workspace to use the power of NightwatchJS for E2E and Component Testing on Desktop and Mobile",
|
||||
|
||||
@@ -216,6 +216,14 @@ You can have a more dynamic visualization of a terminal output by using the foll
|
||||
{% terminal-video src="/documentation/shared/images/caching/cache-terminal-animation.mp4" /%}
|
||||
```
|
||||
|
||||
#### Table of Contents
|
||||
|
||||
You can add a table of contents to your document by using the following component. This is mostly useful for blog posts.
|
||||
|
||||
```markdown
|
||||
{% toc /%}
|
||||
```
|
||||
|
||||
#### Custom iframes
|
||||
|
||||
We can display a special iframe and setting its width inside the document.
|
||||
|
||||
@@ -180,9 +180,9 @@ export default presetGenerator;
|
||||
|
||||
The preset generator does 2 things:
|
||||
|
||||
- Create an Nx project using the`addProjectConfiguration` function. This creates a `project.json` file which allows Nx to run commands on it.
|
||||
- Generates files in the project using the`generateFiles` function. This uses the templates under `src/generators/preset/files` which are interpolated to become the files that are generated for the user.
|
||||
- Format the generated files with `prettier` with the`formatFiles` function
|
||||
- Create an Nx project using the `addProjectConfiguration` function. This creates a `project.json` file which allows Nx to run commands on it.
|
||||
- Generates files in the project using the `generateFiles` function. This uses the templates under `src/generators/preset/files` which are interpolated to become the files that are generated for the user.
|
||||
- Format the generated files with `prettier` with the `formatFiles` function
|
||||
|
||||

|
||||
_preset generator_
|
||||
@@ -588,7 +588,7 @@ You can modify this e2e test to test your CLI. Then, run it using the command `n
|
||||
|
||||
The default test works like this:
|
||||
|
||||
1. Creates a test workspace at `tmp/`using the `create-my-own-react-app` CLI
|
||||
1. Creates a test workspace at `tmp/` using the `create-my-own-react-app` CLI
|
||||
2. Runs `npm ls my-own-react` to validate that the plugin is installed in the test workspace
|
||||
3. Cleans up the test workspace
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ authors: [Mike Hartington]
|
||||
tags: [nx, release]
|
||||
cover_image: /blog/images/2024-10-03/nx-20-header.png
|
||||
youtubeUrl: https://youtu.be/5-QwtlhaJK8
|
||||
pinned: true
|
||||
---
|
||||
|
||||
I know it's hard to believe but Nx 20 is here! There's a lot of great updates in this release, but look back at some of the major features from Nx 19:
|
||||
|
||||
@@ -3,7 +3,7 @@ title: Nx Cloud Pipelines Come To Nx Console
|
||||
slug: nx-cloud-pipelines-come-to-nx-console
|
||||
authors: [Zack DeRose]
|
||||
tags: [nx, nx-cloud, nx-console, enterprise]
|
||||
cover_image: '/blog/images/2024-11-25/thumbnail.png'
|
||||
cover_image: /blog/images/2024-11-25/thumbnail.png
|
||||
---
|
||||
|
||||
## Your CI Pipelines, Now At-A-Glance In Your IDE
|
||||
@@ -12,7 +12,7 @@ We're in the process of adding a new integration between [Nx Cloud](/nx-cloud),
|
||||
|
||||
Now, once you've [connected your Nx Console to Nx Cloud](https://blog.nrwl.io/nx-console-meets-nx-cloud-d45dc099dc5d), you will have access to a new panel in the console that shows all of your recent CI Pipelines, including those in progress now.
|
||||
|
||||

|
||||
{% video-player src="/documentation/blog/media/nx-console-pipeline-running.mp4" alt="Nx Console CI Pipeline Execution" /%}
|
||||
|
||||
This way, you can keep an eye on the status of the pipeline of your latest PR, and always just 1 click away from seeing detailed your results on Nx Cloud.
|
||||
|
||||
@@ -20,7 +20,7 @@ This way, you can keep an eye on the status of the pipeline of your latest PR, a
|
||||
|
||||
In addition to a panel where you can see all of your recent pipelines, you can also receive pop-up notifications when a pipeline completes
|
||||
|
||||

|
||||
{% video-player src="/documentation/blog/media/nxconsole-ci-completion.mp4" alt="Nx Console CI Completion" /%}
|
||||
|
||||
Head to the Nx Console settings to adjust controls on these notifications. This will allow you to adjust notifications to only show for failed runs if you prefer, or to turn off the notifications altogether.
|
||||
|
||||
@@ -30,9 +30,7 @@ Head to the Nx Console settings to adjust controls on these notifications. This
|
||||
|
||||
We're in the process of landing this on Nx Console for VSCode now - and we're already dog-fooding it on the `nx` and `nx-console` repos. It will be landing in the next few week for all Nx Cloud projects, with the feature landing on Nx Console for JetBrains IDEs to follow soon!
|
||||
|
||||
For more on Nx Cloud, check out the Solutions tab above! Nx Cloud is free to get started, so you can [create your free account today](https://cloud.nx.app)!
|
||||
|
||||
[](/nx-cloud)
|
||||
For more on Nx Cloud go to [nx.dev/nx-cloud](/nx-cloud)! Nx Cloud is free to get started, so you can [create your free account today](https://nx.app)!
|
||||
|
||||
And if you're not using Nx Console yet, you can get started now with Nx Console available for VSCode and JetBrains IDEs:
|
||||
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
---
|
||||
title: 'Nx Highlights 2024'
|
||||
slug: nx-highlights-2024
|
||||
authors: ['Juri Strumpflohner', 'Victor Savkin']
|
||||
tags: ['nx']
|
||||
cover_image: /blog/images/articles/nx-highlights-2024-bg.jpg
|
||||
pinned: true,
|
||||
youtubeUrl: https://youtu.be/n4MBETdLBVg
|
||||
---
|
||||
|
||||
Thank you for being part of the Nx community. Your support and contributions make Nx what it is today, driving it forward as the go-to solution for delivering the best developer experience in monorepos. Nx and Nx Cloud integrate to provide a **complete solution for managing monorepos** at every stage of the development cycle, both locally and in CI.
|
||||
|
||||
In 2024, we focused on making Nx **more adaptable**, seamlessly fitting into OSS projects and large enterprise codebases. Our goal was to balance **flexibility and power**, ensuring Nx scales with your needs while remaining easy to adopt in **existing setups**. This progress is the result of **working with the community, collaborating with other tooling teams, and engaging directly with enterprises**.
|
||||
|
||||
{% github-repository title="Star Nx on GitHub" url="https://github.com/nrwl/nx" /%}
|
||||
|
||||
As we wrap up the year, we’re excited to share the highlights and give you a look at what’s coming in 2025.
|
||||
|
||||
{% toc /%}
|
||||
|
||||
## Top 10 Highlights
|
||||
|
||||
Here are our picks for the top 10 highlights for 2024. There's no particular ordering here in terms of more or less relevance.
|
||||
|
||||
### Project Crystal
|
||||
|
||||
Nx has been solving monorepos long before package managers introduced features like workspaces. Early on, we used **TypeScript path mappings** to manage project linking, resulting in **integrated monorepos** powered by [Nx plugins](/concepts/nx-plugins#what-are-nx-plugins) to automate setup and tool integration.
|
||||
|
||||
When **npm**, **Yarn**, and **PNPM** introduced "workspaces," another option emerged: **package-based monorepos**, where Nx primarily acted as a task runner with caching and parallelization.
|
||||
|
||||
Integrated monorepos offer automation but are opinionated. Package-based monorepos provide flexibility but require more effort to manage. Combining the two wasn’t easy, largely because of how Nx plugins were initially designed.
|
||||
|
||||
We wanted to make sure you didn’t have to choose. This led to **Project Crystal**, an effort to make Nx plugins more adaptable. These "crystalized" plugins:
|
||||
|
||||
- **Rely on your existing config files** (e.g., `@nx/vite` enhances `vite.config.ts` rather than replacing it).
|
||||
- **Enhance without interfering**, allowing for a better experience without locking you into the plugin.
|
||||
|
||||
Over the past year, most of our plugins have been "crystalized," making it easier to mix and match both monorepo styles.
|
||||
|
||||

|
||||
|
||||
This approach gives you flexibility while keeping the automation and developer experience that make Nx essential for scaling monorepos.
|
||||
|
||||
Want to dive deeper? Check out [our announcement blog post](/blog/what-if-nx-plugins-were-more-like-vscode-extensions) and [documentation](/concepts/inferred-tasks).
|
||||
|
||||
### Nx Agents, Atomizer, and Flakiness Detection
|
||||
|
||||

|
||||
|
||||
Nx isn’t just about managing monorepos locally. A big part of the workflow happens on CI. If your monorepo doesn’t run reliably in CI, it’s not working. That’s why Nx Cloud has been a major focus, especially for scaling monorepos in CI environments. This year, we introduced some of the most impactful features yet:
|
||||
|
||||
- [Nx Agents](/ci/features/distribute-task-execution)
|
||||
- [Atomizer](/ci/features/split-e2e-tasks)
|
||||
- [Flakiness detection](/ci/features/flaky-tasks)
|
||||
|
||||
With remote caching as the foundation, these features take CI performance to the next level. At scale, **parallelizing tasks across machines becomes necessary**. We've done this manually for clients in the past, scripting complex CI setups. A lot of that experience shaped Nx Agents, making distribution seamless and efficient.
|
||||
|
||||
Manually configuring parallelization is time-consuming and fragile. It’s a static optimization, while your monorepo keeps changing. We wrote a [blog post](/blog/reliable-ci-a-new-execution-model-fixing-both-flakiness-and-slowness) that explains the difference between the traditional **push-based model** and Nx’s **pull-based approach** to CI.
|
||||
|
||||

|
||||
|
||||
If you haven’t tried Nx Agents yet, enabling them takes a single line in your CI setup:
|
||||
|
||||
```yaml
|
||||
npx nx-cloud start-ci-run --distribute-on="3 linux-medium-js"
|
||||
```
|
||||
|
||||
Check out [the docs](/ci/features/distribute-task-execution) or watch our [free video course](/courses/pnpm-nx-next/lessons-11-nx-agents) where we update an existing CI pipeline to use Nx Agents.
|
||||
|
||||
Even with distribution in place, **long-running tasks like e2e tests** can still slow things down. **Atomizer automatically breaks these into smaller pieces** that can run in parallel, making task distribution much more efficient.
|
||||
|
||||

|
||||
|
||||
Finally, we introduced **flakiness detection** across all CI task runs. Flaky tasks (usually tests) waste a lot of time. Instead of forcing you to re-run CI manually, Nx Cloud retries flaky tasks automatically. If the task passes on the second run, the pipeline continues without interruption.
|
||||
|
||||

|
||||
|
||||
### Nx Release
|
||||
|
||||
A big use case for Nx isn’t just managing application monorepos but also developing and publishing packages to NPM. One missing piece was a built-in way to handle versioning, changelog generation, and publishing. While tools like [Lerna](https://lerna.js.org) or [Changesets](https://github.com/changesets/changesets) could be integrated, many wanted something more tightly connected to Nx itself.
|
||||
|
||||
That’s why we released [**Nx Release**](/features/manage-releases). It handles the entire process:
|
||||
|
||||
- Bumping versions
|
||||
- Generating changelogs
|
||||
- Publishing to NPM or other registries
|
||||
|
||||
Nx Release is designed to be flexible. It comes with a [programmatic API](/features/manage-releases#using-the-programmatic-api-for-nx-release) and is implemented in a tech-agnostic way. Nx provides the foundation, and plugins like `@nx/js` extend it to support JavaScript and TypeScript packages. The same system can be extended for Go, Rust, Java, and more.
|
||||
|
||||
In the future, Nx Release could grow to handle other release processes, like Docker containers or application deployments.
|
||||
|
||||
Take a look at [our docs](/features/manage-releases) or browse [Nx Release recipes](/recipes/nx-release) to see how you can adapt it to fit your workflow.
|
||||
|
||||
### Gradle Support
|
||||
|
||||
2024 was a big step forward with the release of the **@nx/gradle plugin**. This plugin lets you manage Gradle projects within an Nx workspace, bringing features like task caching, parallel execution, and dependency-aware task orchestration to Java and Kotlin projects.
|
||||
|
||||
Gradle projects were traditionally tricky to manage alongside JavaScript libraries in monorepos. Few tools could handle mixed tech stacks smoothly. With **@nx/gradle**, that process is much simpler. Gradle projects now work seamlessly inside Nx, allowing you to:
|
||||
|
||||
- **Run Gradle tasks directly with Nx commands** (e.g., `nx build myproject`)
|
||||
- **Visualize dependencies across projects**, including composite builds, with the Nx Graph
|
||||
- **Only execute tasks that are affected by code changes**, speeding up builds and CI pipelines
|
||||
|
||||
**Composite Build Support** takes this further by pulling in dependencies from composite builds and reflecting them in the task graph. This release reflects our larger goal of breaking down barriers between ecosystems. Nx aims to enable efficient workflows across different languages and technologies, moving closer to a seamless polyglot monorepo experience.
|
||||
|
||||
For more details, check out the blog post ["Manage Your Gradle Project using Nx"](/blog/manage-your-gradle-project-using-nx) and our [Gradle tutorial](/getting-started/tutorials/gradle-tutorial).
|
||||
|
||||
### Nx Import
|
||||
|
||||
Migrating projects into a monorepo is often seen as complicated and error-prone. With **`nx import`**, introduced in 2024, we've simplified the process, preserving **Git history** and minimizing disruptions along the way.
|
||||
|
||||
`nx import` builds on the work done in **Project Crystal**, making migrations smoother. For example, when importing a Gradle-based app, Nx detects the Gradle configuration, suggests installing the `@nx/gradle` plugin, and sets everything up for you. It reads your Gradle files, identifies runnable tasks, configures caching, and integrates the project into the workspace.
|
||||
|
||||
The experience is the same whether you’re importing a JavaScript app, a Gradle project, or another tech stack.
|
||||
|
||||
With `nx import`, you can:
|
||||
|
||||
- Map existing repositories into an Nx workspace (while preserving Git history)
|
||||
- Retain existing configurations and workflows without losing functionality
|
||||
- Get consistent orchestration, caching, and dependency graphing across projects
|
||||
|
||||
This significantly reduces the effort involved in **polyrepo-to-monorepo migrations**, allowing teams to consolidate projects without downtime or loss of productivity. What used to be a hassle is now a streamlined process that works across different technologies.
|
||||
|
||||
Check out the [docs](/recipes/adopting-nx/import-project) for more details.
|
||||
|
||||
### GitHub Managed Orgs in Nx Cloud
|
||||
|
||||

|
||||
|
||||
One of our goals with Nx Cloud is to make onboarding as simple as possible. We want to improve CI performance without adding unnecessary configuration or complexity. This year, we introduced **GitHub-powered organizations** to streamline team management and onboarding.
|
||||
|
||||
By connecting your GitHub organization, you can select repositories directly. Nx Cloud will automatically submit a PR to configure your workspace and handle GitHub integration.
|
||||
|
||||

|
||||
|
||||
A major benefit is that user management syncs directly with GitHub. When someone links their GitHub account to Nx Cloud, they’re automatically added to the correct organization. Membership changes in GitHub are reflected in Nx Cloud without manual intervention.
|
||||
|
||||
Learn more about how it works [here](/ci/intro/connect-to-nx-cloud).
|
||||
|
||||
### Module Federation
|
||||
|
||||
In 2024, we continued expanding Nx's **Module Federation** support, improving functionality and making it easier to use based on feedback from the community.
|
||||
|
||||
One major update was the new pattern for the **module-federation-dev-server**. It improves compatibility, reduces resource usage, and provides a smoother local development experience. This approach makes working with Module Federation in Nx easier to set up and scale across larger workspaces.
|
||||
|
||||
We also adopted **Module Federation 2.0**, adding runtime plugins and key enhancements. On top of that, we introduced support for **Rspack Module Federation**, allowing Nx users to take advantage of newer tools in the ecosystem.
|
||||
|
||||
To simplify setup, we launched **@nx/module-federation**, a package that streamlines configuration and management of federated apps in Nx workspaces. Nx’s approach to Module Federation has been highlighted by the community and is now featured on [module-federation.io](https://module-federation.io/practice/monorepos/nx-for-module-federation.html).
|
||||
|
||||
For more details on how Module Federation works in Nx, check out the [technical overview](/concepts/module-federation/nx-module-federation-technical-overview) or catch one of our [live streams on Rspack and Module Federation](https://www.youtube.com/watch?v=_c4zjYm0pYE).
|
||||
|
||||
### Nx Console
|
||||
|
||||
Nx Console has become a key part of the "Nx experience." In 2024, we introduced several updates to make it even more useful.
|
||||
|
||||
One standout feature is the **Project Detail View**. With Project Crystal, Nx can dynamically infer targets from your configuration. You can see these [inferred tasks](/concepts/inferred-tasks) by running `nx show project my-project --web`, but now they’re directly available inside Nx Console. This gives you quick access to project details, command options, caching configurations, and more, right in your editor.
|
||||
|
||||
{% video-player src="/documentation/blog/media/nxconsole-project-view.mp4" alt="Nx Console Project Detail View" /%}
|
||||
|
||||
Another big addition is **CI Pipeline Visualization** for Nx Cloud.
|
||||
|
||||
{% video-player src="/documentation/blog/media/nx-console-pipeline-running.mp4" alt="Nx Console CI Pipeline Execution" /%}
|
||||
|
||||
Nx Console notifies you inside your IDE when pipelines succeed or fail. This removes the need to monitor CI logs or constantly switch between tools.
|
||||
|
||||
{% video-player src="/documentation/blog/media/nxconsole-ci-completion.mp4" alt="Nx Console CI Completion" /%}
|
||||
|
||||
These updates make CI more accessible and help you stay focused without leaving your development environment.
|
||||
|
||||
If you haven’t tried it yet, grab Nx Console for [VSCode](https://marketplace.visualstudio.com/items?itemName=nrwl.angular-console) or [JetBrains IDEs](https://plugins.jetbrains.com/plugin/21060-nx-console).
|
||||
|
||||
### Affected Project Graph in Nx Cloud
|
||||
|
||||
Understanding how changes impact your workspace is key to optimizing and debugging CI pipelines. This year, we introduced the **Affected Project Graph** in Nx Cloud to make this easier.
|
||||
|
||||

|
||||
|
||||
The Affected Project Graph builds on existing tools like [cache miss troubleshooting](/troubleshooting/troubleshoot-cache-misses#check-3-use-the-nx-cloud-troubleshooting-tools), helping you track down what triggered a cache reset. Now, you can visually map which projects are affected by changes, providing clearer insights into your pipeline.
|
||||
|
||||
This feature lets you:
|
||||
|
||||
- **Debug pipelines faster** by seeing why tasks were triggered
|
||||
- **Spot bottlenecks** in dependency chains
|
||||
- Use insights to optimize your workspace structure
|
||||
|
||||
For a deeper look at this feature, check out our blog post: [Understand the CI Affected Graph](/blog/ci-affected-graph). Combined with other Nx Cloud tools, the Affected Project Graph makes it easier to fine-tune CI performance and manage complex workspaces.
|
||||
|
||||
### Local Caching Moves to DB Storage
|
||||
|
||||
Speed is always a priority at Nx. This year, we transitioned **local caching** from a file-based system to a **database-backed approach**. This shift reduces I/O overhead, improving performance and laying the groundwork for smarter cache management.
|
||||
|
||||
With database-backed caching, we can introduce features like:
|
||||
|
||||
- Retaining only the most relevant cache entries based on usage
|
||||
- Setting cache size limits
|
||||
- Prioritizing failed tasks earlier in the queue
|
||||
|
||||
This change not only boosts performance but also opens up opportunities for more efficient task orchestration. The move to database storage is part of a broader effort to rewrite critical parts of Nx in **Rust**, pushing for better performance and scalability.
|
||||
|
||||
## Coming up in 2025
|
||||
|
||||
We have already a lot of exciting features that we want to ship next year. Here are some of them we're looking forward to.
|
||||
|
||||
### Local Agents
|
||||
|
||||
Nx Cloud already helps balance **speed and cost** by dynamically scaling agents based on PR size and workload. This ensures you only use the resources you need, keeping CI fast without unnecessary expense.
|
||||
|
||||
But there’s still untapped potential. **Local Agents** will let you connect your developer machine to Nx Cloud, allowing it to handle tasks when it’s underutilized. This reduces the need for extra CI agents while making better use of the machines you already have, without disrupting your workflow.
|
||||
|
||||
We have a proof-of-concept in place, and we're working to turn this into a full feature.
|
||||
|
||||
### Polygraph
|
||||
|
||||
Many teams manage **multiple monorepos or polyrepos** instead of consolidating everything into a single large repository. This often happens for practical reasons or as part of a gradual transition.
|
||||
|
||||
**Polygraph** gives teams some of the benefits of a larger monorepo like consistency and visibility **without needing to merge everything at once**. Even if a monorepo is the end goal, the goal of Polygraph is to help teams make progress early, rather than waiting until everything is in one place.
|
||||
|
||||
This is especially useful for **platform teams** responsible for maintaining consistency, security, and best practices across many repositories. Polygraph provides better control by allowing you to:
|
||||
|
||||
- Run **conformance rules** across all repositories in your Nx Cloud organization
|
||||
- Visualize **dependencies** between projects in different repositories using a multi-repo graph
|
||||
|
||||
Stay tuned as we keep working on this and releasing it in 2025.
|
||||
|
||||
### Rewriting Nx Core in Rust
|
||||
|
||||
We’ve already rewritten performance-critical parts of Nx in **Rust**, and in 2025 we plan to extend this to more of the core. One of the main goals is to ensure that Nx adds as little overhead as possible to your monorepo, keeping things fast and efficient at scale.
|
||||
|
||||
This rewrite aligns with Nx’s goal of being **tech agnostic**, supporting polyglot monorepos and workflows beyond JavaScript. By reducing reliance on Node.js for the core, Nx can fit into a wider range of environments, while plugins will continue to be written in **TypeScript** to keep development familiar and extensible.
|
||||
|
||||
By moving core parts to Rust, we’re focusing on performance where it matters most, while maintaining the flexibility that makes Nx useful across different tech stacks.
|
||||
|
||||
### Support for Long-Running Tasks
|
||||
|
||||
Nx is built around tasks that start and finish, but that doesn’t always fit workflows involving **dev servers, watch-mode builds, or background services**. These tasks run indefinitely, which can be tricky to manage, especially when other processes depend on them, like e2e tests that require a server to stay up.
|
||||
|
||||
We’re working on adding support for **long-running tasks** that can run alongside regular build and test processes. This will make workflows smoother and help avoid the need for manual orchestration. The upcoming **Nx terminal UI rewrite** will reflect this, making it easier to track and visualize long-running processes alongside other tasks (more about that later in the article).
|
||||
|
||||
If you’re curious about the direction we’re heading, take a look at the ongoing [RFC](https://github.com/nrwl/nx/discussions/29025).
|
||||
|
||||
### Unified Monorepo Support
|
||||
|
||||
Nx already works well with both **integrated monorepos** driven by Nx plugins and **package-based monorepos** using npm, Yarn, or PNPM workspaces. Where things get trickier is mixing the two approaches since integrated plugins didn’t always align smoothly with workspace-based setups.
|
||||
|
||||
With **Nx Crystal Plugins**, we’re making it easier to blend these approaches. You get the automation and guardrails of Nx plugins while retaining the flexibility of workspace-based monorepos. This lets you enhance a PNPM, npm, or Yarn workspace with Nx’s powerful task orchestration, caching, and dependency management, without fully committing to an integrated setup.
|
||||
|
||||
A great example of this is our [Next.js + Nx + PNPM course](/courses/pnpm-nx-next), where we show how to enhance a PNPM workspace with Nx. But this is just the beginning. Our goal is to make the experience even better, reducing friction and making it seamless to mix and match both models.
|
||||
|
||||
Also note: existing workspaces won’t be affected. Integrated monorepos will continue to work as they always have, but new projects will default to this more flexible model.
|
||||
|
||||
You can read more about this direction in the [RFC](https://github.com/nrwl/nx/discussions/29099).
|
||||
|
||||
### Nx Terminal Redesign
|
||||
|
||||
The terminal is one of the main ways developers interact with Nx, and we’re working on a complete redesign to make it nicer, clearer, and easier to use.
|
||||
|
||||
The goal is to reduce noise and surface the most important information for each task. Regardless whether you're running multiple dev servers, long-running tasks, or parallel builds.
|
||||
|
||||
This redesign also ties into our work on **long-running tasks**. Processes like dev servers or background services will be easier to manage directly from the terminal, letting you see their status alongside regular build and test processes.
|
||||
|
||||
We want the terminal to feel more intuitive and stay out of your way while giving you the details you need when you need them.
|
||||
|
||||
### Improved Node.js Experience
|
||||
|
||||
The goal is to bring the same level of polish to Node development that Nx already provides for frontend frameworks. This includes **better tools for managing microservices**, **streamlining Docker** builds, and improving backend workflows in monorepos.
|
||||
|
||||
We want Nx to feel just as natural for scaling Node applications as it does for frontend projects.
|
||||
|
||||
### Expanding Polyglot Support
|
||||
|
||||
Nx has grown beyond JavaScript, with support for tools across different ecosystems. In 2024, we introduced **Gradle support** to simplify Java app management in monorepos.
|
||||
|
||||
Next, we’re expanding to **Maven** and **.NET**, continuing to break down barriers for teams managing projects in multiple languages. The goal is to make Nx a natural fit for polyglot monorepos, keeping workflows fast and consistent across tech stacks.
|
||||
|
||||
## Excited for 2025?
|
||||
|
||||
We are too. As we wrap up 2024, we want to thank you for being part of the Nx community. Your feedback and contributions continue to shape Nx and push it forward.
|
||||
|
||||
We hope you enjoy the holidays and, if you're celebrating, Merry Christmas! Looking forward to seeing what we build in 2025.
|
||||
@@ -0,0 +1,236 @@
|
||||
---
|
||||
title: 'Nx Update: 20.3!'
|
||||
slug: nx-update-20-3
|
||||
authors: [Zack DeRose]
|
||||
tags: [nx]
|
||||
cover_image: /blog/images/2025-01-06/nx-20-3.png
|
||||
---
|
||||
|
||||
It's been a few months now since our last major update with [Nx 20](), let's check in with the latest updates since 20 came out, as we've now dropped 3 minor releases since them. Here's a rundown of everything new:
|
||||
|
||||
- [Typescript Project References And Workspaces Support](#typescript-project-references-and-workspaces-support)
|
||||
- [Rspack updates](#rspack-updates)
|
||||
- [New Nx Console Feature: CI Pipeline Status in Your IDE!](#new-nx-console-feature-ci-pipeline-status-in-your-ide)
|
||||
- [Nx Cloud Enhancements](#nx-cloud-enhancements)
|
||||
- [Angular 19 Support](#angular-19-support)
|
||||
- [Core Performance Improvements](#core-performance-improvements)
|
||||
- [Powerpack Updates](#powerpack-updates)
|
||||
- [RFC for Infinite Tasks](#rfc-for-infinite-tasks)
|
||||
|
||||
You can also catch most of these updates direct from the team from our livestream:
|
||||
|
||||
{% youtube
|
||||
src="https://www.youtube.com/embed/GRiXj4tmqKA"
|
||||
title="Nx Live: Nx 20.3" /%}
|
||||
|
||||
## Typescript Project References And Workspaces Support
|
||||
|
||||
Our new support for Typescript project references has been launched!
|
||||
|
||||
Prior this enhancement, by default, Nx workspaces were treated as a single unit from the perspective of your package manager, and at the Typescript level, we used path aliases (using the `path` compiler option in your `tsconfig.base.json` file) in order for your different projects to know where to import from in import statements like:
|
||||
|
||||
```typescript
|
||||
import { foo } from '@my-workspace/my-project;
|
||||
```
|
||||
|
||||
With the new enhancement, when you run our generators, we'll instead manage creating each project as a new project from the perspective of your package manager (whether that's `npm`, `yarn`, `pnpm`, or `bun`), and setting up Typescript to know about this project as a ["project reference"](https://www.typescriptlang.org/docs/handbook/project-references.html) using the `references` compiler option in a `tsconfig.json` file at the root of your workspace.
|
||||
|
||||
You can see this now by creating a new nx workspace with:
|
||||
|
||||
```shell
|
||||
npx create-nx-workspace@latest my-workspace --preset=ts
|
||||
```
|
||||
|
||||
After creation, you'll notice that there's now a `workspaces` field set up in your root `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "@my-workspace/source",
|
||||
"version": "0.0.0",
|
||||
"scripts": {},
|
||||
"dependencies": {},
|
||||
"devDependencies": {},
|
||||
"workspaces": ["libs/**", "packages/**"]
|
||||
}
|
||||
```
|
||||
|
||||
This is how you inform your package manager as to where to find packages in your workspace. Note that because we used `npx` to create this workspace, Nx set up the workspace for Npm's workspaces feature.
|
||||
|
||||
If we had set up this workspace to use Pnpm as our package manager (which you can do either by using pnpx:
|
||||
|
||||
```shell
|
||||
pnpx create-nx-workspace@latest
|
||||
```
|
||||
|
||||
or providing a `pm` option:
|
||||
|
||||
```shell
|
||||
npx create-nx-workspace@latest --pm=pnpm
|
||||
```
|
||||
|
||||
Then a `pnpm-workspace.yaml` file would have been created to support pnpm's workspaces feature. Similar support and considerations are given for [Yarn workspaces](https://yarnpkg.com/features/workspaces).
|
||||
|
||||
You should now also see in your `tsconfig.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compileOnSave": false,
|
||||
"files": [],
|
||||
"references": []
|
||||
}
|
||||
```
|
||||
|
||||
A new `references` field here that is currently empty. If we use an nx generator to create a new lib:
|
||||
|
||||
```shell
|
||||
nx g lib packages/my-package
|
||||
```
|
||||
|
||||
We'll see that this gets updated now to:
|
||||
|
||||
```json
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compileOnSave": false,
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./packages/my-package"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Nx will continue to update this file as you add packages.
|
||||
|
||||
Note that dependending on which package manager you use, you may need to run an install from your package manager (for example `npm install`) after adding a new package for your package manager to set things up correctly!
|
||||
|
||||
This new approach is currently what you'll get using the `ts` preset by default:
|
||||
|
||||
```shell
|
||||
npx create-nx-workspaces@latest --preset=ts
|
||||
```
|
||||
|
||||
You can opt-in to the experimental support now for our react, vue, and node presets using the `--workspaces` flag:
|
||||
|
||||
```shell
|
||||
npx create-nx-workspace@latest --preset=react --workspaces
|
||||
```
|
||||
|
||||
And we will continue to add this as the default to all our official presets soon. You can follow along with [the discussion on the RFC here](https://github.com/nrwl/nx/discussions/29099) for more!
|
||||
|
||||
## Rspack updates
|
||||
|
||||
Our [`@nx/rspack`](/nx-api/rspack) plugin has moved from labs into our main repo now, and we now have [a dedicated plugin](/nx-api/rsbuild) for [Rsbuild](https://rsbuild.dev/) (a vite-like build tool with streamlined config built on top of Rspack) as well.
|
||||
|
||||
With the latest improvements, our Rspack plugin is now at feature parity with our [`@nx/webpack`](/nx-api/webpack) plugin, which should make it easier for teams to migrate from Webpack to Rspack now and take advantage of the significant performance gains.
|
||||
|
||||
Our new [Rsbuild plugin](/nx-api/rsbuild) will bring support for React and Vue applications, and we have measured a massive performance increase here over webpack. Running the command:
|
||||
|
||||
```shell
|
||||
nx g @nx/rsbuild:configuration
|
||||
```
|
||||
|
||||
Will create the necessary configuration for an existing targetted React or Vue application to work with Rsbuild.
|
||||
|
||||
When using the `@nx/react` and `@nx/vue` application generators, we'll also now include `rsbuild` as an option for the bundler to use when setting up the application.
|
||||
|
||||
For more on our Rspack support, be sure to check out previous livestreams with Colum, including this one for setting up Module Federation with Rspack:
|
||||
|
||||
{% youtube
|
||||
src="https://www.youtube.com/embed/_c4zjYm0pYE"
|
||||
title="Nx Live: Module Federation w/ Rspack" /%}
|
||||
|
||||
And Colum's package for setting up Angular with Rspack:
|
||||
|
||||
{% youtube
|
||||
src="https://www.youtube.com/embed/raDY8RzdFI0"
|
||||
title="Nx Live: Rspack for Angular Projects" /%}
|
||||
|
||||
## New Nx Console Feature: CI Pipeline Status in Your IDE!
|
||||
|
||||
We launched a [new feature](/blog/nx-cloud-pipelines-come-to-nx-console) for [Nx Console](/getting-started/editor-setup#download), our IDE plugin that works for Visual Studio Code and JetBrains IDEs (like IntelliJ and WebStorm)!
|
||||
|
||||
This feature allows you to have access to the status of your pending pipelines - including the ability to get notifications when one of your Nx Cloud pipelines completes - right from your IDE!
|
||||
|
||||
{% video-player src="/documentation/blog/media/nxconsole-ci-completion.mp4" alt="Nx Console CI Completion" /%}
|
||||
|
||||
You have access to this feature today if you're using the VsCode version of the plugin, and we plan to bring this to the JetBrains version soon.
|
||||
|
||||
## Nx Cloud Enhancements
|
||||
|
||||
Available now on Nx Cloud - you can see the affected project graph for each individual PR, using the new composite graph view! Check out this video for more details:
|
||||
|
||||
{% youtube
|
||||
src="https://www.youtube.com/embed/TS-Fp2iSlVM"
|
||||
title="At-A-Glance Affected Repo Architecture On Every Pull Request! Now on Nx Cloud." /%}
|
||||
|
||||
We also now have support for [Assignment Rules](/ci/reference/assignment-rules). Assignment Rules allow you to specify specific machines for various tasks when running your Nx Cloud pipelines with a fleet of different machines. For example, if you have a very computationally intensive `build` task, you can specify that this task should be picked up only by your largest sized machine.
|
||||
|
||||
## Angular 19 Support
|
||||
|
||||
Angular 19 is here, and Nx now supports migrating your Angular 18 projects to Angular 19 via [`nx migrate`](/features/automate-updating-dependencies).
|
||||
|
||||
```shell
|
||||
nx migrate latest --interactive
|
||||
```
|
||||
|
||||
By using the `--interactive` flag (like above) you can opt into migrating specific dependencies (for example if you want to upgrade to the latest version of Nx, but not Angular just yet).
|
||||
|
||||
Keep in mind as well that as Angular 16 has now [fallen out of Long-Term Support (LTS)](https://angular.dev/reference/releases#actively-supported-versions), Nx will no longer support Angular 16 going forward.
|
||||
|
||||
## Core Performance Improvements
|
||||
|
||||
Over the past many months, we've addressed most of the glaring issues regarding the new ['inferred tasks plugins'](/concepts/inferred-tasks) that were first introduced in Nx v18. Now, in the last recent months, we've started to address some of the more fine-grained issues that we has seen in supporting this new functionality - including performance.
|
||||
|
||||
One place we've improved performance is in the loading of a local custom plugin, written in Typescript. Prior to this enhancement, loading of these plugins was fairly costly in terms of time and memory, and we've now reduced both of those costs dramatically. If you had been using a custom inferred task plugin, you should see a noticeable performance improvement now.
|
||||
|
||||
We've also introduced spinners into the terminal ui for instances where a task is delayed due to Nx calculating your workspace's graph. This will give some visual feedback to the user whenever Nx is still calculating your graph - whereas prior to this, it might seem as if nothing is happening.
|
||||
|
||||

|
||||
|
||||
## Powerpack Updates
|
||||
|
||||
[Nx Powerpack](/nx-enterprise/powerpack) is a suite of paid extensions for the Nx CLI, that enables some features like [allowing for third-party storage services to be used for your remoted and shared caching](/nx-enterprise/powerpack/custom-caching), [specifying specific developers as owners of specific projects in your repo](/nx-enterprise/powerpack/owners), and [running language-agnostic conformance rules](/nx-enterprise/powerpack/conformance):
|
||||
|
||||
{% cards cols="2" lgCols="3" mdCols="3" smCols="2" %}
|
||||
|
||||
{% link-card title="Conformance" type="Powerpack Feature" url="/nx-enterprise/powerpack/conformance" icon="CheckBadgeIcon" /%}
|
||||
{% link-card title="Owners" type="Powerpack Feature" url="/nx-enterprise/powerpack/owners" icon="UserGroupIcon" /%}
|
||||
{% link-card title="Self-Hosted Remote Cache" type="Powerpack Feature" url="/nx-enterprise/powerpack/custom-caching" icon="ServerIcon" /%}
|
||||
|
||||
{% /cards %}
|
||||
|
||||
Recently, we've added support for [Azure](/nx-api/powerpack-azure-cache) and [Google Cloud Storage](/nx-api/powerpack-gcs-cache) as supported storage providers (in addition to [S3](/nx-api/powerpack-s3-cache) or [simply using a shared file system](/nx-api/powerpack-shared-fs-cache)).
|
||||
|
||||
We've also [added support for S3 compatible providers](/nx-api/powerpack-s3-cache#s3-compatible-providers) such as:
|
||||
|
||||
- [MinIO](https://min.io/product/s3-compatibility)
|
||||
- [LocalStack](https://www.localstack.cloud)
|
||||
- [DigitalOcean Spaces](https://www.digitalocean.com/products/spaces)
|
||||
- [Cloudflare](https://www.cloudflare.com/developer-platform/solutions/s3-compatible-object-storage)
|
||||
|
||||
We've also added the capability to allow folks to authenticate their Powerpack license via Nx Cloud (rather than requiring that the license be committed to the repository). This should enable oss library authors use Powerpack - without exposing their private license via their repo.
|
||||
|
||||
We are offering free Nx Powerpack licenses to all open source libraries. We also offer free licenses to smaller businesses. If you are interested, you may [request a license now using Nx Cloud](https://cloud.nx.app/powerpack/purchase).
|
||||
|
||||
## RFC for Infinite Tasks
|
||||
|
||||
Last up, we have an [RFC for the concept of "Infinite Tasks"](https://github.com/nrwl/nx/discussions/29025) on GitHub now. This is a new feature that we'll be adding soon to support non-discrete tasks in terms of the task dependencies. It should come in handy for use-cases like:
|
||||
|
||||
- starting a local backend application whenever you start a frontend web server that depends on that backend running (while developing)
|
||||
- starting a local database whenever you start a backend application that depends on that database istance (while developing)
|
||||
- making sure you publish to a local registry (like verdaccio) when you are testing a package before publishing to npm
|
||||
|
||||
We're very excited to implement this soon and greatly appreciate feedback as we know this is something that many of our users have been asking about!
|
||||
|
||||
## Learn more
|
||||
|
||||
- [Nx Docs](/getting-started/intro)
|
||||
- [X/Twitter](https://twitter.com/nxdevtools)
|
||||
- [LinkedIn](https://www.linkedin.com/company/nrwl/)
|
||||
- [Nx GitHub](https://github.com/nrwl/nx)
|
||||
- [Nx Official Discord Server](https://go.nx.dev/community)
|
||||
- [Nx Youtube Channel](https://www.youtube.com/@nxdevtools)
|
||||
- [Speed up your CI](/nx-cloud)
|
||||
@@ -0,0 +1,201 @@
|
||||
---
|
||||
title: 'Who Gave JavaScript a Build Step?!?'
|
||||
slug: who-gave-js-a-build-step
|
||||
authors: [Zack DeRose]
|
||||
tags: []
|
||||
cover_image: /blog/images/2025-01-09/thumbnail.png
|
||||
---
|
||||
|
||||
JavaScript is awesome for many reasons. Not the least of these is as an interpreted language, we can actually send our JavaScript as-is over HTTP just as plain text right to a browser.
|
||||
|
||||
So how did we take something so simple and straightforward and mess it up to the point where we actually gave our JavaScript code a build step? Why are we web-packing? What is a Vite? And why do we hate ourselves?
|
||||
|
||||
I sat down with Zack Jackson - maintainer of the [Webpack](https://webpack.js.org/) and [Rspack](https://rspack.dev/) bundlers, and creator of Module Federation to get the answers to these burning questions. Part 1 is here where we talk a good bit about the history of bundlers (I'll throw part 2 a little further down):
|
||||
|
||||
{% youtube
|
||||
src="https://www.youtube.com/watch?v=ma_c6UNHddI"
|
||||
title="Zack Jackson: Everything You Ever Wanted To Know About Web Bundlers (and Rspack!)"
|
||||
width="100%"
|
||||
/%}
|
||||
|
||||

|
||||
|
||||
## Why A Build Step
|
||||
|
||||
Now that you're here and past the first few paragraphs, we can drop the clickbait facade.
|
||||
|
||||
We all _kinda_ know why we have a build step for our JavaScript. An entire over-simplification, but this is essentially what we're going for:
|
||||
|
||||

|
||||
|
||||
We have an "Input format" that represents a format that we want to work with while developing. This should be human-readable, logically organized, and generally conducive to the process of development.
|
||||
|
||||
Then we have an "Output format" that represents what we'd like to ship, and the priorities here are very much efficiency-focused. We want to ship code that is functionally equivalent to the code from our "Input format", but we want to ship it in a way that has as few network requests as possible, with files that are as small as possible, and ideally we are always only sending code that is needed for the application to function properly for the current page or state - no more (or else we wasted sending code to the user's browser that is just not getting used) and no less (or else the application won't function properly).
|
||||
|
||||
The representation of this "build step" as a "black box" in the image above is very much on purpose. In most cases, I think an appropriately pragmatically-minded engineer (read this as a "good engineer") should probably not over-obsess with what this build process looks like. Preferably, you'd use the many available tools to leverage the work done by other engineers and the wider ecosystem to leverage work done by engineers focused on this problem.
|
||||
|
||||
## Inhale and Exhale: The Core Dimension
|
||||
|
||||
The "core dimension" as I like to think of it, comes down to "bundling" and "code-splitting". We'll hit the other dimensions later, but for the most part, those dimensions of the problem are either splintering of this core dimension, or somewhat ancillary to this core dimension.
|
||||
|
||||
"Bundling" refers to bundling up all the javascript files or "modules" of your application (and yes, that includes the black hole on your hard drive that is your `node_modules`) and bundling it all up into 1 file.
|
||||
|
||||
We'll flesh that process just a bit more because that's all very hand-wavey.
|
||||
|
||||
The first step of bundling is creating a graph of those "modules" mentioned. This graph will represent how each module depends on the others. From there, we _could_ collapse that entire graph into the 1 massive monolithic file, by replacing `import` or `require` statements with the contents of the code being imported. The reality is we often don't immediately, the graph itself is a good enough representation of that one file that we don't have to actually create the file, but you could think of it the same, because functionally, they're pretty much equivalent.
|
||||
|
||||
I like to think of bundling as an "inhale", where we gather up all your code into the module graph (or the 1 file).
|
||||
|
||||
From here we move into the "exhale", or the "code-splitting." The motivation here is without such a step, we could send over all our javascript in one shot with a single `script` tag:
|
||||
|
||||
```html {% filename="index.html" %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>My Webapp</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<!-- Content will be injected by the JavaScript application -->
|
||||
</div>
|
||||
|
||||
<!-- Reference to the bundled JavaScript -->
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
This is good: after the html comes in, we only need to make 1 request for all the javascript we need.
|
||||
|
||||
But this is also very very bad: if that 1 shot of javascript is massive, we end up waiting a long time for it all to arrive before our users can interact with the site.
|
||||
|
||||
When we utilize "code-splitting", we're "chunking" that 1 massive javascript file into smaller pieces, so that our `main.js` file can be small - just enough to "bootstrap" the application so the user can interact with it as soon as possible. When we use this approach, our `index.html` actually remains the same, but the `main.js` file will include code to dynamically load other chunks of javascript as we need them.
|
||||
|
||||
Our bundler is capable of automating some of this code-splitting for us, but by using [dynamic `import()` statements](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import), we can signal to most bundlers to create dynamic chunks around those imports. This can be particularly effective when using a router on your application - to ensure that the code for each route is dynamically loaded when the route is activated.
|
||||
|
||||
Let’s break down code-splitting with an actual example. Imagine you’re building a single-page application (SPA) with React and `react-router` that dynamically loads components for each route:
|
||||
|
||||
```tsx {% filename="app.tsx" %}
|
||||
import React from 'react';
|
||||
import { BrowserRouter as Router, Routes, Route } from 'react-router';
|
||||
|
||||
const Bar = React.lazy(() => import('./bar'));
|
||||
const Foo = React.lazy(() => import('./foo'));
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Router>
|
||||
<nav>
|
||||
<Link to="/">Foo</Link>
|
||||
<Link to="/bar">Bar</Link>
|
||||
</nav>
|
||||
<Routes>
|
||||
<Route path="/" element={<Foo />} />
|
||||
<Route path="/bar" element={<Bar />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
When building this (we'll use vite for this example), the bundler creates separate files for each chunk:
|
||||
|
||||
```shell
|
||||
> vite build
|
||||
|
||||
vite v5.4.11 building for production...
|
||||
✓ 42 modules transformed.
|
||||
../../dist/apps/my-webapp/index.html 0.48 kB │ gzip: 0.30 kB
|
||||
../../dist/apps/my-webapp/assets/foo-DjRQIFuQ.js 0.16 kB │ gzip: 0.15 kB
|
||||
../../dist/apps/my-webapp/assets/bar-oew334y7.js 0.22 kB │ gzip: 0.19 kB
|
||||
../../dist/apps/my-webapp/assets/index-3-BhaWR_.js 177.96 kB │ gzip: 58.71 kB
|
||||
✓ built in 589ms
|
||||
```
|
||||
|
||||
This way, our bundler creates a separate "chunk" per route, and the chunk can be dynamically loaded when it is needed.
|
||||
|
||||
This code-splitting then operates as the "exhale". Essentially, we move from a series of files we are working with as we develop, "inhale" them into "the graph", then "exhale", splitting them into their respective chunks - a different set of files optimized for your built artifact.
|
||||
|
||||
## The Other Dimensions
|
||||
|
||||
I view the rest of these as somewhat secondary to the core dimension above, but they're still worth mentioning!
|
||||
|
||||
### Non-JS Assets
|
||||
|
||||
Similar bundling is required for non-js assets, the most significant for front-end projects is usually css (where similar bundling/splitting strategies come into play), but other assets like images and fonts also need to end up in the final artifact, and things need to work in terms of where these assets are located in the subdirectories and where your javascript and html reference these!
|
||||
|
||||
Bundlers handle these as well.
|
||||
|
||||
### Transpilation / Polyfilling
|
||||
|
||||
Transpilation mainly refers to the process of transforming your Typescript files from Typescript (which a browser can't understand) to JavaScript (which a browser can understand!).
|
||||
|
||||
Typescript serves as a way of placing typing assertions on our code to make sure we don't have errors in our code that violate our typing instructions. We can think of type-checking our code as a "lint step" - something that we should run before committing code into our codebase via [Continuous Integration](/ci), but this type-check step we can safely view as separate from our "build step." From the perspective of our "build step" we're usually looking at simply stripping out the types from your code to create valid JavaScript.
|
||||
|
||||
Transpilation can also refer to adjusting the code we write in to code that browsers support. In prior days, tools like [Babel](https://babeljs.io/) were required to "transpile" down from ES6 javascript to ES5. Tools like Babel are still widely used, to support [esnext](https://developer.mozilla.org/en-US/docs/Web/JavaScript/JavaScript_technologies_overview#standardization_process) features that browsers don't support yet. This is also referred to as polyfilling sometimes - as in polyfilling a JS feature that is not supported yet by browsers, or sometimes for features that can be optionally loaded by specific browsers that are missing various features.
|
||||
|
||||
### "Last Mile" Optimizations
|
||||
|
||||
There's a series of other optimizations that bundlers perform as part of the build step, the main ones that come to mind here are:
|
||||
|
||||
- **Minification** - reduces file size by reducing whitespace to minimum, removing any superfluous text, and shortening all variable names
|
||||
- **Tree Shaking** - Analyzes the Abstract Syntax Tree (AST) of your bundled graph for unreferenced branches of the overall tree. Those unreferenced branches are then "shaken off" so this code does not end up in your end artifact.
|
||||
- **Cache Busting** - Appends hashes to filenames (e.g., `main-[hash].js`) to ensure browsers load updated files after a deployment.
|
||||
|
||||
Some may consider code splitting a "last mile" optimization as well, and some other items like creating source maps for a development build or asset optimization could be seen as a last-mile optimization as well.
|
||||
|
||||
### Module Formats
|
||||
|
||||
EcmaScript Modules (or ESM or `"type": "module"` or `.mjs`) vs. CommonJS modules (or `require` or `"type": "common"` or `.cjs`) is another terrible dimension of this whole discussion.
|
||||
|
||||
Quoting from [an article I read on this topic that rang especially true](https://redfin.engineering/node-modules-at-war-why-commonjs-and-es-modules-cant-get-along-9617135eeca1):
|
||||
|
||||
> Superficially, ESM looks very similar to CJS, but their implementations couldn’t be more different. One of them is a honey bee, and the other is a murder hornet. But I can never remember which one is which.
|
||||
|
||||
The ESM module format uses `import` statements to bring in other modules, and `export` statements to mark public/consumable pieces of a module.
|
||||
|
||||
CommonJS module format uses `require` statements to bring in other modules, and `module.exports = ...` statements to mark public/consumable pieces of a module.
|
||||
|
||||
But it's unfortunately a bit more complicated than that, but that's beyond the scope of this article. There's other formats too, like AMD (Asynchronous module definition, popular in legacy browser projects) and UMD (a universal format that works in both Node.js and the browser)
|
||||
|
||||
Bundlers are great, cuz they pave over much of this headache - allowing you to write your code in either module format or even in a bit of both, and exporting to what makes most sense. Typically you'd target esm, as that's the current consensus for front-end applications.
|
||||
|
||||
### Building for Other Purposes
|
||||
|
||||
Another interesting dimension is building for other purposes than simply front-end apps.
|
||||
|
||||
One interesting alternative here is building a package to publish to npm or a private registry. There's a parallel set of concerns here, you still have a desired input format that probably looks very similar! But you have entirely different priorities for your output format! Size is largely unimportant, as `node_modules` are already an endless black hole of bytes, there's no sense fretting over adding a few more megs on that heap.
|
||||
|
||||
That's mainly a joke - but the idea is your end consumer would figure out minification and etcetera when running their own build step.
|
||||
|
||||
You also don't really need to "bundle" in your dependencies, as you can simply reference other dependencies in your exported `package.json` for your consumer to bring in. You do typically need to worry about types - typically building to `.js` files, but providing `d.ts` files to inform the typing of your package.
|
||||
|
||||
Another alternative is building for backend node applications. Different priorities here too as bundle size isn't all that important - but some interesting folds can come in around Module Federation on the backend.
|
||||
|
||||
## Wrapping it up
|
||||
|
||||
Here's the part 2 of my discussion with Zack Jackson - where we go a bit deeper, laying out the rough map of bundler lifecycle hooks and diving into webpack source code a bit to see it in action:
|
||||
|
||||
{% youtube
|
||||
src="https://www.youtube.com/watch?v=RE1AzJIcbdk"
|
||||
title="Everything You Need To Know About Bundlers! Part 2 w/ Zack Jackson (RsPack creator)"
|
||||
width="100%" /%}
|
||||
|
||||

|
||||
|
||||
Jump into [our discord](https://go.nx.dev/community) if you have more questions, but I think the gist of it is summarized by this image:
|
||||
|
||||

|
||||
|
||||
We have an input format we want to work with - and an output format that we want to export our artifact in. Generally, your build step should be a black-box of getting you from that input format to your output format. This article hopefully helped you flesh out that black box just a bit - at least enough to point you in the right direction if you want to dig deeper!
|
||||
|
||||
## Learn More
|
||||
|
||||
- 🧠 [Nx Docs](/getting-started/intro)
|
||||
- 👩💻 [Nx GitHub](https://github.com/nrwl/nx)
|
||||
- 💬 [Nx Official Discord Server](https://go.nx.dev/community)
|
||||
- 📹 [Nx Youtube Channel](https://www.youtube.com/nrwl_io)
|
||||
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 310 KiB |
|
After Width: | Height: | Size: 472 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 25 KiB |
@@ -35,6 +35,17 @@
|
||||
"path": "powerpack-conformance/documents/overview",
|
||||
"tags": [],
|
||||
"originalFilePath": "shared/packages/powerpack-conformance/powerpack-conformance-plugin"
|
||||
},
|
||||
{
|
||||
"id": "create-conformance-rule",
|
||||
"name": "Create a Conformance Rule",
|
||||
"description": "A Nx Powerpack plugin which allows users to write and apply rules for your entire workspace that help with consistency, maintainability, reliability and security.",
|
||||
"file": "external-generated/packages/powerpack-conformance/documents/create-conformance-rule",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "powerpack-conformance/documents/create-conformance-rule",
|
||||
"tags": [],
|
||||
"originalFilePath": "shared/packages/powerpack-conformance/create-conformance-rule"
|
||||
}
|
||||
],
|
||||
"executors": [
|
||||
@@ -48,7 +59,17 @@
|
||||
"type": "executor"
|
||||
}
|
||||
],
|
||||
"generators": [],
|
||||
"generators": [
|
||||
{
|
||||
"description": "Create a new conformance rule",
|
||||
"file": "external-generated/packages/powerpack-conformance/generators/create-rule.json",
|
||||
"hidden": false,
|
||||
"name": "create-rule",
|
||||
"originalFilePath": "/libs/nx-packages/powerpack-conformance/src/generators/create-rule/schema.json",
|
||||
"path": "powerpack-conformance/generators/create-rule",
|
||||
"type": "generator"
|
||||
}
|
||||
],
|
||||
"githubRoot": "https://github.com/nrwl/nx/blob/master",
|
||||
"name": "powerpack-conformance",
|
||||
"packageName": "@nx/powerpack-conformance",
|
||||
@@ -168,17 +189,7 @@
|
||||
}
|
||||
],
|
||||
"executors": [],
|
||||
"generators": [
|
||||
{
|
||||
"description": "Initialize the S3 Cache",
|
||||
"file": "external-generated/packages/powerpack-s3-cache/generators/init.json",
|
||||
"hidden": false,
|
||||
"name": "init",
|
||||
"originalFilePath": "/libs/nx-packages/powerpack-s3-cache/src/generators/init/schema.json",
|
||||
"path": "powerpack-s3-cache/generators/init",
|
||||
"type": "generator"
|
||||
}
|
||||
],
|
||||
"generators": [],
|
||||
"githubRoot": "https://github.com/nrwl/nx/blob/master",
|
||||
"name": "powerpack-s3-cache",
|
||||
"packageName": "@nx/powerpack-s3-cache",
|
||||
|
||||
@@ -81,3 +81,29 @@ Finally, you need to configure your Nx cache in the `nx.json` file. The `contain
|
||||
| --------------- | -------------------------------- |
|
||||
| **container** | The name of the container to use |
|
||||
| **accountName** | The name of blob storage account |
|
||||
|
||||
# Cache Modes
|
||||
|
||||
By default, Nx will try to write and read from the remote cache while running locally. This means that permissions must be set for users who are expected to access the remote cache.
|
||||
|
||||
Nx will only show warnings when the remote cache is not writable. You can disable these warnings by setting `localMode` to `read-only` or `no-cache` in the `nx.json` file.
|
||||
|
||||
```jsonc {% fileName="nx.json" %}
|
||||
{
|
||||
"azure": {
|
||||
// ...
|
||||
"localMode": "read-only"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The cache mode in CI can also be configured by setting `ciMode` to `read-only` or `no-cache` in the `nx.json` file. Or setting `NX_POWERPACK_CACHE_MODE` to `read-only` or `no-cache` in the CI environment.
|
||||
|
||||
```jsonc {% fileName="nx.json" %}
|
||||
{
|
||||
"azure": {
|
||||
// ...
|
||||
"ciMode": "read-only"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
# Create a Conformance Rule
|
||||
|
||||
For local conformance rules, the resolution utilities from `@nx/js` are used in the same way they are for all other JavaScript/TypeScript files in Nx. Therefore, you can simply reference an adhoc JavaScript file or TypeScript file in your `"rule"` property (as long as the path is resolvable based on your package manager and/or tsconfig setup), and the rule will be loaded/transpiled as needed. The rule implementation file should also have a `schema.json` file next to it that defines the available rule options, if any.
|
||||
|
||||
Therefore, in practice, writing your local conformance rules in an Nx generated library is the easiest way to organize them and ensure that they are easily resolvable via TypeScript. The library in question could also be an Nx plugin, but it does not have to be.
|
||||
|
||||
To write your own conformance rule, run the `@nx/powerpack-conformance:create-rule` generator and answer the prompts.
|
||||
|
||||
```text {% command="nx g @nx/powerpack-conformance:create-rule" %}
|
||||
NX Generating @nx/powerpack-conformance:create-rule
|
||||
|
||||
✔ What is the name of the rule? · local-conformance-rule-example
|
||||
✔ Which directory do you want to create the rule directory in? · packages/my-plugin/local-conformance-rule
|
||||
✔ What category does this rule belong to? · security
|
||||
✔ What reporter do you want to use for this rule? · project-reporter
|
||||
✔ What is the description of the rule? · an example of a conformance rule
|
||||
CREATE packages/my-plugin/local-conformance-rule/local-conformance-rule-example/index.ts
|
||||
CREATE packages/my-plugin/local-conformance-rule/local-conformance-rule-example/schema.json
|
||||
```
|
||||
|
||||
The generated rule definition file should look like this:
|
||||
|
||||
```ts {% fileName="packages/my-plugin/local-conformance-rule/index.ts" %}
|
||||
import {
|
||||
createConformanceRule,
|
||||
ProjectViolation,
|
||||
} from '@nx/powerpack-conformance';
|
||||
|
||||
export default createConformanceRule({
|
||||
name: 'local-conformance-rule-example',
|
||||
category: 'security',
|
||||
description: 'an example of a conformance rule',
|
||||
reporter: 'project-reporter',
|
||||
implementation: async (context) => {
|
||||
const violations: ProjectViolation[] = [];
|
||||
|
||||
return {
|
||||
severity: 'low',
|
||||
details: {
|
||||
violations,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
To enable the rule, you need to register it in the `nx.json` file.
|
||||
|
||||
```json {% fileName="nx.json" %}
|
||||
{
|
||||
"conformance": {
|
||||
"rules": [
|
||||
{
|
||||
"rule": "./packages/my-plugin/local-conformance-rule/index.ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that the severity of the error is defined by the rule author and can be adjusted based on the specific violations that are found.
|
||||
|
||||
## Conformance Rule Examples
|
||||
|
||||
There are three types of reporters that a rule can use.
|
||||
|
||||
- `project-reporter` - The rule evaluates an entire project at a time.
|
||||
- `project-files-reporter` - The rule evaluates a single project file at a time.
|
||||
- `non-project-files-reporter` - The rule evaluates files that don't belong to any project.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="project-reporter" %}
|
||||
|
||||
The `@nx/powerpack-conformance:ensure-owners` rule provides us an example of how to write a `project-reporter` rule. The `@nx/powerpack-owners` plugin adds an `owners` metadata property to every project node that has an owner in the project graph. This rule checks each project node metadata to make sure that each project has some owner defined.
|
||||
|
||||
```ts
|
||||
import { ProjectGraphProjectNode } from '@nx/devkit';
|
||||
import {
|
||||
createConformanceRule,
|
||||
ProjectViolation,
|
||||
} from '@nx/powerpack-conformance';
|
||||
|
||||
export default createConformanceRule({
|
||||
name: 'ensure-owners',
|
||||
category: 'consistency',
|
||||
description: 'Ensure that all projects have owners defined via Nx Owners.',
|
||||
reporter: 'project-reporter',
|
||||
implementation: async (context) => {
|
||||
const violations: ProjectViolation[] = [];
|
||||
|
||||
for (const node of Object.values(
|
||||
context.projectGraph.nodes
|
||||
) as ProjectGraphProjectNode[]) {
|
||||
const metadata = node.data.metadata;
|
||||
if (!metadata?.owners || Object.keys(metadata.owners).length === 0) {
|
||||
violations.push({
|
||||
sourceProject: node.name,
|
||||
message: `This project currently has no owners defined via Nx Owners.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
severity: 'medium',
|
||||
details: {
|
||||
violations,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="project-files-reporter" %}
|
||||
|
||||
This rule uses TypeScript AST processing to ensure that `index.ts` files use a client-side style of export syntax and `server.ts` files use a server-side style of export syntax.
|
||||
|
||||
```ts
|
||||
import {
|
||||
createConformanceRule,
|
||||
ProjectFilesViolation,
|
||||
} from '@nx/powerpack-conformance';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import {
|
||||
createSourceFile,
|
||||
isExportDeclaration,
|
||||
isStringLiteral,
|
||||
isToken,
|
||||
ScriptKind,
|
||||
ScriptTarget,
|
||||
} from 'typescript';
|
||||
|
||||
export default createConformanceRule({
|
||||
name: 'server-client-public-api',
|
||||
category: 'consistency',
|
||||
description: 'Ensure server-only and client-only public APIs are not mixed',
|
||||
reporter: 'project-files-reporter',
|
||||
implementation: async ({ projectGraph }) => {
|
||||
const violations: ProjectFilesViolation[] = [];
|
||||
|
||||
for (const nodeId in projectGraph.nodes) {
|
||||
const node = projectGraph.nodes[nodeId];
|
||||
|
||||
const sourceRoot = node.data.root;
|
||||
|
||||
const indexPath = join(sourceRoot, 'src/index.ts');
|
||||
const serverPath = join(sourceRoot, 'src/server.ts');
|
||||
|
||||
if (existsSync(indexPath)) {
|
||||
const fileContent = readFileSync(indexPath, 'utf8');
|
||||
violations.push(
|
||||
...processEntryPoint(fileContent, indexPath, nodeId, 'client')
|
||||
);
|
||||
}
|
||||
|
||||
if (existsSync(serverPath)) {
|
||||
const fileContent = readFileSync(serverPath, 'utf8');
|
||||
violations.push(
|
||||
...processEntryPoint(fileContent, serverPath, nodeId, 'server')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
severity: 'medium',
|
||||
details: { violations },
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export function processEntryPoint(
|
||||
fileContent: string,
|
||||
entryPoint: string,
|
||||
project: string,
|
||||
style: 'server' | 'client'
|
||||
) {
|
||||
const violations: ProjectFilesViolation[] = [];
|
||||
|
||||
const sf = createSourceFile(
|
||||
entryPoint,
|
||||
fileContent,
|
||||
ScriptTarget.Latest,
|
||||
true,
|
||||
ScriptKind.TS
|
||||
);
|
||||
|
||||
let hasNotOnlyExports = false;
|
||||
sf.forEachChild((node) => {
|
||||
if (isExportDeclaration(node)) {
|
||||
const moduleSpecifier =
|
||||
node.moduleSpecifier && isStringLiteral(node.moduleSpecifier)
|
||||
? node.moduleSpecifier.getText()
|
||||
: '';
|
||||
|
||||
if (isModuleSpecifierViolated(moduleSpecifier, style)) {
|
||||
if (
|
||||
violations.find(
|
||||
(v) => v.file === entryPoint && v.sourceProject === project
|
||||
)
|
||||
) {
|
||||
// we already have a violation for this file and project, so we don't need to add another one
|
||||
return;
|
||||
}
|
||||
|
||||
violations.push({
|
||||
message:
|
||||
style === 'client'
|
||||
? 'Client-side only entry point cannot export from server-side modules'
|
||||
: 'Server-side only entry point can only export server-side modules ',
|
||||
file: entryPoint,
|
||||
sourceProject: project,
|
||||
});
|
||||
}
|
||||
} else if (isToken(node) && node === sf.endOfFileToken) {
|
||||
// do nothing
|
||||
} else {
|
||||
hasNotOnlyExports = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasNotOnlyExports) {
|
||||
violations.push({
|
||||
message: `Entry point should only contain exported APIs`,
|
||||
file: entryPoint,
|
||||
sourceProject: project,
|
||||
});
|
||||
}
|
||||
|
||||
return violations;
|
||||
}
|
||||
|
||||
function isModuleSpecifierViolated(
|
||||
moduleSpecifier: string,
|
||||
style: 'server' | 'client'
|
||||
) {
|
||||
// should not get here. if this is the case, it's a grammar error in the source code.
|
||||
if (!moduleSpecifier) return false;
|
||||
|
||||
if (style === 'server' && !moduleSpecifier.includes('.server')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (style === 'client' && moduleSpecifier.includes('.server')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="non-project-files-reporter" %}
|
||||
|
||||
This rule checks the root `package.json` file and ensures that if the `tmp` package is included as a dependency, it has a minimum version of 0.2.3.
|
||||
|
||||
```ts
|
||||
import { readJsonFile, workspaceRoot } from '@nx/devkit';
|
||||
import {
|
||||
createConformanceRule,
|
||||
NonProjectFilesViolation,
|
||||
} from '@nx/powerpack-conformance';
|
||||
import { join } from 'node:path';
|
||||
import { satisfies } from 'semver';
|
||||
|
||||
export default createConformanceRule<object>({
|
||||
name: 'package-tmp-0.2.3',
|
||||
category: 'maintainability',
|
||||
description: 'The tmp dependency should be a minimum version of 0.2.3',
|
||||
reporter: 'non-project-files-reporter',
|
||||
implementation: async () => {
|
||||
const violations: NonProjectFilesViolation[] = [];
|
||||
const applyViolationIfApplicable = (version: string | undefined) => {
|
||||
if (version && !satisfies(version, '>=0.2.3')) {
|
||||
violations.push({
|
||||
message: 'The "tmp" package must be version "0.2.3" or higher',
|
||||
file: 'package.json',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const workspaceRootPackageJson = await readJsonFile(
|
||||
join(workspaceRoot, 'package.json')
|
||||
);
|
||||
applyViolationIfApplicable(workspaceRootPackageJson.dependencies?.['tmp']);
|
||||
applyViolationIfApplicable(
|
||||
workspaceRootPackageJson.devDependencies?.['tmp']
|
||||
);
|
||||
|
||||
return {
|
||||
severity: 'low',
|
||||
details: {
|
||||
violations,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
## Share Conformance Rules Across Workspaces
|
||||
|
||||
If you have an Enterprise Nx Cloud contract, you can share your conformance rules across every repository in your organization. Read more in these articles:
|
||||
|
||||
- [Publish Conformance Rules to Nx Cloud](/ci/recipes/enterprise/conformance/publish-conformance-rules-to-nx-cloud)
|
||||
- [Configure Conformance Rules in Nx Cloud](/ci/recipes/enterprise/conformance/configure-conformance-rules-in-nx-cloud)
|
||||
@@ -166,51 +166,10 @@ Set the `rule` property to: `@nx/powerpack-conformance/ensure-owners`
|
||||
}
|
||||
```
|
||||
|
||||
## Custom Conformance Rules
|
||||
## Next Steps
|
||||
|
||||
To write your own conformance rule, specify a relative path to a TypeScript or JavaScript file as the rule name:
|
||||
For more information about the conformance plugin, consult the following articles:
|
||||
|
||||
```json {% fileName="nx.json" %}
|
||||
{
|
||||
"conformance": {
|
||||
"rules": [
|
||||
{
|
||||
"rule": "./tools/local-conformance-rule.ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The rule definition file should look like this:
|
||||
|
||||
```ts {% fileName="tools/local-conformance-rule.ts" %}
|
||||
import { createConformanceRule } from '@nx/powerpack-conformance';
|
||||
|
||||
const rule = createConformanceRule({
|
||||
name: 'local-conformance-rule-example',
|
||||
description: 'The description of the rule',
|
||||
category: 'security', // `consistency`, `maintainability`, `reliability` or `security`
|
||||
reporter: 'project-reporter', // `project-reporter` or `project-files-reporter`
|
||||
implementation: async (context) => {
|
||||
const { projectGraph, ruleOptions } = context;
|
||||
// Your rule logic goes here
|
||||
return {
|
||||
severity: 'low', // 'high', 'medium' or 'low'
|
||||
details: {
|
||||
violations: [
|
||||
// Return an empty array if the rule passes
|
||||
{
|
||||
sourceProject: 'my-project',
|
||||
message: 'This is an informative error message.',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default rule;
|
||||
```
|
||||
|
||||
Note that the severity of the error is defined by the rule author and can be adjusted based on the specific violations that are found.
|
||||
- [Create a Conformance Rule](/nx-api/powerpack-conformance/documents/create-conformance-rule)
|
||||
- [Publish Conformance Rules to Nx Cloud](/ci/recipes/enterprise/conformance/publish-conformance-rules-to-nx-cloud)
|
||||
- [Configure Conformance Rules in Nx Cloud](/ci/recipes/enterprise/conformance/configure-conformance-rules-in-nx-cloud)
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "create-rule",
|
||||
"factory": "./src/generators/create-rule/create-rule",
|
||||
"schema": {
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"id": "NxPowerpackConformanceCreateRule",
|
||||
"title": "Create a new conformance rule",
|
||||
"type": "object",
|
||||
"cli": "nx",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the rule.",
|
||||
"$default": { "$source": "argv", "index": 0 },
|
||||
"x-prompt": "What is the name of the rule?",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"directory": {
|
||||
"type": "string",
|
||||
"description": "A directory where the rule directory is created.",
|
||||
"x-prompt": "Which directory do you want to create the rule directory in?",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"enum": ["consistency", "maintainability", "reliability", "security"],
|
||||
"description": "The category of the rule.",
|
||||
"x-prompt": "What category does this rule belong to?",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"reporter": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"project-reporter",
|
||||
"project-files-reporter",
|
||||
"non-project-files-reporter"
|
||||
],
|
||||
"description": "The reporter of the rule.",
|
||||
"x-prompt": "What reporter do you want to use for this rule?",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "The description of the rule.",
|
||||
"x-prompt": "What is the description of the rule?",
|
||||
"x-priority": "important"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["name", "directory", "category", "reporter"],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Create a new conformance rule",
|
||||
"implementation": "/libs/nx-packages/powerpack-conformance/src/generators/create-rule/create-rule.ts",
|
||||
"aliases": [],
|
||||
"hidden": false,
|
||||
"path": "/libs/nx-packages/powerpack-conformance/src/generators/create-rule/schema.json",
|
||||
"type": "generator"
|
||||
}
|
||||
@@ -80,3 +80,29 @@ Finally, you need to configure your Nx cache in the `nx.json` file. The `bucket`
|
||||
| **Property** | **Description** |
|
||||
| ------------ | ----------------------------- |
|
||||
| **bucket** | The name of the bucket to use |
|
||||
|
||||
# Cache Modes
|
||||
|
||||
By default, Nx will try to write and read from the remote cache while running locally. This means that permissions must be set for users who are expected to access the remote cache.
|
||||
|
||||
Nx will only show warnings when the remote cache is not writable. You can disable these warnings by setting `localMode` to `read-only` or `no-cache` in the `nx.json` file.
|
||||
|
||||
```jsonc {% fileName="nx.json" %}
|
||||
{
|
||||
"gcs": {
|
||||
// ...
|
||||
"localMode": "read-only"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The cache mode in CI can also be configured by setting `ciMode` to `read-only` or `no-cache` in the `nx.json` file. Or setting `NX_POWERPACK_CACHE_MODE` to `read-only` or `no-cache` in the CI environment.
|
||||
|
||||
```jsonc {% fileName="nx.json" %}
|
||||
{
|
||||
"gcs": {
|
||||
// ...
|
||||
"ciMode": "read-only"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -152,3 +152,31 @@ Below is an example on how to connect to MinIO:
|
||||
| **endpoint** | The custom endpoint to upload artifacts to. If endpoint is not defined, the default AWS endpoint is used |
|
||||
| **accessKeyId** | AWS Access Key ID (optional if `AWS_ACCESS_KEY_ID` is set in the environment) |
|
||||
| **secretAccessKey** | AWS secret access key (optional if `AWS_SECRET_ACCESS_KEY` is set in the environment) |
|
||||
|
||||
# Cache Modes
|
||||
|
||||
By default, Nx will try to write and read from the remote cache while running locally. This means that permissions must be set for users who are expected to access the remote cache.
|
||||
|
||||
Nx will only show warnings when the remote cache is not writable. You can disable these warnings by setting `localMode` to `read-only` or `no-cache` in the `nx.json` file.
|
||||
|
||||
```jsonc {% fileName="nx.json" %}
|
||||
{
|
||||
"s3": {
|
||||
"region": "us-east-1",
|
||||
"bucket": "my-bucket",
|
||||
"localMode": "read-only"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The cache mode in CI can also be configured by setting `ciMode` to `read-only` or `no-cache` in the `nx.json` file. Or setting `NX_POWERPACK_CACHE_MODE` to `read-only` or `no-cache` in the CI environment.
|
||||
|
||||
```jsonc {% fileName="nx.json" %}
|
||||
{
|
||||
"s3": {
|
||||
"region": "us-east-1",
|
||||
"bucket": "my-bucket",
|
||||
"ciMode": "read-only"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -630,6 +630,14 @@
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "TypeScript Project Linking",
|
||||
"path": "/concepts/typescript-project-linking",
|
||||
"id": "typescript-project-linking",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Nx and Turborepo",
|
||||
"path": "/concepts/turbo-and-nx",
|
||||
@@ -843,6 +851,14 @@
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "TypeScript Project Linking",
|
||||
"path": "/concepts/typescript-project-linking",
|
||||
"id": "typescript-project-linking",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Nx and Turborepo",
|
||||
"path": "/concepts/turbo-and-nx",
|
||||
@@ -1954,6 +1970,14 @@
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Switch to Workspaces and TS Project References",
|
||||
"path": "/recipes/tips-n-tricks/switch-to-workspaces-project-references",
|
||||
"id": "switch-to-workspaces-project-references",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Enable Typescript Batch Mode",
|
||||
"path": "/recipes/tips-n-tricks/enable-tsc-batch-mode",
|
||||
@@ -3680,6 +3704,14 @@
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Switch to Workspaces and TS Project References",
|
||||
"path": "/recipes/tips-n-tricks/switch-to-workspaces-project-references",
|
||||
"id": "switch-to-workspaces-project-references",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Enable Typescript Batch Mode",
|
||||
"path": "/recipes/tips-n-tricks/enable-tsc-batch-mode",
|
||||
@@ -3811,6 +3843,14 @@
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Switch to Workspaces and TS Project References",
|
||||
"path": "/recipes/tips-n-tricks/switch-to-workspaces-project-references",
|
||||
"id": "switch-to-workspaces-project-references",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Enable Typescript Batch Mode",
|
||||
"path": "/recipes/tips-n-tricks/enable-tsc-batch-mode",
|
||||
@@ -8235,14 +8275,6 @@
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Configure TypeScript Project References in an Nx Workspace",
|
||||
"path": "/nx-api/js/documents/typescript-project-references",
|
||||
"id": "typescript-project-references",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
@@ -10797,6 +10829,14 @@
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Create a Conformance Rule",
|
||||
"path": "/nx-api/powerpack-conformance/documents/create-conformance-rule",
|
||||
"id": "create-conformance-rule",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
@@ -10818,6 +10858,23 @@
|
||||
],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "generators",
|
||||
"path": "/nx-api/powerpack-conformance/generators",
|
||||
"name": "generators",
|
||||
"children": [
|
||||
{
|
||||
"id": "create-rule",
|
||||
"path": "/nx-api/powerpack-conformance/generators/create-rule",
|
||||
"name": "create-rule",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
@@ -10955,23 +11012,6 @@
|
||||
],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "generators",
|
||||
"path": "/nx-api/powerpack-s3-cache/generators",
|
||||
"name": "generators",
|
||||
"children": [
|
||||
{
|
||||
"id": "init",
|
||||
"path": "/nx-api/powerpack-s3-cache/generators/init",
|
||||
"name": "init",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
|
||||
@@ -1168,17 +1168,6 @@
|
||||
"path": "/nx-api/js/documents/overview",
|
||||
"tags": [],
|
||||
"originalFilePath": "shared/packages/js/js-plugin"
|
||||
},
|
||||
"/nx-api/js/documents/typescript-project-references": {
|
||||
"id": "typescript-project-references",
|
||||
"name": "Configure TypeScript Project References in an Nx Workspace",
|
||||
"description": "The JS plugin for Nx contains executors and generators that provide the best experience for developing JavaScript and TypeScript projects. ",
|
||||
"file": "generated/packages/js/documents/typescript-project-references",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/nx-api/js/documents/typescript-project-references",
|
||||
"tags": [],
|
||||
"originalFilePath": "shared/packages/js/typescript-project-references"
|
||||
}
|
||||
},
|
||||
"root": "/packages/js",
|
||||
@@ -3707,6 +3696,17 @@
|
||||
"path": "/nx-api/powerpack-conformance/documents/overview",
|
||||
"tags": [],
|
||||
"originalFilePath": "shared/packages/powerpack-conformance/powerpack-conformance-plugin"
|
||||
},
|
||||
"/nx-api/powerpack-conformance/documents/create-conformance-rule": {
|
||||
"id": "create-conformance-rule",
|
||||
"name": "Create a Conformance Rule",
|
||||
"description": "A Nx Powerpack plugin which allows users to write and apply rules for your entire workspace that help with consistency, maintainability, reliability and security.",
|
||||
"file": "external-generated/packages/powerpack-conformance/documents/create-conformance-rule",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/nx-api/powerpack-conformance/documents/create-conformance-rule",
|
||||
"tags": [],
|
||||
"originalFilePath": "shared/packages/powerpack-conformance/create-conformance-rule"
|
||||
}
|
||||
},
|
||||
"root": "/libs/nx-packages/powerpack-conformance",
|
||||
@@ -3722,7 +3722,17 @@
|
||||
"type": "executor"
|
||||
}
|
||||
},
|
||||
"generators": {},
|
||||
"generators": {
|
||||
"/nx-api/powerpack-conformance/generators/create-rule": {
|
||||
"description": "Create a new conformance rule",
|
||||
"file": "external-generated/packages/powerpack-conformance/generators/create-rule.json",
|
||||
"hidden": false,
|
||||
"name": "create-rule",
|
||||
"originalFilePath": "/libs/nx-packages/powerpack-conformance/src/generators/create-rule/schema.json",
|
||||
"path": "/nx-api/powerpack-conformance/generators/create-rule",
|
||||
"type": "generator"
|
||||
}
|
||||
},
|
||||
"path": "/nx-api/powerpack-conformance"
|
||||
},
|
||||
"powerpack-enterprise-cloud": {
|
||||
@@ -3847,17 +3857,7 @@
|
||||
"root": "/libs/nx-packages/powerpack-s3-cache",
|
||||
"source": "/libs/nx-packages/powerpack-s3-cache/src",
|
||||
"executors": {},
|
||||
"generators": {
|
||||
"/nx-api/powerpack-s3-cache/generators/init": {
|
||||
"description": "Initialize the S3 Cache",
|
||||
"file": "external-generated/packages/powerpack-s3-cache/generators/init.json",
|
||||
"hidden": false,
|
||||
"name": "init",
|
||||
"originalFilePath": "/libs/nx-packages/powerpack-s3-cache/src/generators/init/schema.json",
|
||||
"path": "/nx-api/powerpack-s3-cache/generators/init",
|
||||
"type": "generator"
|
||||
}
|
||||
},
|
||||
"generators": {},
|
||||
"path": "/nx-api/powerpack-s3-cache"
|
||||
},
|
||||
"powerpack-shared-fs-cache": {
|
||||
|
||||
@@ -864,6 +864,17 @@
|
||||
"path": "/concepts/sync-generators",
|
||||
"tags": ["sync"]
|
||||
},
|
||||
{
|
||||
"id": "typescript-project-linking",
|
||||
"name": "TypeScript Project Linking",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/concepts/typescript-project-linking",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/concepts/typescript-project-linking",
|
||||
"tags": ["project-linking"]
|
||||
},
|
||||
{
|
||||
"id": "turbo-and-nx",
|
||||
"name": "Nx and Turborepo",
|
||||
@@ -1157,6 +1168,17 @@
|
||||
"path": "/concepts/sync-generators",
|
||||
"tags": ["sync"]
|
||||
},
|
||||
"/concepts/typescript-project-linking": {
|
||||
"id": "typescript-project-linking",
|
||||
"name": "TypeScript Project Linking",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/concepts/typescript-project-linking",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/concepts/typescript-project-linking",
|
||||
"tags": ["project-linking"]
|
||||
},
|
||||
"/concepts/turbo-and-nx": {
|
||||
"id": "turbo-and-nx",
|
||||
"name": "Nx and Turborepo",
|
||||
@@ -2675,6 +2697,17 @@
|
||||
"path": "/recipes/tips-n-tricks/eslint",
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"id": "switch-to-workspaces-project-references",
|
||||
"name": "Switch to Workspaces and TS Project References",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/recipes/tips-n-tricks/switch-to-workspaces-project-references",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/recipes/tips-n-tricks/switch-to-workspaces-project-references",
|
||||
"tags": ["project-linking"]
|
||||
},
|
||||
{
|
||||
"id": "enable-tsc-batch-mode",
|
||||
"name": "Enable Typescript Batch Mode",
|
||||
@@ -5042,6 +5075,17 @@
|
||||
"path": "/recipes/tips-n-tricks/eslint",
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"id": "switch-to-workspaces-project-references",
|
||||
"name": "Switch to Workspaces and TS Project References",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/recipes/tips-n-tricks/switch-to-workspaces-project-references",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/recipes/tips-n-tricks/switch-to-workspaces-project-references",
|
||||
"tags": ["project-linking"]
|
||||
},
|
||||
{
|
||||
"id": "enable-tsc-batch-mode",
|
||||
"name": "Enable Typescript Batch Mode",
|
||||
@@ -5223,6 +5267,17 @@
|
||||
"path": "/recipes/tips-n-tricks/eslint",
|
||||
"tags": []
|
||||
},
|
||||
"/recipes/tips-n-tricks/switch-to-workspaces-project-references": {
|
||||
"id": "switch-to-workspaces-project-references",
|
||||
"name": "Switch to Workspaces and TS Project References",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/recipes/tips-n-tricks/switch-to-workspaces-project-references",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/recipes/tips-n-tricks/switch-to-workspaces-project-references",
|
||||
"tags": ["project-linking"]
|
||||
},
|
||||
"/recipes/tips-n-tricks/enable-tsc-batch-mode": {
|
||||
"id": "enable-tsc-batch-mode",
|
||||
"name": "Enable Typescript Batch Mode",
|
||||
|
||||
@@ -769,6 +769,22 @@
|
||||
"path": "/nx-api/nx/documents/sync-check"
|
||||
}
|
||||
],
|
||||
"project-linking": [
|
||||
{
|
||||
"description": "",
|
||||
"file": "shared/concepts/typescript-project-linking",
|
||||
"id": "typescript-project-linking",
|
||||
"name": "TypeScript Project Linking",
|
||||
"path": "/concepts/typescript-project-linking"
|
||||
},
|
||||
{
|
||||
"description": "",
|
||||
"file": "shared/recipes/tips-n-tricks/switch-to-workspaces-project-references",
|
||||
"id": "switch-to-workspaces-project-references",
|
||||
"name": "Switch to Workspaces and TS Project References",
|
||||
"path": "/recipes/tips-n-tricks/switch-to-workspaces-project-references"
|
||||
}
|
||||
],
|
||||
"module-federation": [
|
||||
{
|
||||
"description": "",
|
||||
|
||||
@@ -1152,17 +1152,6 @@
|
||||
"path": "js/documents/overview",
|
||||
"tags": [],
|
||||
"originalFilePath": "shared/packages/js/js-plugin"
|
||||
},
|
||||
{
|
||||
"id": "typescript-project-references",
|
||||
"name": "Configure TypeScript Project References in an Nx Workspace",
|
||||
"description": "The JS plugin for Nx contains executors and generators that provide the best experience for developing JavaScript and TypeScript projects. ",
|
||||
"file": "generated/packages/js/documents/typescript-project-references",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "js/documents/typescript-project-references",
|
||||
"tags": [],
|
||||
"originalFilePath": "shared/packages/js/typescript-project-references"
|
||||
}
|
||||
],
|
||||
"executors": [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/nameExpo is an open-source framework for apps that run natively on Android, iOS, and the web. Expo brings together the best of mobile and the web and enables many important features for building and scaling an app.
|
||||
Expo is an open-source framework for apps that run natively on Android, iOS, and the web. Expo brings together the best of mobile and the web and enables many important features for building and scaling an app.
|
||||
|
||||
Expo is a set of tools built on top of React Native. The Nx Plugin for Expo contains generators for managing Expo applications and libraries within an Nx workspace.
|
||||
|
||||
|
||||
@@ -91,6 +91,27 @@ target with that name which can be used in CI to run the tests for each file in
|
||||
}
|
||||
```
|
||||
|
||||
### Customizing atomized unit/e2e tasks group name
|
||||
|
||||
By default, the atomized tasks group name is derived from the `ciTargetName`. For example, atomized tasks for the `e2e-ci` target will be grouped under the name "E2E (CI)" when displayed in Nx Cloud or `nx show project <project> --web` UI.
|
||||
You can customize that name by explicitly providing the optional `ciGroupName` plugin option as such:
|
||||
|
||||
```json {% fileName="nx.json" %}
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"plugin": "@nx/jest/plugin",
|
||||
"include": ["e2e/**/*"],
|
||||
"options": {
|
||||
"targetName": "e2e-local",
|
||||
"ciTargetName": "e2e-ci",
|
||||
"ciGroupname": "My E2E tests (CI)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### How @nx/jest Infers Tasks
|
||||
|
||||
{% callout type="note" title="Inferred Tasks" %}
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
# Configure TypeScript Project References in an Nx Workspace
|
||||
|
||||
In Nx 20, the `@nx/js` plugin provides the ability to incrementally build projects in a monorepo using [TypeScript Project References](https://www.typescriptlang.org/docs/handbook/project-references.html). Nx also provides a `ts` preset for `create-nx-workspace` that configures project references and uses `workspaces` to link projects instead of [TypeScript compilerOptions Paths](https://www.typescriptlang.org/docs/handbook/modules/reference.html#paths).
|
||||
|
||||
The TypeScript team recommends using project references when working in a monorepo, but until now the configuration settings were difficult to maintain. Each project is required to list its own project dependencies in the `references` property of the `tsconfig.json` file so that TypeScript can incrementally compile projects in the correct order. In a large monorepo, maintaining those settings manually is cost prohibitive. To solve this problem, the `@nx/js` plugin registers a [sync generator](/concepts/sync-generators) to automatically update the references based on Nx's project graph before any TypeScript `build` task is executed.
|
||||
|
||||
## Create a New Nx Workspace Using Project References
|
||||
|
||||
We anticipate that this style of compiling projects will eventually become the default, but currently it will only be enabled for repositories configured in a specific way. Existing workspaces will continue to function as usual and there is no migration path yet. You can generate a new repository with these settings by using the `--preset=ts` flag of the `create-nx-workspace` command.
|
||||
|
||||
```shell
|
||||
npx create-nx-workspace --preset=ts
|
||||
```
|
||||
|
||||
{% callout type="note" title="Empty Workspace with Paths" %}
|
||||
To generate an empty Nx workspace that links projects with the `compilerOptions.paths` property and does not use project references, use `create-nx-workspace --preset=apps`
|
||||
{% /callout %}
|
||||
|
||||
This will generate an empty repository that is configured to use TypeScript project references. To see the new functionality in action, create some TypeScript projects and make sure to use the `tsc` bundler option.
|
||||
|
||||
```shell
|
||||
nx g @nx/js:lib packages/cart --bundler=tsc
|
||||
nx g @nx/js:lib packages/utils --bundler=tsc
|
||||
```
|
||||
|
||||
These generators will detect that your repository is configured to use project references and update the configuration accordingly. If these generators were executed in an Nx repository that used `compilerOptions.paths`, they would update that setting instead.
|
||||
|
||||
To make `cart` depend on `utils`, update `packages/cart/package.json` like this:
|
||||
|
||||
```jsonc {% fileName="packages/cart/package.json" %}
|
||||
{
|
||||
"dependencies": {
|
||||
"utils": "*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now if you run `nx build cart` or directly run `nx sync`, the `packages/cart/tsconfig.json` file will have its references updated for you.
|
||||
|
||||
## Project Reference Configuration Files
|
||||
|
||||
Nx expects the following configuration settings to be in place in order to use TypeScript project references to build projects. Most of this configuration is set up and maintained for you automatically by Nx.
|
||||
|
||||
Identify projects in the `workspaces` property in the root `package.json` file.
|
||||
|
||||
```json {% fileName="package.json" %}
|
||||
{
|
||||
"workspaces": ["packages/*"]
|
||||
}
|
||||
```
|
||||
|
||||
The root `tsconfig.base.json` should contain a `compilerOptions` property and no other properties. `compilerOptions.composite` and `compilerOptions.declaration` should be set to `true`. `compilerOptions.paths` should not be set.
|
||||
|
||||
```jsonc {% fileName="tsconfig.base.json" %}
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Required compiler options
|
||||
"composite": true,
|
||||
"declaration": true
|
||||
// Other options...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The root `tsconfig.json` file should extend `tsconfig.base.json` and not include any files. It needs to have `references` for every project in the repository so that editor tooling works correctly.
|
||||
|
||||
```jsonc {% fileName="tsconfig.json" %}
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"files": [], // intentionally empty
|
||||
"references": [
|
||||
// UPDATED BY PROJECT GENERATORS
|
||||
// All projects in the repository
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Each project's `tsconfig.json` file should extend the `tsconfig.base.json` file and list `references` to the project's dependencies.
|
||||
|
||||
```jsonc {% fileName="packages/cart/tsconfig.json" %}
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"files": [], // intentionally empty
|
||||
"references": [
|
||||
// UPDATED BY NX SYNC
|
||||
// All project dependencies
|
||||
{
|
||||
"path": "../utils"
|
||||
},
|
||||
// This project's other tsconfig.*.json files
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Each project's `tsconfig.lib.json` file extends the project's `tsconfig.json` file and adds `references` to the `tsconfig.lib.json` files of project dependencies.
|
||||
|
||||
```jsonc {% fileName="packages/cart/tsconfig.lib.json" %}
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// Any overrides
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": [
|
||||
// exclude config and test files
|
||||
],
|
||||
"references": [
|
||||
// UPDATED BY NX SYNC
|
||||
// tsconfig.lib.json files for project dependencies
|
||||
{
|
||||
"path": "../utils/tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The project's `tsconfig.spec.json` does not need to reference project dependencies.
|
||||
|
||||
```jsonc {% fileName="packages/cart/tsconfig.spec.json" %}
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// Any overrides
|
||||
},
|
||||
"include": [
|
||||
// test files
|
||||
],
|
||||
"references": [
|
||||
// tsconfig.lib.json for this project
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -72,7 +72,7 @@
|
||||
"description": "Path where the build artifacts are located. If not provided then it will be infered from the buildTarget executor options as outputPath"
|
||||
},
|
||||
"cors": {
|
||||
"type": "boolean",
|
||||
"oneOf": [{ "type": "boolean" }, { "type": "string" }],
|
||||
"description": "Enable CORS",
|
||||
"default": true
|
||||
},
|
||||
|
||||
@@ -254,6 +254,12 @@
|
||||
"tags": ["sync"],
|
||||
"file": "shared/concepts/sync-generators"
|
||||
},
|
||||
{
|
||||
"name": "TypeScript Project Linking",
|
||||
"id": "typescript-project-linking",
|
||||
"tags": ["project-linking"],
|
||||
"file": "shared/concepts/typescript-project-linking"
|
||||
},
|
||||
{
|
||||
"name": "Nx and Turborepo",
|
||||
"id": "turbo-and-nx",
|
||||
@@ -1012,6 +1018,12 @@
|
||||
"id": "eslint",
|
||||
"file": "shared/eslint"
|
||||
},
|
||||
{
|
||||
"name": "Switch to Workspaces and TS Project References",
|
||||
"id": "switch-to-workspaces-project-references",
|
||||
"tags": ["project-linking"],
|
||||
"file": "shared/recipes/tips-n-tricks/switch-to-workspaces-project-references"
|
||||
},
|
||||
{
|
||||
"name": "Enable Typescript Batch Mode",
|
||||
"id": "enable-tsc-batch-mode",
|
||||
@@ -2303,12 +2315,6 @@
|
||||
"id": "overview",
|
||||
"path": "/nx-api/js",
|
||||
"file": "shared/packages/js/js-plugin"
|
||||
},
|
||||
{
|
||||
"name": "Configure TypeScript Project References in an Nx Workspace",
|
||||
"id": "typescript-project-references",
|
||||
"path": "/nx-api/js/typescript-project-references",
|
||||
"file": "shared/packages/js/typescript-project-references"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -2680,6 +2686,12 @@
|
||||
"id": "overview",
|
||||
"path": "/nx-api/powerpack-conformance",
|
||||
"file": "shared/packages/powerpack-conformance/powerpack-conformance-plugin"
|
||||
},
|
||||
{
|
||||
"name": "Create a Conformance Rule",
|
||||
"id": "create-conformance-rule",
|
||||
"path": "/nx-api/powerpack-conformance",
|
||||
"file": "shared/packages/powerpack-conformance/create-conformance-rule"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -8,48 +8,10 @@ Let's create a custom rule which we can then publish to Nx Cloud. We will first
|
||||
nx generate @nx/js:library cloud-conformance-rules
|
||||
```
|
||||
|
||||
The Nx Cloud distribution mechanism expects each rule to be created in a named subdirectory in the `src/` directory of our new project, and each rule directory to contain an `index.ts` and a `schema.json` file.
|
||||
The Nx Cloud distribution mechanism expects each rule to be created in a named subdirectory in the `src/` directory of our new project, and each rule directory to contain an `index.ts` and a `schema.json` file. You can read more about [creating a conformance rule](/nx-api/powerpack-conformance/documents/create-conformance-rule) in the dedicated guide. For this recipe, we'll generate a default rule to use in the publishing process.
|
||||
|
||||
E.g.
|
||||
|
||||
```
|
||||
cloud-conformance-rules/
|
||||
├── src/
|
||||
│ ├── test-cloud-rule/
|
||||
│ │ ├── index.ts // Our rule implementation
|
||||
│ │ └── schema.json // The schema definition for the options supported by our rule
|
||||
```
|
||||
|
||||
Our simple rule implementation in `test-cloud-rule/index.ts`, that will currently not report any violations, might look like this:
|
||||
|
||||
```ts
|
||||
import { createConformanceRule } from '@nx/powerpack-conformance';
|
||||
|
||||
export default createConformanceRule<object>({
|
||||
name: 'test-cloud-rule',
|
||||
category: 'reliability',
|
||||
description: 'A test cloud rule',
|
||||
reporter: 'non-project-files-reporter',
|
||||
implementation: async () => {
|
||||
return {
|
||||
severity: 'low',
|
||||
details: {
|
||||
violations: [],
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
And because we do not yet have any options that we want to support for our rule, our `schema.json` file will looks like this (using the [JSON Schema](https://json-schema.org/) format):
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
}
|
||||
```shell
|
||||
nx g @nx/powerpack-conformance:create-rule --name=test-cloud-rule --directory=cloud-conformance-rules/src/test-cloud-rule --category=reliability --description="A test cloud rule" --reporter=non-project-files-reporter
|
||||
```
|
||||
|
||||
We now have a valid implementation of a rule and we are ready to build it and publish it to Nx Cloud. The [`@nx/powerpack-conformance` plugin](/nx-api/powerpack-conformance) provides a [dedicated executor called `bundle-rules`](/nx-api/powerpack-conformance/executors/bundle-rules) for creating appropriate build artifacts for this purpose, so we will wire that executor up to a new build target in our `cloud-conformance-rules` project's `project.json` file:
|
||||
|
||||
@@ -10,7 +10,7 @@ By default, when you set up [Nx Agents](/ci/features/distribute-task-execution)
|
||||
...
|
||||
jobs:
|
||||
- job: main
|
||||
displayName: Main Job
|
||||
name: Main Job
|
||||
...
|
||||
steps:
|
||||
...
|
||||
@@ -68,7 +68,7 @@ You can then reference your distribution configuration in your CI pipeline confi
|
||||
...
|
||||
jobs:
|
||||
- job: main
|
||||
displayName: Main Job
|
||||
name: Main Job
|
||||
...
|
||||
steps:
|
||||
...
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
keywords: [split tasks, atomizer]
|
||||
---
|
||||
|
||||
# Automatically Split E2E Tasks by File (Atomizer)
|
||||
|
||||
{% youtube
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
# Assignment Rules (beta)
|
||||
|
||||
Assignment rules allow you to control which tasks can run on which agents. Save on agent costs by provisioning different sizes of agents all with the confidence that your tasks will be run on the agents that are best suited for them. You can ensure resource intensive targets like `e2e-ci` and `build` have what they need by using larger agents. Lighter tasks like `lint` and `test` can run on smaller agents.
|
||||
Assignment rules allow you to control which tasks can run on which agents. Save on agent costs by provisioning different sizes of agents to suite the individual needs of your tasks. You can ensure resource intensive targets like `e2e-ci` and `build` have what they need by using larger agents. Lighter tasks like `lint` and `test` can run on smaller agents.
|
||||
|
||||
Assignment rules are defined in your workspaces `distribution-config.yaml` file. This file should be created in the `.nx/workflows` directory of your repository. Note that this means that you must have [dynamic agents](/ci/features/dynamic-agents) also configured in your `distribution-config.yaml` file.
|
||||
Assignment rules are defined in `yaml` files within your workspace's `.nx/workflows` directory. You can use assignment rules with self-hosted agents or with [dynamic Nx agents](/ci/features/dynamic-agents). Note that additional configuration is required when using self-hosted agents.
|
||||
|
||||
## How to Define an Assignment Rule
|
||||
|
||||
Each assignment rule has one of the following properties that it matches against tasks: `project`, `target`, and/or `configuration`. It also has a list of possible [agent types](/ci/reference/launch-templates) that tasks with the matching properties can run on. Rules are defined in yaml like the following:
|
||||
|
||||
```yaml {% fileName=".nx/workflows/distribution-config.yaml" %}
|
||||
distribute-on:
|
||||
default: 3 linux-small-js, 2 linux-medium-js, 1 linux-large-js
|
||||
|
||||
```yaml {% fileName=".nx/workflows/assignment-rules.yaml" %}
|
||||
assignment-rules:
|
||||
- project: app1
|
||||
target: build
|
||||
configuration: production
|
||||
runs-on:
|
||||
- linux-large-js
|
||||
- linux-medium-js
|
||||
- linux-large-js
|
||||
```
|
||||
|
||||
The above rule will match any task that has a project named `app1`, a target named `build`, and a configuration named `production`. Any tasks that match this rule will only be allowed to run on agents with the `linux-large-js` and `linux-medium-js` launch templates.
|
||||
@@ -26,58 +23,13 @@ The above rule will match any task that has a project named `app1`, a target nam
|
||||
You can mix and match any of the criteria in an assignment rule provided that you follow the constraints:
|
||||
|
||||
- At least one of the following properties is defined: `project`, `target`, `configuration`.
|
||||
- There is at least one [agent type](/ci/reference/launch-templates) specified in the `run-on` field.
|
||||
- Every changeset in your `distribute-on` field must include at **least one agent** that matches each agent type specified in the run-on field across all assignment rules. For example, if your rules distribute tasks on `linux-small-js`, `linux-medium-js`, and `linux-large-js`, then at least one agent of each type must be available; otherwise, tasks associated with those rules cannot be executed.
|
||||
- There is at least one [agent type](/ci/reference/launch-templates) specified in the `runs-on` field.
|
||||
- Every changeset in your `distribute-on` field must include at **least one agent** that matches each agent type specified in the `runs-on` field across all assignment rules. For example, if your rules distribute tasks on `linux-small-js`, `linux-medium-js`, and `linux-large-js`, then at least one agent of each type must be available; otherwise, tasks associated with those rules cannot be executed.
|
||||
|
||||
### Invalid Assignment Rules Example
|
||||
|
||||
```yaml {% fileName=".nx/workflows/distribution-config.yaml" %}
|
||||
distribute-on:
|
||||
# Invalid changeset that is missing `linux-large-js`. Tasks assigned to large agents won't be able to execute.
|
||||
small-changeset: 1 linux-small-js, 2 linux-medium-js
|
||||
medium-changeset: 2 linux-small-js, 2 linux-medium-js, 3 linux-large-js
|
||||
large-changeset: 3 linux-small-js, 3 linux-medium-js, 4 linux-large-js
|
||||
|
||||
assignment-rules:
|
||||
# Missing one of `project`, `target`, `configuration`
|
||||
- runs-on:
|
||||
- linux-medium-js
|
||||
- linux-large-js
|
||||
|
||||
# Missing `runs-on`
|
||||
- target: lint
|
||||
configuration: production
|
||||
|
||||
# Agent type not found in any of the `distribute-on` changesets
|
||||
- project: lib1
|
||||
target: test
|
||||
runs-on:
|
||||
- linux-extra-large-js
|
||||
```
|
||||
|
||||
### Valid Assignment Rules Example
|
||||
|
||||
```yaml {% fileName=".nx/workflows/distribution-config.yaml" %}
|
||||
distribute-on:
|
||||
default: 3 linux-small-js, 2 linux-medium-js, 1 linux-large-js
|
||||
|
||||
# All rules below are valid assignment rules
|
||||
assignment-rules:
|
||||
- project: app1
|
||||
runs-on:
|
||||
- linux-medium-js
|
||||
- linux-large-js
|
||||
|
||||
- target: lint
|
||||
configuration: production
|
||||
runs-on:
|
||||
- linux-large-js
|
||||
|
||||
- project: lib1
|
||||
target: test
|
||||
runs-on:
|
||||
- linux-medium-js
|
||||
```
|
||||
{% callout type="note" title="If you are using self-hosted agents, you must define your own agent types" %}
|
||||
You must define your own agent types and attach them to your self-hosted agents using the `NX_AGENT_LAUNCH_TEMPLATE` environment variable. Ensure that for each `runs-on` field in your assignment rules, you have corresponding agents in your agent pool that have the same agent type.
|
||||
See below for an [example](#using-assignment-rules-with-selfhosted-agents) of how to define your own agent types when using self-hosted agents.
|
||||
{% /callout %}
|
||||
|
||||
## Assignment Rule Precedence
|
||||
|
||||
@@ -95,7 +47,7 @@ Having multiple assignment rules means that often rules may overlap or apply to
|
||||
|
||||
### Rule Precedence Example
|
||||
|
||||
In this example, the task defined below can match multiple assignment rules. However, since the second rule specifies all three properties (`project`, `target`, and `configuration`) rather than just two (`project` and `target`), it takes precedence, and we apply the second rule when distributing the task.
|
||||
In this example, the task defined below can match multiple assignment rules. However, since the second rule specifies all three properties (`project`, `target`, and `configuration`) rather than just two (`project` and `target`), it takes precedence, and we automatically apply the second rule when distributing the task.
|
||||
|
||||
```json {% fileName="A task from your workspace" %}
|
||||
{
|
||||
@@ -106,10 +58,8 @@ In this example, the task defined below can match multiple assignment rules. How
|
||||
```
|
||||
|
||||
```yaml {% fileName=".nx/workflows/distribution-config.yaml" %}
|
||||
distribute-on:
|
||||
default: 10 linux-medium-js, 8 linux-large-js
|
||||
|
||||
assignment-rules:
|
||||
# A task for app1:build:production will use this rule because it is more specific (matches all three properties instead of just two)
|
||||
- project: app1
|
||||
target: build
|
||||
configuration: production
|
||||
@@ -122,7 +72,110 @@ assignment-rules:
|
||||
- linux-large-js
|
||||
```
|
||||
|
||||
## Using Assignment Rules in your CI Pipeline
|
||||
## Using Assignment Rules with Self-Hosted Agents
|
||||
|
||||
A typical `assignment-rules.yaml` file might look like this:
|
||||
|
||||
```yaml {% fileName=".nx/workflows/assignment-rules.yaml" %}
|
||||
assignment-rules:
|
||||
- project: app1
|
||||
target: build
|
||||
configuration: production
|
||||
runs-on:
|
||||
- linux-medium
|
||||
- linux-large
|
||||
|
||||
- target: lint
|
||||
runs-on:
|
||||
- linux-medium
|
||||
|
||||
- configuration: development
|
||||
runs-on:
|
||||
- linux-medium
|
||||
- linux-large
|
||||
```
|
||||
|
||||
Note that the agent types supplied in the `runs-on` property will be used to determine which agents will have rules applied to them.
|
||||
You can choose to name your agent types anything you want, but they must be set on your agents via the `NX_AGENT_LAUNCH_TEMPLATE` environment variable.
|
||||
|
||||
You can then reference your assignment rules file within your `start-ci-run` command:
|
||||
|
||||
```shell
|
||||
npx nx-cloud start-ci-run --distribute-on="manual" --assignment-rules=".nx/workflows/assignment-rules.yaml"
|
||||
```
|
||||
|
||||
The following is an example of what this looks like within a Github Actions pipeline:
|
||||
|
||||
```yaml {% fileName=".github/workflows/ci.yaml" %}
|
||||
---
|
||||
jobs:
|
||||
main:
|
||||
name: Main Job
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- ... # setup steps for your main job
|
||||
|
||||
- run: npx nx-cloud start-ci-run --distribute-on="manual" --assignment-rules=".nx/workflows/assignment-rules.yaml" --stop-agents-after="e2e-ci"
|
||||
|
||||
- ... # Nx commands you want to distribute
|
||||
|
||||
medium-agents:
|
||||
name: Agents ${{ matrix.agent }}
|
||||
runs-on:
|
||||
group: medium-agents
|
||||
strategy:
|
||||
matrix:
|
||||
agent: [1, 2, 3]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'npm'
|
||||
|
||||
- ... # other setup steps you may need
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Start Agent ${{ matrix.agent }}
|
||||
run: npx nx-cloud start-agent
|
||||
env:
|
||||
NX_AGENT_NAME: ${{ matrix.agent }}
|
||||
NX_AGENT_LAUNCH_TEMPLATE: 'linux-medium' # This value needs to match one of the 'runs-on' values defined in the assignment rules
|
||||
|
||||
large-agents:
|
||||
name: Agents ${{ matrix.agent }}
|
||||
runs-on:
|
||||
group: large-agents
|
||||
strategy:
|
||||
matrix:
|
||||
agent: [1, 2, 3]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'npm'
|
||||
|
||||
- ... # other setup steps you may need
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Start Agent ${{ matrix.agent }}
|
||||
run: npx nx-cloud start-agent
|
||||
env:
|
||||
NX_AGENT_NAME: ${{ matrix.agent }}
|
||||
NX_AGENT_LAUNCH_TEMPLATE: 'linux-large' # This value needs to match one of the 'runs-on' values defined in the assignment rules
|
||||
```
|
||||
|
||||
## Using Assignment Rules with Dynamic Nx Agents
|
||||
|
||||
A typical `distribution-config.yaml` file might look like this:
|
||||
|
||||
@@ -155,10 +208,62 @@ You can then reference your distribution configuration in your CI pipeline confi
|
||||
...
|
||||
jobs:
|
||||
- job: main
|
||||
displayName: Main Job
|
||||
name: Main Job
|
||||
...
|
||||
steps:
|
||||
...
|
||||
- run: npx nx-cloud start-ci-run --distribute-on=".nx/workflows/distribution-config.yaml" --stop-agents-after="e2e-ci"
|
||||
- ..
|
||||
```
|
||||
|
||||
### More Examples of Assignment Rules with Dynamic Agents
|
||||
|
||||
#### Invalid Assignment Rules Example
|
||||
|
||||
```yaml {% fileName=".nx/workflows/distribution-config.yaml" %}
|
||||
distribute-on:
|
||||
# Invalid changeset that is missing `linux-large-js`. Tasks assigned to large agents won't be able to execute.
|
||||
small-changeset: 1 linux-small-js, 2 linux-medium-js
|
||||
medium-changeset: 2 linux-small-js, 2 linux-medium-js, 3 linux-large-js
|
||||
large-changeset: 3 linux-small-js, 3 linux-medium-js, 4 linux-large-js
|
||||
|
||||
assignment-rules:
|
||||
# Missing one of `project`, `target`, `configuration`
|
||||
- runs-on:
|
||||
- linux-medium-js
|
||||
- linux-large-js
|
||||
|
||||
# Missing `runs-on`
|
||||
- target: lint
|
||||
configuration: production
|
||||
|
||||
# Agent type not found in any of the `distribute-on` changesets
|
||||
- project: lib1
|
||||
target: test
|
||||
runs-on:
|
||||
- linux-extra-large-js
|
||||
```
|
||||
|
||||
#### Valid Assignment Rules Example
|
||||
|
||||
```yaml {% fileName=".nx/workflows/distribution-config.yaml" %}
|
||||
distribute-on:
|
||||
default: 3 linux-small-js, 2 linux-medium-js, 1 linux-large-js
|
||||
|
||||
# All rules below are valid assignment rules
|
||||
assignment-rules:
|
||||
- project: app1
|
||||
runs-on:
|
||||
- linux-medium-js
|
||||
- linux-large-js
|
||||
|
||||
- target: lint
|
||||
configuration: production
|
||||
runs-on:
|
||||
- linux-large-js
|
||||
|
||||
- project: lib1
|
||||
target: test
|
||||
runs-on:
|
||||
- linux-medium-js
|
||||
```
|
||||
|
||||
@@ -141,9 +141,9 @@ You can find the [list of Nx Cloud reusable steps here](https://github.com/nrwl/
|
||||
launch-templates:
|
||||
template-one:
|
||||
init-steps:
|
||||
- uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/checkout/main.yaml'
|
||||
- uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/checkout/main.yaml'
|
||||
- name: 'Install Node Modules'
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/install-node-modules/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/install-node-modules/main.yaml'
|
||||
```
|
||||
|
||||
### `launch-templates.<template-name>.init-steps[*].script`
|
||||
@@ -186,10 +186,13 @@ launch-templates:
|
||||
template-one:
|
||||
init-steps:
|
||||
- name: Restore Node Modules Cache
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/cache/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/cache/main.yaml'
|
||||
inputs:
|
||||
key: 'package-lock.json|yarn.lock|pnpm-lock.yaml'
|
||||
paths: 'node_modules'
|
||||
paths: |
|
||||
~/.npm
|
||||
# or ~/.cache/yarn
|
||||
# or .pnpm-store
|
||||
base-branch: 'main'
|
||||
```
|
||||
|
||||
@@ -215,27 +218,29 @@ launch-templates:
|
||||
- name: Checkout
|
||||
# using a reusable step in an external GitHub repo,
|
||||
# this step is provided by Nx Cloud: https://github.com/nrwl/nx-cloud-workflows/tree/main/workflow-steps
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/checkout/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/checkout/main.yaml'
|
||||
- name: Restore Node Modules Cache
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/cache/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/cache/main.yaml'
|
||||
# the cache step requires configuration via env vars
|
||||
# https://github.com/nrwl/nx-cloud-workflows/tree/main/workflow-steps/cache#options
|
||||
inputs:
|
||||
key: 'package-lock.json|yarn.lock|pnpm-lock.yaml'
|
||||
paths: 'node_modules'
|
||||
paths: |
|
||||
~/.npm
|
||||
# or ~/.cache/yarn
|
||||
# or .pnpm-store
|
||||
base-branch: 'main'
|
||||
- name: Restore Browser Binary Cache
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/cache/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/cache/main.yaml'
|
||||
inputs:
|
||||
key: 'package-lock.json|yarn.lock|pnpm-lock.yaml|"browsers"'
|
||||
paths: |
|
||||
'~/.cache/Cypress'
|
||||
'~/.cache/ms-playwright'
|
||||
base-branch: 'main'
|
||||
- name: Install Node Modules
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/install-node-modules/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/install-node-modules/main.yaml'
|
||||
- name: Install Browsers (if needed)
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/install-browsers/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/install-browsers/main.yaml'
|
||||
# You can also run a custom script to configure various things on the agent machine
|
||||
- name: Run a custom script
|
||||
script: |
|
||||
@@ -268,25 +273,27 @@ launch-templates:
|
||||
MY_ENV_VAR: shared
|
||||
init-steps:
|
||||
- name: Checkout
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/checkout/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/checkout/main.yaml'
|
||||
- name: Restore Node Modules Cache
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/cache/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/cache/main.yaml'
|
||||
inputs:
|
||||
key: 'package-lock.json|yarn.lock|pnpm-lock.yaml'
|
||||
paths: 'node_modules'
|
||||
paths: |
|
||||
~/.npm
|
||||
# or ~/.cache/yarn
|
||||
# or .pnpm-store
|
||||
base-branch: 'main'
|
||||
- name: Restore Browser Binary Cache
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/cache/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/cache/main.yaml'
|
||||
inputs:
|
||||
key: 'package-lock.json|yarn.lock|pnpm-lock.yaml|"browsers"'
|
||||
paths: |
|
||||
'~/.cache/Cypress'
|
||||
'~/.cache/ms-playwright'
|
||||
base-branch: 'main'
|
||||
- name: Install Node Modules
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/install-node-modules/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/install-node-modules/main.yaml'
|
||||
- name: Install Browsers (if needed)
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/install-browsers/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/install-browsers/main.yaml'
|
||||
- name: Run a custom script
|
||||
script: |
|
||||
git config --global user.email test@test.com
|
||||
@@ -306,15 +313,18 @@ launch-templates:
|
||||
image: 'ubuntu22.04-node20.11-v9'
|
||||
init-steps:
|
||||
- name: Checkout
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/checkout/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/checkout/main.yaml'
|
||||
- name: Restore Node Modules Cache
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/cache/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/cache/main.yaml'
|
||||
inputs:
|
||||
key: 'package-lock.json|yarn.lock|pnpm-lock.yaml'
|
||||
paths: 'node_modules'
|
||||
paths: |
|
||||
~/.npm
|
||||
# or ~/.cache/yarn
|
||||
# or .pnpm-store
|
||||
base-branch: 'main'
|
||||
- name: Install Node Modules
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/install-node-modules/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/install-node-modules/main.yaml'
|
||||
- name: Install Rust
|
||||
script: |
|
||||
curl --proto '=https' --tlsv1.3 https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
@@ -391,14 +401,14 @@ launch-templates:
|
||||
image: 'ubuntu22.04-node20.11-v9'
|
||||
init-steps:
|
||||
- name: Checkout
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/checkout/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/checkout/main.yaml'
|
||||
- name: Auth to Registry
|
||||
script: |
|
||||
# create .npmrc with @myorg scoped packages pointing to GH npm registry
|
||||
echo "@myorg:registry=https://npm.pkg.github.com" >> .npmrc
|
||||
echo "//npm.pkg.github.com/:_authToken=${SOME_AUTH_TOKEN}" >> .npmrc
|
||||
- name: Install Node Modules
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/install-node-modules/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/install-node-modules/main.yaml'
|
||||
```
|
||||
|
||||
Pass `SOME_AUTH_TOKEN` via `--with-env-vars`
|
||||
@@ -426,10 +436,10 @@ launch-templates:
|
||||
image: 'ubuntu22.04-node20.11-v9'
|
||||
init-steps:
|
||||
- name: Checkout
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/checkout/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/checkout/main.yaml'
|
||||
- name: Install Node
|
||||
# note the step is only released as of v4 of the workflow steps
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/install-node/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/install-node/main.yaml'
|
||||
inputs:
|
||||
# can omit value if a '.nvmrc' file is within the root of the repo
|
||||
node_version: '21'
|
||||
@@ -453,7 +463,7 @@ launch-templates:
|
||||
image: 'ubuntu22.04-node20.11-v9'
|
||||
init-steps:
|
||||
- name: Checkout
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/checkout/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/checkout/main.yaml'
|
||||
- name: Install nvm
|
||||
script: |
|
||||
# run nvm install script
|
||||
@@ -468,7 +478,7 @@ launch-templates:
|
||||
# confirm that the node version has changed
|
||||
script: node -v
|
||||
- name: Install Node Modules
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/install-node-modules/main.yaml'
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/install-node-modules/main.yaml'
|
||||
# Continue setup steps as needed
|
||||
```
|
||||
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Enterprise Release Notes
|
||||
|
||||
### 2024.10.2
|
||||
|
||||
- Fix: AWS S3 bucket connections when using STS role-based authentication
|
||||
|
||||
### 2024.10.1
|
||||
|
||||
- Fix: GitHub and external bucket connection issues when using a proxy
|
||||
|
||||
### 2024.10
|
||||
|
||||
This is a big release so let's go through the highlights first. There is also an important "Breaking changes" section at the end.
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
keywords: [build, serve, test, lint]
|
||||
---
|
||||
|
||||
# Common Tasks
|
||||
|
||||
The tasks that are [inferred by plugins](/concepts/inferred-tasks) or that you define in your [project configuration](/reference/project-configuration) can have any name that you want, but it is helpful for developers if you keep your task naming convention consistent across the projects in your repository. This way, if a developer moves from one project to another, they already know how to launch tasks for the new project. Here are some common task names that you can define for your projects.
|
||||
|
||||
@@ -8,7 +8,7 @@ Note that the relationship between Nx and Turborepo isn’t the same as with Nx
|
||||
|
||||
We do our best to be unbiased, but, of course you should do your own research. Read the docs, try things out and build your own opinion.
|
||||
|
||||
We clearly separate Nx (the open source tool) and [Nx Cloud](https://nx.app) (the SAAS product). Turborepo doesn’t have such a separation. Hence, in this guide we compare Turborepo with Nx+Nx Cloud (so it’s apples to apples). That said, you don’t have to use Nx Cloud to get features such as remote caching and distributed task execution. We provide public APIs so you can build your own, if you'd prefer not to use Nx Cloud.
|
||||
We clearly separate Nx (the open source tool) and [Nx Cloud](https://nx.app) (the SAAS product). Turborepo doesn’t have such a separation. Hence, in this guide we compare Turborepo with Nx+Nx Cloud (so it’s apples to apples).
|
||||
|
||||
We are going to compare the tools in three different ways: **features**, **tech and performance**, and **community**.
|
||||
|
||||
@@ -80,7 +80,6 @@ A crucial feature in Nx is the ability to not only parallelize your tasks on a s
|
||||
- **Turborepo doesn’t support it.** The best thing you can do when using Turborepo is binning/sharding, and that doesn’t work for non-trivial workspaces.
|
||||
- **Distributed task execution has a significantly higher impact on the ability to scale the repo than the computation cache.** You can scale without the cache, you cannot scale without the distribution.
|
||||
- This is the biggest feature related to performance and scaling that Turborepo is missing. And it’s by far the hardest one to build.
|
||||
- As with the rest of Nx, you can build your own version of the distributed task execution given the provided public API. If you choose not to implement your own version of the remote cache, you can use Nx Cloud. There is an [on-prem version of Nx Cloud](https://nx.app/private-cloud), so you have full control over where the artifacts are stored.
|
||||
|
||||
If you want to learn more, check out our article on [Distributing CI - Binning and Distributed Task Execution](https://blog.nrwl.io/distributing-ci-binning-and-distributed-task-execution-632fe31a8953)
|
||||
|
||||
@@ -147,7 +146,7 @@ Benchmarking is hard because a lot depends on what you are trying to run, in wha
|
||||
This is the result:
|
||||

|
||||
|
||||
Nx is 9.4 times faster on the latest MBP. We have made several changes to [the benchmark](https://github.com/vsavkin/large-monorepo/) since it was released (removed the usage `npx` and addressed other concerns folks had), but the result remained roughly the same. Please check out the benchmark.
|
||||
Nx is 4 times faster on the latest MBP. We have made several changes to [the benchmark](https://github.com/vsavkin/large-monorepo/) since it was released (removed the usage `npx` and addressed other concerns folks had), but the result remained roughly the same. Please check out the benchmark.
|
||||
|
||||
Why is it faster? Nx is in many ways akin to React in that it's doing tree diffing when restoring files from the cache. If the right files are in the right place, Nx won't touch them. Turbo blows everything away every time. Nx's version isn't just faster, it's also more useful (again similarly to tree diffing in React). Blowing everything away on every restoration means that if any tools watch the folders (which is common when you build large apps or build microfrontends), they are going to get confused or triggered for no reason. This is similar to how recreating the DOM from scratch isn't just slower, but results in worse UX. But even if you disable tree-diffing and make Nx do what Turbo does, it is still 1.7 times faster.
|
||||
|
||||
@@ -163,7 +162,7 @@ The one advantage Turbo's Go implementation has, is that any time you run an Nx
|
||||
|
||||
**The reason why we stuck with TypeScript is that our focus was always on extensibility.** If the rise of VSCode taught us anything, it is that it’s easier to extend things when they are written in JavaScript/TypeScript. Also, 5 years of **working with Fortune 500 companies** clearly showed us that **extensibility is key**!
|
||||
|
||||
It’s also worth noting that the backend of Nx Cloud is written in Kotlin. This is because the only contributors to our API, work at Nrwl, and we decided it was the best technology for the job.
|
||||
It’s also worth noting that the backend of Nx Cloud is written in Kotlin. This is because the only contributors to our API work at Nx, and we decided it was the best technology for the job.
|
||||
|
||||
## Community
|
||||
|
||||
@@ -173,8 +172,6 @@ Nx was released in 2016. Turborepo was open sourced in December of 2021. Turbore
|
||||
- There are about 1 million+ unique [Nx Console](https://marketplace.visualstudio.com/items?itemName=nrwl.angular-console) (a plugin for VSCode) installations.
|
||||
- There is a rich ecosystem of [third-party plugins](/plugin-registry).
|
||||
|
||||
From day 1 Nx has always been an **MIT-licensed open source project**, and we did everything to make sure companies using Nx won’t end up in the vendor lock-in. We clearly separated Nx the open source project and Nx Cloud the SAAS product. For instance, Nx Cloud is built using the public APIs Nx provides (you can build your own and some companies do). Nx Cloud docs are on a separate domain etc.
|
||||
|
||||
## Switch to Nx
|
||||
|
||||
If you're ready to switch from Turborepo to Nx, the [migration process is fully documented](/recipes/adopting-nx/from-turborepo).
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
# Typescript Project Linking
|
||||
|
||||
The naive way to reference code in a separate project is to use a relative path in the `import` statement.
|
||||
|
||||
```ts
|
||||
import { someFunction } from '../../teamA/otherProject';
|
||||
|
||||
const result = someFunction();
|
||||
```
|
||||
|
||||
The problem with this approach is that all your import statements become tied to your folder structure. Developers need to know the full path to any project from which they want to import code. Also, if `otherProject` ever moves locations, there will be superfluous code changes across the entire repository.
|
||||
|
||||
A more ergonomic solution is to reference your local projects as if they were external npm packages and then use a project linking mechanism to automatically resolve the project file path behind the scenes.
|
||||
|
||||
```ts
|
||||
import { someFunction } from '@myorg/otherProject';
|
||||
|
||||
const result = someFunction();
|
||||
```
|
||||
|
||||
There are two different methods that Nx supports for linking TypeScript projects: package manager workspaces and TypeScript path aliases. Project linking with TS path aliases was available with Nx before package managers offered a workspaces project linking approach. The Nx Team has since added full support for workspaces because (1) it has become more common across the TypeScript ecosystem and (2) packages will be resolved using native node module resolution instead of relying on TypeScript. Nx provides a cohesive experience for repositories using TypeScript path aliases without project references or repositories using package manager workspaces with TypeScript project references enabled.
|
||||
|
||||
## Project Linking with Workspaces
|
||||
|
||||
To create a new Nx workspace that links projects with package manager workspaces, use the `--workspaces` flag.
|
||||
|
||||
```shell
|
||||
npx create-nx-workspace --workspaces
|
||||
```
|
||||
|
||||
### Set Up Package Manager Workspaces
|
||||
|
||||
The configuration for package manager workspaces varies based on which package manager you're using.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="npm" %}
|
||||
|
||||
```json {% fileName="package.json" %}
|
||||
{
|
||||
"workspaces": ["apps/**", "packages/**"]
|
||||
}
|
||||
```
|
||||
|
||||
Defining the `workspaces` property in the root `package.json` file lets npm know to look for other `package.json` files in the specified folders. With this configuration in place, all the dependencies for the individual projects will be installed in the root `node_modules` folder when `npm install` is run in the root folder. Also, the projects themselves will be linked in the root `node_modules` folder to be accessed as if they were npm packages.
|
||||
|
||||
If you want to reference a local library project with its own `build` task, you should include the library in the `devDependencies` of the application's `package.json` with `*` specified as the library's version. `*` tells npm to use whatever version of the project is available.
|
||||
|
||||
```json {% fileName="/apps/my-app/package.json" %}
|
||||
{
|
||||
"devDependencies": {
|
||||
"@my-org/some-project": "*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="yarn" %}
|
||||
|
||||
```json {% fileName="package.json" %}
|
||||
{
|
||||
"workspaces": ["apps/**", "packages/**"]
|
||||
}
|
||||
```
|
||||
|
||||
Defining the `workspaces` property in the root `package.json` file lets yarn know to look for other `package.json` files in the specified folders. With this configuration in place, all the dependencies for the individual projects will be installed in the root `node_modules` folder when `yarn` is run in the root folder. Also, the projects themselves will be linked in the root `node_modules` folder to be accessed as if they were npm packages.
|
||||
|
||||
If you want to reference a local library project with its own `build` task, you should include the library in the `devDependencies` of the application's `package.json` with `workspace:*` specified as the library's version. [`workspace:*` tells yarn that the project is in the same repository](https://yarnpkg.com/features/workspaces) and not an npm package. You want to specify local projects as `devDependencies` instead of `dependencies` so that the library is not included twice in the production bundle of the application.
|
||||
|
||||
```json {% fileName="/apps/my-app/package.json" %}
|
||||
{
|
||||
"devDependencies": {
|
||||
"@my-org/some-project": "*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="bun" %}
|
||||
|
||||
```json {% fileName="package.json" %}
|
||||
{
|
||||
"workspaces": ["apps/**", "packages/**"]
|
||||
}
|
||||
```
|
||||
|
||||
Defining the `workspaces` property in the root `package.json` file lets bun know to look for other `package.json` files in the specified folders. With this configuration in place, all the dependencies for the individual projects will be installed in the root `node_modules` folder when `bun install` is run in the root folder. Also, the projects themselves will be linked in the root `node_modules` folder to be accessed as if they were npm packages.
|
||||
|
||||
If you want to reference a local library project with its own `build` task, you should include the library in the `devDependencies` of the application's `package.json` with `workspace:*` specified as the library's version. [`workspace:*` tells bun that the project is in the same repository](https://bun.sh/docs/install/workspaces) and not an npm package. You want to specify local projects as `devDependencies` instead of `dependencies` so that the library is not included twice in the production bundle of the application.
|
||||
|
||||
```json {% fileName="/apps/my-app/package.json" %}
|
||||
{
|
||||
"devDependencies": {
|
||||
"@my-org/some-project": "workspace:*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="pnpm" %}
|
||||
|
||||
```yaml {% fileName="pnpm-workspace.yaml" %}
|
||||
packages:
|
||||
- 'apps/**'
|
||||
- 'packages/**'
|
||||
```
|
||||
|
||||
Defining the `packages` property in the root `pnpm-workspaces.yaml` file lets pnpm know to look for project `package.json` files in the specified folders. With this configuration in place, all the dependencies for the individual projects will be installed in the root `node_modules` folder when `pnpm install` is run in the root folder.
|
||||
|
||||
If you want to reference a local library project from an application, you need to include the library in the `devDependencies` of the application's `package.json` with `workspace:*` specified as the library's version. [`workspace:*` tells pnpm that the project is in the same repository](https://pnpm.io/workspaces#workspace-protocol-workspace) and not an npm package. You want to specify local projects as `devDependencies` instead of `dependencies` so that the library is not included twice in the production bundle of the application.
|
||||
|
||||
```json {% fileName="/apps/my-app/package.json" %}
|
||||
{
|
||||
"devDependencies": {
|
||||
"@my-org/some-project": "workspace:*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### Set Up TypeScript Project References
|
||||
|
||||
With workspaces enabled, you can also configure TypeScript project references to speed up your build and typecheck tasks.
|
||||
|
||||
The root `tsconfig.base.json` should contain a `compilerOptions` property and no other properties. `compilerOptions.composite` and `compilerOptions.declaration` should be set to `true`. `compilerOptions.paths` should not be set.
|
||||
|
||||
```jsonc {% fileName="tsconfig.base.json" %}
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Required compiler options
|
||||
"composite": true,
|
||||
"declaration": true,
|
||||
"declarationMaps": true
|
||||
// Other options...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The root `tsconfig.json` file should extend `tsconfig.base.json` and not include any files. It needs to have `references` for every project in the repository so that editor tooling works correctly.
|
||||
|
||||
```jsonc {% fileName="tsconfig.json" %}
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"files": [], // intentionally empty
|
||||
"references": [
|
||||
// UPDATED BY PROJECT GENERATORS
|
||||
// All projects in the repository
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Individual Project TypeScript Configuration
|
||||
|
||||
Each project's `tsconfig.json` file should extend the `tsconfig.base.json` file and list `references` to the project's dependencies.
|
||||
|
||||
```jsonc {% fileName="packages/cart/tsconfig.json" %}
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"files": [], // intentionally empty
|
||||
"references": [
|
||||
// UPDATED BY NX SYNC
|
||||
// All project dependencies
|
||||
{
|
||||
"path": "../utils"
|
||||
},
|
||||
// This project's other tsconfig.*.json files
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Each project's `tsconfig.lib.json` file extends the project's `tsconfig.json` file and adds `references` to the `tsconfig.lib.json` files of project dependencies.
|
||||
|
||||
```jsonc {% fileName="packages/cart/tsconfig.lib.json" %}
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// Any overrides
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": [
|
||||
// exclude config and test files
|
||||
],
|
||||
"references": [
|
||||
// UPDATED BY NX SYNC
|
||||
// tsconfig.lib.json files for project dependencies
|
||||
{
|
||||
"path": "../utils/tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The project's `tsconfig.spec.json` does not need to reference project dependencies.
|
||||
|
||||
```jsonc {% fileName="packages/cart/tsconfig.spec.json" %}
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// Any overrides
|
||||
},
|
||||
"include": [
|
||||
// test files
|
||||
],
|
||||
"references": [
|
||||
// tsconfig.lib.json for this project
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### TypeScript Project References Performance Benefits
|
||||
|
||||
Using TypeScript project references improves both the speed and memory usage of build and typecheck tasks. The repository below contains benchmarks showing the difference between running typecheck with and without using TypeScript project references.
|
||||
|
||||
{% github-repository title="TypeScript Project References Benchmark" url="https://github.com/jaysoo/typecheck-timings" /%}
|
||||
|
||||
Here are the baseline typecheck task performance results.
|
||||
|
||||
```text
|
||||
Typecheck without using project references: 186 seconds, max memory 6.14 GB
|
||||
```
|
||||
|
||||
Using project references allows the TypeScript compiler to individually check the types for each project and store the results of that calculation in a `.tsbuildinfo` file for later use. Because of this, the TypeScript compiler does not need to load the entire codebase into memory at the same time, which you can see from the decreased memory usage on the first run with project references enabled.
|
||||
|
||||
```text
|
||||
Typecheck with project references first run: 175 seconds, max memory 945 MB
|
||||
```
|
||||
|
||||
Once the `.tsbuildinfo` files have been created, subsequent runs will be much faster.
|
||||
|
||||
```text
|
||||
Typecheck with all `.tsbuildinfo` files created: 25 seconds, max memory 429 MB
|
||||
```
|
||||
|
||||
Even if some projects have been updated and individual projects need to be type checked again, the TypeScript compiler can still use the cached `.tsbuildinfo` files for any projects that were not affected. This is very similar to the way Nx's caching and affected features work.
|
||||
|
||||
```text
|
||||
Typecheck (1 pkg updated): 36.33 seconds, max memory 655.14 MB
|
||||
Typecheck (5 pkg updated): 48.21 seconds, max memory 702.96 MB
|
||||
Typecheck (25 pkg updated): 65.25 seconds, max memory 666.78 MB
|
||||
Typecheck (100 pkg updated): 80.69 seconds, max memory 664.58 MB
|
||||
Typecheck (1 nested leaf pkg updated): 26.66 seconds, max memory 407.54 MB
|
||||
Typecheck (2 nested leaf pkg updated): 31.17 seconds, max memory 889.86 MB
|
||||
Typecheck (1 nested root pkg updated): 26.67 seconds, max memory 393.78 MB
|
||||
```
|
||||
|
||||
These performance benefits will be more noticeable for larger repositories, but even small code bases will see some benefits.
|
||||
|
||||
### Local TypeScript Path Aliases
|
||||
|
||||
If you define TS path aliases in an individual project's tsconfig files, you should not define them also in the root `tsconfig.base.json` file because TypeScript does not merge the paths. The paths defined in the root file would be completely overwritten by the ones defined in the project tsconfig. For instance, you could define paths like this in an application's tsconfig file.
|
||||
|
||||
```jsonc {% fileName="/apps/my-remix-app/tsconfig.app.json" %}
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"#app/*": ["./app/*"],
|
||||
"#tests/*": ["./tests/*"],
|
||||
"@/icon-name": [
|
||||
"./app/components/ui/icons/name.d.ts",
|
||||
"./types/icon-name.d.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Project Linking with TypeScript Path Aliases
|
||||
|
||||
{% callout type="warning" title="Path Aliases Overwrite Extended Configuration Files" %}
|
||||
If you define path aliases in a project's specific `tsconfig.*.json` file, those path aliases will overwrite the path aliases defined in the root `tsconfig.base.json`. You can't use both project-level path aliases and root path aliases.
|
||||
{% /callout %}
|
||||
|
||||
Linking projects with TypeScript path aliases is configured entirely in the tsconfig files. You can still use package manager workspaces to enable you to define separate third-party dependencies for individual projects, but the local project linking is done by TypeScript instead of the package manager.
|
||||
|
||||
The paths for each library are defined in the root `tsconfig.base.json` and each project's `tsconfig.json` should extend that file. Note that application projects do not need to have a path defined because no projects will import code from a top-level application.
|
||||
|
||||
```jsonc {% fileName="/tsconfig.base.json" %}
|
||||
{
|
||||
"compilerOptions": {
|
||||
// common compiler option defaults for all projects
|
||||
// ...
|
||||
// These compiler options must be false or undefined
|
||||
"composite": false,
|
||||
"declaration": false,
|
||||
"paths": {
|
||||
// These paths are automatically added by Nx library generators
|
||||
"@myorg/shared-ui": ["packages/shared-ui/src/index.ts"]
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
keywords: [update]
|
||||
---
|
||||
|
||||
# Automate Updating Dependencies
|
||||
|
||||
{% youtube
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
keywords: [distributed tasks]
|
||||
---
|
||||
|
||||
# Distribute Task Execution (Nx Agents)
|
||||
|
||||
{% youtube
|
||||
|
||||
@@ -16,7 +16,7 @@ See the [Adding Nx to NPM/Yarn/PNPM Workspace](/recipes/adopting-nx/adding-to-mo
|
||||
|
||||
## Convert turbo.json into Nx Configuration
|
||||
|
||||
Most of the settings in your `turbo.json` file can be converted directly into `nx.json` equivalents. The key configuration properties of `dependsOn`, `inputs` and `outputs` have a very similar syntax and can probably be copied over directly from the `turbo.json` `pipeline` into the `nx.json` `targetDefaults`.
|
||||
Most of the settings in your `turbo.json` file can be converted directly into `nx.json` equivalents. The key configuration properties of `dependsOn`, `inputs` and `outputs` have a very similar syntax and can probably be copied over directly from the `turbo.json` `tasks` into the `nx.json` `targetDefaults`.
|
||||
|
||||
If you have project-specific tasks defined in the root `turbo.json` (i.e. `myreactapp#build`) or in project-level `turbo.json` files (i.e. `/packages/myreactapp/turbo.json`), those settings should go in the `nx` property of the project's `package.json` (i.e. `/packages/myreactapp/package.json`).
|
||||
|
||||
@@ -29,7 +29,7 @@ Let's say you start with the following `turbo.json` file:
|
||||
```json {% fileName="/turbo.json" %}
|
||||
{
|
||||
"$schema": "https://turbo.build/schema.json",
|
||||
"pipeline": {
|
||||
"tasks": {
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["dist/**"]
|
||||
@@ -100,58 +100,61 @@ Creating the equivalent configuration with Nx yields the following files:
|
||||
|
||||
For each `turbo.json` configuration property, the equivalent Nx property is listed.
|
||||
|
||||
| **Global Configuration:** | |
|
||||
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `globalDependencies` | add to the [`sharedGlobals` `namedInput`](/recipes/running-tasks/configure-inputs) |
|
||||
| `globalEnv` | add to the [`sharedGlobals` `namedInput`](/recipes/running-tasks/configure-inputs) as an [`env` input](/reference/inputs#environment-variables) |
|
||||
| `globalPassThroughEnv` | N/A. See [Defining Environment Variables](/recipes/tips-n-tricks/define-environment-variables) |
|
||||
| `globalDotEnv` | add to the [`sharedGlobals` `namedInput`](/recipes/running-tasks/configure-inputs) |
|
||||
|
||||
| **Task Configuration:** | |
|
||||
| ------------------------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| `extends` | N/A. The project configurations will always extend the `targetDefaults` defined in `nx.json`. |
|
||||
| `pipeline[task].dependsOn` | [Same syntax](/reference/project-configuration#dependson). |
|
||||
| `pipeline[task].dotEnv` | Define [file `inputs`](/reference/inputs#source-files) |
|
||||
| `pipeline[task].env` | Define [env `inputs`](/reference/inputs#environment-variables) |
|
||||
| `pipeline[task].passThroughEnv` | N/A. See [Defining Environment Variables](/recipes/tips-n-tricks/define-environment-variables) |
|
||||
| `pipeline[task].outputs` | [Same syntax](/reference/project-configuration#outputs). |
|
||||
| `pipeline[task].cache` | [Same syntax](/reference/project-configuration#cache) |
|
||||
| `pipeline[task].inputs` | [Same syntax](/reference/inputs#source-files). |
|
||||
| `pipeline[task].outputMode` | Use the [`--output-style` command line flag](/nx-api/nx/documents/run-many#output-style) |
|
||||
| `pipeline[task].persistent` | N/A. |
|
||||
| **Global Configuration:** | |
|
||||
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `cacheDir` | Set in [`cacheDirectory`](/reference/nx-json#task-options) |
|
||||
| `deamon` | Use [`NX_DAEMON=false` or set `useDaemonProcess: false`](/concepts/nx-daemon#turning-it-off) in `nx.json` |
|
||||
| `envMode` | Nx core does not block any environment variables. There are [React](/recipes/react/use-environment-variables-in-react) and [Angular](/recipes/angular/use-environment-variables-in-angular) build executors that handle environment variables in a customized way. |
|
||||
| `globalDependencies` | add to the [`sharedGlobals` `namedInput`](/recipes/running-tasks/configure-inputs) |
|
||||
| `globalEnv` | add to the [`sharedGlobals` `namedInput`](/recipes/running-tasks/configure-inputs) as an [`env` input](/reference/inputs#environment-variables) |
|
||||
| `globalPassThroughEnv` | N/A. See [Defining Environment Variables](/recipes/tips-n-tricks/define-environment-variables) |
|
||||
| `remoteCache` | See [Nx Replay](/ci/features/remote-cache) |
|
||||
| `ui` | Use [`--output-style`](/nx-api/nx/documents/run-many#output-style) |
|
||||
| **Task Configuration:** | |
|
||||
| ---------------------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| `extends` | N/A. The project configurations will always extend the `targetDefaults` defined in `nx.json`. |
|
||||
| `tasks[task].dependsOn` | [Same syntax](/reference/project-configuration#dependson). |
|
||||
| `tasks[task].env` | Define [env `inputs`](/reference/inputs#environment-variables) |
|
||||
| `tasks[task].passThroughEnv` | N/A. See [Defining Environment Variables](/recipes/tips-n-tricks/define-environment-variables) |
|
||||
| `tasks[task].outputs` | [Same syntax](/reference/project-configuration#outputs). |
|
||||
| `tasks[task].cache` | [Same syntax](/reference/project-configuration#cache) |
|
||||
| `tasks[task].inputs` | [Same syntax](/reference/inputs#source-files). |
|
||||
| `tasks[task].outputLogs` | Use the [`--output-style` command line flag](/nx-api/nx/documents/run-many#output-style) |
|
||||
| `tasks[task].persistent` | N/A. |
|
||||
| `tasks[task].interactive` | N/A. |
|
||||
|
||||
## Command Equivalents
|
||||
|
||||
| | |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `turbo run test lint build` | [`nx run-many -t test lint build`](/nx-api/nx/documents/run-many) |
|
||||
| `--cache-dir` | Set in [`nx.json` under `cacheDirectory`](/reference/nx-json#task-options) |
|
||||
| `--concurrency` | [`--parallel`](/nx-api/nx/documents/run-many#parallel) |
|
||||
| `--continue` | [Use `--nx-bail`](/nx-api/nx/documents/run-many#nx-bail) with the inverse value |
|
||||
| `--cwd` | Available when using the [`run-commands` executor](/nx-api/nx/executors/run-commands#cwd) |
|
||||
| `--dry-run` | N/A. Nx has `--dry-run` for `nx generate` but not for running tasks. |
|
||||
| `--env-mode` | N/A |
|
||||
| `--filter` | Use [`-p admin-*` or `-p tag:api-*`](/nx-api/nx/documents/run-many#projects). Also see [`nx affected`](/nx-api/nx/documents/affected). |
|
||||
| `--graph` | [Same syntax](/nx-api/nx/documents/run-many#graph) or [`nx graph`](/nx-api/nx/documents/dep-graph) for the entire graph |
|
||||
| `--force` | [`nx reset`](/nx-api/nx/documents/reset) and then run the command again |
|
||||
| `--global-deps` | Use [`inputs` in the `nx.json`](/recipes/running-tasks/configure-inputs) or project configuration |
|
||||
| `--framework-inference` | Nx knows if you're using a particular framework if you use an executor for that framework. |
|
||||
| `--ignore` | Use an [`.nxignore` file](/reference/nxignore) (or `.gitignore`) |
|
||||
| `--log-order` | Use [`--output-style`](/nx-api/nx/documents/run-many#output-style) |
|
||||
| `--no-cache` | Use [`--skip-nx-cache`](/nx-api/nx/documents/run-many#skip-nx-cache) |
|
||||
| `--no-daemon` | Use [`NX_DAEMON=false` or set `useDaemonProcess: false`](/concepts/nx-daemon#turning-it-off) in `nx.json` |
|
||||
| `--output-logs` | Use [`--output-style`](/nx-api/nx/documents/run-many#output-style) |
|
||||
| `--only` | N/A |
|
||||
| `--parallel` | N/A |
|
||||
| `--remote-only` | N/A. Can [ignore the remote cache](/ci/features/remote-cache#skipping-cloud-cache) with `--no-cloud`. |
|
||||
| `--summarize` | N/A |
|
||||
| `--token` | Set the [Nx Cloud CI Access Token](/ci/recipes/security/access-tokens#setting-ci-access-tokens) or as an environment variable (`NX_CLOUD_ACCESS_TOKEN`) |
|
||||
| `--team` | See `--token` for choosing a different Nx Cloud workspace. You can [use `--runner`](/nx-api/nx/documents/run-many#runner) to choose a different runner defined in the `nx.json` file. |
|
||||
| `--preflight` | N/A |
|
||||
| `--trace` | N/A. [`--verbose`](/nx-api/nx/documents/run-many#verbose) for more logging. |
|
||||
| `--heap` | N/A. [`--verbose`](/nx-api/nx/documents/run-many#verbose) for more logging. |
|
||||
| `--cpuprofile` | Use [`NX_PROFILE=profile.json`](/troubleshooting/performance-profiling). |
|
||||
| `--verbosity` | Use [`--verbose`](/nx-api/nx/documents/run-many#verbose) |
|
||||
| `turbo gen` | [Use `nx generate`](/nx-api/nx/documents/generate) |
|
||||
| `turbo login` | No need. [Create an Nx Cloud account](/nx-api/nx/documents/connect-to-nx-cloud) once to set up Nx Cloud. |
|
||||
| `turbo link` | [Create an Nx Cloud account](/nx-api/nx/documents/connect-to-nx-cloud) |
|
||||
| | |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `turbo run test lint build` | [`nx run-many -t test lint build`](/nx-api/nx/documents/run-many) |
|
||||
| `--cache-dir` | Set in [`nx.json` under `cacheDirectory`](/reference/nx-json#task-options) |
|
||||
| `--concurrency` | [`--parallel`](/nx-api/nx/documents/run-many#parallel) |
|
||||
| `--continue` | [Use `--nx-bail`](/nx-api/nx/documents/run-many#nx-bail) with the inverse value |
|
||||
| `--cpuprofile` | Use [`NX_PROFILE=profile.json`](/troubleshooting/performance-profiling). |
|
||||
| `--cwd` | Available when using the [`run-commands` executor](/nx-api/nx/executors/run-commands#cwd) |
|
||||
| `--deamon` | Use [`NX_DAEMON=false` or set `useDaemonProcess: false`](/concepts/nx-daemon#turning-it-off) in `nx.json` |
|
||||
| `--dry-run` | N/A. Nx has `--dry-run` for `nx generate` but not for running tasks. |
|
||||
| `--env-mode` | Nx core does not block any environment variables. There are [React](/recipes/react/use-environment-variables-in-react) and [Angular](/recipes/angular/use-environment-variables-in-angular) build executors that handle environment variables in a customized way. |
|
||||
| `--filter` | Use [`-p admin-*` or `-p tag:api-*`](/nx-api/nx/documents/run-many#projects). Also see [`nx affected`](/nx-api/nx/documents/affected). |
|
||||
| `--force` | [`nx reset`](/nx-api/nx/documents/reset) and then run the command again |
|
||||
| `--framework-inference` | Nx knows if you're using a particular framework if you use an executor for that framework. |
|
||||
| `--global-deps` | Use [`inputs` in the `nx.json`](/recipes/running-tasks/configure-inputs) or project configuration |
|
||||
| `--graph` | [Same syntax](/nx-api/nx/documents/run-many#graph) or [`nx graph`](/nx-api/nx/documents/dep-graph) for the entire graph |
|
||||
| `--heap` | N/A. [`--verbose`](/nx-api/nx/documents/run-many#verbose) for more logging. |
|
||||
| `--ignore` | Use an [`.nxignore` file](/reference/nxignore) (or `.gitignore`) |
|
||||
| `--log-order` | Use [`--output-style`](/nx-api/nx/documents/run-many#output-style) |
|
||||
| `--no-cache` | Use [`--skip-nx-cache`](/nx-api/nx/documents/run-many#skip-nx-cache) |
|
||||
| `--output-logs` | Use [`--output-style`](/nx-api/nx/documents/run-many#output-style) |
|
||||
| `--only` | N/A |
|
||||
| `--parallel` | N/A |
|
||||
| `--preflight` | N/A |
|
||||
| `--remote-only` | N/A. Can [ignore the remote cache](/ci/features/remote-cache#skipping-cloud-cache) with `--no-cloud`. |
|
||||
| `--summarize` | N/A |
|
||||
| `--token` | Set the [Nx Cloud CI Access Token](/ci/recipes/security/access-tokens#setting-ci-access-tokens) or as an environment variable (`NX_CLOUD_ACCESS_TOKEN`) |
|
||||
| `--team` | See `--token` for choosing a different Nx Cloud workspace. You can [use `--runner`](/nx-api/nx/documents/run-many#runner) to choose a different runner defined in the `nx.json` file. |
|
||||
| `--trace` | N/A. [`--verbose`](/nx-api/nx/documents/run-many#verbose) for more logging. |
|
||||
| `--verbosity` | Use [`--verbose`](/nx-api/nx/documents/run-many#verbose) |
|
||||
| `turbo gen` | [Use `nx generate`](/nx-api/nx/documents/generate) |
|
||||
| `turbo login` | No need. [Create an Nx Cloud account](/nx-api/nx/documents/connect-to-nx-cloud) once to set up Nx Cloud. |
|
||||
| `turbo link` | [Create an Nx Cloud account](/nx-api/nx/documents/connect-to-nx-cloud) |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/nameExpo is an open-source framework for apps that run natively on Android, iOS, and the web. Expo brings together the best of mobile and the web and enables many important features for building and scaling an app.
|
||||
Expo is an open-source framework for apps that run natively on Android, iOS, and the web. Expo brings together the best of mobile and the web and enables many important features for building and scaling an app.
|
||||
|
||||
Expo is a set of tools built on top of React Native. The Nx Plugin for Expo contains generators for managing Expo applications and libraries within an Nx workspace.
|
||||
|
||||
|
||||
@@ -91,6 +91,27 @@ target with that name which can be used in CI to run the tests for each file in
|
||||
}
|
||||
```
|
||||
|
||||
### Customizing atomized unit/e2e tasks group name
|
||||
|
||||
By default, the atomized tasks group name is derived from the `ciTargetName`. For example, atomized tasks for the `e2e-ci` target will be grouped under the name "E2E (CI)" when displayed in Nx Cloud or `nx show project <project> --web` UI.
|
||||
You can customize that name by explicitly providing the optional `ciGroupName` plugin option as such:
|
||||
|
||||
```json {% fileName="nx.json" %}
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"plugin": "@nx/jest/plugin",
|
||||
"include": ["e2e/**/*"],
|
||||
"options": {
|
||||
"targetName": "e2e-local",
|
||||
"ciTargetName": "e2e-ci",
|
||||
"ciGroupname": "My E2E tests (CI)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### How @nx/jest Infers Tasks
|
||||
|
||||
{% callout type="note" title="Inferred Tasks" %}
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
# Configure TypeScript Project References in an Nx Workspace
|
||||
|
||||
In Nx 20, the `@nx/js` plugin provides the ability to incrementally build projects in a monorepo using [TypeScript Project References](https://www.typescriptlang.org/docs/handbook/project-references.html). Nx also provides a `ts` preset for `create-nx-workspace` that configures project references and uses `workspaces` to link projects instead of [TypeScript compilerOptions Paths](https://www.typescriptlang.org/docs/handbook/modules/reference.html#paths).
|
||||
|
||||
The TypeScript team recommends using project references when working in a monorepo, but until now the configuration settings were difficult to maintain. Each project is required to list its own project dependencies in the `references` property of the `tsconfig.json` file so that TypeScript can incrementally compile projects in the correct order. In a large monorepo, maintaining those settings manually is cost prohibitive. To solve this problem, the `@nx/js` plugin registers a [sync generator](/concepts/sync-generators) to automatically update the references based on Nx's project graph before any TypeScript `build` task is executed.
|
||||
|
||||
## Create a New Nx Workspace Using Project References
|
||||
|
||||
We anticipate that this style of compiling projects will eventually become the default, but currently it will only be enabled for repositories configured in a specific way. Existing workspaces will continue to function as usual and there is no migration path yet. You can generate a new repository with these settings by using the `--preset=ts` flag of the `create-nx-workspace` command.
|
||||
|
||||
```shell
|
||||
npx create-nx-workspace --preset=ts
|
||||
```
|
||||
|
||||
{% callout type="note" title="Empty Workspace with Paths" %}
|
||||
To generate an empty Nx workspace that links projects with the `compilerOptions.paths` property and does not use project references, use `create-nx-workspace --preset=apps`
|
||||
{% /callout %}
|
||||
|
||||
This will generate an empty repository that is configured to use TypeScript project references. To see the new functionality in action, create some TypeScript projects and make sure to use the `tsc` bundler option.
|
||||
|
||||
```shell
|
||||
nx g @nx/js:lib packages/cart --bundler=tsc
|
||||
nx g @nx/js:lib packages/utils --bundler=tsc
|
||||
```
|
||||
|
||||
These generators will detect that your repository is configured to use project references and update the configuration accordingly. If these generators were executed in an Nx repository that used `compilerOptions.paths`, they would update that setting instead.
|
||||
|
||||
To make `cart` depend on `utils`, update `packages/cart/package.json` like this:
|
||||
|
||||
```jsonc {% fileName="packages/cart/package.json" %}
|
||||
{
|
||||
"dependencies": {
|
||||
"utils": "*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now if you run `nx build cart` or directly run `nx sync`, the `packages/cart/tsconfig.json` file will have its references updated for you.
|
||||
|
||||
## Project Reference Configuration Files
|
||||
|
||||
Nx expects the following configuration settings to be in place in order to use TypeScript project references to build projects. Most of this configuration is set up and maintained for you automatically by Nx.
|
||||
|
||||
Identify projects in the `workspaces` property in the root `package.json` file.
|
||||
|
||||
```json {% fileName="package.json" %}
|
||||
{
|
||||
"workspaces": ["packages/*"]
|
||||
}
|
||||
```
|
||||
|
||||
The root `tsconfig.base.json` should contain a `compilerOptions` property and no other properties. `compilerOptions.composite` and `compilerOptions.declaration` should be set to `true`. `compilerOptions.paths` should not be set.
|
||||
|
||||
```jsonc {% fileName="tsconfig.base.json" %}
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Required compiler options
|
||||
"composite": true,
|
||||
"declaration": true
|
||||
// Other options...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The root `tsconfig.json` file should extend `tsconfig.base.json` and not include any files. It needs to have `references` for every project in the repository so that editor tooling works correctly.
|
||||
|
||||
```jsonc {% fileName="tsconfig.json" %}
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"files": [], // intentionally empty
|
||||
"references": [
|
||||
// UPDATED BY PROJECT GENERATORS
|
||||
// All projects in the repository
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Each project's `tsconfig.json` file should extend the `tsconfig.base.json` file and list `references` to the project's dependencies.
|
||||
|
||||
```jsonc {% fileName="packages/cart/tsconfig.json" %}
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"files": [], // intentionally empty
|
||||
"references": [
|
||||
// UPDATED BY NX SYNC
|
||||
// All project dependencies
|
||||
{
|
||||
"path": "../utils"
|
||||
},
|
||||
// This project's other tsconfig.*.json files
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Each project's `tsconfig.lib.json` file extends the project's `tsconfig.json` file and adds `references` to the `tsconfig.lib.json` files of project dependencies.
|
||||
|
||||
```jsonc {% fileName="packages/cart/tsconfig.lib.json" %}
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// Any overrides
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": [
|
||||
// exclude config and test files
|
||||
],
|
||||
"references": [
|
||||
// UPDATED BY NX SYNC
|
||||
// tsconfig.lib.json files for project dependencies
|
||||
{
|
||||
"path": "../utils/tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The project's `tsconfig.spec.json` does not need to reference project dependencies.
|
||||
|
||||
```jsonc {% fileName="packages/cart/tsconfig.spec.json" %}
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// Any overrides
|
||||
},
|
||||
"include": [
|
||||
// test files
|
||||
],
|
||||
"references": [
|
||||
// tsconfig.lib.json for this project
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -81,3 +81,29 @@ Finally, you need to configure your Nx cache in the `nx.json` file. The `contain
|
||||
| --------------- | -------------------------------- |
|
||||
| **container** | The name of the container to use |
|
||||
| **accountName** | The name of blob storage account |
|
||||
|
||||
# Cache Modes
|
||||
|
||||
By default, Nx will try to write and read from the remote cache while running locally. This means that permissions must be set for users who are expected to access the remote cache.
|
||||
|
||||
Nx will only show warnings when the remote cache is not writable. You can disable these warnings by setting `localMode` to `read-only` or `no-cache` in the `nx.json` file.
|
||||
|
||||
```jsonc {% fileName="nx.json" %}
|
||||
{
|
||||
"azure": {
|
||||
// ...
|
||||
"localMode": "read-only"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The cache mode in CI can also be configured by setting `ciMode` to `read-only` or `no-cache` in the `nx.json` file. Or setting `NX_POWERPACK_CACHE_MODE` to `read-only` or `no-cache` in the CI environment.
|
||||
|
||||
```jsonc {% fileName="nx.json" %}
|
||||
{
|
||||
"azure": {
|
||||
// ...
|
||||
"ciMode": "read-only"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
# Create a Conformance Rule
|
||||
|
||||
For local conformance rules, the resolution utilities from `@nx/js` are used in the same way they are for all other JavaScript/TypeScript files in Nx. Therefore, you can simply reference an adhoc JavaScript file or TypeScript file in your `"rule"` property (as long as the path is resolvable based on your package manager and/or tsconfig setup), and the rule will be loaded/transpiled as needed. The rule implementation file should also have a `schema.json` file next to it that defines the available rule options, if any.
|
||||
|
||||
Therefore, in practice, writing your local conformance rules in an Nx generated library is the easiest way to organize them and ensure that they are easily resolvable via TypeScript. The library in question could also be an Nx plugin, but it does not have to be.
|
||||
|
||||
To write your own conformance rule, run the `@nx/powerpack-conformance:create-rule` generator and answer the prompts.
|
||||
|
||||
```text {% command="nx g @nx/powerpack-conformance:create-rule" %}
|
||||
NX Generating @nx/powerpack-conformance:create-rule
|
||||
|
||||
✔ What is the name of the rule? · local-conformance-rule-example
|
||||
✔ Which directory do you want to create the rule directory in? · packages/my-plugin/local-conformance-rule
|
||||
✔ What category does this rule belong to? · security
|
||||
✔ What reporter do you want to use for this rule? · project-reporter
|
||||
✔ What is the description of the rule? · an example of a conformance rule
|
||||
CREATE packages/my-plugin/local-conformance-rule/local-conformance-rule-example/index.ts
|
||||
CREATE packages/my-plugin/local-conformance-rule/local-conformance-rule-example/schema.json
|
||||
```
|
||||
|
||||
The generated rule definition file should look like this:
|
||||
|
||||
```ts {% fileName="packages/my-plugin/local-conformance-rule/index.ts" %}
|
||||
import {
|
||||
createConformanceRule,
|
||||
ProjectViolation,
|
||||
} from '@nx/powerpack-conformance';
|
||||
|
||||
export default createConformanceRule({
|
||||
name: 'local-conformance-rule-example',
|
||||
category: 'security',
|
||||
description: 'an example of a conformance rule',
|
||||
reporter: 'project-reporter',
|
||||
implementation: async (context) => {
|
||||
const violations: ProjectViolation[] = [];
|
||||
|
||||
return {
|
||||
severity: 'low',
|
||||
details: {
|
||||
violations,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
To enable the rule, you need to register it in the `nx.json` file.
|
||||
|
||||
```json {% fileName="nx.json" %}
|
||||
{
|
||||
"conformance": {
|
||||
"rules": [
|
||||
{
|
||||
"rule": "./packages/my-plugin/local-conformance-rule/index.ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that the severity of the error is defined by the rule author and can be adjusted based on the specific violations that are found.
|
||||
|
||||
## Conformance Rule Examples
|
||||
|
||||
There are three types of reporters that a rule can use.
|
||||
|
||||
- `project-reporter` - The rule evaluates an entire project at a time.
|
||||
- `project-files-reporter` - The rule evaluates a single project file at a time.
|
||||
- `non-project-files-reporter` - The rule evaluates files that don't belong to any project.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="project-reporter" %}
|
||||
|
||||
The `@nx/powerpack-conformance:ensure-owners` rule provides us an example of how to write a `project-reporter` rule. The `@nx/powerpack-owners` plugin adds an `owners` metadata property to every project node that has an owner in the project graph. This rule checks each project node metadata to make sure that each project has some owner defined.
|
||||
|
||||
```ts
|
||||
import { ProjectGraphProjectNode } from '@nx/devkit';
|
||||
import {
|
||||
createConformanceRule,
|
||||
ProjectViolation,
|
||||
} from '@nx/powerpack-conformance';
|
||||
|
||||
export default createConformanceRule({
|
||||
name: 'ensure-owners',
|
||||
category: 'consistency',
|
||||
description: 'Ensure that all projects have owners defined via Nx Owners.',
|
||||
reporter: 'project-reporter',
|
||||
implementation: async (context) => {
|
||||
const violations: ProjectViolation[] = [];
|
||||
|
||||
for (const node of Object.values(
|
||||
context.projectGraph.nodes
|
||||
) as ProjectGraphProjectNode[]) {
|
||||
const metadata = node.data.metadata;
|
||||
if (!metadata?.owners || Object.keys(metadata.owners).length === 0) {
|
||||
violations.push({
|
||||
sourceProject: node.name,
|
||||
message: `This project currently has no owners defined via Nx Owners.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
severity: 'medium',
|
||||
details: {
|
||||
violations,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="project-files-reporter" %}
|
||||
|
||||
This rule uses TypeScript AST processing to ensure that `index.ts` files use a client-side style of export syntax and `server.ts` files use a server-side style of export syntax.
|
||||
|
||||
```ts
|
||||
import {
|
||||
createConformanceRule,
|
||||
ProjectFilesViolation,
|
||||
} from '@nx/powerpack-conformance';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import {
|
||||
createSourceFile,
|
||||
isExportDeclaration,
|
||||
isStringLiteral,
|
||||
isToken,
|
||||
ScriptKind,
|
||||
ScriptTarget,
|
||||
} from 'typescript';
|
||||
|
||||
export default createConformanceRule({
|
||||
name: 'server-client-public-api',
|
||||
category: 'consistency',
|
||||
description: 'Ensure server-only and client-only public APIs are not mixed',
|
||||
reporter: 'project-files-reporter',
|
||||
implementation: async ({ projectGraph }) => {
|
||||
const violations: ProjectFilesViolation[] = [];
|
||||
|
||||
for (const nodeId in projectGraph.nodes) {
|
||||
const node = projectGraph.nodes[nodeId];
|
||||
|
||||
const sourceRoot = node.data.root;
|
||||
|
||||
const indexPath = join(sourceRoot, 'src/index.ts');
|
||||
const serverPath = join(sourceRoot, 'src/server.ts');
|
||||
|
||||
if (existsSync(indexPath)) {
|
||||
const fileContent = readFileSync(indexPath, 'utf8');
|
||||
violations.push(
|
||||
...processEntryPoint(fileContent, indexPath, nodeId, 'client')
|
||||
);
|
||||
}
|
||||
|
||||
if (existsSync(serverPath)) {
|
||||
const fileContent = readFileSync(serverPath, 'utf8');
|
||||
violations.push(
|
||||
...processEntryPoint(fileContent, serverPath, nodeId, 'server')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
severity: 'medium',
|
||||
details: { violations },
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export function processEntryPoint(
|
||||
fileContent: string,
|
||||
entryPoint: string,
|
||||
project: string,
|
||||
style: 'server' | 'client'
|
||||
) {
|
||||
const violations: ProjectFilesViolation[] = [];
|
||||
|
||||
const sf = createSourceFile(
|
||||
entryPoint,
|
||||
fileContent,
|
||||
ScriptTarget.Latest,
|
||||
true,
|
||||
ScriptKind.TS
|
||||
);
|
||||
|
||||
let hasNotOnlyExports = false;
|
||||
sf.forEachChild((node) => {
|
||||
if (isExportDeclaration(node)) {
|
||||
const moduleSpecifier =
|
||||
node.moduleSpecifier && isStringLiteral(node.moduleSpecifier)
|
||||
? node.moduleSpecifier.getText()
|
||||
: '';
|
||||
|
||||
if (isModuleSpecifierViolated(moduleSpecifier, style)) {
|
||||
if (
|
||||
violations.find(
|
||||
(v) => v.file === entryPoint && v.sourceProject === project
|
||||
)
|
||||
) {
|
||||
// we already have a violation for this file and project, so we don't need to add another one
|
||||
return;
|
||||
}
|
||||
|
||||
violations.push({
|
||||
message:
|
||||
style === 'client'
|
||||
? 'Client-side only entry point cannot export from server-side modules'
|
||||
: 'Server-side only entry point can only export server-side modules ',
|
||||
file: entryPoint,
|
||||
sourceProject: project,
|
||||
});
|
||||
}
|
||||
} else if (isToken(node) && node === sf.endOfFileToken) {
|
||||
// do nothing
|
||||
} else {
|
||||
hasNotOnlyExports = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasNotOnlyExports) {
|
||||
violations.push({
|
||||
message: `Entry point should only contain exported APIs`,
|
||||
file: entryPoint,
|
||||
sourceProject: project,
|
||||
});
|
||||
}
|
||||
|
||||
return violations;
|
||||
}
|
||||
|
||||
function isModuleSpecifierViolated(
|
||||
moduleSpecifier: string,
|
||||
style: 'server' | 'client'
|
||||
) {
|
||||
// should not get here. if this is the case, it's a grammar error in the source code.
|
||||
if (!moduleSpecifier) return false;
|
||||
|
||||
if (style === 'server' && !moduleSpecifier.includes('.server')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (style === 'client' && moduleSpecifier.includes('.server')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="non-project-files-reporter" %}
|
||||
|
||||
This rule checks the root `package.json` file and ensures that if the `tmp` package is included as a dependency, it has a minimum version of 0.2.3.
|
||||
|
||||
```ts
|
||||
import { readJsonFile, workspaceRoot } from '@nx/devkit';
|
||||
import {
|
||||
createConformanceRule,
|
||||
NonProjectFilesViolation,
|
||||
} from '@nx/powerpack-conformance';
|
||||
import { join } from 'node:path';
|
||||
import { satisfies } from 'semver';
|
||||
|
||||
export default createConformanceRule<object>({
|
||||
name: 'package-tmp-0.2.3',
|
||||
category: 'maintainability',
|
||||
description: 'The tmp dependency should be a minimum version of 0.2.3',
|
||||
reporter: 'non-project-files-reporter',
|
||||
implementation: async () => {
|
||||
const violations: NonProjectFilesViolation[] = [];
|
||||
const applyViolationIfApplicable = (version: string | undefined) => {
|
||||
if (version && !satisfies(version, '>=0.2.3')) {
|
||||
violations.push({
|
||||
message: 'The "tmp" package must be version "0.2.3" or higher',
|
||||
file: 'package.json',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const workspaceRootPackageJson = await readJsonFile(
|
||||
join(workspaceRoot, 'package.json')
|
||||
);
|
||||
applyViolationIfApplicable(workspaceRootPackageJson.dependencies?.['tmp']);
|
||||
applyViolationIfApplicable(
|
||||
workspaceRootPackageJson.devDependencies?.['tmp']
|
||||
);
|
||||
|
||||
return {
|
||||
severity: 'low',
|
||||
details: {
|
||||
violations,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
## Share Conformance Rules Across Workspaces
|
||||
|
||||
If you have an Enterprise Nx Cloud contract, you can share your conformance rules across every repository in your organization. Read more in these articles:
|
||||
|
||||
- [Publish Conformance Rules to Nx Cloud](/ci/recipes/enterprise/conformance/publish-conformance-rules-to-nx-cloud)
|
||||
- [Configure Conformance Rules in Nx Cloud](/ci/recipes/enterprise/conformance/configure-conformance-rules-in-nx-cloud)
|
||||
@@ -166,51 +166,10 @@ Set the `rule` property to: `@nx/powerpack-conformance/ensure-owners`
|
||||
}
|
||||
```
|
||||
|
||||
## Custom Conformance Rules
|
||||
## Next Steps
|
||||
|
||||
To write your own conformance rule, specify a relative path to a TypeScript or JavaScript file as the rule name:
|
||||
For more information about the conformance plugin, consult the following articles:
|
||||
|
||||
```json {% fileName="nx.json" %}
|
||||
{
|
||||
"conformance": {
|
||||
"rules": [
|
||||
{
|
||||
"rule": "./tools/local-conformance-rule.ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The rule definition file should look like this:
|
||||
|
||||
```ts {% fileName="tools/local-conformance-rule.ts" %}
|
||||
import { createConformanceRule } from '@nx/powerpack-conformance';
|
||||
|
||||
const rule = createConformanceRule({
|
||||
name: 'local-conformance-rule-example',
|
||||
description: 'The description of the rule',
|
||||
category: 'security', // `consistency`, `maintainability`, `reliability` or `security`
|
||||
reporter: 'project-reporter', // `project-reporter` or `project-files-reporter`
|
||||
implementation: async (context) => {
|
||||
const { projectGraph, ruleOptions } = context;
|
||||
// Your rule logic goes here
|
||||
return {
|
||||
severity: 'low', // 'high', 'medium' or 'low'
|
||||
details: {
|
||||
violations: [
|
||||
// Return an empty array if the rule passes
|
||||
{
|
||||
sourceProject: 'my-project',
|
||||
message: 'This is an informative error message.',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default rule;
|
||||
```
|
||||
|
||||
Note that the severity of the error is defined by the rule author and can be adjusted based on the specific violations that are found.
|
||||
- [Create a Conformance Rule](/nx-api/powerpack-conformance/documents/create-conformance-rule)
|
||||
- [Publish Conformance Rules to Nx Cloud](/ci/recipes/enterprise/conformance/publish-conformance-rules-to-nx-cloud)
|
||||
- [Configure Conformance Rules in Nx Cloud](/ci/recipes/enterprise/conformance/configure-conformance-rules-in-nx-cloud)
|
||||
|
||||
@@ -80,3 +80,29 @@ Finally, you need to configure your Nx cache in the `nx.json` file. The `bucket`
|
||||
| **Property** | **Description** |
|
||||
| ------------ | ----------------------------- |
|
||||
| **bucket** | The name of the bucket to use |
|
||||
|
||||
# Cache Modes
|
||||
|
||||
By default, Nx will try to write and read from the remote cache while running locally. This means that permissions must be set for users who are expected to access the remote cache.
|
||||
|
||||
Nx will only show warnings when the remote cache is not writable. You can disable these warnings by setting `localMode` to `read-only` or `no-cache` in the `nx.json` file.
|
||||
|
||||
```jsonc {% fileName="nx.json" %}
|
||||
{
|
||||
"gcs": {
|
||||
// ...
|
||||
"localMode": "read-only"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The cache mode in CI can also be configured by setting `ciMode` to `read-only` or `no-cache` in the `nx.json` file. Or setting `NX_POWERPACK_CACHE_MODE` to `read-only` or `no-cache` in the CI environment.
|
||||
|
||||
```jsonc {% fileName="nx.json" %}
|
||||
{
|
||||
"gcs": {
|
||||
// ...
|
||||
"ciMode": "read-only"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -152,3 +152,31 @@ Below is an example on how to connect to MinIO:
|
||||
| **endpoint** | The custom endpoint to upload artifacts to. If endpoint is not defined, the default AWS endpoint is used |
|
||||
| **accessKeyId** | AWS Access Key ID (optional if `AWS_ACCESS_KEY_ID` is set in the environment) |
|
||||
| **secretAccessKey** | AWS secret access key (optional if `AWS_SECRET_ACCESS_KEY` is set in the environment) |
|
||||
|
||||
# Cache Modes
|
||||
|
||||
By default, Nx will try to write and read from the remote cache while running locally. This means that permissions must be set for users who are expected to access the remote cache.
|
||||
|
||||
Nx will only show warnings when the remote cache is not writable. You can disable these warnings by setting `localMode` to `read-only` or `no-cache` in the `nx.json` file.
|
||||
|
||||
```jsonc {% fileName="nx.json" %}
|
||||
{
|
||||
"s3": {
|
||||
"region": "us-east-1",
|
||||
"bucket": "my-bucket",
|
||||
"localMode": "read-only"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The cache mode in CI can also be configured by setting `ciMode` to `read-only` or `no-cache` in the `nx.json` file. Or setting `NX_POWERPACK_CACHE_MODE` to `read-only` or `no-cache` in the CI environment.
|
||||
|
||||
```jsonc {% fileName="nx.json" %}
|
||||
{
|
||||
"s3": {
|
||||
"region": "us-east-1",
|
||||
"bucket": "my-bucket",
|
||||
"ciMode": "read-only"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -18,7 +18,7 @@ To generate only a host application in your workspace, run the following command
|
||||
{% tabs %}
|
||||
{% tab label="React" %}
|
||||
|
||||
```{% command="nx g @nx/react:host apps/shell --directory=apps/react/shell" %}
|
||||
```{% command="nx g @nx/react:host apps/react/shell" %}
|
||||
NX Generating @nx/react:host
|
||||
|
||||
CREATE apps/react/shell/src/app/app.spec.tsx
|
||||
@@ -48,7 +48,7 @@ CREATE apps/react/shell/webpack.config.prod.ts
|
||||
{% /tab %}
|
||||
{% tab label="Angular" %}
|
||||
|
||||
```{% command="nx g @nx/angular:host apps/shell --directory=apps/angular/shell" %}
|
||||
```{% command="nx g @nx/angular:host apps/angular/shell" %}
|
||||
NX Generating @nx/angular:host
|
||||
|
||||
CREATE apps/angular/shell/project.json
|
||||
|
||||
@@ -118,7 +118,7 @@ Attempting to keep track of this manually as a part of pull request reviews can
|
||||
nx release plan:check
|
||||
```
|
||||
|
||||
Running this command will analyze the changed files (supporting the same options you may be familiar with from `nx affected`, such as `--base`, `--head`, `--files`, `--uncommitted`, etc) and then determine which projects have been "touched" as a result. Note that it is specifically touched projects, and not affected in this case, because only directly changed projects are relevant for versioning. The side-effects of versioning independently released dependents are handled by the release process itself (controllable via the `version.generatorOptions.updatedDependents` option).
|
||||
Running this command will analyze the changed files (supporting the same options you may be familiar with from `nx affected`, such as `--base`, `--head`, `--files`, `--uncommitted`, etc) and then determine which projects have been "touched" as a result. Note that it is specifically touched projects, and not affected in this case, because only directly changed projects are relevant for versioning. The side-effects of versioning independently released dependents are handled by the release process itself (controllable via the `version.generatorOptions.updateDependents` option).
|
||||
|
||||
<!-- Prettier will mess up the end tag of the callout causing it to capture all content that follows it -->
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
@@ -21,7 +21,7 @@ Let’s see how we can implement this solution:
|
||||
According to the framework you are using, use the corresponding generator to generate a new library. Let’s suppose that you are using React and all your stories are using the `@storybook/react-vite` framework:
|
||||
|
||||
```shell
|
||||
nx g @nx/react:library lib/storybook-host --bundler=none --unitTestRunner=none
|
||||
nx g @nx/react:library libs/storybook-host --bundler=none --unitTestRunner=none
|
||||
```
|
||||
|
||||
Now, you have a new library, which will act as a shell/host for all your stories.
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
# Switch to Workspaces and Project References
|
||||
|
||||
If you want to take advantage of the [performance benefits](/concepts/typescript-project-linking#typescript-project-references-performance-benefits) of TypeScript project references, it is recommended to use package manager workspaces for local [project linking](/concepts/typescript-project-linking). If you are currently using TypeScript path aliases for project linking, follow the steps in this guide to switch to workspaces project linking and enable TypeScript project references.
|
||||
|
||||
## Enable Package Manager Workspaces
|
||||
|
||||
Follow the specific instructions for your package manager to enable workspaces project linking.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="npm" %}
|
||||
|
||||
```json {% fileName="package.json" %}
|
||||
{
|
||||
"workspaces": ["apps/**", "libs/**"]
|
||||
}
|
||||
```
|
||||
|
||||
Defining the `workspaces` property in the root `package.json` file lets npm know to look for other `package.json` files in the specified folders. With this configuration in place, all the dependencies for the individual projects will be installed in the root `node_modules` folder when `npm install` is run in the root folder. Also, the projects themselves will be linked in the root `node_modules` folder to be accessed as if they were npm packages.
|
||||
|
||||
If you reference a local library project with its own `build` task, you should include the library in the `devDependencies` of the application's `package.json` with `*` specified as the library's version. `*` tells npm to use whatever version of the project is available.
|
||||
|
||||
```json {% fileName="/apps/my-app/package.json" %}
|
||||
{
|
||||
"devDependencies": {
|
||||
"@my-org/some-project": "*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="yarn" %}
|
||||
|
||||
```json {% fileName="package.json" %}
|
||||
{
|
||||
"workspaces": ["apps/**", "libs/**"]
|
||||
}
|
||||
```
|
||||
|
||||
Defining the `workspaces` property in the root `package.json` file lets yarn know to look for other `package.json` files in the specified folders. With this configuration in place, all the dependencies for the individual projects will be installed in the root `node_modules` folder when `yarn` is run in the root folder. Also, the projects themselves will be linked in the root `node_modules` folder to be accessed as if they were npm packages.
|
||||
|
||||
If you reference a local library project with its own `build` task, you should include the library in the `devDependencies` of the application's `package.json` with `workspace:*` specified as the library's version. [`workspace:*` tells yarn that the project is in the same repository](https://yarnpkg.com/features/workspaces) and not an npm package. You want to specify local projects as `devDependencies` instead of `dependencies` so that the library is not included twice in the production bundle of the application.
|
||||
|
||||
```json {% fileName="/apps/my-app/package.json" %}
|
||||
{
|
||||
"devDependencies": {
|
||||
"@my-org/some-project": "*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="bun" %}
|
||||
|
||||
```json {% fileName="package.json" %}
|
||||
{
|
||||
"workspaces": ["apps/**", "libs/**"]
|
||||
}
|
||||
```
|
||||
|
||||
Defining the `workspaces` property in the root `package.json` file lets bun know to look for other `package.json` files in the specified folders. With this configuration in place, all the dependencies for the individual projects will be installed in the root `node_modules` folder when `bun install` is run in the root folder. Also, the projects themselves will be linked in the root `node_modules` folder to be accessed as if they were npm packages.
|
||||
|
||||
If you reference a local library project with its own `build` task, you should include the library in the `devDependencies` of the application's `package.json` with `workspace:*` specified as the library's version. [`workspace:*` tells bun that the project is in the same repository](https://bun.sh/docs/install/workspaces) and not an npm package. You want to specify local projects as `devDependencies` instead of `dependencies` so that the library is not included twice in the production bundle of the application.
|
||||
|
||||
```json {% fileName="/apps/my-app/package.json" %}
|
||||
{
|
||||
"devDependencies": {
|
||||
"@my-org/some-project": "workspace:*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="pnpm" %}
|
||||
|
||||
```yaml {% fileName="pnpm-workspace.yaml" %}
|
||||
packages:
|
||||
- 'apps/**'
|
||||
- 'libs/**'
|
||||
```
|
||||
|
||||
Defining the `packages` property in the root `pnpm-workspaces.yaml` file lets pnpm know to look for project `package.json` files in the specified folders. With this configuration in place, all the dependencies for the individual projects will be installed in the root `node_modules` folder when `pnpm install` is run in the root folder.
|
||||
|
||||
If you reference a local library project from an application, you need to include the library in the `devDependencies` of the application's `package.json` with `workspace:*` specified as the library's version. [`workspace:*` tells pnpm that the project is in the same repository](https://pnpm.io/workspaces#workspace-protocol-workspace) and not an npm package. You want to specify local projects as `devDependencies` instead of `dependencies` so that the library is not included twice in the production bundle of the application.
|
||||
|
||||
```json {% fileName="/apps/my-app/package.json" %}
|
||||
{
|
||||
"devDependencies": {
|
||||
"@my-org/some-project": "workspace:*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
## Update Root TypeScript Configuration
|
||||
|
||||
The root `tsconfig.base.json` should contain a `compilerOptions` property and no other properties. `compilerOptions.composite` and `compilerOptions.declaration` should be set to `true`. `compilerOptions.paths` and `compilerOptions.rootDir` should not be set.
|
||||
|
||||
Note: Before you delete the `paths` property, copy the project paths for use as `references` in the `tsconfig.json` file.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="Before" %}
|
||||
|
||||
```jsonc {% fileName="tsconfig.base.json" %}
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
// ...
|
||||
"paths": {
|
||||
"@myorg/utils": ["libs/utils/src/index.ts"],
|
||||
"@myorg/ui": ["libs/ui/src/index.ts"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="After" %}
|
||||
|
||||
```jsonc {% fileName="tsconfig.base.json" %}
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Required compiler options
|
||||
"composite": true,
|
||||
"declaration": true, // defaults to true when composite is true
|
||||
// Delete the paths property
|
||||
// Other options...
|
||||
"allowJs": false,
|
||||
"allowSyntheticDefaultImports": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The root `tsconfig.json` file should extend `tsconfig.base.json` and not include any files. It needs to have `references` for every project in the repository so that editor tooling works correctly.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="Before" %}
|
||||
|
||||
```jsonc {% fileName="tsconfig.json" %}
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"files": [] // intentionally empty
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="After" %}
|
||||
|
||||
```jsonc {% fileName="tsconfig.json" %}
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"files": [], // intentionally empty
|
||||
"references": [
|
||||
// All projects in the repository
|
||||
{
|
||||
"path": "./libs/utils"
|
||||
},
|
||||
{
|
||||
"path": "./libs/ui"
|
||||
}
|
||||
// Future generated projects will automatically be added here by the generator
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
## Create Individual Project package.json files
|
||||
|
||||
When using package manager project linking, every project needs to have a `package.json` file. You can leave all the task configuration in the existing `project.json` file. For application projects, you only need to specify the `name` property. For library projects, you should add an `exports` property that accounts for any TypeScript path aliases that referenced the project. A typical configuration is shown below:
|
||||
|
||||
```json {% fileName="libs/ui/package.json" %}
|
||||
{
|
||||
"name": "@myorg/ui",
|
||||
"exports": {
|
||||
".": "./src/index.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% callout type="warning" title="Package Names with Multiple Slashes" %}
|
||||
The `package.json` name can only have one `/` character in it. This is more restrictive than the TypeScript path aliases. So if you have a project that you have been referencing with `@myorg/shared/ui`, you'll need to make the `package.json` name be something like `@myorg/shared-ui` and update all the import statements in your codebase to reference the new name.
|
||||
{% /callout %}
|
||||
|
||||
## Update Individual Project TypeScript Configuration
|
||||
|
||||
Each project's `tsconfig.json` file should extend the `tsconfig.base.json` file and list `references` to the project's dependencies.
|
||||
|
||||
```jsonc {% fileName="libs/ui/tsconfig.json" %}
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"files": [], // intentionally empty
|
||||
"references": [
|
||||
// All project dependencies
|
||||
// UPDATED BY NX SYNC
|
||||
// This project's other tsconfig.*.json files
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Each project's `tsconfig.lib.json` file extends the project's `tsconfig.json` file and adds `references` to the `tsconfig.lib.json` files of project dependencies.
|
||||
|
||||
```jsonc {% fileName="libs/ui/tsconfig.lib.json" %}
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// Any overrides
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": [
|
||||
// exclude config and test files
|
||||
],
|
||||
"references": [
|
||||
// tsconfig.lib.json files for project dependencies
|
||||
// UPDATED BY NX SYNC
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The project's `tsconfig.spec.json` does not need to reference project dependencies.
|
||||
|
||||
```jsonc {% fileName="libs/ui/tsconfig.spec.json" %}
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// Any overrides
|
||||
},
|
||||
"include": [
|
||||
// test files
|
||||
],
|
||||
"references": [
|
||||
// tsconfig.lib.json for this project
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
After creating these `tsconfig.*.json` files, run `nx sync` to have Nx automatically add the correct references for each project.
|
||||
|
||||
## Future Plans
|
||||
|
||||
We realize that this manual migration process is tedious. We are investigating automating parts of this process with generators.
|
||||
@@ -17,7 +17,10 @@ The following environment variables are ones that you can set to change the beha
|
||||
| NX_WORKSPACE_DATA_DIRECTORY | string | The project graph cache and some other internal nx caches are stored in `.nx/workspace-data` by default. Set this variable to use a different directory. |
|
||||
| NX_PARALLEL | number | The number of tasks Nx should run in parallel. Overrides any configured value inside nx.json |
|
||||
| NX_RUNNER | string | The name of task runner from the config to use. Can be overridden on the command line with `--runner`. Not read if `NX_TASKS_RUNNER` is set. |
|
||||
| NX_SKIP_NX_CACHE | boolean | Rerun the tasks even when the results are available in the cache |
|
||||
| NX_SKIP_NX_CACHE | boolean | Rerun the tasks even when the results are available in the cache. |
|
||||
| NX_DISABLE_NX_CACHE | boolean | Rerun the tasks even when the results are available in the cache. |
|
||||
| NX_SKIP_REMOTE_CACHE | boolean | Disable all remote caching features. This means that the remote cache will not be read from or written to. Includes Nx Cloud and Powerpack Caches. |
|
||||
| NX_DISABLE_REMOTE_CACHE | boolean | Disable all remote caching features. This means that the remote cache will not be read from or written to. Includes Nx Cloud and Powerpack Caches. |
|
||||
| NX_TASKS_RUNNER | string | The name of task runner from the config to use. Can be overridden on the command line with `--runner`. Preferred over `NX_RUNNER`. |
|
||||
| NX_TASKS_RUNNER_DYNAMIC_OUTPUT | boolean | If set to `false`, will use non-dynamic terminal output strategy (what you see in CI), even when you terminal can support the dynamic version |
|
||||
| NX_VERBOSE_LOGGING | boolean | If set to `true`, will print debug information useful for troubleshooting |
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
keywords: [project.json]
|
||||
---
|
||||
|
||||
# Project Configuration
|
||||
|
||||
A project's configuration is constructed by Nx from three sources:
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
- [Common Tasks](/concepts/common-tasks)
|
||||
- [Nx Daemon](/concepts/nx-daemon)
|
||||
- [Sync Generators](/concepts/sync-generators)
|
||||
- [TypeScript Project Linking](/concepts/typescript-project-linking)
|
||||
- [Nx and Turborepo](/concepts/turbo-and-nx)
|
||||
- [Buildable and Publishable Libraries](/concepts/buildable-and-publishable-libraries)
|
||||
- [Module Federation](/concepts/module-federation)
|
||||
@@ -158,6 +159,7 @@
|
||||
- [Tips and tricks](/recipes/tips-n-tricks)
|
||||
- [Convert from a Standalone Repository to a Monorepo](/recipes/tips-n-tricks/standalone-to-monorepo)
|
||||
- [Configuring ESLint with Typescript](/recipes/tips-n-tricks/eslint)
|
||||
- [Switch to Workspaces and TS Project References](/recipes/tips-n-tricks/switch-to-workspaces-project-references)
|
||||
- [Enable Typescript Batch Mode](/recipes/tips-n-tricks/enable-tsc-batch-mode)
|
||||
- [Define Secondary Entrypoints for Typescript Packages](/recipes/tips-n-tricks/define-secondary-entrypoints)
|
||||
- [Compile Typescript Packages to Multiple Formats](/recipes/tips-n-tricks/compile-multiple-formats)
|
||||
@@ -482,7 +484,6 @@
|
||||
- [js](/nx-api/js)
|
||||
- [documents](/nx-api/js/documents)
|
||||
- [Overview](/nx-api/js/documents/overview)
|
||||
- [Configure TypeScript Project References in an Nx Workspace](/nx-api/js/documents/typescript-project-references)
|
||||
- [executors](/nx-api/js/executors)
|
||||
- [tsc](/nx-api/js/executors/tsc)
|
||||
- [swc](/nx-api/js/executors/swc)
|
||||
@@ -793,8 +794,11 @@
|
||||
- [powerpack-conformance](/nx-api/powerpack-conformance)
|
||||
- [documents](/nx-api/powerpack-conformance/documents)
|
||||
- [Overview](/nx-api/powerpack-conformance/documents/overview)
|
||||
- [Create a Conformance Rule](/nx-api/powerpack-conformance/documents/create-conformance-rule)
|
||||
- [executors](/nx-api/powerpack-conformance/executors)
|
||||
- [bundle-rules](/nx-api/powerpack-conformance/executors/bundle-rules)
|
||||
- [generators](/nx-api/powerpack-conformance/generators)
|
||||
- [create-rule](/nx-api/powerpack-conformance/generators/create-rule)
|
||||
- [powerpack-enterprise-cloud](/nx-api/powerpack-enterprise-cloud)
|
||||
- [generators](/nx-api/powerpack-enterprise-cloud/generators)
|
||||
- [init](/nx-api/powerpack-enterprise-cloud/generators/init)
|
||||
@@ -811,8 +815,6 @@
|
||||
- [powerpack-s3-cache](/nx-api/powerpack-s3-cache)
|
||||
- [documents](/nx-api/powerpack-s3-cache/documents)
|
||||
- [Overview](/nx-api/powerpack-s3-cache/documents/overview)
|
||||
- [generators](/nx-api/powerpack-s3-cache/generators)
|
||||
- [init](/nx-api/powerpack-s3-cache/generators/init)
|
||||
- [powerpack-shared-fs-cache](/nx-api/powerpack-shared-fs-cache)
|
||||
- [documents](/nx-api/powerpack-shared-fs-cache/documents)
|
||||
- [Overview](/nx-api/powerpack-shared-fs-cache/documents/overview)
|
||||
|
||||
@@ -153,10 +153,32 @@ function runNgNew(projectName: string, cwd: string): void {
|
||||
const pmc = getPackageManagerCommand({ packageManager });
|
||||
|
||||
const command = `${pmc.runUninstalledPackage} @angular/cli@${angularCliVersion} new ${projectName} --package-manager=${packageManager}`;
|
||||
cwd = join(tmpProjPath(), cwd);
|
||||
ensureDirSync(cwd);
|
||||
const fullCwd = join(tmpProjPath(), cwd);
|
||||
ensureDirSync(fullCwd);
|
||||
execSync(command, {
|
||||
cwd,
|
||||
cwd: fullCwd,
|
||||
stdio: isVerbose() ? 'inherit' : 'pipe',
|
||||
env: process.env,
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
// ensure angular packages are installed with ~ instead of ^ to prevent
|
||||
// potential failures when new minor versions are released
|
||||
function updateAngularDependencies(dependencies: any): void {
|
||||
Object.keys(dependencies).forEach((key) => {
|
||||
if (key.startsWith('@angular/') || key.startsWith('@angular-devkit/')) {
|
||||
dependencies[key] = dependencies[key].replace(/^\^/, '~');
|
||||
}
|
||||
});
|
||||
}
|
||||
updateJson(join(cwd, projectName, 'package.json'), (json) => {
|
||||
updateAngularDependencies(json.dependencies ?? {});
|
||||
updateAngularDependencies(json.devDependencies ?? {});
|
||||
return json;
|
||||
});
|
||||
|
||||
execSync(pmc.install, {
|
||||
cwd: join(fullCwd, projectName),
|
||||
stdio: isVerbose() ? 'inherit' : 'pipe',
|
||||
env: process.env,
|
||||
encoding: 'utf-8',
|
||||
|
||||
@@ -174,10 +174,13 @@ describe('Tailwind support', () => {
|
||||
|
||||
const assertLibComponentStyles = (
|
||||
lib: string,
|
||||
libSpacing: (typeof spacing)['root']
|
||||
libSpacing: (typeof spacing)['root'],
|
||||
isPublishable: boolean = true
|
||||
) => {
|
||||
const builtComponentContent = readFile(
|
||||
`dist/${lib}/fesm2022/${project}-${lib}.mjs`
|
||||
isPublishable
|
||||
? `dist/${lib}/fesm2022/${project}-${lib}.mjs`
|
||||
: `dist/${lib}/esm2022/lib/foo.component.mjs`
|
||||
);
|
||||
let expectedStylesRegex = new RegExp(
|
||||
`styles: \\[\\"\\.custom\\-btn(\\[_ngcontent\\-%COMP%\\])?{margin:${libSpacing.md};padding:${libSpacing.sm}}(\\\\n)?\\"\\]`
|
||||
@@ -203,7 +206,8 @@ describe('Tailwind support', () => {
|
||||
|
||||
assertLibComponentStyles(
|
||||
buildLibWithTailwind.name,
|
||||
spacing.projectVariant1
|
||||
spacing.projectVariant1,
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
@@ -223,7 +227,11 @@ describe('Tailwind support', () => {
|
||||
|
||||
runCLI(`build ${buildLibSetupTailwind}`);
|
||||
|
||||
assertLibComponentStyles(buildLibSetupTailwind, spacing.projectVariant2);
|
||||
assertLibComponentStyles(
|
||||
buildLibSetupTailwind,
|
||||
spacing.projectVariant2,
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it('should correctly build a buildable library with a tailwind.config.js file in the project root or workspace root', () => {
|
||||
@@ -241,7 +249,8 @@ describe('Tailwind support', () => {
|
||||
|
||||
assertLibComponentStyles(
|
||||
buildLibNoProjectConfig,
|
||||
spacing.projectVariant3
|
||||
spacing.projectVariant3,
|
||||
false
|
||||
);
|
||||
|
||||
// remove tailwind.config.js file from the project root to test the one in the workspace root
|
||||
@@ -249,7 +258,7 @@ describe('Tailwind support', () => {
|
||||
|
||||
runCLI(`build ${buildLibNoProjectConfig}`);
|
||||
|
||||
assertLibComponentStyles(buildLibNoProjectConfig, spacing.root);
|
||||
assertLibComponentStyles(buildLibNoProjectConfig, spacing.root, false);
|
||||
});
|
||||
|
||||
it('should generate a publishable library with tailwind and build correctly', () => {
|
||||
|
||||
@@ -48,7 +48,7 @@ describe('EsBuild Plugin', () => {
|
||||
private: true,
|
||||
type: 'commonjs',
|
||||
main: './index.cjs',
|
||||
typings: './index.d.ts',
|
||||
types: './index.d.ts',
|
||||
dependencies: {},
|
||||
});
|
||||
|
||||
|
||||
@@ -567,7 +567,7 @@ describe('Linter', () => {
|
||||
name: `@proj/${mylib}`,
|
||||
private: true,
|
||||
type: 'commonjs',
|
||||
typings: './src/index.d.ts',
|
||||
types: './src/index.d.ts',
|
||||
version: '0.0.1',
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
import {
|
||||
cleanupProject,
|
||||
getPackageManagerCommand,
|
||||
getSelectedPackageManager,
|
||||
newProject,
|
||||
runCLI,
|
||||
runCommand,
|
||||
uniq,
|
||||
updateFile,
|
||||
updateJson,
|
||||
} from '@nx/e2e/utils';
|
||||
|
||||
describe('JS - TS solution setup', () => {
|
||||
beforeAll(() => {
|
||||
newProject({
|
||||
packages: ['@nx/js'],
|
||||
preset: 'ts',
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
cleanupProject();
|
||||
});
|
||||
|
||||
it('should generate libraries with different bundlers and link them successfully', () => {
|
||||
const esbuildParentLib = uniq('esbuild-parent-lib');
|
||||
const esbuildChildLib = uniq('esbuild-child-lib');
|
||||
const rollupParentLib = uniq('rollup-parent-lib');
|
||||
const rollupChildLib = uniq('rollup-child-lib');
|
||||
const swcParentLib = uniq('swc-parent-lib');
|
||||
const swcChildLib = uniq('swc-child-lib');
|
||||
const tscParentLib = uniq('tsc-parent-lib');
|
||||
const tscChildLib = uniq('tsc-child-lib');
|
||||
const viteParentLib = uniq('vite-parent-lib');
|
||||
const viteChildLib = uniq('vite-child-lib');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/js:lib packages/${esbuildParentLib} --bundler=esbuild --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/js:lib packages/${esbuildChildLib} --bundler=esbuild --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/js:lib packages/${rollupParentLib} --bundler=rollup --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/js:lib packages/${rollupChildLib} --bundler=rollup --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/js:lib packages/${swcParentLib} --bundler=swc --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/js:lib packages/${swcChildLib} --bundler=swc --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/js:lib packages/${tscParentLib} --bundler=tsc --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/js:lib packages/${tscChildLib} --bundler=tsc --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/js:lib packages/${viteParentLib} --bundler=vite --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/js:lib packages/${viteChildLib} --bundler=vite --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
|
||||
// add deps, each parent lib imports all child libs
|
||||
const addImports = (parentLib: string) => {
|
||||
updateFile(
|
||||
`packages/${parentLib}/src/index.ts`,
|
||||
(content) => `export * from '@proj/${esbuildChildLib}';
|
||||
export * from '@proj/${rollupChildLib}';
|
||||
export * from '@proj/${swcChildLib}';
|
||||
export * from '@proj/${tscChildLib}';
|
||||
export * from '@proj/${viteChildLib}';
|
||||
${content}`
|
||||
);
|
||||
};
|
||||
|
||||
addImports(esbuildParentLib);
|
||||
addImports(rollupParentLib);
|
||||
addImports(swcParentLib);
|
||||
addImports(tscParentLib);
|
||||
addImports(viteParentLib);
|
||||
|
||||
const pm = getSelectedPackageManager();
|
||||
if (pm === 'pnpm') {
|
||||
// for pnpm we need to add the local packages as dependencies to each consumer package.json
|
||||
const addDeps = (parentLib: string) => {
|
||||
updateJson(`packages/${parentLib}/package.json`, (json) => {
|
||||
json.dependencies ??= {};
|
||||
json.dependencies[`@proj/${esbuildChildLib}`] = 'workspace:*';
|
||||
json.dependencies[`@proj/${rollupChildLib}`] = 'workspace:*';
|
||||
json.dependencies[`@proj/${swcChildLib}`] = 'workspace:*';
|
||||
json.dependencies[`@proj/${tscChildLib}`] = 'workspace:*';
|
||||
json.dependencies[`@proj/${viteChildLib}`] = 'workspace:*';
|
||||
return json;
|
||||
});
|
||||
};
|
||||
|
||||
addDeps(esbuildParentLib);
|
||||
addDeps(rollupParentLib);
|
||||
addDeps(swcParentLib);
|
||||
addDeps(tscParentLib);
|
||||
addDeps(viteParentLib);
|
||||
|
||||
const pmc = getPackageManagerCommand({ packageManager: pm });
|
||||
runCommand(pmc.install);
|
||||
}
|
||||
|
||||
// sync to ensure the TS project references are updated
|
||||
runCLI(`sync`);
|
||||
|
||||
// check build
|
||||
expect(runCLI(`build ${esbuildParentLib}`)).toContain(
|
||||
`Successfully ran target build for project ${esbuildParentLib} and 5 tasks it depends on`
|
||||
);
|
||||
expect(runCLI(`build ${rollupParentLib}`)).toContain(
|
||||
`Successfully ran target build for project ${rollupParentLib} and 5 tasks it depends on`
|
||||
);
|
||||
expect(runCLI(`build ${swcParentLib}`)).toContain(
|
||||
`Successfully ran target build for project ${swcParentLib} and 5 tasks it depends on`
|
||||
);
|
||||
expect(runCLI(`build ${tscParentLib}`)).toContain(
|
||||
`Successfully ran target build for project ${tscParentLib} and 5 tasks it depends on`
|
||||
);
|
||||
expect(runCLI(`build ${viteParentLib}`)).toContain(
|
||||
`Successfully ran target build for project ${viteParentLib} and 5 tasks it depends on`
|
||||
);
|
||||
|
||||
// check typecheck
|
||||
expect(runCLI(`typecheck ${esbuildParentLib}`)).toContain(
|
||||
`Successfully ran target typecheck for project ${esbuildParentLib} and 5 tasks it depends on`
|
||||
);
|
||||
expect(runCLI(`typecheck ${rollupParentLib}`)).toContain(
|
||||
`Successfully ran target typecheck for project ${rollupParentLib} and 5 tasks it depends on`
|
||||
);
|
||||
expect(runCLI(`typecheck ${swcParentLib}`)).toContain(
|
||||
`Successfully ran target typecheck for project ${swcParentLib} and 5 tasks it depends on`
|
||||
);
|
||||
expect(runCLI(`typecheck ${tscParentLib}`)).toContain(
|
||||
`Successfully ran target typecheck for project ${tscParentLib} and 5 tasks it depends on`
|
||||
);
|
||||
expect(runCLI(`typecheck ${viteParentLib}`)).toContain(
|
||||
`Successfully ran target typecheck for project ${viteParentLib} and 5 tasks it depends on`
|
||||
);
|
||||
|
||||
// check lint
|
||||
expect(runCLI(`lint ${esbuildParentLib}`)).toContain(
|
||||
`Successfully ran target lint for project ${esbuildParentLib}`
|
||||
);
|
||||
expect(runCLI(`lint ${rollupParentLib}`)).toContain(
|
||||
`Successfully ran target lint for project ${rollupParentLib}`
|
||||
);
|
||||
expect(runCLI(`lint ${swcParentLib}`)).toContain(
|
||||
`Successfully ran target lint for project ${swcParentLib}`
|
||||
);
|
||||
expect(runCLI(`lint ${tscParentLib}`)).toContain(
|
||||
`Successfully ran target lint for project ${tscParentLib}`
|
||||
);
|
||||
expect(runCLI(`lint ${viteParentLib}`)).toContain(
|
||||
`Successfully ran target lint for project ${viteParentLib}`
|
||||
);
|
||||
|
||||
// check test
|
||||
expect(runCLI(`test ${esbuildParentLib}`)).toContain(
|
||||
`Successfully ran target test for project ${esbuildParentLib}`
|
||||
);
|
||||
expect(runCLI(`test ${rollupParentLib}`)).toContain(
|
||||
`Successfully ran target test for project ${rollupParentLib}`
|
||||
);
|
||||
expect(runCLI(`test ${swcParentLib}`)).toContain(
|
||||
`Successfully ran target test for project ${swcParentLib}`
|
||||
);
|
||||
expect(runCLI(`test ${tscParentLib}`)).toContain(
|
||||
`Successfully ran target test for project ${tscParentLib}`
|
||||
);
|
||||
expect(runCLI(`test ${viteParentLib}`)).toContain(
|
||||
`Successfully ran target test for project ${viteParentLib}`
|
||||
);
|
||||
}, 300_000);
|
||||
});
|
||||
@@ -28,21 +28,6 @@ describe('Node Applications', () => {
|
||||
packages: ['@nx/node', '@nx/express', '@nx/nest', '@nx/webpack'],
|
||||
preset: 'ts',
|
||||
});
|
||||
if (pm === 'pnpm') {
|
||||
updateFile(
|
||||
'pnpm-workspace.yaml',
|
||||
`
|
||||
packages:
|
||||
- 'apps/**'
|
||||
- 'packages/**'
|
||||
`
|
||||
);
|
||||
} else {
|
||||
updateJson('package.json', (json) => {
|
||||
json.workspaces = ['apps/**', 'packages/**'];
|
||||
return json;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
@@ -116,9 +101,11 @@ packages:
|
||||
expect(() => runCLI(`lint ${nodeapp}`)).not.toThrow();
|
||||
expect(() => runCLI(`test ${nodeapp}`)).not.toThrow();
|
||||
expect(() => runCLI(`build ${nodeapp}`)).not.toThrow();
|
||||
expect(() => runCLI(`typecheck ${nodeapp}`)).not.toThrow();
|
||||
expect(() => runCLI(`lint ${nodelib}`)).not.toThrow();
|
||||
expect(() => runCLI(`test ${nodelib}`)).not.toThrow();
|
||||
expect(() => runCLI(`build ${nodelib}`)).not.toThrow();
|
||||
expect(() => runCLI(`typecheck ${nodelib}`)).not.toThrow();
|
||||
|
||||
const p = await runCommandUntil(
|
||||
`serve ${nodeapp}`,
|
||||
@@ -144,6 +131,45 @@ packages:
|
||||
expect(err).toBeFalsy();
|
||||
}
|
||||
}, 300_000);
|
||||
|
||||
it('should be able to generate a nest application', async () => {
|
||||
const nestapp = uniq('nodeapp');
|
||||
const port = getRandomPort();
|
||||
process.env.PORT = `${port}`;
|
||||
runCLI(
|
||||
`generate @nx/nest:app apps/${nestapp} --linter=eslint --unitTestRunner=jest`
|
||||
);
|
||||
|
||||
expect(() => runCLI(`lint ${nestapp}`)).not.toThrow();
|
||||
expect(() => runCLI(`test ${nestapp}`)).not.toThrow();
|
||||
|
||||
runCLI(`build ${nestapp}`);
|
||||
checkFilesExist(`dist/apps/${nestapp}/main.js`);
|
||||
|
||||
const p = await runCommandUntil(
|
||||
`serve ${nestapp}`,
|
||||
(output) =>
|
||||
output.includes(
|
||||
`Application is running on: http://localhost:${port}/api`
|
||||
),
|
||||
|
||||
{
|
||||
env: {
|
||||
NX_DAEMON: 'true',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const result = await getData(port, '/api');
|
||||
expect(result.message).toMatch('Hello');
|
||||
|
||||
try {
|
||||
await promisifiedTreeKill(p.pid, 'SIGKILL');
|
||||
expect(await killPorts(port)).toBeTruthy();
|
||||
} catch (err) {
|
||||
expect(err).toBeFalsy();
|
||||
}
|
||||
}, 300_000);
|
||||
});
|
||||
|
||||
function getRandomPort() {
|
||||
|
||||
@@ -31,8 +31,8 @@ describe('Nx Affected and Graph Tests', () => {
|
||||
const mylib = uniq('mylib');
|
||||
const mylib2 = uniq('mylib2');
|
||||
const mypublishablelib = uniq('mypublishablelib');
|
||||
runCLI(`generate @nx/web:app apps/${myapp}`);
|
||||
runCLI(`generate @nx/web:app apps/${myapp2}`);
|
||||
runCLI(`generate @nx/web:app apps/${myapp} --unitTestRunner=vitest`);
|
||||
runCLI(`generate @nx/web:app apps/${myapp2} --unitTestRunner=vitest`);
|
||||
runCLI(`generate @nx/js:lib libs/${mylib}`);
|
||||
runCLI(`generate @nx/js:lib libs/${mylib2}`);
|
||||
runCLI(
|
||||
@@ -193,8 +193,12 @@ describe('Nx Affected and Graph Tests', () => {
|
||||
});
|
||||
|
||||
function generateAll() {
|
||||
runCLI(`generate @nx/web:app apps/${myapp}`);
|
||||
runCLI(`generate @nx/web:app apps/${myapp2}`);
|
||||
runCLI(
|
||||
`generate @nx/web:app apps/${myapp} --bundler=webpack --unitTestRunner=vitest`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/web:app apps/${myapp2} --bundler=webpack --unitTestRunner=vitest`
|
||||
);
|
||||
runCLI(`generate @nx/js:lib libs/${mylib}`);
|
||||
runCommand(`git add . && git commit -am "add all"`);
|
||||
}
|
||||
@@ -536,8 +540,12 @@ describe('show projects --affected', () => {
|
||||
const mylib2 = uniq('mylib2');
|
||||
const mypublishablelib = uniq('mypublishablelib');
|
||||
|
||||
runCLI(`generate @nx/web:app ${myapp} --directory=apps/${myapp}`);
|
||||
runCLI(`generate @nx/web:app ${myapp2} --directory=apps/${myapp2}`);
|
||||
runCLI(
|
||||
`generate @nx/web:app ${myapp} --directory=apps/${myapp} --unitTestRunner=vitest`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/web:app ${myapp2} --directory=apps/${myapp2} --unitTestRunner=vitest`
|
||||
);
|
||||
runCLI(`generate @nx/js:lib ${mylib} --directory=libs/${mylib}`);
|
||||
runCLI(`generate @nx/js:lib ${mylib2} --directory=libs/${mylib2}`);
|
||||
runCLI(
|
||||
|
||||
@@ -68,9 +68,11 @@ describe('Nx Commands', () => {
|
||||
|
||||
it('should show detailed project info', () => {
|
||||
const app = uniq('myapp');
|
||||
runCLI(`generate @nx/web:app apps/${app}`);
|
||||
runCLI(
|
||||
`generate @nx/web:app apps/${app} --bundler=webpack --unitTestRunner=vitest --linter=eslint`
|
||||
);
|
||||
const project: ProjectConfiguration = JSON.parse(
|
||||
runCLI(`show project ${app}`)
|
||||
runCLI(`show project ${app} --json`)
|
||||
);
|
||||
expect(project.targets.build).toBeDefined();
|
||||
expect(project.targets.lint).toBeDefined();
|
||||
|
||||
@@ -695,8 +695,12 @@ describe('Nx Running Tests', () => {
|
||||
it('should run multiple targets', () => {
|
||||
const myapp1 = uniq('myapp');
|
||||
const myapp2 = uniq('myapp');
|
||||
runCLI(`generate @nx/web:app ${myapp1} --directory=apps/${myapp1}`);
|
||||
runCLI(`generate @nx/web:app ${myapp2} --directory=apps/${myapp2}`);
|
||||
runCLI(
|
||||
`generate @nx/web:app ${myapp1} --directory=apps/${myapp1} --unitTestRunner=vitest`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/web:app ${myapp2} --directory=apps/${myapp2} --unitTestRunner=vitest`
|
||||
);
|
||||
|
||||
let outputs = runCLI(
|
||||
// Options with lists can be specified using multiple args or with a delimiter (comma or space).
|
||||
|
||||
@@ -23,6 +23,62 @@ describe('Nx Plugin (TS solution)', () => {
|
||||
|
||||
afterAll(() => cleanupProject());
|
||||
|
||||
it('should be able to generate a Nx Plugin with generators, executors and migrations', async () => {
|
||||
const plugin = uniq('plugin');
|
||||
const generator = uniq('generator');
|
||||
const executor = uniq('executor');
|
||||
const migrationVersion = '1.0.0';
|
||||
|
||||
runCLI(
|
||||
`generate @nx/plugin:plugin packages/${plugin} --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest --publishable`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/plugin:generator packages/${plugin}/src/generators/${generator}/generator --name ${generator}`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/plugin:executor packages/${plugin}/src/executors/${executor}/executor --name ${executor} --includeHasher`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/plugin:migration packages/${plugin}/src/migrations/update-${migrationVersion}/update-${migrationVersion} --packageVersion=${migrationVersion} --packageJsonUpdates=false`
|
||||
);
|
||||
|
||||
expect(runCLI(`lint ${plugin}`)).toContain(
|
||||
`Successfully ran target lint for project ${plugin}`
|
||||
);
|
||||
expect(runCLI(`typecheck ${plugin}`)).toContain(
|
||||
`Successfully ran target typecheck for project ${plugin}`
|
||||
);
|
||||
expect(runCLI(`build ${plugin}`)).toContain(
|
||||
`Successfully ran target build for project ${plugin}`
|
||||
);
|
||||
checkFilesExist(
|
||||
// entry point
|
||||
`packages/${plugin}/dist/index.js`,
|
||||
`packages/${plugin}/dist/index.d.ts`,
|
||||
// generator
|
||||
`packages/${plugin}/dist/generators/${generator}/schema.json`,
|
||||
`packages/${plugin}/dist/generators/${generator}/schema.d.ts`,
|
||||
`packages/${plugin}/dist/generators/${generator}/generator.js`,
|
||||
`packages/${plugin}/dist/generators/${generator}/generator.d.ts`,
|
||||
// executor
|
||||
`packages/${plugin}/dist/executors/${executor}/schema.json`,
|
||||
`packages/${plugin}/dist/executors/${executor}/schema.d.ts`,
|
||||
`packages/${plugin}/dist/executors/${executor}/executor.js`,
|
||||
`packages/${plugin}/dist/executors/${executor}/executor.d.ts`,
|
||||
`packages/${plugin}/dist/executors/${executor}/hasher.js`,
|
||||
`packages/${plugin}/dist/executors/${executor}/hasher.d.ts`,
|
||||
// migration
|
||||
`packages/${plugin}/dist/migrations/update-${migrationVersion}/update-${migrationVersion}.js`,
|
||||
`packages/${plugin}/dist/migrations/update-${migrationVersion}/update-${migrationVersion}.d.ts`
|
||||
);
|
||||
expect(runCLI(`test ${plugin}`)).toContain(
|
||||
`Successfully ran target test for project ${plugin}`
|
||||
);
|
||||
expect(runCLI(`e2e ${plugin}-e2e`)).toContain(
|
||||
`Successfully ran target e2e for project ${plugin}-e2e`
|
||||
);
|
||||
}, 90000);
|
||||
|
||||
it('should be able to infer projects and targets', async () => {
|
||||
const plugin = uniq('plugin');
|
||||
runCLI(`generate @nx/plugin:plugin packages/${plugin}`);
|
||||
|
||||
@@ -486,6 +486,29 @@ export function runNgNew(
|
||||
env: process.env,
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
// ensure angular packages are installed with ~ instead of ^ to prevent
|
||||
// potential failures when new minor versions are released
|
||||
function updateAngularDependencies(dependencies: any): void {
|
||||
Object.keys(dependencies).forEach((key) => {
|
||||
if (key.startsWith('@angular/') || key.startsWith('@angular-devkit/')) {
|
||||
dependencies[key] = dependencies[key].replace(/^\^/, '~');
|
||||
}
|
||||
});
|
||||
}
|
||||
updateJson('package.json', (json) => {
|
||||
updateAngularDependencies(json.dependencies ?? {});
|
||||
updateAngularDependencies(json.devDependencies ?? {});
|
||||
return json;
|
||||
});
|
||||
|
||||
execSync(pmc.install, {
|
||||
cwd: join(e2eCwd, projName),
|
||||
stdio: isVerbose() ? 'inherit' : 'pipe',
|
||||
env: process.env,
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
copySync(tmpProjPath(), tmpBackupNgCliProjPath());
|
||||
|
||||
if (isVerboseE2ERun()) {
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import { names } from '@nx/devkit';
|
||||
import {
|
||||
cleanupProject,
|
||||
getPackageManagerCommand,
|
||||
getSelectedPackageManager,
|
||||
newProject,
|
||||
runCLI,
|
||||
runCommand,
|
||||
uniq,
|
||||
updateFile,
|
||||
updateJson,
|
||||
} from '@nx/e2e/utils';
|
||||
|
||||
describe('Vite - TS solution setup', () => {
|
||||
beforeAll(() => {
|
||||
newProject({
|
||||
packages: ['@nx/react', '@nx/js'],
|
||||
preset: 'ts',
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
cleanupProject();
|
||||
});
|
||||
|
||||
it('should generate app and consume libraries with different bundlers', () => {
|
||||
const reactApp = uniq('react-app');
|
||||
const esbuildLib = uniq('esbuild-lib');
|
||||
const rollupLib = uniq('rollup-lib');
|
||||
const swcLib = uniq('swc-lib');
|
||||
const tscLib = uniq('tsc-lib');
|
||||
const viteLib = uniq('vite-lib');
|
||||
const noBundlerLib = uniq('no-bundler-lib');
|
||||
|
||||
runCLI(`generate @nx/react:app apps/${reactApp} --bundler=vite`);
|
||||
runCLI(`generate @nx/js:lib packages/${esbuildLib} --bundler=esbuild`);
|
||||
runCLI(`generate @nx/js:lib packages/${rollupLib} --bundler=rollup`);
|
||||
runCLI(`generate @nx/js:lib packages/${swcLib} --bundler=swc`);
|
||||
runCLI(`generate @nx/js:lib packages/${tscLib} --bundler=tsc`);
|
||||
runCLI(`generate @nx/js:lib packages/${viteLib} --bundler=vite`);
|
||||
runCLI(`generate @nx/js:lib packages/${noBundlerLib} --bundler=none`);
|
||||
|
||||
// import all libs from the app
|
||||
updateFile(
|
||||
`apps/${reactApp}/src/app/app.tsx`,
|
||||
(content) => `import { ${
|
||||
names(esbuildLib).propertyName
|
||||
} } from '@proj/${esbuildLib}';
|
||||
import { ${names(rollupLib).propertyName} } from '@proj/${rollupLib}';
|
||||
import { ${names(swcLib).propertyName} } from '@proj/${swcLib}';
|
||||
import { ${names(tscLib).propertyName} } from '@proj/${tscLib}';
|
||||
import { ${names(viteLib).propertyName} } from '@proj/${viteLib}';
|
||||
import { ${names(noBundlerLib).propertyName} } from '@proj/${noBundlerLib}';
|
||||
|
||||
console.log(
|
||||
${names(esbuildLib).propertyName}(),
|
||||
${names(rollupLib).propertyName}(),
|
||||
${names(swcLib).propertyName}(),
|
||||
${names(tscLib).propertyName}(),
|
||||
${names(viteLib).propertyName}(),
|
||||
${names(noBundlerLib).propertyName}()
|
||||
);
|
||||
|
||||
${content}`
|
||||
);
|
||||
|
||||
const pm = getSelectedPackageManager();
|
||||
if (pm === 'pnpm') {
|
||||
// for pnpm we need to add the local packages as dependencies to each consumer package.json
|
||||
updateJson(`apps/${reactApp}/package.json`, (json) => {
|
||||
json.dependencies ??= {};
|
||||
json.dependencies[`@proj/${esbuildLib}`] = 'workspace:*';
|
||||
json.dependencies[`@proj/${rollupLib}`] = 'workspace:*';
|
||||
json.dependencies[`@proj/${swcLib}`] = 'workspace:*';
|
||||
json.dependencies[`@proj/${tscLib}`] = 'workspace:*';
|
||||
json.dependencies[`@proj/${viteLib}`] = 'workspace:*';
|
||||
json.dependencies[`@proj/${noBundlerLib}`] = 'workspace:*';
|
||||
return json;
|
||||
});
|
||||
|
||||
const pmc = getPackageManagerCommand({ packageManager: pm });
|
||||
runCommand(pmc.install);
|
||||
}
|
||||
|
||||
// sync to ensure the TS project references are updated
|
||||
runCLI(`sync`);
|
||||
|
||||
// check build
|
||||
expect(runCLI(`build ${reactApp}`)).toContain(
|
||||
`Successfully ran target build for project ${reactApp} and 5 tasks it depends on`
|
||||
);
|
||||
|
||||
// check typecheck
|
||||
expect(runCLI(`typecheck ${reactApp}`)).toContain(
|
||||
`Successfully ran target typecheck for project ${reactApp} and 6 tasks it depends on`
|
||||
);
|
||||
}, 300_000);
|
||||
});
|
||||
@@ -206,6 +206,47 @@ describe('@nx/vite/plugin', () => {
|
||||
|
||||
expect(() => runCLI(`test ${mylib}`)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not partially match a path mapping', () => {
|
||||
const lib1 = uniq('lib1');
|
||||
const lib2 = uniq('lib2');
|
||||
const lib3 = uniq('lib3');
|
||||
runCLI(
|
||||
`generate @nx/react:library libs/${lib1} --bundler=none --unitTestRunner=vitest`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/react:library libs/${lib2} --bundler=none --unitTestRunner=vitest`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/react:library libs/${lib3} --bundler=none --unitTestRunner=vitest`
|
||||
);
|
||||
updateFile(`libs/${lib1}/src/foo.enum.ts`, `export const foo = 'foo';`);
|
||||
updateFile(`libs/${lib2}/src/bar.enum.ts`, `export const bar = 'bar';`);
|
||||
updateFile(`libs/${lib3}/src/bam.enum.ts`, `export const bam = 'bam';`);
|
||||
updateFile(
|
||||
`libs/${lib1}/src/foo.spec.ts`,
|
||||
`
|
||||
import { foo } from 'match-lib-deep/foo.enum';
|
||||
import { bar } from 'match-lib-top-level';
|
||||
import { bam } from 'match-lib/bam.enum';
|
||||
test('should work', () => {
|
||||
expect(foo).toBeDefined();
|
||||
expect(bar).toBeDefined();
|
||||
expect(bam).toBeDefined();
|
||||
});
|
||||
`
|
||||
);
|
||||
updateJson('tsconfig.base.json', (json) => {
|
||||
json.compilerOptions.paths['match-lib-deep/*'] = [`libs/${lib1}/src/*`];
|
||||
json.compilerOptions.paths['match-lib-top-level'] = [
|
||||
`libs/${lib2}/src/bar.enum.ts`,
|
||||
];
|
||||
json.compilerOptions.paths['match-lib/*'] = [`libs/${lib3}/src/*`];
|
||||
return json;
|
||||
});
|
||||
|
||||
expect(() => runCLI(`test ${lib1}`)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('react with vitest only', () => {
|
||||
|
||||
@@ -18,21 +18,6 @@ describe('Vue Plugin', () => {
|
||||
packages: ['@nx/vue'],
|
||||
preset: 'ts',
|
||||
});
|
||||
if (pm === 'pnpm') {
|
||||
updateFile(
|
||||
'pnpm-workspace.yaml',
|
||||
`
|
||||
packages:
|
||||
- 'apps/**'
|
||||
- 'packages/**'
|
||||
`
|
||||
);
|
||||
} else {
|
||||
updateJson('package.json', (json) => {
|
||||
json.workspaces = ['apps/**', 'packages/**'];
|
||||
return json;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(() => cleanupProject());
|
||||
|
||||
@@ -85,6 +85,9 @@ exports[`Webpack Plugin (legacy) ConvertConfigToWebpackPlugin, should convert wi
|
||||
}
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint"
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nx/vite:test",
|
||||
"outputs": ["{options.reportsDirectory}"],
|
||||
@@ -92,9 +95,6 @@ exports[`Webpack Plugin (legacy) ConvertConfigToWebpackPlugin, should convert wi
|
||||
"reportsDirectory": "../coverage/app3224373"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint"
|
||||
},
|
||||
"serve-static": {
|
||||
"executor": "@nx/web:file-server",
|
||||
"dependsOn": ["build"],
|
||||
|
||||
@@ -31,10 +31,7 @@ export function TargetExecutorTitle({
|
||||
<span className="font-medium">
|
||||
Command
|
||||
<span className="mb-1 ml-2 hidden group-hover:inline">
|
||||
<CopyToClipboardButton
|
||||
text={`"command": "${command}"`}
|
||||
tooltipText="Copy Command"
|
||||
/>
|
||||
<CopyToClipboardButton text={command} tooltipText="Copy Command" />
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -96,6 +96,16 @@ export function DocViewer({
|
||||
siteName: 'Nx',
|
||||
type: 'website',
|
||||
}}
|
||||
additionalMetaTags={
|
||||
metadata.keywords
|
||||
? [
|
||||
{
|
||||
name: 'keywords',
|
||||
content: metadata.keywords,
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="mx-auto w-full grow items-stretch px-4 sm:px-6 lg:px-8 2xl:max-w-6xl">
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
import {
|
||||
Hero,
|
||||
TrustedBy,
|
||||
FasterAndCheaper,
|
||||
UnderstandWorkspace,
|
||||
EnhancedWithAi,
|
||||
AutomatedAgentsManagement,
|
||||
AgentNumberOverTime,
|
||||
AutomatedAgentsManagement,
|
||||
EnhancedWithAi,
|
||||
FasterAndCheaper,
|
||||
Hero,
|
||||
Statistics,
|
||||
TrustedBy,
|
||||
UnderstandWorkspace,
|
||||
} from '@nx/nx-dev/ui-cloud';
|
||||
|
||||
import {
|
||||
ButtonLinkProps,
|
||||
CallToAction,
|
||||
DefaultLayout,
|
||||
} from '@nx/nx-dev/ui-common';
|
||||
|
||||
import type { Metadata } from 'next';
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Nx Cloud',
|
||||
@@ -40,10 +39,10 @@ export const metadata: Metadata = {
|
||||
},
|
||||
};
|
||||
|
||||
export default function NxCloudPage(): JSX.Element {
|
||||
export default function NxCloudPage(): ReactElement {
|
||||
const headerCTAConfig: ButtonLinkProps[] = [
|
||||
{
|
||||
href: '/pricing',
|
||||
href: 'https://cloud.nx.app/get-started?utm_source=nx-dev&utm_medium=nx-cloud-header&utm_campaign=get-started',
|
||||
variant: 'primary',
|
||||
size: 'small',
|
||||
title: 'Get started for free',
|
||||
|
||||
@@ -5,9 +5,9 @@ import {
|
||||
Hero,
|
||||
PowerpackFeatures,
|
||||
} from '@nx/nx-dev/ui-powerpack';
|
||||
import { contactButton } from '../../lib/components/headerCtaConfigs';
|
||||
|
||||
import type { Metadata } from 'next';
|
||||
import { type ReactElement } from 'react';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Nx Powerpack',
|
||||
@@ -32,10 +32,10 @@ export const metadata: Metadata = {
|
||||
},
|
||||
};
|
||||
|
||||
export default function NxPowerPackPage(): JSX.Element {
|
||||
export default function NxPowerPackPage(): ReactElement {
|
||||
const headerCTAConfig: ButtonLinkProps[] = [
|
||||
{
|
||||
href: 'https://cloud.nx.app/powerpack/purchase?licenseBusinessType=small&utm_source=nx.dev&utm_medium=referral&utm_campaign=nx-powerpackurl',
|
||||
href: '/powerpack/trial',
|
||||
variant: 'primary',
|
||||
size: 'small',
|
||||
title: 'Request a free trial',
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ButtonLinkProps } from '@nx/nx-dev/ui-common';
|
||||
import { NxCloudAnimatedIcon } from '@nx/nx-dev/ui-icons';
|
||||
|
||||
export const requestFreeTrial: ButtonLinkProps = {
|
||||
href: '/contact/sales',
|
||||
href: '/enterprise/trial',
|
||||
variant: 'primary',
|
||||
size: 'small',
|
||||
title: 'Request a free trial',
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import { NextSeo } from 'next-seo';
|
||||
import { Footer, Header } from '@nx/nx-dev/ui-common';
|
||||
import { TalkToOurSalesTeam } from '@nx/nx-dev/ui-contact';
|
||||
import { TalkToOurTeam } from '@nx/nx-dev/ui-contact';
|
||||
import { type ReactElement } from 'react';
|
||||
|
||||
export function ContactSales(): JSX.Element {
|
||||
export function ContactSales(): ReactElement {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<>
|
||||
<NextSeo
|
||||
title="Talk to our Sales team"
|
||||
description="We’re here to help you find the right plan and pricing for your needs and discuss how Nx Cloud Enterprise can drive better business outcomes for your organization."
|
||||
title="Contact Nx Sales – Discover the Best Solution for Your Team"
|
||||
description="Get in touch with Nx experts to learn how we can optimize your development workflow. Whether you're scaling up or seeking enhanced CI performance, our team is here to help."
|
||||
openGraph={{
|
||||
url: 'https://nx.dev' + router.asPath,
|
||||
title: 'Talk to our Sales team',
|
||||
title: 'Contact Nx Sales – Discover the Best Solution for Your Team',
|
||||
description:
|
||||
'We’re here to help you find the right plan and pricing for your needs and discuss how Nx Cloud Enterprise can drive better business outcomes for your organization.',
|
||||
"Get in touch with Nx experts to learn how we can optimize your development workflow. Whether you're scaling up or seeking enhanced CI performance, our team is here to help.",
|
||||
images: [
|
||||
{
|
||||
url: 'https://nx.dev/socials/nx-media.png',
|
||||
@@ -32,7 +33,7 @@ export function ContactSales(): JSX.Element {
|
||||
<Header />
|
||||
<main id="main" role="main" className="py-24 lg:py-32">
|
||||
<div>
|
||||
<TalkToOurSalesTeam />
|
||||
<TalkToOurTeam />
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
Hero,
|
||||
OssProjects,
|
||||
} from '@nx/nx-dev/ui-customers';
|
||||
import { tryNxCloudForFree } from '../lib/components/headerCtaConfigs';
|
||||
import { contactButton } from '../lib/components/headerCtaConfigs';
|
||||
|
||||
export function Customers(): JSX.Element {
|
||||
const router = useRouter();
|
||||
@@ -34,7 +34,7 @@ export function Customers(): JSX.Element {
|
||||
type: 'website',
|
||||
}}
|
||||
/>
|
||||
<DefaultLayout headerCTAConfig={[tryNxCloudForFree]}>
|
||||
<DefaultLayout headerCTAConfig={[contactButton]}>
|
||||
<div>
|
||||
<Hero />
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
TestimonialCarousel,
|
||||
VmwareTestimonial,
|
||||
} from '@nx/nx-dev/ui-enterprise';
|
||||
import { requestFreeTrial } from '../lib/components/headerCtaConfigs';
|
||||
import { requestFreeTrial } from '../../lib/components/headerCtaConfigs';
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
export function Enterprise(): ReactElement {
|
||||