From a90de969ab8436fa6fbcfc701523d4991daccc42 Mon Sep 17 00:00:00 2001 From: Isaac Mann Date: Thu, 20 Feb 2025 15:47:04 -0500 Subject: [PATCH] docs(core): remove standalone tutorials (#30125) Remove standalone tutorials --- ...-you-always-wanted-but-didnt-know-about.md | 4 +- docs/blog/2022-10-14-whats-new-in-nx-15.md | 1 - ...9-setup-react-and-tailwind-the-easy-way.md | 5 +- ...10-create-your-own-create-react-app-cli.md | 2 +- docs/blog/2023-10-20-nx-17-release.md | 4 +- docs/blog/2023-12-28-highlights-2023.md | 2 +- docs/generated/manifests/menus.json | 72 -- docs/generated/manifests/nx.json | 99 -- .../angular/documents/nx-and-angular.md | 7 +- .../packages/angular/documents/overview.md | 5 +- .../packages/react/documents/overview.md | 5 +- .../packages/vue/documents/overview.md | 8 - docs/map.json | 15 - .../deprecated/integrated-vs-package-based.md | 2 - docs/shared/getting-started/installation.md | 3 - docs/shared/getting-started/intro.md | 12 +- docs/shared/guides/nx-and-angular-cli.md | 7 +- .../shared/packages/angular/angular-plugin.md | 5 +- docs/shared/packages/react/react-plugin.md | 5 +- docs/shared/packages/vue/vue-plugin.md | 8 - docs/shared/reference/sitemap.md | 3 - docs/shared/tutorials/angular-monorepo.md | 4 - docs/shared/tutorials/angular-standalone.md | 1151 ----------------- docs/shared/tutorials/react-monorepo.md | 4 - .../tutorials/react-standalone-pdv.json | 248 ---- .../react-standalone-products-route.png | Bin 36869 -> 0 bytes docs/shared/tutorials/react-standalone.md | 1103 ---------------- docs/shared/tutorials/vue-app-pdv.json | 186 --- .../tutorials/vue-app-products-route.png | Bin 30394 -> 0 bytes docs/shared/tutorials/vue-standalone.md | 1145 ---------------- nx-dev/nx-dev/redirect-rules.js | 38 +- nx-dev/nx-dev/redirect-rules.spec.js | 6 +- .../src/lib/smarter-tools-for-monorepos.tsx | 28 - 33 files changed, 42 insertions(+), 4145 deletions(-) delete mode 100644 docs/shared/tutorials/angular-standalone.md delete mode 100644 docs/shared/tutorials/react-standalone-pdv.json delete mode 100644 docs/shared/tutorials/react-standalone-products-route.png delete mode 100644 docs/shared/tutorials/react-standalone.md delete mode 100644 docs/shared/tutorials/vue-app-pdv.json delete mode 100644 docs/shared/tutorials/vue-app-products-route.png delete mode 100644 docs/shared/tutorials/vue-standalone.md diff --git a/docs/blog/2022-03-29-the-react-cli-you-always-wanted-but-didnt-know-about.md b/docs/blog/2022-03-29-the-react-cli-you-always-wanted-but-didnt-know-about.md index 904777d10d..5c7beb2377 100644 --- a/docs/blog/2022-03-29-the-react-cli-you-always-wanted-but-didnt-know-about.md +++ b/docs/blog/2022-03-29-the-react-cli-you-always-wanted-but-didnt-know-about.md @@ -19,9 +19,7 @@ While speed is of major importance, developer ergonomics shouldn't be left behin ## Update (Aug 2023): Want a non-monorepo setup? -This article walks you through how to setup a new Nx monorepo workspace with React. If you rather prefer starting with a single-project setup (also named "standalone") then you might want to have a look at this tutorial (including video): - -[/getting-started/tutorials/react-standalone-tutorial](/getting-started/tutorials/react-standalone-tutorial) +This article walks you through how to setup a new Nx monorepo workspace with React. If you rather prefer starting with a single-project setup (also named "standalone") then you might want to have a look at the standalone tutorial (including video). ## Why use a devtool CLI? diff --git a/docs/blog/2022-10-14-whats-new-in-nx-15.md b/docs/blog/2022-10-14-whats-new-in-nx-15.md index 0e112595d8..c0a81f02b3 100644 --- a/docs/blog/2022-10-14-whats-new-in-nx-15.md +++ b/docs/blog/2022-10-14-whats-new-in-nx-15.md @@ -181,7 +181,6 @@ It is an ongoing process, and we have a lot of content to cover! We follow the [ Besides the two new [package-based](/getting-started/tutorials/typescript-packages-tutorial) and [integrated style tutorials](/getting-started/tutorials/react-monorepo-tutorial) we also have two brand new reworked tutorials -- [/getting-started/tutorials/react-standalone-tutorial](/getting-started/tutorials/react-standalone-tutorial) - [/getting-started/tutorials](/getting-started/tutorials) Stay tuned for more updates to come. diff --git a/docs/blog/2023-02-09-setup-react-and-tailwind-the-easy-way.md b/docs/blog/2023-02-09-setup-react-and-tailwind-the-easy-way.md index 0e417d2b22..d7a6aa153d 100644 --- a/docs/blog/2023-02-09-setup-react-and-tailwind-the-easy-way.md +++ b/docs/blog/2023-02-09-setup-react-and-tailwind-the-easy-way.md @@ -39,7 +39,7 @@ And so did also [Fireship](https://youtu.be/2OTq15A5s0Y) and ultimately [Dan Abr Code generators speed up such configuration tasks. They are valuable for scaffolding the initial project structure and adding new features to the app setup, such as Tailwind. -Nx has such generators. To use them, you need an Nx-based React setup. If you're starting new, you can create an [Nx Standalone React project](/getting-started/tutorials/react-standalone-tutorial) easily using the following command +Nx has such generators. To use them, you need an Nx-based React setup. If you're starting new, you can create an Nx Standalone React project easily using the following command ```shell $ npx create-nx-workspace reactapp --preset=react-standalone @@ -59,7 +59,7 @@ You can pass `--vite=false` if you still want to keep the Webpack configuration ## Generating a Tailwind Setup -Once you have a [Nx-based React](/getting-started/tutorials/react-standalone-tutorial) setup, adding Tailwind is as easy as running: +Once you have a [Nx-based React](/getting-started/tutorials/react-monorepo-tutorial) setup, adding Tailwind is as easy as running: ```shell $ npx nx g @nrwl/react:setup-tailwind @@ -79,7 +79,6 @@ You'll get You should be all setup and ready now! Here are some related resources to explore: -- [Nx docs: React Standalone tutorial](/getting-started/tutorials/react-standalone-tutorial) - [Nx docs: React Monorepo tutorial](/getting-started/tutorials/react-monorepo-tutorial) - [Youtube: Is CRA Dead](https://youtu.be/fkTz6KJxhhE) - [Nx docs: Migrate CRA to React and Vite](/recipes/adopting-nx/adding-to-existing-project) diff --git a/docs/blog/2023-08-10-create-your-own-create-react-app-cli.md b/docs/blog/2023-08-10-create-your-own-create-react-app-cli.md index 7935c2b7c8..7ea28344fe 100644 --- a/docs/blog/2023-08-10-create-your-own-create-react-app-cli.md +++ b/docs/blog/2023-08-10-create-your-own-create-react-app-cli.md @@ -636,7 +636,7 @@ This should give you a good insight into how to get started. But there's more to - We could also include "[executors](/extending-nx/recipes/local-executors)", which are wrappers around tasks to abstract the lower-level details of it - etc. -Now clearly this was a simple example of how you could build your own CRA using Nx. If you want to see a real-world React setup powered by Nx, check out our React Tutorial: [/getting-started/tutorials/react-standalone-tutorial](/getting-started/tutorials/react-standalone-tutorial) +Now clearly this was a simple example of how you could build your own CRA using Nx. If you want to see a real-world React setup powered by Nx, check out our React Tutorial: [/getting-started/tutorials/react-monorepo-tutorial](/getting-started/tutorials/react-monorepo-tutorial) ## Learn more diff --git a/docs/blog/2023-10-20-nx-17-release.md b/docs/blog/2023-10-20-nx-17-release.md index 0451eaade4..bef33d67fd 100644 --- a/docs/blog/2023-10-20-nx-17-release.md +++ b/docs/blog/2023-10-20-nx-17-release.md @@ -54,8 +54,6 @@ And you'll have access to Nx generators so that you can generate Vue application We're very excited for this support to land, and we're eager to get it into our user's hands and see what Nx can do to help Vue developers so we can continue to refine our support and make Vue with Nx an excellent developer experience. -If you're eager to learn more, make sure to check out our new [Vue standalone tutorial](/getting-started/tutorials/vue-standalone-tutorial). - ## Enhancements to Module Federation Support Nx already had great support for Module Federation — Nx 17 improves on this support: @@ -236,7 +234,7 @@ For more [checkout our API docs](/nx-api/nx/documents/release), and be sure to c At Nx, we're OBSESSED with building a better, more robust experience for our developers. Towards this end, we're now in [v2 of our Project Inference API](/extending-nx/recipes/project-graph-plugins). -This API is a way of extending the Nx project graph, which can be particularly helpful for extending Nx to support other languages, allowing Nx to determine where to find and draw boundaries around projects in your workspace. A great example is our very own [Vue plugin](/getting-started/tutorials/vue-standalone-tutorial). +This API is a way of extending the Nx project graph, which can be particularly helpful for extending Nx to support other languages, allowing Nx to determine where to find and draw boundaries around projects in your workspace. A great example is our very own [Vue plugin](/nx-api/vue). Interestingly, v2 includes support for dynamic targets as well. This opens up exciting new doors to reducing configuration, and we hope to expand on this to better support our first-party plugins in the near future. diff --git a/docs/blog/2023-12-28-highlights-2023.md b/docs/blog/2023-12-28-highlights-2023.md index 207aff654e..eb4393b30c 100644 --- a/docs/blog/2023-12-28-highlights-2023.md +++ b/docs/blog/2023-12-28-highlights-2023.md @@ -220,7 +220,7 @@ And you'll then have access to Nx generators so you can create Vue applications, ![](/blog/images/2023-12-28/bodyimg6.gif) -Checkout out our [Vue standalone tutorial](/getting-started/tutorials/vue-standalone-tutorial) for more, as well as our [Vue API docs](/nx-api/vue), and stay tuned as Nx prepares to offer more Vue support (including support for [Nuxt](https://nuxt.com/), a full-stack framework built around Vue) in the near future! +Checkout out our [Vue API docs](/nx-api/vue), and stay tuned as Nx prepares to offer more Vue support (including support for [Nuxt](https://nuxt.com/), a full-stack framework built around Vue) in the near future! ### Extending Nx: Local Generators, Build your Own CLI, Verdaccio Support diff --git a/docs/generated/manifests/menus.json b/docs/generated/manifests/menus.json index 7004daa35d..28b0150f27 100644 --- a/docs/generated/manifests/menus.json +++ b/docs/generated/manifests/menus.json @@ -54,14 +54,6 @@ "children": [], "disableCollapsible": false }, - { - "name": "React Standalone", - "path": "/getting-started/tutorials/react-standalone-tutorial", - "id": "react-standalone-tutorial", - "isExternal": false, - "children": [], - "disableCollapsible": false - }, { "name": "React Monorepo", "path": "/getting-started/tutorials/react-monorepo-tutorial", @@ -70,14 +62,6 @@ "children": [], "disableCollapsible": false }, - { - "name": "Angular Standalone", - "path": "/getting-started/tutorials/angular-standalone-tutorial", - "id": "angular-standalone-tutorial", - "isExternal": false, - "children": [], - "disableCollapsible": false - }, { "name": "Angular Monorepo", "path": "/getting-started/tutorials/angular-monorepo-tutorial", @@ -86,14 +70,6 @@ "children": [], "disableCollapsible": false }, - { - "name": "Vue Standalone", - "path": "/getting-started/tutorials/vue-standalone-tutorial", - "id": "vue-standalone-tutorial", - "isExternal": false, - "children": [], - "disableCollapsible": false - }, { "name": "Gradle Monorepo", "path": "/getting-started/tutorials/gradle-tutorial", @@ -154,14 +130,6 @@ "children": [], "disableCollapsible": false }, - { - "name": "React Standalone", - "path": "/getting-started/tutorials/react-standalone-tutorial", - "id": "react-standalone-tutorial", - "isExternal": false, - "children": [], - "disableCollapsible": false - }, { "name": "React Monorepo", "path": "/getting-started/tutorials/react-monorepo-tutorial", @@ -170,14 +138,6 @@ "children": [], "disableCollapsible": false }, - { - "name": "Angular Standalone", - "path": "/getting-started/tutorials/angular-standalone-tutorial", - "id": "angular-standalone-tutorial", - "isExternal": false, - "children": [], - "disableCollapsible": false - }, { "name": "Angular Monorepo", "path": "/getting-started/tutorials/angular-monorepo-tutorial", @@ -186,14 +146,6 @@ "children": [], "disableCollapsible": false }, - { - "name": "Vue Standalone", - "path": "/getting-started/tutorials/vue-standalone-tutorial", - "id": "vue-standalone-tutorial", - "isExternal": false, - "children": [], - "disableCollapsible": false - }, { "name": "Gradle Monorepo", "path": "/getting-started/tutorials/gradle-tutorial", @@ -213,14 +165,6 @@ "children": [], "disableCollapsible": false }, - { - "name": "React Standalone", - "path": "/getting-started/tutorials/react-standalone-tutorial", - "id": "react-standalone-tutorial", - "isExternal": false, - "children": [], - "disableCollapsible": false - }, { "name": "React Monorepo", "path": "/getting-started/tutorials/react-monorepo-tutorial", @@ -229,14 +173,6 @@ "children": [], "disableCollapsible": false }, - { - "name": "Angular Standalone", - "path": "/getting-started/tutorials/angular-standalone-tutorial", - "id": "angular-standalone-tutorial", - "isExternal": false, - "children": [], - "disableCollapsible": false - }, { "name": "Angular Monorepo", "path": "/getting-started/tutorials/angular-monorepo-tutorial", @@ -245,14 +181,6 @@ "children": [], "disableCollapsible": false }, - { - "name": "Vue Standalone", - "path": "/getting-started/tutorials/vue-standalone-tutorial", - "id": "vue-standalone-tutorial", - "isExternal": false, - "children": [], - "disableCollapsible": false - }, { "name": "Gradle Monorepo", "path": "/getting-started/tutorials/gradle-tutorial", diff --git a/docs/generated/manifests/nx.json b/docs/generated/manifests/nx.json index 797686fa61..f012e4a932 100644 --- a/docs/generated/manifests/nx.json +++ b/docs/generated/manifests/nx.json @@ -68,17 +68,6 @@ "path": "/getting-started/tutorials/typescript-packages-tutorial", "tags": [] }, - { - "id": "react-standalone-tutorial", - "name": "React Standalone", - "description": "", - "mediaImage": "", - "file": "shared/tutorials/react-standalone", - "itemList": [], - "isExternal": false, - "path": "/getting-started/tutorials/react-standalone-tutorial", - "tags": [] - }, { "id": "react-monorepo-tutorial", "name": "React Monorepo", @@ -90,17 +79,6 @@ "path": "/getting-started/tutorials/react-monorepo-tutorial", "tags": [] }, - { - "id": "angular-standalone-tutorial", - "name": "Angular Standalone", - "description": "", - "mediaImage": "", - "file": "shared/tutorials/angular-standalone", - "itemList": [], - "isExternal": false, - "path": "/getting-started/tutorials/angular-standalone-tutorial", - "tags": [] - }, { "id": "angular-monorepo-tutorial", "name": "Angular Monorepo", @@ -112,17 +90,6 @@ "path": "/getting-started/tutorials/angular-monorepo-tutorial", "tags": [] }, - { - "id": "vue-standalone-tutorial", - "name": "Vue Standalone", - "description": "", - "mediaImage": "", - "file": "shared/tutorials/vue-standalone", - "itemList": [], - "isExternal": false, - "path": "/getting-started/tutorials/vue-standalone-tutorial", - "tags": [] - }, { "id": "gradle-tutorial", "name": "Gradle Monorepo", @@ -206,17 +173,6 @@ "path": "/getting-started/tutorials/typescript-packages-tutorial", "tags": [] }, - { - "id": "react-standalone-tutorial", - "name": "React Standalone", - "description": "", - "mediaImage": "", - "file": "shared/tutorials/react-standalone", - "itemList": [], - "isExternal": false, - "path": "/getting-started/tutorials/react-standalone-tutorial", - "tags": [] - }, { "id": "react-monorepo-tutorial", "name": "React Monorepo", @@ -228,17 +184,6 @@ "path": "/getting-started/tutorials/react-monorepo-tutorial", "tags": [] }, - { - "id": "angular-standalone-tutorial", - "name": "Angular Standalone", - "description": "", - "mediaImage": "", - "file": "shared/tutorials/angular-standalone", - "itemList": [], - "isExternal": false, - "path": "/getting-started/tutorials/angular-standalone-tutorial", - "tags": [] - }, { "id": "angular-monorepo-tutorial", "name": "Angular Monorepo", @@ -250,17 +195,6 @@ "path": "/getting-started/tutorials/angular-monorepo-tutorial", "tags": [] }, - { - "id": "vue-standalone-tutorial", - "name": "Vue Standalone", - "description": "", - "mediaImage": "", - "file": "shared/tutorials/vue-standalone", - "itemList": [], - "isExternal": false, - "path": "/getting-started/tutorials/vue-standalone-tutorial", - "tags": [] - }, { "id": "gradle-tutorial", "name": "Gradle Monorepo", @@ -288,17 +222,6 @@ "path": "/getting-started/tutorials/typescript-packages-tutorial", "tags": [] }, - "/getting-started/tutorials/react-standalone-tutorial": { - "id": "react-standalone-tutorial", - "name": "React Standalone", - "description": "", - "mediaImage": "", - "file": "shared/tutorials/react-standalone", - "itemList": [], - "isExternal": false, - "path": "/getting-started/tutorials/react-standalone-tutorial", - "tags": [] - }, "/getting-started/tutorials/react-monorepo-tutorial": { "id": "react-monorepo-tutorial", "name": "React Monorepo", @@ -310,17 +233,6 @@ "path": "/getting-started/tutorials/react-monorepo-tutorial", "tags": [] }, - "/getting-started/tutorials/angular-standalone-tutorial": { - "id": "angular-standalone-tutorial", - "name": "Angular Standalone", - "description": "", - "mediaImage": "", - "file": "shared/tutorials/angular-standalone", - "itemList": [], - "isExternal": false, - "path": "/getting-started/tutorials/angular-standalone-tutorial", - "tags": [] - }, "/getting-started/tutorials/angular-monorepo-tutorial": { "id": "angular-monorepo-tutorial", "name": "Angular Monorepo", @@ -332,17 +244,6 @@ "path": "/getting-started/tutorials/angular-monorepo-tutorial", "tags": [] }, - "/getting-started/tutorials/vue-standalone-tutorial": { - "id": "vue-standalone-tutorial", - "name": "Vue Standalone", - "description": "", - "mediaImage": "", - "file": "shared/tutorials/vue-standalone", - "itemList": [], - "isExternal": false, - "path": "/getting-started/tutorials/vue-standalone-tutorial", - "tags": [] - }, "/getting-started/tutorials/gradle-tutorial": { "id": "gradle-tutorial", "name": "Gradle Monorepo", diff --git a/docs/generated/packages/angular/documents/nx-and-angular.md b/docs/generated/packages/angular/documents/nx-and-angular.md index 5cc3ff4708..1694ae540c 100644 --- a/docs/generated/packages/angular/documents/nx-and-angular.md +++ b/docs/generated/packages/angular/documents/nx-and-angular.md @@ -35,7 +35,7 @@ Here's a quick side-by-side overview comparing the features between the Angular | Executors | ✅ (Builders) | ✅ | | Advanced Generators (e.g. Module Federation, Tailwind,...) | ❌ | ✅ | | Integrated Tooling (Jest, Cypress, Playwright etc.) | ❌ | ✅ | -| Support for [single-project Workspaces](/getting-started/tutorials/angular-standalone-tutorial) | ✅ | ✅ | +| Support for single-project Workspaces | ✅ | ✅ | | First-Class [Monorepo Support](/getting-started/tutorials/angular-monorepo-tutorial) | ❌\* | ✅ | | [Enforced Module Boundaries](/features/enforce-module-boundaries) | ❌ | ✅ | | Interactive [Project Graph](/features/explore-graph) | ❌ | ✅ | @@ -67,8 +67,6 @@ Nx is not just exclusively for monorepos, but can create - a single-project workspace (basically what the Angular CLI gives you) - a monorepo workspace (multiple projects in a single repo) -You can check out the [Angular single-project workspace tutorial](/getting-started/tutorials/angular-standalone-tutorial) to learn more about it. - ### Generate a new project You can create a new Nx single-project workspace using the following command: @@ -270,7 +268,7 @@ Nx goes beyond being just a CLI and comes with [Nx Console](/getting-started/edi Nx is really made to scale with you. You can - start small with a single-project workspace -- modularize your application into more fine-grained libraries for better maintainability as your application (and team) grows ([more about that here](/getting-started/tutorials/angular-standalone-tutorial#modularizing-your-angular-app-with-local-libraries)), including mechanisms to make sure [things stay within their boundaries](/features/enforce-module-boundaries) +- modularize your application into more fine-grained libraries for better maintainability as your application (and team) grows, including mechanisms to make sure [things stay within their boundaries](/features/enforce-module-boundaries) - you can then migrate to a monorepo when you are ready and need one ([more here](/recipes/tips-n-tricks/standalone-to-monorepo)) - or even [add Webpack Module Federation support](/recipes/angular/module-federation-with-ssr) @@ -408,5 +406,4 @@ There is also a guide describing how to [consolidate multiple Angular CLI projec You can learn more about Angular & Nx by following our dedicated tutorials: -- [Tutorial: Building Angular Apps with the Nx Standalone Projects Setup](/getting-started/tutorials/angular-standalone-tutorial) - [Tutorial: Building Angular Apps in an Nx Monorepo](/getting-started/tutorials/angular-monorepo-tutorial) diff --git a/docs/generated/packages/angular/documents/overview.md b/docs/generated/packages/angular/documents/overview.md index 2dbdd46621..5646bca544 100644 --- a/docs/generated/packages/angular/documents/overview.md +++ b/docs/generated/packages/angular/documents/overview.md @@ -59,8 +59,8 @@ npm add -D @nx/angular {% /tab %} {% /tabs %} -{% callout type="note" title="Angular Tutorials" %} -For a full tutorial experience, follow the [Angular Standalone Tutorial](/getting-started/tutorials/angular-standalone-tutorial) or the [Angular Monorepo Tutorial](/getting-started/tutorials/angular-monorepo-tutorial) +{% callout type="note" title="Angular Tutorial" %} +For a full tutorial experience, follow the [Angular Monorepo Tutorial](/getting-started/tutorials/angular-monorepo-tutorial) {% /callout %} ## Using the Angular Plugin @@ -127,7 +127,6 @@ nx g @nx/angular:service apps/appName/src/lib/my-service/my-service ## More Documentation -- [Angular Standalone Tutorial](/getting-started/tutorials/angular-standalone-tutorial) - [Angular Monorepo Tutorial](/getting-started/tutorials/angular-monorepo-tutorial) - [Migrating from the Angular CLI](/recipes/angular/migration/angular) - [Setup Module Federation with Angular and Nx](/concepts/module-federation/faster-builds-with-module-federation) diff --git a/docs/generated/packages/react/documents/overview.md b/docs/generated/packages/react/documents/overview.md index d9b33e9abe..4099c70688 100644 --- a/docs/generated/packages/react/documents/overview.md +++ b/docs/generated/packages/react/documents/overview.md @@ -12,8 +12,8 @@ It provides: To create a new workspace with React, run `npx create-nx-workspace@latest --preset=react-standalone`. -{% callout type="note" title="React Tutorials" %} -For a full tutorial experience, follow the [React Standalone Tutorial](/getting-started/tutorials/react-standalone-tutorial) or the [React Monorepo Tutorial](/getting-started/tutorials/react-monorepo-tutorial) +{% callout type="note" title="React Tutorial" %} +For a full tutorial experience, follow the [React Monorepo Tutorial](/getting-started/tutorials/react-monorepo-tutorial) {% /callout %} ### Installation @@ -144,7 +144,6 @@ The library in `dist` is publishable to npm or a private registry. ## More Documentation -- [React Standalone Tutorial](/getting-started/tutorials/react-standalone-tutorial) - [React Monorepo Tutorial](/getting-started/tutorials/react-monorepo-tutorial) - [Using Cypress](/nx-api/cypress) - [Using Jest](/nx-api/jest) diff --git a/docs/generated/packages/vue/documents/overview.md b/docs/generated/packages/vue/documents/overview.md index 1ecccc68a6..b49bf5af5b 100644 --- a/docs/generated/packages/vue/documents/overview.md +++ b/docs/generated/packages/vue/documents/overview.md @@ -11,10 +11,6 @@ The Nx plugin for [Vue](https://vuejs.org/). To create a new workspace with Vue, run `npx create-nx-workspace@latest --preset=vue`. -{% callout type="note" title="Vue Standalone Tutorial" %} -For a full tutorial experience, follow the [Vue Standalone Tutorial](/getting-started/tutorials/vue-standalone-tutorial) -{% /callout %} - ### Installation {% callout type="note" title="Keep Nx Package Versions In Sync" %} @@ -59,7 +55,3 @@ To generate a Vue library, run the following: ```shell nx g @nx/vue:lib libs/my-lib ``` - -## More Documentation - -- [Vue Standalone Tutorial](/getting-started/tutorials/vue-standalone-tutorial) diff --git a/docs/map.json b/docs/map.json index c9619c9244..4a78e1e07f 100644 --- a/docs/map.json +++ b/docs/map.json @@ -45,31 +45,16 @@ "id": "typescript-packages-tutorial", "file": "shared/tutorials/typescript-packages" }, - { - "name": "React Standalone", - "id": "react-standalone-tutorial", - "file": "shared/tutorials/react-standalone" - }, { "name": "React Monorepo", "id": "react-monorepo-tutorial", "file": "shared/tutorials/react-monorepo" }, - { - "name": "Angular Standalone", - "id": "angular-standalone-tutorial", - "file": "shared/tutorials/angular-standalone" - }, { "name": "Angular Monorepo", "id": "angular-monorepo-tutorial", "file": "shared/tutorials/angular-monorepo" }, - { - "name": "Vue Standalone", - "id": "vue-standalone-tutorial", - "file": "shared/tutorials/vue-standalone" - }, { "name": "Gradle Monorepo", "id": "gradle-tutorial", diff --git a/docs/shared/deprecated/integrated-vs-package-based.md b/docs/shared/deprecated/integrated-vs-package-based.md index e08d6bd83d..332a7ba376 100644 --- a/docs/shared/deprecated/integrated-vs-package-based.md +++ b/docs/shared/deprecated/integrated-vs-package-based.md @@ -64,8 +64,6 @@ Someone whose main focus is on improving their single application will be most i {% cards %} {% card title="Standalone Applications with Nx" description="Learn what Standlone Apps are and how Nx can be useful" type="video" url="https://youtu.be/qEaVzh-oBBc" /%} -{% card title="Tutorial: React Standalone Tutorial" description="Walkthrough for creating a React standalone application with Nx" type="documentation" url="/getting-started/tutorials/react-standalone-tutorial" /%} -{% card title="Tutorial: Angular Standalone Tutorial" description="Walkthrough for creating an Angular standalone application with Nx" type="documentation" url="/getting-started/tutorials/angular-standalone-tutorial" /%} {% /cards %} ## How to Choose diff --git a/docs/shared/getting-started/installation.md b/docs/shared/getting-started/installation.md index 429f083d5e..831f3aa1c7 100644 --- a/docs/shared/getting-started/installation.md +++ b/docs/shared/getting-started/installation.md @@ -137,9 +137,6 @@ To avoid potential issues, it is [recommended to update one major version of Nx Try one of these tutorials for a full walkthrough of what to do after you install Nx - [TypeScript Packages Tutorial](/getting-started/tutorials/typescript-packages-tutorial) -- [Single React App Tutorial](/getting-started/tutorials/react-standalone-tutorial) -- [Single Angular App Tutorial](/getting-started/tutorials/angular-standalone-tutorial) -- [Single Vue App Tutorial](/getting-started/tutorials/vue-standalone-tutorial) - [React Monorepo Tutorial](/getting-started/tutorials/react-monorepo-tutorial) - [Angular Monorepo Tutorial](/getting-started/tutorials/angular-monorepo-tutorial) diff --git a/docs/shared/getting-started/intro.md b/docs/shared/getting-started/intro.md index 789f8b3641..6c3ad07484 100644 --- a/docs/shared/getting-started/intro.md +++ b/docs/shared/getting-started/intro.md @@ -59,22 +59,12 @@ Also, here are some recipes that give you more details based on the technology s {% /cards %} -{% cards cols="2" lgCols="3" mdCols="3" smCols="2" %} +{% cards cols="2" lgCols="4" mdCols="4" smCols="2" %} {% link-card title="TypeScript Packages" type="tutorial" url="/getting-started/tutorials/typescript-packages-tutorial" icon="jsMono" /%} -{% link-card title="Single React App" type="tutorial" url="/getting-started/tutorials/react-standalone-tutorial" icon="react" /%} - {% link-card title="React Monorepo" type="tutorial" url="/getting-started/tutorials/react-monorepo-tutorial" icon="reactMono" /%} -{% /cards %} - -{% cards cols="2" lgCols="4" mdCols="4" smCols="2" %} - -{% link-card title="Single Vue App" type="tutorial" url="/getting-started/tutorials/vue-standalone-tutorial" icon="vue" /%} - -{% link-card title="Single Angular App" type="tutorial" url="/getting-started/tutorials/angular-standalone-tutorial" icon="angular" /%} - {% link-card title="Angular Monorepo" type="tutorial" url="/getting-started/tutorials/angular-monorepo-tutorial" icon="angularMono" /%} {% link-card title="Gradle Monorepo" type="tutorial" url="/getting-started/tutorials/gradle-tutorial" icon="gradle" /%} diff --git a/docs/shared/guides/nx-and-angular-cli.md b/docs/shared/guides/nx-and-angular-cli.md index 5cc3ff4708..1694ae540c 100644 --- a/docs/shared/guides/nx-and-angular-cli.md +++ b/docs/shared/guides/nx-and-angular-cli.md @@ -35,7 +35,7 @@ Here's a quick side-by-side overview comparing the features between the Angular | Executors | ✅ (Builders) | ✅ | | Advanced Generators (e.g. Module Federation, Tailwind,...) | ❌ | ✅ | | Integrated Tooling (Jest, Cypress, Playwright etc.) | ❌ | ✅ | -| Support for [single-project Workspaces](/getting-started/tutorials/angular-standalone-tutorial) | ✅ | ✅ | +| Support for single-project Workspaces | ✅ | ✅ | | First-Class [Monorepo Support](/getting-started/tutorials/angular-monorepo-tutorial) | ❌\* | ✅ | | [Enforced Module Boundaries](/features/enforce-module-boundaries) | ❌ | ✅ | | Interactive [Project Graph](/features/explore-graph) | ❌ | ✅ | @@ -67,8 +67,6 @@ Nx is not just exclusively for monorepos, but can create - a single-project workspace (basically what the Angular CLI gives you) - a monorepo workspace (multiple projects in a single repo) -You can check out the [Angular single-project workspace tutorial](/getting-started/tutorials/angular-standalone-tutorial) to learn more about it. - ### Generate a new project You can create a new Nx single-project workspace using the following command: @@ -270,7 +268,7 @@ Nx goes beyond being just a CLI and comes with [Nx Console](/getting-started/edi Nx is really made to scale with you. You can - start small with a single-project workspace -- modularize your application into more fine-grained libraries for better maintainability as your application (and team) grows ([more about that here](/getting-started/tutorials/angular-standalone-tutorial#modularizing-your-angular-app-with-local-libraries)), including mechanisms to make sure [things stay within their boundaries](/features/enforce-module-boundaries) +- modularize your application into more fine-grained libraries for better maintainability as your application (and team) grows, including mechanisms to make sure [things stay within their boundaries](/features/enforce-module-boundaries) - you can then migrate to a monorepo when you are ready and need one ([more here](/recipes/tips-n-tricks/standalone-to-monorepo)) - or even [add Webpack Module Federation support](/recipes/angular/module-federation-with-ssr) @@ -408,5 +406,4 @@ There is also a guide describing how to [consolidate multiple Angular CLI projec You can learn more about Angular & Nx by following our dedicated tutorials: -- [Tutorial: Building Angular Apps with the Nx Standalone Projects Setup](/getting-started/tutorials/angular-standalone-tutorial) - [Tutorial: Building Angular Apps in an Nx Monorepo](/getting-started/tutorials/angular-monorepo-tutorial) diff --git a/docs/shared/packages/angular/angular-plugin.md b/docs/shared/packages/angular/angular-plugin.md index 2dbdd46621..5646bca544 100644 --- a/docs/shared/packages/angular/angular-plugin.md +++ b/docs/shared/packages/angular/angular-plugin.md @@ -59,8 +59,8 @@ npm add -D @nx/angular {% /tab %} {% /tabs %} -{% callout type="note" title="Angular Tutorials" %} -For a full tutorial experience, follow the [Angular Standalone Tutorial](/getting-started/tutorials/angular-standalone-tutorial) or the [Angular Monorepo Tutorial](/getting-started/tutorials/angular-monorepo-tutorial) +{% callout type="note" title="Angular Tutorial" %} +For a full tutorial experience, follow the [Angular Monorepo Tutorial](/getting-started/tutorials/angular-monorepo-tutorial) {% /callout %} ## Using the Angular Plugin @@ -127,7 +127,6 @@ nx g @nx/angular:service apps/appName/src/lib/my-service/my-service ## More Documentation -- [Angular Standalone Tutorial](/getting-started/tutorials/angular-standalone-tutorial) - [Angular Monorepo Tutorial](/getting-started/tutorials/angular-monorepo-tutorial) - [Migrating from the Angular CLI](/recipes/angular/migration/angular) - [Setup Module Federation with Angular and Nx](/concepts/module-federation/faster-builds-with-module-federation) diff --git a/docs/shared/packages/react/react-plugin.md b/docs/shared/packages/react/react-plugin.md index d9b33e9abe..4099c70688 100644 --- a/docs/shared/packages/react/react-plugin.md +++ b/docs/shared/packages/react/react-plugin.md @@ -12,8 +12,8 @@ It provides: To create a new workspace with React, run `npx create-nx-workspace@latest --preset=react-standalone`. -{% callout type="note" title="React Tutorials" %} -For a full tutorial experience, follow the [React Standalone Tutorial](/getting-started/tutorials/react-standalone-tutorial) or the [React Monorepo Tutorial](/getting-started/tutorials/react-monorepo-tutorial) +{% callout type="note" title="React Tutorial" %} +For a full tutorial experience, follow the [React Monorepo Tutorial](/getting-started/tutorials/react-monorepo-tutorial) {% /callout %} ### Installation @@ -144,7 +144,6 @@ The library in `dist` is publishable to npm or a private registry. ## More Documentation -- [React Standalone Tutorial](/getting-started/tutorials/react-standalone-tutorial) - [React Monorepo Tutorial](/getting-started/tutorials/react-monorepo-tutorial) - [Using Cypress](/nx-api/cypress) - [Using Jest](/nx-api/jest) diff --git a/docs/shared/packages/vue/vue-plugin.md b/docs/shared/packages/vue/vue-plugin.md index 1ecccc68a6..b49bf5af5b 100644 --- a/docs/shared/packages/vue/vue-plugin.md +++ b/docs/shared/packages/vue/vue-plugin.md @@ -11,10 +11,6 @@ The Nx plugin for [Vue](https://vuejs.org/). To create a new workspace with Vue, run `npx create-nx-workspace@latest --preset=vue`. -{% callout type="note" title="Vue Standalone Tutorial" %} -For a full tutorial experience, follow the [Vue Standalone Tutorial](/getting-started/tutorials/vue-standalone-tutorial) -{% /callout %} - ### Installation {% callout type="note" title="Keep Nx Package Versions In Sync" %} @@ -59,7 +55,3 @@ To generate a Vue library, run the following: ```shell nx g @nx/vue:lib libs/my-lib ``` - -## More Documentation - -- [Vue Standalone Tutorial](/getting-started/tutorials/vue-standalone-tutorial) diff --git a/docs/shared/reference/sitemap.md b/docs/shared/reference/sitemap.md index b01cefd0c1..f04d2c602b 100644 --- a/docs/shared/reference/sitemap.md +++ b/docs/shared/reference/sitemap.md @@ -7,11 +7,8 @@ - [Editor Setup](/getting-started/editor-setup) - [Tutorials](/getting-started/tutorials) - [TypeScript Packages](/getting-started/tutorials/typescript-packages-tutorial) - - [React Standalone](/getting-started/tutorials/react-standalone-tutorial) - [React Monorepo](/getting-started/tutorials/react-monorepo-tutorial) - - [Angular Standalone](/getting-started/tutorials/angular-standalone-tutorial) - [Angular Monorepo](/getting-started/tutorials/angular-monorepo-tutorial) - - [Vue Standalone](/getting-started/tutorials/vue-standalone-tutorial) - [Gradle Monorepo](/getting-started/tutorials/gradle-tutorial) - [Features](/features) - [Run Tasks](/features/run-tasks) diff --git a/docs/shared/tutorials/angular-monorepo.md b/docs/shared/tutorials/angular-monorepo.md index 190ca91e4d..a919381c4d 100644 --- a/docs/shared/tutorials/angular-monorepo.md +++ b/docs/shared/tutorials/angular-monorepo.md @@ -15,10 +15,6 @@ What will you learn? - how to modularize your codebase and impose architectural constraints for better maintainability - [how to speed up CI with Nx Cloud ⚡](#fast-ci) -{% callout type="info" title="Looking for an Angular standalone app?" %} -Note, this tutorial sets up a repo with applications and libraries in their own subfolders. If you are looking for an Angular standalone app setup then check out our [Angular standalone app tutorial](/getting-started/tutorials/angular-standalone-tutorial). -{% /callout %} - ## Nx CLI vs. Angular CLI Nx evolved from being an extension of the Angular CLI to a [fully standalone CLI working with multiple frameworks](/getting-started/why-nx#how-does-nx-work). As a result, adopting Nx as an Angular user is relatively straightforward. Your existing code, including builders and schematics, will still work as before, but you'll also have access to all the benefits Nx offers. diff --git a/docs/shared/tutorials/angular-standalone.md b/docs/shared/tutorials/angular-standalone.md deleted file mode 100644 index b4f4be0377..0000000000 --- a/docs/shared/tutorials/angular-standalone.md +++ /dev/null @@ -1,1151 +0,0 @@ -# Building Angular Apps with the Nx Standalone Projects Setup - -In this tutorial you'll learn how to use Angular with Nx in a "standalone" (non-monorepo) setup. Not to be confused with the "Angular Standalone API", a standalone project in Nx is a non-monorepo setup where you have a single application at the root level. This setup is very similar to what the Angular CLI gives you. - -What will you learn? - -- how to create a new standalone (single-project) Nx workspace setup for Angular -- how to run a single task (i.e. serve your app) or run multiple tasks in parallel -- how to leverage code generators to scaffold components -- how to modularize your codebase and impose architectural constraints for better maintainability -- [how to speed up CI with Nx Cloud ⚡](#fast-ci) - -{% callout type="info" title="Looking for Angular monorepos?" %} -Note, this tutorial sets up a repo with a single application at the root level that breaks out its code into libraries to add structure. If you are looking for an Angular monorepo setup then check out our [Angular monorepo tutorial](/getting-started/tutorials/angular-monorepo-tutorial). - -{% /callout %} - -## Final Code - -Here's the source code of the final result for this tutorial. - -{% github-repository url="https://github.com/nrwl/nx-recipes/tree/main/angular-standalone" /%} - - - -Also, if you prefer learning with a video, join Juri and walk through the tutorial, step by step together. - -{% youtube -src="https://www.youtube.com/embed/ZAO0yXupIIE" -title="Tutorial: Standalone Angular Application" -/%} - -## Creating a new Angular App - -{% video-link link="https://youtu.be/ZAO0yXupIIE?t=49" /%} - -Create a new Angular application with the following command: - -```{% command="npx create-nx-workspace@latest myngapp --preset=angular-standalone" path="~" %} - -NX Let's create a new workspace [https://nx.dev/getting-started/intro] - -✔ Which bundler would you like to use? · esbuild -✔ Default stylesheet format · css -✔ Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? · No -✔ Test runner to use for end to end (E2E) tests · cypress -✔ Which CI provider would you like to use? · github -``` - -You get asked a few questions that help Nx preconfigure your new Angular application. These include: - -- Angular specific questions, such as which bundler to use, whether to enable server-side rendering and which stylesheet format to use -- General Nx questions, such as whether to enable remote caching with Nx Cloud. Nx comes with built-in [local caching](/features/cache-task-results). If you want to benefit from this cache in CI, you can enable [remote caching](/ci/features/remote-cache) which will set up [Nx Cloud](https://nx.app). This is also a prerequisite for enabling [distributed task execution](/ci/features/distribute-task-execution). We'll explore this later in the tutorial. - -For the sake of this tutorial, let's respond to all the questions with the default response. - -The `create-nx-workspace` command generates the following structure: - -``` -└─ myngapp - ├─ .vscode - │ └─ extensions.json - ├─ e2e - │ ├─ ... - │ ├─ project.json - │ ├─ src - │ │ ├─ e2e - │ │ │ └─ app.cy.ts - │ │ ├─ ... - │ └─ tsconfig.json - ├─ src - │ ├─ app - │ │ ├─ app.component.css - │ │ ├─ app.component.html - │ │ ├─ app.component.spec.ts - │ │ ├─ app.component.ts - │ │ ├─ app.config.ts - │ │ ├─ app.routes.ts - │ │ └─ nx-welcome.component.ts - │ ├─ assets - │ ├─ favicon.ico - │ ├─ index.html - │ ├─ main.ts - │ ├─ styles.css - │ └─ test-setup.ts - ├─ jest.config.ts - ├─ jest.preset.js - ├─ nx.json - ├─ package-lock.json - ├─ package.json - ├─ project.json - ├─ README.md - ├─ tsconfig.app.json - ├─ tsconfig.editor.json - ├─ tsconfig.json - └─ tsconfig.spec.json -``` - -The setup includes: - -- a new Angular application at the root of the Nx workspace (`src/app`) -- a Cypress based set of e2e tests (`e2e/`) -- Prettier preconfigured -- ESLint & Angular ESLint preconfigured -- Jest preconfigured - -Compared to the Angular CLI, you might notice the addition of an `nx.json` file and the absence of an `angular.json` file. Instead of the `angular.json` file there is a `project.json` file. Each file is described below: - -| File | Description | -| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `nx.json` | This is where we can fine-tune how Nx works, define the [cacheable operations](/features/cache-task-results), our [task pipelines](/concepts/task-pipeline-configuration) as well as defaults for the Nx generators. Find more details in [the reference docs](/reference/nx-json). | -| `project.json` | Nx uses this file to define targets that can be run, similar to how the Angular CLI uses the `angular.json` file. If you're familiar with the Angular CLI you should have no difficulty navigating the `project.json` file. If you're curious how the two compare, you can learn more in [the Nx and Angular CLI comparision article](/nx-api/angular/documents/nx-and-angular). The [project-configuration documentation page](/reference/project-configuration) has more details on how to use the `project.json` file. | - -## Serving the App - -{% video-link link="https://youtu.be/ZAO0yXupIIE?t=296" /%} - -The most common tasks are already defined in the `package.json` file: - -```json {% fileName="package.json" %} -{ - "name": "myngapp", - "scripts": { - "start": "nx serve", - "build": "nx build", - "test": "nx test" - } - ... -} -``` - -To serve your new Angular application, just run: `npm start`. Alternatively you can directly use Nx by running: - -```shell -nx serve -``` - -Your application should be served at [http://localhost:4200](http://localhost:4200). - -Nx uses the following syntax to run tasks: - -![Syntax for Running Tasks in Nx](/shared/images/run-target-syntax.svg) - -### Manually Defined Tasks - -The project tasks are defined in the `project.json` file: - -```json {% fileName="project.json"} -{ - "name": "myngapp", - ... - "targets": { - "build": { ... }, - "serve": { ... }, - "extract-i18n": { ... }, - "lint": { ... }, - "test": { ... }, - "serve-static": { ... }, - }, -} -``` - -Each target contains a configuration object that tells Nx how to run that target. - -```json {% fileName="project.json"} -{ - "name": "myngapp", - ... - "targets": { - "serve": { - "executor": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "myngapp:build:production" - }, - "development": { - "browserTarget": "myngapp:build:development" - } - }, - "defaultConfiguration": "development" - }, - ... - }, -} -``` - -The most critical parts are: - -- `executor` - This corresponds to the `builder` property in an Angular CLI workspace. You can use Angular builders or executors from [Nx plugins](/extending-nx/intro/getting-started). -- `options` - these are additional properties and flags passed to the executor function to customize it - -Learn more about how to [run tasks with Nx](/features/run-tasks). - -## Testing and Linting - -{% video-link link="https://youtu.be/ZAO0yXupIIE?t=369" /%} - -Our current setup not only has targets for serving and building the Angular application, but also has targets for unit testing, e2e testing and linting. The `test` and `lint` targets are defined in the application `project.json` file, while the `e2e` target is [inferred from the `e2e/cypress.config.ts` file](#inferred-tasks). We can use the same syntax as before to run these tasks: - -```bash -nx test # runs unit tests using Jest -nx lint # runs linting with ESLint -nx e2e e2e # runs e2e tests from the e2e project with Cypress -``` - -### Inferred Tasks - -Nx identifies available tasks for your project from [tooling configuration files](/concepts/inferred-tasks), `package.json` scripts and the targets defined in `project.json`. All tasks from the `myngapp` project are defined in its `project.json` file, but the companion `e2e` project has its tasks inferred from configuration files. To view the tasks that Nx has detected, look in the [Nx Console](/getting-started/editor-setup), [Project Details View](/recipes/nx-console/console-project-details) or run: - -```shell -nx show project e2e --web -``` - -{% project-details title="Project Details View" %} - -```json -{ - "project": { - "name": "e2e", - "type": "e2e", - "data": { - "metadata": { - "targetGroups": { - "E2E (CI)": ["e2e-ci--src/e2e/app.cy.ts", "e2e-ci"] - } - }, - "name": "e2e", - "root": "e2e", - "sourceRoot": "e2e/src", - "projectType": "application", - "tags": [], - "implicitDependencies": ["myngapp"], - "targets": { - "e2e": { - "options": { - "cwd": "e2e", - "command": "cypress run" - }, - "cache": true, - "inputs": [ - "default", - "^production", - { - "externalDependencies": ["cypress"] - } - ], - "outputs": [ - "{workspaceRoot}/dist/cypress/e2e/videos", - "{workspaceRoot}/dist/cypress/e2e/screenshots" - ], - "configurations": { - "production": { - "command": "cypress run --env webServerCommand=\"nx run myngapp:serve:production\"" - } - }, - "executor": "nx:run-commands", - "metadata": { - "technologies": ["cypress"] - } - }, - "e2e-ci--src/e2e/app.cy.ts": { - "outputs": [ - "{workspaceRoot}/dist/cypress/e2e/videos", - "{workspaceRoot}/dist/cypress/e2e/screenshots" - ], - "inputs": [ - "default", - "^production", - { - "externalDependencies": ["cypress"] - } - ], - "cache": true, - "options": { - "cwd": "e2e", - "command": "cypress run --env webServerCommand=\"nx run myngapp:serve-static\" --spec src/e2e/app.cy.ts" - }, - "executor": "nx:run-commands", - "configurations": {}, - "metadata": { - "technologies": ["cypress"] - } - }, - "e2e-ci": { - "executor": "nx:noop", - "cache": true, - "inputs": [ - "default", - "^production", - { - "externalDependencies": ["cypress"] - } - ], - "outputs": [ - "{workspaceRoot}/dist/cypress/e2e/videos", - "{workspaceRoot}/dist/cypress/e2e/screenshots" - ], - "dependsOn": [ - { - "target": "e2e-ci--src/e2e/app.cy.ts", - "projects": "self", - "params": "forward" - } - ], - "options": {}, - "configurations": {}, - "metadata": { - "technologies": ["cypress"] - } - }, - "lint": { - "cache": true, - "options": { - "cwd": "e2e", - "command": "eslint ." - }, - "inputs": [ - "default", - "{workspaceRoot}/.eslintrc.json", - "{workspaceRoot}/e2e/.eslintrc.json", - "{workspaceRoot}/tools/eslint-rules/**/*", - { - "externalDependencies": ["eslint"] - } - ], - "executor": "nx:run-commands", - "configurations": {}, - "metadata": { - "technologies": ["eslint"] - } - } - } - } - }, - "sourceMap": { - "targets": ["project.json", "nx/core/project-json"], - "targets.e2e": ["e2e/cypress.config.ts", "@nx/cypress/plugin"], - "targets.e2e.cache": ["e2e/cypress.config.ts", "@nx/cypress/plugin"], - "targets.e2e.inputs": ["e2e/cypress.config.ts", "@nx/cypress/plugin"], - "targets.e2e.outputs": ["e2e/cypress.config.ts", "@nx/cypress/plugin"], - "targets.e2e.options": ["e2e/cypress.config.ts", "@nx/cypress/plugin"], - "targets.e2e.configurations": [ - "e2e/cypress.config.ts", - "@nx/cypress/plugin" - ], - "targets.e2e-ci--src/e2e/app.cy.ts": [ - "e2e/cypress.config.ts", - "@nx/cypress/plugin" - ], - "targets.e2e-ci--src/e2e/app.cy.ts.cache": [ - "e2e/cypress.config.ts", - "@nx/cypress/plugin" - ], - "targets.e2e-ci--src/e2e/app.cy.ts.inputs": [ - "e2e/cypress.config.ts", - "@nx/cypress/plugin" - ], - "targets.e2e-ci--src/e2e/app.cy.ts.outputs": [ - "e2e/cypress.config.ts", - "@nx/cypress/plugin" - ], - "targets.e2e-ci--src/e2e/app.cy.ts.options": [ - "e2e/cypress.config.ts", - "@nx/cypress/plugin" - ], - "targets.e2e-ci": ["e2e/cypress.config.ts", "@nx/cypress/plugin"], - "targets.e2e-ci.cache": ["e2e/cypress.config.ts", "@nx/cypress/plugin"], - "targets.e2e-ci.dependsOn": ["e2e/cypress.config.ts", "@nx/cypress/plugin"], - "targets.e2e-ci.inputs": ["e2e/cypress.config.ts", "@nx/cypress/plugin"], - "targets.e2e-ci.outputs": ["e2e/cypress.config.ts", "@nx/cypress/plugin"], - "targets.e2e-ci.executor": ["e2e/cypress.config.ts", "@nx/cypress/plugin"], - "targets.lint": ["e2e/project.json", "@nx/eslint/plugin"], - "targets.lint.cache": ["e2e/project.json", "@nx/eslint/plugin"], - "targets.lint.inputs": ["e2e/project.json", "@nx/eslint/plugin"], - "targets.lint.options": ["e2e/project.json", "@nx/eslint/plugin"] - } -} -``` - -{% /project-details %} - -If you expand the `e2e` task, you can see that it was created by the `@nx/cypress` plugin by analyzing the `e2e/cypress.config.ts` file. Notice the outputs are defined as: - -```json -[ - "{workspaceRoot}/dist/cypress/e2e/videos", - "{workspaceRoot}/dist/cypress/e2e/screenshots" -] -``` - -This value is being read from the `videosFolder` and `screenshotsFolder` defined by the `nxE2EPreset` in your `e2e/cypress.config.ts` file. Let's change their value in your `e2e/cypress.config.ts` file: - -```ts {% fileName="e2e/cypress.config.ts" highlightLines=[8,9] %} -// ... -export default defineConfig({ - e2e: { - ...nxE2EPreset(__filename, { - // ... - }), - baseUrl: 'http://localhost:4200', - videosFolder: '../dist/cypress/e2e/videos-changed', - screenshotsFolder: '../dist/cypress/e2e/screenshots-changed', - }, -}); -``` - -Now if you look at the project details view again, the outputs for the `e2e` target will be: - -```json -[ - "{workspaceRoot}/dist/cypress/e2e/videos-changed", - "{workspaceRoot}/dist/cypress/e2e/screenshots-changed" -] -``` - -This feature ensures that Nx will always cache the correct files. - -You can also override the settings for inferred tasks by modifying the [`targetDefaults` in `nx.json`](/reference/nx-json#target-defaults) or setting a value in your [`project.json` file](/reference/project-configuration). Nx will merge the values from the inferred tasks with the values you define in `targetDefaults` and in your specific project's configuration. - -### Running Multiple Tasks - -In addition to running individual tasks, you can also run multiple tasks in parallel using the following syntax: - -```{% command="nx run-many -t test lint e2e" path="myngapp" %} - -✔ nx run e2e:lint (1s) -✔ nx run myngapp:lint (1s) -✔ nx run myngapp:test (2s) -✔ nx run e2e:e2e (6s) - -—————————————————————————————————————————————————————— - -NX Successfully ran targets test, lint, e2e for 2 projects (8s) -``` - -### Caching - -{% video-link link="https://youtu.be/ZAO0yXupIIE?t=443" /%} - -One thing to highlight is that Nx is able to [cache the tasks you run](/features/cache-task-results). - -Note that all of these targets are automatically cached by Nx. If you re-run a single one or all of them again, you'll see that the task completes immediately. In addition, (as can be seen in the output example below) there will be a note that a matching cache result was found and therefore the task was not run again. - -```{% command="nx run-many -t test lint e2e" path="myngapp" %} - -✔ nx run myngapp:lint [existing outputs match the cache, left as is] -✔ nx run e2e:lint [existing outputs match the cache, left as is] -✔ nx run myngapp:test [existing outputs match the cache, left as is] -✔ nx run e2e:e2e [existing outputs match the cache, left as is] - -——————————————————————————————————————————————————————— - - Successfully ran targets test, lint, e2e for 2 projects (143ms) - -Nx read the output from the cache instead of running the command for 4 out of 4 tasks. -``` - -Not all tasks might be cacheable though. You can configure the `cache` properties in the targets under `targetDefaults` in the `nx.json` file. You can also [learn more about how caching works](/features/cache-task-results). - -## Creating New Components - -{% video-link link="https://youtu.be/ZAO0yXupIIE?t=500" /%} - -Similar to the Angular CLI, Nx comes with code generation abilities. What the Angular CLI calls "Schematics", Nx calls "Generators". - -Generators allow you to easily scaffold code, configuration or entire projects. To see what capabilities the `@nx/angular` plugin ships with, run the following command and inspect the output: - -```{% command="npx nx list @nx/angular" path="myngapp" %} - -NX Capabilities in @nx/angular: - -NX Capabilities in @nx/angular: - - GENERATORS - - add-linting : Adds linting configuration to an Angular project. - application : Creates an Angular application. - component : Generate an Angular Component. - ... - library : Creates an Angular library. - library-secondary-entry-point : Creates a secondary entry point for an Angular publishable library. - remote : Generate a Remote Angular Module Federation Application. - move : Moves an Angular application or library to another folder within the workspace and updates the project configuration. - convert-to-with-mf : Converts an old micro frontend configuration... - host : Generate a Host Angular Module Federation Application. - ng-add : Migrates an Angular CLI workspace to Nx or adds the Angular plugin to an Nx workspace. - ngrx : Adds NgRx support to an application or library. - scam-to-standalone : Convert an existing Single Component Angular Module (SCAM) to a Standalone Component. - scam : Generate a component with an accompanying Single Component Angular Module (SCAM). - scam-directive : Generate a directive with an accompanying Single Component Angular Module (SCAM). - scam-pipe : Generate a pipe with an accompanying Single Component Angular Module (SCAM). - setup-mf : Generate a Module Federation configuration for a given Angular application. - setup-ssr : Generate Angular Universal (SSR) setup for an Angular application. - setup-tailwind : Configures Tailwind CSS for an application or a buildable/publishable library. - stories : Creates stories/specs for all components declared in a project. - storybook-configuration : Adds Storybook configuration to a project. - cypress-component-configuration : Setup Cypress component testing for a project. - web-worker : Creates a Web Worker. - directive : Generate an Angular directive. - ngrx-feature-store : Adds an NgRx Feature Store to an application or library. - ngrx-root-store : Adds an NgRx Root Store to an application. - pipe : Generate an Angular Pipe - - EXECUTORS/BUILDERS/ - - delegate-build : Delegates the build to a different target while supporting incremental builds. - ... -``` - -{% callout type="info" title="Prefer a more visual UI?" %} - -If you prefer a more integrated experience, you can install the "Nx Console" extension for your code editor. It has support for VSCode, IntelliJ and ships a LSP for Vim. Nx Console provides autocompletion support in Nx configuration files and has UIs for browsing and running generators. - -More info can be found in [the integrate with editors article](/getting-started/editor-setup). - -{% /callout %} - -Run the following command to generate a new "hello-world" component. Note how we append `--dry-run` to first check the output. - -```{% command="npx nx g @nx/angular:component src/apps/hello-world/hello-world --standalone --dry-run" path="myngapp" %} -NX Generating @nx/angular:component - -CREATE src/app/hello-world/hello-world.component.css -CREATE src/app/hello-world/hello-world.component.html -CREATE src/app/hello-world/hello-world.component.spec.ts -CREATE src/app/hello-world/hello-world.component.ts - -NOTE: The "dryRun" flag means no changes were made. -``` - -As you can see it generates a new component in the `app/hello-world/` folder. If you want to actually run the generator, remove the `--dry-run` flag. - -```ts {% fileName="src/app/hello-world/hello-world.component.ts" %} -import { Component } from '@angular/core'; -import { CommonModule } from '@angular/common'; - -@Component({ - selector: 'myngapp-hello-world', - standalone: true, - imports: [CommonModule], - templateUrl: './hello-world.component.html', - styleUrls: ['./hello-world.component.css'], -}) -export class HelloWorldComponent {} -``` - -## Building the App for Deployment - -{% video-link link="https://youtu.be/ZAO0yXupIIE?t=680" /%} - -If you're ready and want to ship your application, you can build it using - -```{% command="npx nx build" path="myngapp" %} -> nx run myngapp:build:production - -✔ Browser application bundle generation complete. -✔ Copying assets complete. -✔ Index html generation complete. - -Initial Chunk Files | Names | Raw Size | Estimated Transfer Size -main.afa99fe9f64fbdd9.js | main | 193.58 kB | 51.57 kB -polyfills.1acfd3f58d94d542.js | polyfills | 32.98 kB | 10.66 kB -runtime.37059233034b21c2.js | runtime | 892 bytes | 515 bytes -styles.ef46db3751d8e999.css | styles | 0 bytes | - - - | Initial Total | 227.44 kB | 62.73 kB - -Build at: 2023-05-23T14:00:31.981Z - Hash: 9086e92ce0bfefca - Time: 5228ms - -—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— - -Successfully ran target build for project myngapp (7s) -``` - -All the required files will be placed in the `dist/myngapp` folder and can be deployed to your favorite hosting provider. - -## You're ready to go! - -{% video-link link="https://youtu.be/ZAO0yXupIIE?t=723" /%} - -In the previous sections you learned about the basics of using Nx, running tasks and navigating an Nx workspace. You're ready to ship features now! - -But there's more to learn. You have two possibilities here: - -- [Jump to the next steps section](#next-steps) to find where to go from here or -- keep reading and learn some more about what makes Nx unique when working with Angular. - -## Modularizing your Angular App with Local Libraries - -{% video-link link="https://youtu.be/ZAO0yXupIIE?t=750" /%} - -When you develop your Angular application, usually all your logic sits in the `app` folder. Ideally separated by various folder names which represent your "domains". As your app grows, this becomes more and more monolithic though. - -The following structure is a common example of this kind of monolithic code organization: - -``` -└─ myngapp - ├─ ... - ├─ src - │ ├─ app - │ │ ├─ products - │ │ ├─ cart - │ │ ├─ ui - │ │ ├─ ... - │ │ └─ app.component.ts - │ ├─ ... - │ └─ main.ts - ├─ ... - ├─ package.json - ├─ ... -``` - -Nx allows you to separate this logic into "local libraries". The main benefits include - -- better separation of concerns -- better reusability -- more explicit "APIs" between your "domain areas" -- better scalability in CI by enabling independent test/lint/build commands for each library -- better scalability in your teams by allowing different teams to work on separate libraries - -### Creating Local Libraries - -{% video-link link="https://youtu.be/ZAO0yXupIIE?t=818" /%} - -Let's assume our domain areas include `products`, `orders` and some more generic design system components, called `ui`. We can generate a new library for each of these areas using the Angular library generator: - -``` -nx g @nx/angular:library modules/products --standalone -nx g @nx/angular:library modules/orders --standalone -nx g @nx/angular:library modules/shared/ui --standalone -``` - -Note how we use the `--directory` flag to place the libraries into a subfolder. You can choose whatever folder structure you like, even keep all of them at the root-level. - -Running the above commands should lead to the following directory structure: - -``` -└─ myngapp - ├─ ... - ├─ e2e/ - ├─ modules - │ ├─ products - │ │ ├─ .eslintrc.json - │ │ ├─ README.md - │ │ ├─ jest.config.ts - │ │ ├─ project.json - │ │ ├─ src - │ │ │ ├─ index.ts - │ │ │ ├─ lib - │ │ │ │ └─ products - │ │ │ │ ├─ products.component.css - │ │ │ │ ├─ products.component.html - │ │ │ │ ├─ products.component.spec.ts - │ │ │ │ └─ products.component.ts - │ │ │ └─ test-setup.ts - │ │ ├─ tsconfig.json - │ │ ├─ tsconfig.lib.json - │ │ └─ tsconfig.spec.json - │ ├─ orders - │ │ ├─ ... - │ │ ├─ src - │ │ │ ├─ index.ts - │ │ │ ├─ lib - │ │ │ │ └─ orders - │ │ │ │ ├─ ... - │ │ │ │ └─ orders.component.ts - │ │ ├─ ... - │ └─ shared - │ └─ ui - │ ├─ ... - │ ├─ src - │ │ ├─ index.ts - │ │ ├─ lib - │ │ │ └─ shared-ui - │ │ │ ├─ ... - │ │ │ └─ shared-ui.component.ts - │ └─ ... - ├─ ... - ├─ src - │ ├─ app - │ │ ├─ ... - │ │ ├─ app.component.ts - │ ├─ ... - ├─ ... -``` - -Each of these libraries - -- has its own `project.json` file with corresponding targets you can run (e.g. running tests for just orders: `nx test orders`) -- has a dedicated `index.ts` file which is the "public API" of the library -- is mapped in the `tsconfig.base.json` at the root of the workspace - -### Importing Libraries into the Angular Application - -{% video-link link="https://youtu.be/ZAO0yXupIIE?t=976" /%} - -All libraries that we generate automatically have aliases created in the root-level `tsconfig.base.json`. - -```json {% fileName="tsconfig.base.json" %} -{ - "compilerOptions": { - ... - "paths": { - "@myngapp/orders": ["modules/orders/src/index.ts"], - "@myngapp/products": ["modules/products/src/index.ts"], - "@myngapp/shared-ui": ["modules/shared/ui/src/index.ts"] - }, - ... - }, -} -``` - -Hence we can easily import them into other libraries and our Angular application. For example: let's use our existing `ProductsComponent` in `modules/products/src/lib/products/`: - -```ts -import { Component } from '@angular/core'; -import { CommonModule } from '@angular/common'; - -@Component({ - selector: 'myngapp-products', - standalone: true, - imports: [CommonModule], - templateUrl: './products.component.html', - styleUrl: './products.component.css', -}) -export class ProductsComponent {} -``` - -Make sure the `ProductsComponent` is exported via the `index.ts` file of our `products` library (which it should already be). The `modules/products/src/index.ts` file is the public API for the `products` library with the rest of the workspace. Only export what's really necessary to be usable outside the library itself. - -```ts {% fileName="modules/products/src/index.ts" %} -export * from './lib/products/products.component'; -``` - -We're ready to import it into our main application now. If you opted into generating a router configuration when setting up the Nx workspace initially, you should have an `app.routes.ts` file in your `app` folder. If not, create it and configure the Angular router. - -Configure the routing as follows: - -```ts {% fileName="src/app/app.routes.ts" %} -import { Route } from '@angular/router'; -import { NxWelcomeComponent } from './nx-welcome.component'; - -export const appRoutes: Route[] = [ - { - path: '', - component: NxWelcomeComponent, - pathMatch: 'full', - }, - { - path: 'products', - loadComponent: () => - import('@myngapp/products').then((m) => m.ProductsComponent), - }, -]; -``` - -As part of this step, we should also remove the `NxWelcomeComponent` from the `AppComponent.imports` declaration so that it is just loaded over the routing mechanism. The `app.component.html` should just have the `` left: - -```html {% fileName="src/app/app.component.html" %} - -``` - -If you now navigate to [http://localhost:4200/products](http://localhost:4200/products) you should see the `ProductsComponent` being rendered. - -![Browser screenshot of navigating to the products route](/shared/images/tutorial-angular-standalone/app-products-route.png) - -Let's do the same process for our `orders` library. Import the `OrdersComponent` into the `app.routes.ts`: - -```ts {% fileName="src/app/app.routes.ts" %} -import { Route } from '@angular/router'; -import { NxWelcomeComponent } from './nx-welcome.component'; - -export const appRoutes: Route[] = [ - { - path: '', - component: NxWelcomeComponent, - pathMatch: 'full', - }, - { - path: 'products', - loadComponent: () => - import('@myngapp/products').then((m) => m.ProductsComponent), - }, - { - path: 'orders', - loadComponent: () => - import('@myngapp/orders').then((m) => m.OrdersComponent), - }, -]; -``` - -Similarly, navigating to [http://localhost:4200/orders](http://localhost:4200/orders) should now render the `OrdersComponent`. - -A couple of notes: - -- both the `ProductsComponent` and `OrdersComponent` are lazy loaded -- you could go even further and configure routes within the libraries and only import and attach those routes to the application routing mechanism. - -## Visualizing your Project Structure - -{% video-link link="https://youtu.be/ZAO0yXupIIE?t=958" /%} - -Nx automatically detects the dependencies between the various parts of your workspace and builds a [project graph](/features/explore-graph). This graph is used by Nx to perform various optimizations such as determining the correct order of execution when running tasks like `nx build`, identifying [affected projects](/features/run-tasks#run-tasks-on-projects-affected-by-a-pr) and more. Interestingly you can also visualize it. - -Just run: - -```shell -nx graph -``` - -You should be able to see something similar to the following in your browser (hint: click the "Show all projects" button). - -{% graph height="450px" %} - -```json -{ - "projects": [ - { - "name": "myngapp", - "type": "app", - "data": { - "tags": [] - } - }, - { - "name": "e2e", - "type": "e2e", - "data": { - "tags": [] - } - }, - { - "name": "shared-ui", - "type": "lib", - "data": { - "tags": [] - } - }, - { - "name": "orders", - "type": "lib", - "data": { - "tags": [] - } - }, - - { - "name": "products", - "type": "lib", - "data": { - "tags": [] - } - } - ], - "dependencies": { - "myngapp": [ - { "source": "myngapp", "target": "orders", "type": "dynamic" }, - { "source": "myngapp", "target": "products", "type": "dynamic" } - ], - "e2e": [{ "source": "e2e", "target": "myngapp", "type": "implicit" }], - "shared-ui": [], - "orders": [], - "products": [] - }, - "workspaceLayout": { "appsDir": "", "libsDir": "" }, - "affectedProjectIds": [], - "focus": null, - "groupByFolder": false -} -``` - -{% /graph %} - -Notice how `shared-ui` is not yet connected to anything because we didn't import it in any of our projects. Also the arrows to `orders` and `products` are dashed because we're using lazy imports. - -Exercise for you: change the codebase so that `shared-ui` is used by `orders` and `products`. Note: you need to restart the `nx graph` command to update the graph visualization or run the CLI command with the `--watch` flag. - -## Imposing Constraints with Module Boundary Rules - -{% video-link link="https://youtu.be/ZAO0yXupIIE?t=1147" /%} - -Once you modularize your codebase you want to make sure that the modules are not coupled to each other in an uncontrolled way. Here are some examples of how we might want to guard our small demo workspace: - -- we might want to allow `orders` to import from `shared-ui` but not the other way around -- we might want to allow `orders` to import from `products` but not the other way around -- we might want to allow all libraries to import the `shared-ui` components, but not the other way around - -When building these kinds of constraints you usually have two dimensions: - -- **type of project:** what is the type of your library. Example: "feature" library, "utility" library, "data-access" library, "ui" library (see [library types](/concepts/decisions/project-dependency-rules)) -- **scope (domain) of the project:** what domain area is covered by the project. Example: "orders", "products", "shared" ... this really depends on the type of product you're developing - -Nx comes with a generic mechanism that allows you to assign "tags" to projects. "tags" are arbitrary strings you can assign to a project that can be used later when defining boundaries between projects. For example, go to the `project.json` of your `orders` library and assign the tags `type:feature` and `scope:orders` to it. - -```json {% fileName="modules/orders/project.json" %} -{ - ... - "tags": ["type:feature", "scope:orders"], - ... -} -``` - -Then go to the `project.json` of your `products` library and assign the tags `type:feature` and `scope:products` to it. - -```json {% fileName="modules/products/project.json" %} -{ - ... - "tags": ["type:feature", "scope:products"], - ... -} -``` - -Finally, go to the `project.json` of the `shared-ui` library and assign the tags `type:ui` and `scope:shared` to it. - -```json {% fileName="modules/shared/ui/project.json" %} -{ - ... - "tags": ["type:ui", "scope:shared"], - ... -} -``` - -Notice how we assign `scope:shared` to our UI library because it is intended to be used throughout the workspace. - -Next, let's come up with a set of rules based on these tags: - -- `type:feature` should be able to import from `type:feature` and `type:ui` -- `type:ui` should only be able to import from `type:ui` -- `scope:orders` should be able to import from `scope:orders`, `scope:shared` and `scope:products` -- `scope:products` should be able to import from `scope:products` and `scope:shared` - -To enforce the rules, Nx ships with a custom ESLint rule. Open the `.eslintrc.base.json` at the root of the workspace and add the following `depConstraints` in the `@nx/enforce-module-boundaries` rule configuration: - -```json {% fileName=".eslintrc.base.json" %} -{ - ... - "overrides": [ - { - ... - "rules": { - "@nx/enforce-module-boundaries": [ - "error", - { - "enforceBuildableLibDependency": true, - "allow": [], - "depConstraints": [ - { - "sourceTag": "*", - "onlyDependOnLibsWithTags": ["*"] - }, - { - "sourceTag": "type:feature", - "onlyDependOnLibsWithTags": ["type:feature", "type:ui"] - }, - { - "sourceTag": "type:ui", - "onlyDependOnLibsWithTags": ["type:ui"] - }, - { - "sourceTag": "scope:orders", - "onlyDependOnLibsWithTags": [ - "scope:orders", - "scope:products", - "scope:shared" - ] - }, - { - "sourceTag": "scope:products", - "onlyDependOnLibsWithTags": ["scope:products", "scope:shared"] - }, - { - "sourceTag": "scope:shared", - "onlyDependOnLibsWithTags": ["scope:shared"] - } - ] - } - ] - } - }, - ... - ] -} -``` - -To test it, go to your `modules/products/src/lib/products/products.component.ts` file and import the `OrderComponent` from the `orders` project: - -```tsx {% fileName="modules/products/src/lib/products/products.component.ts" %} -import { Component } from '@angular/core'; -import { CommonModule } from '@angular/common'; - -// 👇 this import is not allowed -import { OrdersComponent } from '@myngapp/orders'; - -@Component({ - selector: 'myngapp-products', - standalone: true, - imports: [CommonModule, OrdersComponent], - templateUrl: './products.component.html', - styleUrls: ['./products.component.css'], -}) -export class ProductsComponent {} -``` - -If you lint your workspace you'll get an error now: - -```{% command="nx run-many -t lint" %} -✖ nx run products:lint - Linting "products"... - - /Users/juri/nrwl/content/myngapp/modules/products/src/lib/products/products.component.ts - 3:1 error A project tagged with "scope:products" can only depend on libs tagged with "scope:products", "scope:shared" @nx/enforce-module-boundaries - - ✖ 1 problem (1 error, 0 warnings) - - Lint errors found in the listed files. - -✔ nx run orders:lint (1s) -✔ nx run myngapp:lint (1s) -✔ nx run e2e:lint (682ms) -✔ nx run shared-ui:lint (797ms) - -————————————————————————————————————————————————————————————————————— - -NX Ran target lint for 5 projects (2s) - -✔ 4/5 succeeded [0 read from cache] - -✖ 1/5 targets failed, including the following: - - nx run products:lint - -``` - -If you have the ESLint plugin installed in your IDE you should immediately see an error: - -![ESLint module boundary error](/shared/images/tutorial-angular-standalone/boundary-rule-violation-vscode.png) - -Learn more about how to [enforce module boundaries](/features/enforce-module-boundaries). - -## Migrating to a Monorepo - -When you are ready to add another application to the repo, you'll probably want to move `myngapp` to its own folder. To do this, you can run the [`convert-to-monorepo` generator](/nx-api/workspace/generators/convert-to-monorepo) or [manually move the configuration files](/recipes/tips-n-tricks/standalone-to-monorepo). - -You can also go through the full [Angular monorepo tutorial](/getting-started/tutorials/angular-monorepo-tutorial) - -## Fast CI ⚡ {% highlightColor="green" %} - -{% callout type="check" title="Repository with Nx" %} -Make sure you have completed the previous sections of this tutorial before starting this one. If you want a clean starting point, you can check out the [reference code](https://github.com/nrwl/nx-recipes/tree/main/angular-standalone) as a starting point. -{% /callout %} - -This tutorial walked you through how Nx can improve the local development experience, but the biggest difference Nx makes is in CI. As repositories get bigger, making sure that the CI is fast, reliable and maintainable can get very challenging. Nx provides a solution. - -- Nx reduces wasted time in CI with the [`affected` command](/ci/features/affected). -- Nx Replay's [remote caching](/ci/features/remote-cache) will reuse task artifacts from different CI executions making sure you will never run the same computation twice. -- Nx Agents [efficiently distribute tasks across machines](/ci/concepts/parallelization-distribution) ensuring constant CI time regardless of the repository size. The right number of machines is allocated for each PR to ensure good performance without wasting compute. -- Nx Atomizer [automatically splits](/ci/features/split-e2e-tasks) large e2e tests to distribute them across machines. Nx can also automatically [identify and rerun flaky e2e tests](/ci/features/flaky-tasks). - -### Connect to Nx Cloud {% highlightColor="green" %} - -Nx Cloud is a companion app for your CI system that provides remote caching, task distribution, e2e tests deflaking, better DX and more. - -Now that we're working on the CI pipeline, it is important for your changes to be pushed to a GitHub repository. - -1. Commit your existing changes with `git add . && git commit -am "updates"` -2. [Create a new GitHub repository](https://github.com/new) -3. Follow GitHub's instructions to push your existing code to the repository - -When we set up the repository at the beginning of this tutorial, we chose to use GitHub Actions as a CI provider. This created a basic CI pipeline and configured Nx Cloud in the repository. It also printed a URL in the terminal to register your repository in your [Nx Cloud](https://cloud.nx.app) account. If you didn't click on the link when first creating your repository, you can show it again by running: - -```shell -npx nx connect -``` - -Once you click the link, follow the steps provided and make sure Nx Cloud is enabled on the main branch of your repository. - -### Configure Your CI Workflow {% highlightColor="green" %} - -When you chose GitHub Actions as your CI provider at the beginning of the tutorial, `create-nx-workspace` created a `.github/workflows/ci.yml` file that contains a CI pipeline that will run the `lint`, `test`, `build` and `e2e` tasks for projects that are affected by any given PR. If you would like to also distribute tasks across multiple machines to ensure fast and reliable CI runs, uncomment the `nx-cloud start-ci-run` line and have the `nx affected` line run the `e2e-ci` task instead of `e2e`. - -If you need to generate a new workflow file for GitHub Actions or other providers, you can do so with this command: - -```shell -npx nx generate ci-workflow -``` - -The key lines in the CI pipeline are: - -```yml {% fileName=".github/workflows/ci.yml" highlightLines=["10-14", "21-23"] %} -name: CI -# ... -jobs: - main: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - # This enables task distribution via Nx Cloud - # Run this command as early as possible, before dependencies are installed - # Learn more at https://nx.dev/ci/reference/nx-cloud-cli#npx-nxcloud-startcirun - # Uncomment this line to enable task distribution - # - run: npx nx-cloud start-ci-run --distribute-on="3 linux-medium-js" --stop-agents-after="e2e-ci" - - uses: actions/setup-node@v3 - with: - node-version: 20 - cache: 'npm' - - run: npm ci --legacy-peer-deps - - uses: nrwl/nx-set-shas@v4 - # Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected - # When you enable task distribution, run the e2e-ci task instead of e2e - - run: npx nx affected -t lint test build e2e -``` - -### Open a Pull Request {% highlightColor="green" %} - -Commit the changes and open a new PR on GitHub. - -```shell -git add . -git commit -m 'add CI workflow file' -git push origin add-workflow -``` - -When you view the PR on GitHub, you will see a comment from Nx Cloud that reports on the status of the CI run. - -![Nx Cloud report](/shared/tutorials/github-pr-cloud-report.avif) - -The `See all runs` link goes to a page with the progress and results of tasks that were run in the CI pipeline. - -![Run details](/shared/tutorials/nx-cloud-run-details.avif) - -For more information about how Nx can improve your CI pipeline, check out one of these detailed tutorials: - -- [Circle CI with Nx](/ci/intro/tutorials/circle) -- [GitHub Actions with Nx](/ci/intro/tutorials/github-actions) - -## Next Steps - -Here's some things you can dive into next: - -- Learn more about the [underlying mental model of Nx](/concepts/mental-model) -- Learn about popular generators such as [how to setup Tailwind](/recipes/angular/using-tailwind-css-with-angular-projects) or [add Storybook to your UI library](/recipes/storybook/overview-angular) -- Learn how to [migrate your existing Angular CLI repo to Nx](/recipes/angular/migration/angular) - -Also, make sure you - -- ⭐️ [Star us on GitHub](https://github.com/nrwl/nx) to show your support and stay updated on new releases! -- [Join the Official Nx Discord Server](https://go.nx.dev/community) to ask questions and find out the latest news about Nx. -- [Follow Nx on Twitter](https://twitter.com/nxdevtools) to stay up to date with Nx news -- [Read our Nx blog](/blog) -- [Subscribe to our Youtube channel](https://www.youtube.com/@nxdevtools) for demos and Nx insights diff --git a/docs/shared/tutorials/react-monorepo.md b/docs/shared/tutorials/react-monorepo.md index fc1513587d..b50504b13c 100644 --- a/docs/shared/tutorials/react-monorepo.md +++ b/docs/shared/tutorials/react-monorepo.md @@ -15,10 +15,6 @@ What will you learn? - how to modularize your codebase and impose architectural constraints for better maintainability - [how to speed up CI with Nx Cloud ⚡](#fast-ci) -{% callout type="info" title="Looking for a React standalone app?" %} -Note, this tutorial sets up a repo with applications and libraries in their own subfolders. If you are looking for a React standalone app setup then check out our [React standalone app tutorial](/getting-started/tutorials/react-standalone-tutorial). -{% /callout %} - ## Why Use an Nx Monorepo? In this tutorial, we'll set up a monorepo that is configured with a set of features that work together toward the goal of allowing developers to focus on building features rather than the configuration, coordination and maintenance of the tooling in the repo. diff --git a/docs/shared/tutorials/react-standalone-pdv.json b/docs/shared/tutorials/react-standalone-pdv.json deleted file mode 100644 index 23c5006173..0000000000 --- a/docs/shared/tutorials/react-standalone-pdv.json +++ /dev/null @@ -1,248 +0,0 @@ -{ - "project": { - "name": "react-app", - "type": "lib", - "data": { - "root": ".", - "targets": { - "vite:build": { - "options": { - "cwd": ".", - "command": "vite build" - }, - "cache": true, - "dependsOn": ["^vite:build"], - "inputs": [ - "default", - "^default", - { - "externalDependencies": ["vite"] - } - ], - "outputs": ["{projectRoot}/dist"], - "executor": "nx:run-commands", - "configurations": {} - }, - "build": { - "executor": "nx:run-script", - "metadata": { - "scriptContent": "nx vite:build", - "runCommand": "npm run build" - }, - "cache": true, - "dependsOn": ["typecheck"], - "options": { - "script": "build" - }, - "configurations": {} - } - }, - "sourceRoot": ".", - "name": "react-app", - "projectType": "library", - "metadata": { - "targetGroups": { - "NPM Scripts": ["build"] - } - }, - "implicitDependencies": [], - "tags": [] - } - }, - "sourceMap": { - "root": ["package.json", "nx/core/package-json-workspaces"], - "targets": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:build": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:build.options": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:build.cache": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:build.dependsOn": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:build.inputs": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:build.outputs": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:build.executor": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:build.options.cwd": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:build.options.command": ["vite.config.ts", "@nx/vite/plugin"], - "targets.serve": ["vite.config.ts", "@nx/vite/plugin"], - "targets.serve.options": ["vite.config.ts", "@nx/vite/plugin"], - "targets.serve.executor": ["vite.config.ts", "@nx/vite/plugin"], - "targets.serve.options.cwd": ["vite.config.ts", "@nx/vite/plugin"], - "targets.serve.options.command": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:preview": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:preview.options": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:preview.executor": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:preview.options.cwd": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:preview.options.command": [ - "vite.config.ts", - "@nx/vite/plugin" - ], - "targets.serve-static": ["vite.config.ts", "@nx/vite/plugin"], - "targets.serve-static.executor": ["vite.config.ts", "@nx/vite/plugin"], - "targets.serve-static.options": ["vite.config.ts", "@nx/vite/plugin"], - "targets.serve-static.options.buildTarget": [ - "vite.config.ts", - "@nx/vite/plugin" - ], - "targets.serve-static.options.spa": ["vite.config.ts", "@nx/vite/plugin"], - "targets.eslint:lint": [".eslintrc.cjs", "@nx/eslint/plugin"], - "targets.eslint:lint.cache": [".eslintrc.cjs", "@nx/eslint/plugin"], - "targets.eslint:lint.options": [".eslintrc.cjs", "@nx/eslint/plugin"], - "targets.eslint:lint.inputs": [".eslintrc.cjs", "@nx/eslint/plugin"], - "targets.eslint:lint.outputs": [".eslintrc.cjs", "@nx/eslint/plugin"], - "targets.eslint:lint.executor": [".eslintrc.cjs", "@nx/eslint/plugin"], - "targets.eslint:lint.options.cwd": [".eslintrc.cjs", "@nx/eslint/plugin"], - "targets.eslint:lint.options.command": [ - ".eslintrc.cjs", - "@nx/eslint/plugin" - ], - "targets.typecheck": ["package.json", "nx/core/package-json-workspaces"], - "targets.typecheck.executor": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.typecheck.options": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.typecheck.metadata": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.typecheck.cache": ["nx.json", "nx/core/target-defaults"], - "targets.typecheck.options.script": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.typecheck.metadata.scriptContent": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.typecheck.metadata.runCommand": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.build": ["package.json", "nx/core/package-json-workspaces"], - "targets.build.executor": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.build.options": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.build.metadata": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.build.cache": ["nx.json", "nx/core/target-defaults"], - "targets.build.options.script": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.build.metadata.scriptContent": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.build.metadata.runCommand": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.lint": ["package.json", "nx/core/package-json-workspaces"], - "targets.lint.executor": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.lint.options": ["package.json", "nx/core/package-json-workspaces"], - "targets.lint.metadata": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.lint.cache": ["nx.json", "nx/core/target-defaults"], - "targets.lint.options.script": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.lint.metadata.scriptContent": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.lint.metadata.runCommand": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "sourceRoot": ["package.json", "nx/core/package-json-workspaces"], - "name": ["package.json", "nx/core/package-json-workspaces"], - "projectType": ["package.json", "nx/core/package-json-workspaces"], - "metadata.targetGroups": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "metadata.targetGroups.NPM Scripts": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "metadata.targetGroups.NPM Scripts.0": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "metadata.targetGroups.NPM Scripts.1": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "metadata.targetGroups.NPM Scripts.2": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "metadata.targetGroups.NPM Scripts.3": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "metadata.targetGroups.NPM Scripts.4": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.dev": ["package.json", "nx/core/package-json-workspaces"], - "targets.dev.executor": ["package.json", "nx/core/package-json-workspaces"], - "targets.dev.options": ["package.json", "nx/core/package-json-workspaces"], - "targets.dev.metadata": ["package.json", "nx/core/package-json-workspaces"], - "targets.dev.options.script": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.dev.metadata.scriptContent": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.dev.metadata.runCommand": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.build.dependsOn": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.preview": ["package.json", "nx/core/package-json-workspaces"], - "targets.preview.executor": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.preview.options": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.preview.metadata": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.preview.options.script": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.preview.metadata.scriptContent": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.preview.metadata.runCommand": [ - "package.json", - "nx/core/package-json-workspaces" - ] - } -} diff --git a/docs/shared/tutorials/react-standalone-products-route.png b/docs/shared/tutorials/react-standalone-products-route.png deleted file mode 100644 index a70638b95ba7d2e64b0b9cad110223ebff59955f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36869 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

<2 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 AICHkF)o#m)WEAOq_Y2z&7pj@yPKQYk={3 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 - -## Creating a new React App - -Create a new React application that uses Vite with the following command: - -```{% command="npm create vite react-app -- --template=react-ts" path="~" %} - -Scaffolding project in ~/react-app... - -Done. Now run: - - cd react-app - npm install - npm run dev -``` - -Once you have run `npm install`, set up Git with the following commands: - -```shell -git init -git add . -git commit -m "initial commit" -``` - -Your repository should now have the following structure: - -``` -└─ react-app - ├─ ... - ├─ public - │ └─ ... - ├─ src - │ ├─ assets - │ ├─ App.css - │ ├─ App.tsx - │ ├─ index.css - │ └─ main.tsx - ├─ .eslintrc.cjs - ├─ index.html - ├─ package.json - ├─ README.md - ├─ tsconfig.json - ├─ tsconfig.node.json - └─ vite.config.ts -``` - -The setup includes.. - -- a new React application at the root of the repository (`src`) -- ESLint preconfigured -- Vite preconfigured - -You can build the application with the following command: - -``` -npm run build -``` - -## Add Nx - -Nx offers many features, but at its core, it is a task runner. Out of the box, it can: - -- [cache your tasks](/features/cache-task-results) -- ensure those tasks are [run in the correct order](/features/run-tasks) - -After the initial set up, you can incrementally add on other features that would be helpful in your organization. - -To enable Nx in your repository, run a single command: - -```shell {% path="~/react-app" %} -npx nx@latest init -``` - -This command will download the latest version of Nx and help set up your repository to take advantage of it. - -First, the script will propose installing some plugins based on the packages that are being used in your repository. - -- Leave the plugins deselected so that we can explore what Nx provides without any plugins. - -Second, the script asks a series of questions to help set up caching for you. - -- `Which scripts are cacheable?` - Choose `build` and `lint` -- `Does the "build" script create any outputs?` - Enter `dist` -- `Does the "lint" script create any outputs?` - Enter nothing -- `Would you like remote caching to make your build faster?` - Choose `Skip for now` - -We'll enable Nx Cloud and set up remote caching later in the tutorial. - -## Caching Pre-configured - -Nx has been configured to run your npm scripts as Nx tasks. You can run a single task like this: - -```shell {% path="~/react-app" %} -npx nx build react-app -``` - -During the `init` script, Nx also configured caching for these tasks. You can see in the `nx.json` file that the `build` and `lint` targets have the `cache` property set to `true` and the `build` target specifies that its output goes to the project's `dist` folder. - -```json {% fileName="nx.json" %} -{ - "$schema": "./node_modules/nx/schemas/nx-schema.json", - "targetDefaults": { - "build": { - "outputs": ["{projectRoot}/dist"], - "cache": true - }, - "lint": { - "cache": true - } - }, - "defaultBase": "main" -} -``` - -Try running `build` for the `react-app` app a second time: - -```shell {% path="~/react-app" %} -npx nx build react-app -``` - -The first time `nx build` was run, it took about 2 seconds - just like running `npm run build`. But the second time you run `nx build`, it completes instantly and displays this message: - -```text -Nx read the output from the cache instead of running the command for 1 out of 1 tasks. -``` - -You can see the same caching behavior working when you run `npx nx lint`. - -## Create a Task Pipeline - -If you look at the `build` script in `package.json`, you'll notice that it is actually doing two things. First, it runs `tsc` to type check the application and then it uses Vite to build the application. - -```json {% fileName="package.json" %} -{ - "scripts": { - "build": "tsc && vite build" - } -} -``` - -Let's split this into two separate tasks, so we can run `typecheck` without running the `build` task. - -```json {% fileName="package.json" %} -{ - "scripts": { - "typecheck": "tsc", - "build": "vite build" - } -} -``` - -But we also want to make sure that `typecheck` is always run when you run the `build` task. Nx can take care of this for you with a [task pipeline](/concepts/task-pipeline-configuration). The `dependsOn` property in `nx.json` can be used to ensure that all task dependencies are run first. - -```json {% fileName="nx.json" highlightLines=[6] %} -{ - "targetDefaults": { - "build": { - "outputs": ["{projectRoot}/dist"], - "cache": true, - "dependsOn": ["typecheck"] - }, - "lint": { - "cache": true - } - } -} -``` - -{% callout type="info" title="Project-Specific Settings" %} - -The `targetDefaults` in the `nx.json` file will apply to all the projects in your repository. If you want to specify these options for a specific project, you can set them under `nx.targets.build` in that project's `package.json` file. - -{% /callout %} - -Now if you run `nx build`, Nx will run `typecheck` first. - -```text {% command="npx nx build" path="~/react-app" %} -> nx run react-app:typecheck - - -> react-app@0.0.0 typecheck -> tsc - - -> nx run react-app:build - - -> react-app@0.0.0 build -> vite build - -vite v5.2.12 building for production... -✓ 34 modules transformed. -dist/index.html 0.46 kB │ gzip: 0.30 kB -dist/assets/react-CHdo91hT.svg 4.13 kB │ gzip: 2.05 kB -dist/assets/index-DiwrgTda.css 1.39 kB │ gzip: 0.72 kB -dist/assets/index-DVoHNO1Y.js 143.36 kB │ gzip: 46.09 kB -✓ built in 347ms - -—————————————————————————————————————————————————————— - - NX Successfully ran target build for project react-app and 1 task it depends on (2s) -``` - -We can also cache the `typecheck` task by updating the `nx.json` file. - -```json {% fileName="nx.json" highlightLines=["4-6"] %} -{ - "$schema": "./node_modules/nx/schemas/nx-schema.json", - "targetDefaults": { - "typecheck": { - "cache": true - }, - "build": { - "outputs": ["{projectRoot}/dist"], - "cache": true, - "dependsOn": ["typecheck"] - }, - "lint": { - "cache": true - } - }, - "defaultBase": "main" -} -``` - -Now, running `npx nx build` twice will once again complete instantly. - -## Use Nx Plugins to Enhance Vite Tasks with Caching - -You may remember that we defined the `outputs` property in `nx.json` when we were answering questions in the `nx init` script. The value is currently hard-coded so that if you change the output path in your `vite.config.ts`, you have to remember to also change the `outputs` array in the `build` task configuration. This is where plugins can help. Plugins enable better integration with specific tools. The `@nx/vite` plugin can understand the `vite.config.ts` file and automatically create and configure tasks based on the settings in that file. - -Nx plugins can: - -- automatically configure caching for you, including inputs and outputs based on the underlying tooling configuration -- create tasks for a project using the tooling configuration files -- provide code generators to help scaffold out projects -- automatically keep the tooling versions and configuration files up to date - -For this tutorial, we'll just focus on the automatic caching configuration. - -First, let's delete the `outputs` array from `nx.json` so that we don't override the inferred values from the plugin. Your `nx.json` should look like this: - -```json {% fileName="nx.json" %} -{ - "$schema": "./node_modules/nx/schemas/nx-schema.json", - "targetDefaults": { - "typecheck": { - "cache": true - }, - "build": { - "cache": true, - "dependsOn": ["typecheck"] - }, - "lint": { - "cache": true - } - }, - "defaultBase": "main" -} -``` - -Now let's add the `@nx/vite` plugin: - -```{% command="npx nx add @nx/vite" path="~/react-app" %} -✔ Installing @nx/vite... -✔ Initializing @nx/vite... - - NX Package @nx/vite added successfully. -``` - -The `nx add` command installs the version of the plugin that matches your repo's Nx version and runs that plugin's initialization script. For `@nx/vite`, the initialization script registers the plugin in the `plugins` array of `nx.json` and updates any `package.json` scripts that execute Vite related tasks to run those tasks through Nx. Running the tasks through Nx is necessary for caching and task pipelines to work. - -Open the project details view for the `demo` app and look at the `build` task. You can view the project details using [Nx Console](/getting-started/editor-setup) or by running the following command in the terminal: - -```shell {% path="~/react-app" %} -npx nx show project react-app -``` - -{% project-details title="Project Details View" jsonFile="shared/tutorials/react-standalone-pdv.json" %} -{% /project-details %} - -If you hover over the settings for the `build` task, you can see where those settings come from. The `inputs` and `outputs` are defined by the `@nx/vite` plugin from the `vite.config.ts` file where as the `dependsOn` property we set earlier in the tutorial in the `targetDefaults` in the `nx.json` file. - -Now let's change where the `build` results are output to in the `vite.config.ts` file. - -```{% fileName="vite.config.ts" highlightLines=["7-9"] %} -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react()], - build: { - outDir: 'dist/react-app', - }, -}); -``` - -Now if you look at project details view again, you'll see that the `outputs` property for Nx's caching has been updated to stay in sync with the setting in the `vite.config.ts` file. - -## Creating New Components - -You can just create new React components as you normally would. However, Nx plugins also ship [generators](/features/generate-code). They allow you to easily scaffold code, configuration or entire projects. Let's add the `@nx/react` plugin to take advantage of the generators it provides. - -```shell -npx nx add @nx/react -``` - -To see what capabilities the `@nx/react` plugin ships, run the following command and inspect the output: - -```text {% command="npx nx list @nx/react" path="react-app" %} - NX Capabilities in @nx/react: - -GENERATORS - -init : Initialize the `@nx/react` plugin. -application : Create a React application. -library : Create a React library. -component : Create a React component. -redux : Create a Redux slice for a project. -storybook-configuration : Set up storybook for a React app or library. -component-story : Generate storybook story for a React component -stories : Create stories/specs for all components declared in an app or library. -hook : Create a hook. -host : Generate a host react application -remote : Generate a remote react application -cypress-component-configuration : Setup Cypress component testing for a React project -component-test : Generate a Cypress component test for a React component -setup-tailwind : Set up Tailwind configuration for a project. -setup-ssr : Set up SSR configuration for a project. -federate-module : Federate a module. - -EXECUTORS/BUILDERS - -module-federation-dev-server : Serve a host or remote application. -module-federation-ssr-dev-server : Serve a host application along with it's known remotes. -``` - -{% callout type="info" title="Integrate with Your Editor" %} - -For a more integrated experience, install the "Nx Console" extension for your code editor. It has support for VSCode, IntelliJ and ships a LSP for Vim. Nx Console provides autocompletion support in Nx configuration files and has UIs for browsing and running generators. - -More info can be found in [the integrate with editors article](/getting-started/editor-setup). - -{% /callout %} - -Run the following command to generate a new "hello-world" component. Note how we append `--dry-run` to first check the output. - -```text {% command="npx nx g @nx/react:component src/app/hello-world/hello-world --skipTests=true --dry-run" path="react-app" %} -NX Generating @nx/react:component - -✔ Which stylesheet format would you like to use? · css -✔ Should this component be exported in the project? (y/N) · false -✔ Where should the component be generated? · src/app/hello-world/hello-world.tsx -CREATE src/app/hello-world/hello-world.module.css -CREATE src/app/hello-world/hello-world.tsx - -NOTE: The "dryRun" flag means no changes were made. -``` - -As you can see it generates a new component in the `src/app/hello-world/` folder. If you want to actually run the generator, remove the `--dry-run` flag. - -The `hello-world` component will look like this: - -```tsx {% fileName="src/app/hello-world/hello-world.tsx" highlightLines=[6] %} -import styles from './hello-world.module.css'; - -export function HelloWorld() { - return ( -

-

Welcome to HelloWorld!

-
- ); -} - -export default HelloWorld; -``` - -Let's update the `App.tsx` file to use the new `HelloWorld` component: - -```tsx {% fileName="src/App.tsx" %} -import './App.css'; -import HelloWorld from './app/hello-world/hello-world'; - -function App() { - return ( - <> - - - ); -} - -export default App; -``` - -You can view your app with the `nx serve` command: - -```shell -npx nx serve -``` - -## You're ready to go! - -In the previous sections you learned about the basics of using Nx, running tasks and navigating an Nx workspace. You're ready to ship features now! - -But there's more to learn. You have two possibilities here: - -- [Jump to the next steps section](#next-steps) to find where to go from here or -- keep reading and learn some more about what makes Nx unique when working with React. - -## Modularize your React App with Local Libraries - -When you develop your React application, usually all your logic sits in the `src` folder. Ideally separated by various folder names which represent your "domains". As your app grows, this becomes more and more monolithic though. - -``` -└─ react-app - ├─ ... - ├─ src - │ ├─ app - │ │ ├─ products - │ │ ├─ cart - │ │ ├─ ui - │ │ ├─ ... - │ │ └─ app.tsx - │ ├─ ... - │ └─ main.tsx - ├─ ... - ├─ package.json - ├─ ... -``` - -Nx allows you to separate this logic into "local libraries". The main benefits include - -- better separation of concerns -- better reusability -- more explicit "APIs" between your "domain areas" -- better scalability in CI by enabling independent test/lint/build commands for each library -- better scalability in your teams by allowing different teams to work on separate libraries - -### Create a Local Library - -Let's assume our domain areas include `products`, `orders` and some more generic design system components, called `ui`. We can generate a new library for these areas using the React library generator: - -``` -npx nx g @nx/react:library modules/products --unitTestRunner=vitest --bundler=none -``` - -Note how we use the `--directory` flag to place the library into a subfolder. You can choose whatever folder structure you like to organize your libraries. - -Nx sets up your workspace to work with the modular library architecture, but depending on your existing configuration, you may need to tweak some settings. In this repo, you'll need to make a small change in order to prepare for future steps. - -#### Build Settings - -To make sure that the build can correctly pull in code from libraries, we'll update `vite.config.ts` to account for typescript aliases. Run the following generator to automatically update your configuration file. - -```shell -npx nx g @nx/vite:setup-paths-plugin -``` - -This will update the `vite.config.ts` file to include the `nxViteTsPaths` plugin in the `plugins` array. - -```{% fileName="vite.config.ts" highlightLines=[3,7] %} -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; -import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react(), nxViteTsPaths()], - build: { - outDir: 'dist/react-app', - }, -}); -``` - -### Create More Libraries - -Now that the build system is set up, let's generate the `orders` and `ui` libraries. - -``` -nx g @nx/react:library modules/orders --unitTestRunner=vitest --bundler=none -nx g @nx/react:library modules/ui --unitTestRunner=vitest --bundler=none -``` - -Running the above commands should lead to the following directory structure: - -``` -└─ react-app - ├─ ... - ├─ modules - │ ├─ products - │ │ ├─ ... - │ │ ├─ project.json - │ │ ├─ src - │ │ │ ├─ index.ts - │ │ │ └─ lib - │ │ │ ├─ products.spec.ts - │ │ │ └─ products.ts - │ │ ├─ tsconfig.json - │ │ ├─ tsconfig.lib.json - │ │ ├─ tsconfig.spec.json - │ │ └─ vite.config.ts - │ ├─ orders - │ │ ├─ ... - │ │ ├─ project.json - │ │ ├─ src - │ │ │ ├─ index.ts - │ │ │ └─ ... - │ │ └─ ... - │ └─ shared - │ └─ ui - │ ├─ ... - │ ├─ project.json - │ ├─ src - │ │ ├─ index.ts - │ │ └─ ... - │ └─ ... - ├─ src - │ ├─ app - │ │ ├─ hello-world - │ │ │ ├─ hello-world.module.css - │ │ │ └─ hello-world.tsx - │ │ └─ ... - │ ├─ ... - │ └─ main.tsx - ├─ ... -``` - -Each of these libraries - -- has a project details view where you can see the available tasks (e.g. running tests for just orders: `nx test orders`) -- has its own `project.json` file where you can customize targets -- has a dedicated `index.ts` file which is the "public API" of the library -- is mapped in the `tsconfig.base.json` at the root of the workspace - -### Importing Libraries into the React Application - -All libraries that we generate automatically have aliases created in the root-level `tsconfig.base.json`. - -```json {% fileName="tsconfig.base.json" %} -{ - "compilerOptions": { - ... - "paths": { - "orders": ["modules/orders/src/index.ts"], - "products": ["modules/products/src/index.ts"], - "ui": ["modules/shared/ui/src/index.ts"] - }, - ... - }, -} -``` - -That way we can easily import them into other libraries and our React application. As an example, let's import the `Products` component from the `products` project into our main application. First (if you haven't already), let's set up React Router. - -{% tabs %} -{% tab label="npm" %} - -```shell -npm add react-router-dom -``` - -{% /tab %} -{% tab label="yarn" %} - -```shell -yarn add react-router-dom -``` - -{% /tab %} -{% tab label="pnpm" %} - -```shell -pnpm add react-router-dom -``` - -{% /tab %} - -{% tab label="bun" %} - -```shell -bun add react-router-dom -``` - -{% /tab %} -{% /tabs %} - -Configure it in the `main.tsx`. - -```tsx {% fileName="src/main.tsx" %} -import { StrictMode } from 'react'; -import { BrowserRouter } from 'react-router-dom'; -import ReactDOM from 'react-dom/client'; - -import App from './App'; - -const root = ReactDOM.createRoot( - document.getElementById('root') as HTMLElement -); - -root.render( - - - - - -); -``` - -Then we can import the `Products` component into our `app.tsx` and render it via the routing mechanism whenever a user hits the `/products` route. - -```tsx {% fileName="src/App.tsx" %} -import './App.css'; -import HelloWorld from './app/hello-world/hello-world'; -import { Route, Routes } from 'react-router-dom'; - -// importing the component from the library -import { Products } from 'products'; - -export function App() { - return ( - - }> - }> - - ); -} - -export default App; -``` - -Serving your app (`nx serve`) and then navigating to `/products` should give you the following result: - -![products route](/shared/tutorials/react-standalone-products-route.png) - -Let's apply the same steps for our `orders` library. Import the `Orders` component into the `App.tsx` file and render it via the routing mechanism whenever a user hits the `/orders` route - -In the end, your `App.tsx` should look similar to this: - -```tsx {% fileName="src/App.tsx" %} -import './App.css'; -import HelloWorld from './app/hello-world/hello-world'; -import { Route, Routes } from 'react-router-dom'; -import { Products } from 'products'; -import { Orders } from 'orders'; - -export function App() { - return ( - - }> - }> - }> - - ); -} - -export default App; -``` - -## Visualizing your Project Structure - -Nx automatically detects the dependencies between the various parts of your workspace and builds a [project graph](/features/explore-graph). This graph is used by Nx to perform various optimizations such as determining the correct order of execution when running tasks like `nx build`, identifying [affected projects](/features/run-tasks#run-tasks-on-projects-affected-by-a-pr) and more. Interestingly you can also visualize it. - -Just run: - -```shell -nx graph -``` - -You should be able to see something similar to the following in your browser. - -{% graph height="450px" %} - -```json -{ - "projects": [ - { - "name": "react-app", - "type": "app", - "data": { - "tags": [] - } - }, - { - "name": "ui", - "type": "lib", - "data": { - "tags": [] - } - }, - { - "name": "orders", - "type": "lib", - "data": { - "tags": [] - } - }, - - { - "name": "products", - "type": "lib", - "data": { - "tags": [] - } - } - ], - "dependencies": { - "react-app": [ - { "source": "react-app", "target": "orders", "type": "static" }, - { "source": "react-app", "target": "products", "type": "static" } - ], - "ui": [], - "orders": [], - "products": [] - }, - "workspaceLayout": { "appsDir": "", "libsDir": "" }, - "affectedProjectIds": [], - "focus": null, - "groupByFolder": false -} -``` - -{% /graph %} - -Notice how `ui` is not yet connected to anything because we didn't import it in any of our projects. - -Exercise for you: change the codebase such that `ui` is used by `orders` and `products`. - -## Imposing Constraints with Module Boundary Rules - -Once you modularize your codebase you want to make sure that the modules are not coupled to each other in an uncontrolled way. Here are some examples of how we might want to guard our small demo workspace: - -- we might want to allow `orders` to import from `ui` but not the other way around -- we might want to allow `orders` to import from `products` but not the other way around -- we might want to allow all libraries to import the `ui` components, but not the other way around - -When building these kinds of constraints you usually have two dimensions: - -- **type of project:** what is the type of your library. Example: "feature" library, "utility" library, "data-access" library, "ui" library -- **scope (domain) of the project:** what domain area is covered by the project. Example: "orders", "products", "shared" ... this really depends on the type of product you're developing - -Nx comes with a generic mechanism that allows you to assign "tags" to projects. "tags" are arbitrary strings you can assign to a project that can be used later when defining boundaries between projects. For example, go to the `project.json` of your `orders` library and assign the tags `type:feature` and `scope:orders` to it. - -```json {% fileName="modules/orders/project.json" %} -{ - ... - "tags": ["type:feature", "scope:orders"] -} -``` - -Then go to the `project.json` of your `products` library and assign the tags `type:feature` and `scope:products` to it. - -```json {% fileName="modules/products/project.json" %} -{ - ... - "tags": ["type:feature", "scope:products"] -} -``` - -Finally, go to the `project.json` of the `ui` library and assign the tags `type:ui` and `scope:shared` to it. - -```json {% fileName="modules/shared/ui/project.json" %} -{ - ... - "tags": ["type:ui", "scope:shared"] -} -``` - -Notice how we assign `scope:shared` to our UI library because it is intended to be used throughout the workspace. - -Next, let's come up with a set of rules based on these tags: - -- `type:feature` should be able to import from `type:feature` and `type:ui` -- `type:ui` should only be able to import from `type:ui` -- `scope:orders` should be able to import from `scope:orders`, `scope:shared` and `scope:products` -- `scope:products` should be able to import from `scope:products` and `scope:shared` - -To enforce the rules, Nx ships with a custom ESLint rule. - -### Lint Settings - -We want the `lint` task for the root `react-app` project to only lint the files for that project (in the `src` folder), so we'll change the `lint` command in `package.json`: - -```json {% fileName="package.json" %} -{ - "scripts": { - "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0" - } -} -``` - -Install the `@nx/eslint-plugin` package. This is an ESLint plugin that can be used in the `plugins` property of your ESLint configuration. There is also an Nx plugin named `@nx/eslint` that was automatically installed with the `@nx/react` plugin that was added earlier in the tutorial. - -``` -npx nx add @nx/eslint-plugin -``` - -We need to update the `.eslintrc.cjs` file to extend the `.eslintrc.base.json` file and undo the `ignorePattern` from that config that ignores every file. The `.eslintrc.base.json` file serves as a common set of lint rules for every project in the repository. - -```js {% fileName=".eslintrc.cjs" highlightLines=[11,"17-53"] %} -module.exports = { - root: true, - env: { browser: true, es2020: true }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:react-hooks/recommended', - './.eslintrc.base.json', - ], - ignorePatterns: ['!**/*', 'dist', '.eslintrc.cjs'], - parser: '@typescript-eslint/parser', - plugins: ['react-refresh'], - rules: { - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - }, -}; -``` - -Now we need to update the `.eslintrc.base.json` file and define the `depConstraints` in the `@nx/enforce-module-boundaries` rule: - -```json {% fileName=".eslintrc.base.json" highlightLines=["16-39"] %} -{ - "overrides": [ - { - "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "rules": { - "@nx/enforce-module-boundaries": [ - "error", - { - "enforceBuildableLibDependency": true, - "allow": [], - "depConstraints": [ - { - "sourceTag": "*", - "onlyDependOnLibsWithTags": ["*"] - }, - { - "sourceTag": "type:feature", - "onlyDependOnLibsWithTags": ["type:feature", "type:ui"] - }, - { - "sourceTag": "type:ui", - "onlyDependOnLibsWithTags": ["type:ui"] - }, - { - "sourceTag": "scope:orders", - "onlyDependOnLibsWithTags": [ - "scope:orders", - "scope:products", - "scope:shared" - ] - }, - { - "sourceTag": "scope:products", - "onlyDependOnLibsWithTags": ["scope:products", "scope:shared"] - }, - { - "sourceTag": "scope:shared", - "onlyDependOnLibsWithTags": ["scope:shared"] - } - ] - } - ] - } - } - ... - ] -} -``` - -When Nx set up the `@nx/eslint` plugin, it chose a task name that would not conflict with the pre-existing `lint` script. Let's overwrite that name so that all the linting tasks use the same `lint` name. Update the setting in the `nx.json` file: - -```json {% fileName="nx.json" highlightLines=[7] %} -{ - ... - "plugins": [ - { - "plugin": "@nx/eslint/plugin", - "options": { - "targetName": "lint" - } - } - ] -} -``` - -### Test Boundary Rules - -To test the boundary rules, go to your `modules/products/src/lib/products.tsx` file and import the `Orders` from the `orders` project: - -```tsx {% fileName="modules/products/src/lib/products.tsx" %} -import styles from './products.module.css'; - -// This import is not allowed 👇 -import { Orders } from 'orders'; - -/* eslint-disable-next-line */ -export interface ProductsProps {} - -export function Products() { - return ( -
-

Welcome to Products!

-
- ); -} - -export default Products; -``` - -If you lint your workspace you'll get an error now: - -```{% command="nx run-many -t lint" %} -✔ nx run orders:lint [existing outputs match the cache, left as is] -✔ nx run ui:lint (1s) - -✖ nx run products:lint - Linting "products"... - - /Users/.../react-app/modules/products/src/lib/products.tsx - 3:1 error A project tagged with "scope:products" can only depend on libs tagged with "scope:products", "scope:shared" @nx/enforce-module-boundaries - - ✖ 1 problem (1 error, 0 warnings) - - Lint errors found in the listed files. - -✔ nx run react-app:lint (1s) - -———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— - -NX Ran target lint for 4 projects (1s) - -✔ 3/4 succeeded [1 read from cache] - -✖ 1/4 targets failed, including the following: - - nx run products:lint -``` - -If you have the ESLint plugin installed in your IDE you should also immediately see an error. - -Learn more about how to [enforce module boundaries](/features/enforce-module-boundaries). - -## Migrating to a Monorepo - -When you are ready to add another application to the repo, you'll probably want to move `react-app` to its own folder. To do this, you can run the [`convert-to-monorepo` generator](/nx-api/workspace/generators/convert-to-monorepo) or [manually move the configuration files](/recipes/tips-n-tricks/standalone-to-monorepo). - -You can also go through the full [React monorepo tutorial](/getting-started/tutorials/react-monorepo-tutorial) - -## Fast CI ⚡ {% highlightColor="green" %} - -{% callout type="check" title="Repository with Nx" %} -Make sure you have completed the previous sections of this tutorial before starting this one. If you want a clean starting point, you can check out the [reference code](https://github.com/nrwl/nx-recipes/tree/main/react-app) as a starting point. -{% /callout %} - -This tutorial walked you through how Nx can improve the local development experience, but the biggest difference Nx makes is in CI. As repositories get bigger, making sure that the CI is fast, reliable and maintainable can get very challenging. Nx provides a solution. - -- Nx reduces wasted time in CI with the [`affected` command](/ci/features/affected). -- Nx Replay's [remote caching](/ci/features/remote-cache) will reuse task artifacts from different CI executions making sure you will never run the same computation twice. -- Nx Agents [efficiently distribute tasks across machines](/ci/concepts/parallelization-distribution) ensuring constant CI time regardless of the repository size. The right number of machines is allocated for each PR to ensure good performance without wasting compute. -- Nx Atomizer [automatically splits](/ci/features/split-e2e-tasks) large e2e tests to distribute them across machines. Nx can also automatically [identify and rerun flaky e2e tests](/ci/features/flaky-tasks). - -### Connect to Nx Cloud {% highlightColor="green" %} - -Nx Cloud is a companion app for your CI system that provides remote caching, task distribution, e2e tests deflaking, better DX and more. - -Now that we're working on the CI pipeline, it is important for your changes to be pushed to a GitHub repository. - -1. Commit your existing changes with `git add . && git commit -am "updates"` -2. [Create a new GitHub repository](https://github.com/new) -3. Follow GitHub's instructions to push your existing code to the repository - -Now connect your repository to Nx Cloud with the following command: - -```shell -npx nx connect -``` - -A browser window will open to register your repository in your [Nx Cloud](https://cloud.nx.app) account. The link is also printed to the terminal if the windows does not open, or you closed it before finishing the steps. The app will guide you to create a PR to enable Nx Cloud on your repository. - -![](/shared/tutorials/nx-cloud-github-connect.avif) - -Once the PR is created, merge it into your main branch. - -![](/shared/tutorials/github-cloud-pr-merged.avif) - -And make sure you pull the latest changes locally: - -```shell -git pull -``` - -You should now have an `nxCloudId` property specified in the `nx.json` file. - -### Create a CI Workflow {% highlightColor="green" %} - -Use the following command to generate a CI workflow file. - -```shell -npx nx generate ci-workflow --ci=github -``` - -This generator creates a `.github/workflows/ci.yml` file that contains a CI pipeline that will run the `lint`, `test`, `build` and `e2e` tasks for projects that are affected by any given PR. If you would like to also distribute tasks across multiple machines to ensure fast and reliable CI runs, uncomment the `nx-cloud start-ci-run` line and have the `nx affected` line run the `e2e-ci` task instead of `e2e`. - -The key lines in the CI pipeline are: - -```yml {% fileName=".github/workflows/ci.yml" highlightLines=["10-14", "21-23"] %} -name: CI -# ... -jobs: - main: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - # This enables task distribution via Nx Cloud - # Run this command as early as possible, before dependencies are installed - # Learn more at https://nx.dev/ci/reference/nx-cloud-cli#npx-nxcloud-startcirun - # Uncomment this line to enable task distribution - # - run: npx nx-cloud start-ci-run --distribute-on="3 linux-medium-js" --stop-agents-after="e2e-ci" - - uses: actions/setup-node@v3 - with: - node-version: 20 - cache: 'npm' - - run: npm ci --legacy-peer-deps - - uses: nrwl/nx-set-shas@v4 - # Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected - # When you enable task distribution, run the e2e-ci task instead of e2e - - run: npx nx affected -t lint test build e2e -``` - -### Open a Pull Request {% highlightColor="green" %} - -Commit the changes and open a new PR on GitHub. - -```shell -git add . -git commit -m 'add CI workflow file' -git push origin add-workflow -``` - -When you view the PR on GitHub, you will see a comment from Nx Cloud that reports on the status of the CI run. - -![Nx Cloud report](/shared/tutorials/github-pr-cloud-report.avif) - -The `See all runs` link goes to a page with the progress and results of tasks that were run in the CI pipeline. - -![Run details](/shared/tutorials/nx-cloud-run-details.avif) - -For more information about how Nx can improve your CI pipeline, check out one of these detailed tutorials: - -- [Circle CI with Nx](/ci/intro/tutorials/circle) -- [GitHub Actions with Nx](/ci/intro/tutorials/github-actions) - -## Next Steps - -Here's some things you can dive into next: - -- Learn more about the [underlying mental model of Nx](/concepts/mental-model) -- Learn how to [migrate your React app to Nx](/recipes/adopting-nx/adding-to-existing-project) -- [Learn how to setup Tailwind](/recipes/react/using-tailwind-css-in-react) -- [Setup Storybook for our shared UI library](/recipes/storybook/overview-react) - -Also, make sure you - -- ⭐️ [Star us on GitHub](https://github.com/nrwl/nx) to show your support and stay updated on new releases! -- [Join the Official Nx Discord Server](https://go.nx.dev/community) to ask questions and find out the latest news about Nx. -- [Follow Nx on Twitter](https://twitter.com/nxdevtools) to stay up to date with Nx news -- [Read our Nx blog](/blog) -- [Subscribe to our Youtube channel](https://www.youtube.com/@nxdevtools) for demos and Nx insights diff --git a/docs/shared/tutorials/vue-app-pdv.json b/docs/shared/tutorials/vue-app-pdv.json deleted file mode 100644 index 9f071c40c5..0000000000 --- a/docs/shared/tutorials/vue-app-pdv.json +++ /dev/null @@ -1,186 +0,0 @@ -{ - "project": { - "name": "vue-app", - "type": "lib", - "data": { - "root": ".", - "targets": { - "vite:build": { - "options": { - "cwd": ".", - "command": "vite build" - }, - "cache": true, - "dependsOn": ["^vite:build"], - "inputs": [ - "default", - "^default", - { - "externalDependencies": ["vite"] - } - ], - "outputs": ["{projectRoot}/dist"], - "executor": "nx:run-commands", - "configurations": {} - }, - "build-only": { - "executor": "nx:run-script", - "metadata": { - "scriptContent": "nx vite:build", - "runCommand": "npm run build-only" - }, - "outputs": ["{projectRoot}/dist"], - "cache": true, - "options": { - "script": "build-only" - }, - "configurations": {} - }, - "build": { - "executor": "nx:run-script", - "metadata": { - "scriptContent": "nx exec -- echo 'Ran type-check and build-only'", - "runCommand": "npm run build" - }, - "dependsOn": [ - "type-check", - { - "target": "build-only", - "params": "forward" - } - ], - "options": { - "script": "build" - }, - "configurations": {} - } - }, - "sourceRoot": ".", - "name": "vue-app", - "projectType": "library", - "metadata": { - "targetGroups": { - "NPM Scripts": ["build", "build-only"] - } - }, - "implicitDependencies": [], - "tags": [] - } - }, - "sourceMap": { - "root": ["package.json", "nx/core/package-json-workspaces"], - "targets": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:build": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:build.options": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:build.cache": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:build.dependsOn": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:build.inputs": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:build.outputs": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:build.executor": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:build.options.cwd": ["vite.config.ts", "@nx/vite/plugin"], - "targets.vite:build.options.command": ["vite.config.ts", "@nx/vite/plugin"], - "targets.build-only": ["package.json", "nx/core/package-json-workspaces"], - "targets.build-only.executor": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.build-only.options": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.build-only.metadata": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.build-only.outputs": ["nx.json", "nx/core/target-defaults"], - "targets.build-only.cache": ["nx.json", "nx/core/target-defaults"], - "targets.build-only.options.script": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.build-only.metadata.scriptContent": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.build-only.metadata.runCommand": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "sourceRoot": ["package.json", "nx/core/package-json-workspaces"], - "name": ["package.json", "nx/core/package-json-workspaces"], - "projectType": ["package.json", "nx/core/package-json-workspaces"], - "metadata.targetGroups": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "metadata.targetGroups.NPM Scripts": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "metadata.targetGroups.NPM Scripts.0": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "metadata.targetGroups.NPM Scripts.1": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "metadata.targetGroups.NPM Scripts.2": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "metadata.targetGroups.NPM Scripts.3": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "metadata.targetGroups.NPM Scripts.4": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "metadata.targetGroups.NPM Scripts.5": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "metadata.targetGroups.NPM Scripts.6": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "metadata.targetGroups.NPM Scripts.7": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "metadata.targetGroups.NPM Scripts.8": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.build": ["package.json", "nx/core/package-json-workspaces"], - "targets.build.executor": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.build.metadata": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.build.dependsOn": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.build.options": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.build.options.script": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.build.metadata.scriptContent": [ - "package.json", - "nx/core/package-json-workspaces" - ], - "targets.build.metadata.runCommand": [ - "package.json", - "nx/core/package-json-workspaces" - ] - } -} diff --git a/docs/shared/tutorials/vue-app-products-route.png b/docs/shared/tutorials/vue-app-products-route.png deleted file mode 100644 index 861fc7a0f23bd9cf730943de518a8f0e2d00a0dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30394 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 - -## Creating a new Vue App - - - -Create a new Vue application with the following command: - -```{% command="npm create vue vue-app -- --ts --jsx --router --vitest --playwright --eslint-with-prettier" path="~" %} - -Vue.js - The Progressive JavaScript Framework - -Scaffolding project in ~/vue-app... - -Done. Now run: - - cd vue-app - npm install - npm run format - npm run dev -``` - -Once you have run `npm install`, set up Git with the following commands: - -```shell -git init -git add . -git commit -m "initial commit" -``` - -This command sets up a Vue app that uses Typescript and is configured to use the Vue router, Vitest for unit tests, Playwright for e2e tests and ESLint and Prettier for formatting. Your repository should now have the following structure: - -``` -└─ vue-app - ├─ .vscode - │ └─ extensions.json - ├─ e2e - │ ├─ tsconfig.json - │ └─ vue.spec.ts - ├─ public - ├─ src - │ ├─ assets - │ ├─ components - │ ├─ router - │ ├─ views - │ ├─ App.vue - │ └─ main.ts - ├─ .eslintrc.cjs - ├─ .prettierrc.json - ├─ index.html - ├─ package.json - ├─ playwright.config.ts - ├─ README.md - ├─ tsconfig.app.json - ├─ tsconfig.json - ├─ tsconfig.node.json - ├─ tsconfig.vitest.json - ├─ vite.config.ts - └─ vitest.config.ts -``` - -The setup includes.. - -- a new Vue application at the root of the Nx workspace (`src`) -- a Playwright based set of e2e tests (`e2e/`) -- Prettier preconfigured -- ESLint preconfigured -- Vitest preconfigured - -You can build the application with the following command: - -``` -npm run build -``` - -## Add Nx - -Nx offers many features, but at its core, it is a task runner. Out of the box, it can: - -- [cache your tasks](/features/cache-task-results) -- ensure those tasks are [run in the correct order](/features/run-tasks) - -After the initial set up, you can incrementally add on other features that would be helpful in your organization. - -To enable Nx in your repository, run a single command: - -```shell {% path="~/vue-app" %} -npx nx@latest init -``` - -This command will download the latest version of Nx and help set up your repository to take advantage of it. - -First, the script will propose installing some plugins based on the packages that are being used in your repository. - -- Leave the plugins deselected so that we can explore what Nx provides without any plugins. - -Second, the script asks a series of questions to help set up caching for you. - -- `Which scripts are cacheable?` - Choose `test:e2e`, `build-only`, `type-check` and `lint` -- `Does the "test:e2e" script create any outputs?` - Enter `playwright-report` -- `Does the "build-only" script create any outputs?` - Enter `dist` -- `Does the "type-check" script create any outputs?` - Enter nothing -- `Does the "lint" script create any outputs?` - Enter nothing -- `Would you like remote caching to make your build faster?` - Choose `Skip for now`. - -We'll enable Nx Cloud and add remote caching later in the tutorial. - -## Caching Pre-configured - -Nx has been configured to run your npm scripts as Nx tasks. You can run a single task like this: - -```shell {% path="~/vue-app" %} -npx nx type-check vue-app -``` - -During the `init` script, Nx also configured caching for these tasks. You can see in the `nx.json` file that the scripts we identified as cacheable have the `cache` property set to `true` and the `build-only` target specifies that its output goes to the project's `dist` folder. - -```json {% fileName="nx.json" %} -{ - "$schema": "./node_modules/nx/schemas/nx-schema.json", - "targetDefaults": { - "test:e2e": { - "outputs": ["{projectRoot}/playwright-report"], - "cache": true - }, - "build-only": { - "outputs": ["{projectRoot}/dist"], - "cache": true - }, - "type-check": { - "cache": true - }, - "lint": { - "cache": true - } - }, - "defaultBase": "main" -} -``` - -Try running `type-check` for the `vue-app` app a second time: - -```shell {% path="~/vue-app" %} -npx nx type-check vue-app -``` - -The first time `nx type-check` was run, it took about 2 seconds - just like running `npm run type-check`. But the second time you run `nx type-check`, it completes instantly and displays this message: - -```text -Nx read the output from the cache instead of running the command for 1 out of 1 tasks. -``` - -You can see the same caching behavior working when you run `npx nx lint`. - -## Create a Task Pipeline - -If you look at the `build` script in `package.json`, you'll notice that it is actually doing two things. It runs the `type-check` script and, in parallel, runs the `build-only` script. - -```json {% fileName="package.json" %} -{ - "scripts": { - "build": "run-p type-check \"build-only {@}\" --", - "build-only": "vite build", - "type-check": "vue-tsc --build --force" - } -} -``` - -Instead of deciding ahead of time whether tasks need to be run in parallel or series, let's define the dependencies between tasks and then allow Nx to run them in the most efficient way possible. Update the `package.json` file with the following information: - -```json {% fileName="package.json" highlightLines=[3] %} -{ - "scripts": { - "build": "nx exec -- echo 'Ran type-check and build-only'", - "build-only": "vite build", - "type-check": "vue-tsc --build --force" - } -} -``` - -Since the `build` command itself doesn't do anything, we can replace it with an echo to the console. The dependencies between the tasks we can define in the `dependsOn` property in the `nx.json` file: - -```json {% fileName="nx.json" highlightLines=["3-5"] %} -{ - "targetDefaults": { - "build": { - "dependsOn": ["type-check", "build-only"] - } - } -} -``` - -Whenever Nx runs the `build` task, it knows that the `type-check` and `build-only` tasks need to be run first. However, if the `build` script is run without using Nx (say with `npm run build`), the task dependencies will not be run. That's why we add the `nx exec --` command at the beginning of the `build` script in `package.json`. If you run `npm run build`, Nx will be used to run the task and any dependencies will be invoked correctly. - -Now if you run `nx build vue-app`, Nx will run `type-check` and `build-only` first. - -There's still one piece of functionality that was lost by this change. The `{@}` syntax in the original `build` script was used to forward command line arguments to the `build-only` script. To accomplish the same thing with Nx, instead of using a string for the `build-only` task dependency, we'll use an object and tell Nx to forward arguments on to that task. - -```json {% fileName="nx.json" highlightLines=["6-9"] %} -{ - "targetDefaults": { - "build": { - "dependsOn": [ - "type-check", - { - "target": "build-only", - "params": "forward" - } - ] - } - } -} -``` - -If you run `nx build vue-app -l=error`, Nx will run the `build-only` script with a log level of error. - -```text {% command="npx nx build" path="~/vue-app" %} -> nx run vue-app:type-check [existing outputs match the cache, left as is] - - -> vue-app@0.0.0 type-check -> vue-tsc --build --force - - -> nx run vue-app:build-only [local cache] - - -> vue-app@0.0.0 build-only -> vite build -l error - - -> nx run vue-app:build -l error - - -> vue-app@0.0.0 build -> nx exec -- echo 'Ran type-check and build-only' -l error - -Ran type-check and build-only -l error - -————————————————————————————————————————————————————————————————————— - - NX Successfully ran target build for project vue-app and 2 tasks it depends on (456ms) - - With additional flags: - --l=error -``` - -And once again, running `npx nx build` or `npm run build` twice will complete instantly. - -## Use Nx Plugins to Enhance Vite Tasks with Caching - -You may remember that we defined the `outputs` property in `nx.json` when we were answering questions in the `nx init` script. The value is currently hard-coded so that if you change the output path in your `vite.config.ts`, you have to remember to also change the `outputs` array in the `build` task configuration. This is where plugins can help. Plugins enable better integration with specific tools. The `@nx/vite` plugin can understand the `vite.config.ts` file and automatically create and configure tasks based on the settings in that file. - -Nx plugins can: - -- automatically configure caching for you, including inputs and outputs based on the underlying tooling configuration -- create tasks for a project using the tooling configuration files -- provide code generators to help scaffold out projects -- automatically keep the tooling versions and configuration files up to date - -For this tutorial, we'll just focus on the automatic caching configuration. - -First, let's delete the `outputs` array for the `build-only` task in `nx.json` so that we don't override the inferred values from the plugin. Your `nx.json` should look like this: - -```json {% fileName="nx.json" %} -{ - "$schema": "./node_modules/nx/schemas/nx-schema.json", - "targetDefaults": { - "test:e2e": { - "outputs": ["{projectRoot}/playwright-report"], - "cache": true - }, - "build": { - "dependsOn": [ - "type-check", - { - "target": "build-only", - "params": "forward" - } - ] - }, - "build-only": { - "cache": true - }, - "type-check": { - "cache": true - }, - "lint": { - "cache": true - } - }, - "defaultBase": "main" -} -``` - -Now let's add the `@nx/vite` plugin: - -```{% command="npx nx add @nx/vite" path="~/vue-app" %} -✔ Installing @nx/vite... -✔ Initializing @nx/vite... - - NX Package @nx/vite added successfully. -``` - -The `nx add` command installs the version of the plugin that matches your repo's Nx version and runs that plugin's initialization script. For `@nx/vite`, the initialization script registers the plugin in the `plugins` array of `nx.json` and updates any `package.json` scripts that execute Vite related tasks. Open the project details view for the `vue-app` app and look at the `vite:build` task. - -```shell {% path="~/vue-app" %} -npx nx show project vue-app -``` - -{% project-details title="Project Details View" jsonFile="shared/tutorials/vue-app-pdv.json" %} -{% /project-details %} - -If you hover over the settings for the `vite:build` task, you can see where those settings come from. The `inputs` and `outputs` are defined by the `@nx/vite` plugin from the `vite.config.ts` file where as the `dependsOn` property we set earlier in the tutorial is set in the `targetDefaults` in the `nx.json` file. - -Now let's change where the `vite:build` results are output to in the `vite.config.ts` file. - -```{% fileName="vite.config.ts" highlightLines=["10-12"] %} -import { fileURLToPath, URL } from 'node:url' - -import { defineConfig } from 'vite' -import vue from '@vitejs/plugin-vue' -import vueJsx from '@vitejs/plugin-vue-jsx' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [vue(), vueJsx()], - build: { - outDir: 'dist/vue-app' - }, - resolve: { - alias: { - '@': fileURLToPath(new URL('./src', import.meta.url)) - } - } -}) -``` - -Now if you look at project details view again, you'll see that the `outputs` property for Nx's caching has been updated to stay in sync with the setting in the `vite.config.ts` file. - -You can also add the `@nx/eslint` plugin to see how it infers `lint` tasks based on the ESLint configuration files. - -```text -npx nx add @nx/eslint -``` - -## Creating New Components - - - -You can just create new Vue components as you normally would. However, Nx plugins also ship [generators](/features/generate-code). They allow you to easily scaffold code, configuration or entire projects. Let's add the `@nx/vue` plugin to take advantage of the generators it provides. - -```text -npx nx add @nx/vue -``` - -To see what capabilities the `@nx/vue` plugin ships, run the following command and inspect the output: - -```{% command="npx nx list @nx/vue" path="~/vue-app" %} - -NX Capabilities in @nx/vue: - - GENERATORS - - init : Initialize the `@nx/vue` plugin. - application : Create a Vue application. - library : Create a Vue library. - component : Create a Vue component. - setup-tailwind : Set up Tailwind configuration for a project. - storybook-configuration : Set up storybook for a Vue app or library. - stories : Create stories for all components declared in an app or library. -``` - -{% callout type="info" title="Prefer a more visual UI?" %} - -If you prefer a more integrated experience, you can install the "Nx Console" extension for your code editor. It has support for VSCode, IntelliJ and ships a LSP for Vim. Nx Console provides autocompletion support in Nx configuration files and has UIs for browsing and running generators. - -More info can be found in [the editor setup page](/getting-started/editor-setup). - -{% /callout %} - -Run the following command to generate a new "BaseButton" component. Note how we append `--dry-run` to first check the output. - -```{% command="npx nx g @nx/vue:component src/components/BaseButton --no-export --skipTests --dry-run" path="~/vue-app" %} - NX Generating @nx/vue:component - -✔ Where should the component be generated? · src/components/BaseButton.vue -CREATE src/components/BaseButton.vue - -NOTE: The "dryRun" flag means no changes were made. -``` - -As you can see it generates a new component in the `src/components/` folder. If you want to actually run the generator, remove the `--dry-run` flag. - -```ts {% fileName="src/components/BaseButton.vue" %} - - - - - -``` - -## You're ready to go! - -In the previous sections you learned about the basics of using Nx, running tasks and navigating an Nx workspace. You're ready to ship features now! - -But there's more to learn. You have two possibilities here: - -- [Jump to the next steps section](#next-steps) to find where to go from here or -- keep reading and learn some more about what makes Nx unique when working with Vue. - -## Modularize your Vue App with Local Libraries - -When you develop your Vue application, usually all your logic sits in the `src` folder. Ideally separated by various folder names which represent your "domains". As your app grows, this becomes more and more monolithic though. - -``` -└─ vue-app - ├─ ... - ├─ src - │ ├─ views - │ │ ├─ products - │ │ └─ cart - │ ├─ components - │ │ ├─ ui - │ │ └─ ... - │ ├─ App.vue - │ └─ main.ts - ├─ ... - ├─ package.json - ├─ ... -``` - -Nx allows you to separate this logic into "local libraries". The main benefits include - -- better separation of concerns -- better reusability -- more explicit "APIs" between your "domain areas" -- better scalability in CI by enabling independent test/lint/build commands for each library -- better scalability in your teams by allowing different teams to work on separate libraries - -### Create a Local Library - -Let's assume our domain areas include `products`, `orders` and some more generic design system components, called `ui`. We can generate a new library for these areas using the Vue library generator: - -``` -nx g @nx/vue:library modules/products --unitTestRunner=vitest --bundler=vite --component -``` - -Note how we use the `--directory` flag to place the library into a subfolder. You can choose whatever folder structure you like to organize your libraries. - -Nx tries to set up your workspace to work with the modular library architecture, but depending on your existing configuration, you may need to tweak some settings. In this repo, you'll need to do a few things in order to prepare for future steps. - -#### Lint Settings - -We want the `lint` task for the root `vue-app` project to only lint the files for that project, so we'll change the `lint` command in `package.json`: - -```json {% fileName="package.json" %} -{ - "scripts": { - "lint": "eslint src --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" - } -} -``` - -Now we need to update the `.eslintrc.cjs` file to extend the `.eslintrc.base.json` file: - -```js {% fileName=".eslintrc.cjs" highlightLines=[11,13] %} -/* eslint-env node */ -require('@rushstack/eslint-patch/modern-module-resolution'); - -module.exports = { - root: true, - extends: [ - 'plugin:vue/vue3-essential', - 'eslint:recommended', - '@vue/eslint-config-typescript', - '@vue/eslint-config-prettier/skip-formatting', - './.eslintrc.base.json', - ], - ignorePatterns: ['!**/*'], - overrides: [ - { - files: ['e2e/**/*.{test,spec}.{js,ts,jsx,tsx}'], - extends: ['plugin:playwright/recommended'], - }, - ], - parserOptions: { - ecmaVersion: 'latest', - }, -}; -``` - -#### Build Settings - -To make sure that the build can correctly pull in code from libraries, we need to move the typescript `paths` from the `tsconfig.app.json` file to the newly created `tsconfig.base.json` and extend that base file. - -```json {% fileName="tsconfig.app.json" highlightLines=[2] %} -{ - "extends": ["@vue/tsconfig/tsconfig.dom.json", "./tsconfig.base.json"], - "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], - "exclude": ["src/**/__tests__/*"] -} -``` - -```json {% fileName="tsconfig.vitest.json" highlightLines=[2] %} -{ - "extends": "./tsconfig.app.json", - "exclude": [], - "compilerOptions": { - "lib": [], - "types": ["node", "jsdom"] - } -} -``` - -```json {% fileName="tsconfig.base.json" highlightLines=["5-8"] %} -{ - "compileOnSave": false, - "compilerOptions": { - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"], - "products": ["modules/products/src/index.ts"] - } - } -} -``` - -We also need to update `vite.config.ts` to account for typescript aliases. Run the following generator to automatically update your configuration file. - -```shell -npx nx g @nx/vite:setup-paths-plugin -``` - -This will update the `vite.config.ts` file to include the `nxViteTsPaths` plugin in the `plugins` array. - -```ts {% fileName="vite.config.ts" highlightLines=[6,10] %} -import { fileURLToPath, URL } from 'node:url'; - -import { defineConfig } from 'vite'; -import vue from '@vitejs/plugin-vue'; -import vueJsx from '@vitejs/plugin-vue-jsx'; -import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [vue(), vueJsx(), nxViteTsPaths()], - build: { - outDir: 'dist/vue-app', - }, - resolve: { - alias: { - '@': fileURLToPath(new URL('./src', import.meta.url)), - }, - }, -}); -``` - -### Create More Libraries - -Now that the repository is set up, let's generate the `orders` and `ui` libraries. - -``` -nx g @nx/vue:library modules/orders --unitTestRunner=vitest --bundler=vite --component -nx g @nx/vue:library modules/ui --unitTestRunner=vitest --bundler=vite --component -``` - -Running the above commands should lead to the following directory structure: - -``` -└─ vue-app - ├─ ... - ├─ e2e/ - ├─ modules - │ ├─ products - │ │ ├─ .eslintrc.json - │ │ ├─ README.md - │ │ ├─ vite.config.ts - │ │ ├─ package.json - │ │ ├─ project.json - │ │ ├─ src - │ │ │ ├─ index.ts - │ │ │ └─ lib - │ │ │ ├─ products.spec.ts - │ │ │ └─ products.vue - │ │ ├─ tsconfig.json - │ │ ├─ tsconfig.lib.json - │ │ ├─ tsconfig.spec.json - │ │ └─ vite.config.ts - │ ├─ orders - │ │ ├─ ... - │ │ ├─ project.json - │ │ ├─ src - │ │ │ ├─ index.ts - │ │ │ └─ ... - │ │ └─ ... - │ └─ shared - │ └─ ui - │ ├─ ... - │ ├─ project.json - │ ├─ src - │ │ ├─ index.ts - │ │ └─ ... - │ └─ ... - ├─ src - │ ├─ components - │ ├─ ... - │ ├─ App.vue - │ └─ main.tsx - ├─ ... -``` - -Each of these libraries - -- has a project details view where you can see the available tasks (e.g. running tests for just orders: `nx test orders`) -- has its own `project.json` file where you can customize targets -- has a dedicated `index.ts` file which is the "public API" of the library -- is mapped in the `tsconfig.base.json` at the root of the workspace - -### Importing Libraries into the Vue Application - -All libraries that we generate automatically have aliases created in the root-level `tsconfig.base.json`. - -```json {% fileName="tsconfig.base.json" %} -{ - "compilerOptions": { - ... - "paths": { - "@/*": ["./src/*"], - "orders": ["modules/orders/src/index.ts"], - "products": ["modules/products/src/index.ts"], - "ui": ["modules/shared/ui/src/index.ts"] - }, - ... - }, -} -``` - -That way we can easily import them into other libraries and our Vue application. As an example, let's import the `Products` component from the `products` project into our main application. First, configure the router in the `src/router/index.ts`. - -```tsx {% fileName="src/router/index.ts" %} -import { createRouter, createWebHistory } from 'vue-router'; -import HomeView from '../views/HomeView.vue'; - -const router = createRouter({ - history: createWebHistory(import.meta.env.BASE_URL), - routes: [ - { - path: '/', - name: 'home', - component: HomeView, - }, - { - path: '/about', - name: 'about', - // route level code-splitting - // this generates a separate chunk (About.[hash].js) for this route - // which is lazy-loaded when the route is visited. - component: () => import('../views/AboutView.vue'), - }, - { - path: '/products', - name: 'products', - component: () => import('products').then((m) => m.Products), - }, - { - path: '/orders', - name: 'orders', - component: () => import('orders').then((m) => m.Orders), - }, - ], -}); - -export default router; -``` - -Then we can add links to the routes in `App.vue`. - -```tsx {% fileName="src/App.vue" %} - - - - -... -``` - -Serving your app (`nx serve`) and then navigating to `/products` should give you the following result: - -![products route](/shared/tutorials/vue-app-products-route.png) - -## Visualizing your Project Structure - - - -Nx automatically detects the dependencies between the various parts of your workspace and builds a [project graph](/features/explore-graph). This graph is used by Nx to perform various optimizations such as determining the correct order of execution when running tasks like `nx build`, identifying [affected projects](/features/run-tasks#run-tasks-on-projects-affected-by-a-pr) and more. Interestingly you can also visualize it. - -Just run: - -```shell -nx graph -``` - -You should be able to see something similar to the following in your browser (hint: click the "Show all projects" button). - -{% graph height="450px" %} - -```json -{ - "projects": [ - { - "name": "vue-app", - "type": "app", - "data": { - "tags": [] - } - }, - { - "name": "ui", - "type": "lib", - "data": { - "tags": [] - } - }, - { - "name": "orders", - "type": "lib", - "data": { - "tags": [] - } - }, - - { - "name": "products", - "type": "lib", - "data": { - "tags": [] - } - } - ], - "dependencies": { - "vue-app": [ - { "source": "vue-app", "target": "orders", "type": "dynamic" }, - { "source": "vue-app", "target": "products", "type": "dynamic" } - ], - "ui": [], - "orders": [], - "products": [] - }, - "workspaceLayout": { "appsDir": "", "libsDir": "" }, - "affectedProjectIds": [], - "focus": null, - "groupByFolder": false -} -``` - -{% /graph %} - -Notice how `ui` is not yet connected to anything because we didn't import it in any of our projects. Also the arrows to `orders` and `products` are dashed because we're using lazy imports. - -Exercise for you: change the codebase so that `ui` is used by `orders` and `products`. - -## Imposing Constraints with Module Boundary Rules - - - -Once you modularize your codebase you want to make sure that the modules are not coupled to each other in an uncontrolled way. Here are some examples of how we might want to guard our small demo workspace: - -- we might want to allow `orders` to import from `shared-ui` but not the other way around -- we might want to allow `orders` to import from `products` but not the other way around -- we might want to allow all libraries to import the `shared-ui` components, but not the other way around - -When building these kinds of constraints you usually have two dimensions: - -- **type of project:** what is the type of your library. Example: "feature" library, "utility" library, "data-access" library, "ui" library (see [library types](/concepts/decisions/project-dependency-rules)) -- **scope (domain) of the project:** what domain area is covered by the project. Example: "orders", "products", "shared" ... this really depends on the type of product you're developing - -Nx comes with a generic mechanism that allows you to assign "tags" to projects. "tags" are arbitrary strings you can assign to a project that can be used later when defining boundaries between projects. For example, go to the `project.json` of your `orders` library and assign the tags `type:feature` and `scope:orders` to it. - -```json {% fileName="modules/orders/project.json" %} -{ - ... - "tags": ["type:feature", "scope:orders"], - ... -} -``` - -Then go to the `project.json` of your `products` library and assign the tags `type:feature` and `scope:products` to it. - -```json {% fileName="modules/products/project.json" %} -{ - ... - "tags": ["type:feature", "scope:products"], - ... -} -``` - -Finally, go to the `project.json` of the `shared-ui` library and assign the tags `type:ui` and `scope:shared` to it. - -```json {% fileName="modules/shared/ui/project.json" %} -{ - ... - "tags": ["type:ui", "scope:shared"], - ... -} -``` - -Notice how we assign `scope:shared` to our UI library because it is intended to be used throughout the workspace. - -Next, let's come up with a set of rules based on these tags: - -- `type:feature` should be able to import from `type:feature` and `type:ui` -- `type:ui` should only be able to import from `type:ui` -- `scope:orders` should be able to import from `scope:orders`, `scope:shared` and `scope:products` -- `scope:products` should be able to import from `scope:products` and `scope:shared` - -To enforce the rules, Nx ships with a custom ESLint rule. - -### Lint Settings - -We want the `lint` task for the root `react-app` project to only lint the files for that project (in the `src` folder), so we'll change the `lint` command in `package.json`: - -```json {% fileName="package.json" %} -{ - "scripts": { - "lint": "eslint src --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" - } -} -``` - -We need to update the `.eslintrc.cjs` file to extend the `.eslintrc.base.json` file and undo the `ignorePattern` from that config that ignores every file. The `.eslintrc.base.json` file serves as a common set of lint rules for every project in the repository. - -```js {% fileName=".eslintrc.cjs" highlightLines=[11,13] %} -/* eslint-env node */ -require('@rushstack/eslint-patch/modern-module-resolution'); - -module.exports = { - root: true, - extends: [ - 'plugin:vue/vue3-essential', - 'eslint:recommended', - '@vue/eslint-config-typescript', - '@vue/eslint-config-prettier/skip-formatting', - './.eslintrc.base.json', - ], - ignorePatterns: ['!**/*'], - overrides: [ - { - files: ['e2e/**/*.{test,spec}.{js,ts,jsx,tsx}'], - extends: ['plugin:playwright/recommended'], - }, - ], - parserOptions: { - ecmaVersion: 'latest', - }, -}; -``` - -Now we need to update the `.eslintrc.base.json` file and define the `depConstraints` in the `@nx/enforce-module-boundaries` rule: - -```json {% fileName=".eslintrc.base.json" highlightLines=["16-39"] %} -{ - "overrides": [ - { - "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "rules": { - "@nx/enforce-module-boundaries": [ - "error", - { - "enforceBuildableLibDependency": true, - "allow": [], - "depConstraints": [ - { - "sourceTag": "*", - "onlyDependOnLibsWithTags": ["*"] - }, - { - "sourceTag": "type:feature", - "onlyDependOnLibsWithTags": ["type:feature", "type:ui"] - }, - { - "sourceTag": "type:ui", - "onlyDependOnLibsWithTags": ["type:ui"] - }, - { - "sourceTag": "scope:orders", - "onlyDependOnLibsWithTags": [ - "scope:orders", - "scope:products", - "scope:shared" - ] - }, - { - "sourceTag": "scope:products", - "onlyDependOnLibsWithTags": ["scope:products", "scope:shared"] - }, - { - "sourceTag": "scope:shared", - "onlyDependOnLibsWithTags": ["scope:shared"] - } - ] - } - ] - } - } - ... - ] -} -``` - -When Nx set up the `@nx/eslint` plugin, it chose a task name that would not conflict with the pre-existing `lint` script. Let's overwrite that name so that all the linting tasks use the same `lint` name. Update the setting in the `nx.json` file: - -```json {% fileName="nx.json" highlightLines=[7] %} -{ - ... - "plugins": [ - { - "plugin": "@nx/eslint/plugin", - "options": { - "targetName": "lint" - } - } - ] -} -``` - -### Test Boundary Rules - -To test the boundary rules, go to your `modules/products/src/lib/products.tsx` file and import the `Orders` from the `orders` project: - -To test it, go to your `modules/products/src/lib/products.vue` file and import the `Orders` component from the `orders` project: - -```tsx {% fileName="modules/products/src/lib/products.vue" %} - - - - - -``` - -If you lint your workspace you'll get an error now: - -```{% command="nx run-many -t lint" %} - ✔ nx run ui:lint [existing outputs match the cache, left as is] - ✔ nx run orders:lint [existing outputs match the cache, left as is] - -—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— - ✖ nx run products:lint - > eslint . - - ~/vue-app/modules/products/src/lib/products.vue - 5:1 error A project tagged with "scope:products" can only depend on libs tagged with "scope:products", "scope:shared" @nx/enforce-module-boundaries - - ✖ 1 problem (1 error, 0 warnings) - - ✔ nx run vue-app:lint (892ms) - ✔ nx run vue-app:lint (1s) - -—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— - - NX Ran targets lint for 4 projects (1s) - - ✔ 4/5 succeeded [2 read from cache] - - ✖ 1/5 targets failed, including the following: - - - nx run products:lint -``` - -Learn more about how to [enforce module boundaries](/features/enforce-module-boundaries). - -## Migrating to a Monorepo - -When you are ready to add another application to the repo, you'll probably want to move `myvueapp` to its own folder. To do this, you can run the [`convert-to-monorepo` generator](/nx-api/workspace/generators/convert-to-monorepo) or [manually move the configuration files](/recipes/tips-n-tricks/standalone-to-monorepo). - -## Fast CI ⚡ {% highlightColor="green" %} - -{% callout type="check" title="Repository with Nx" %} -Make sure you have completed the previous sections of this tutorial before starting this one. If you want a clean starting point, you can check out the [reference code](https://github.com/nrwl/nx-recipes/tree/main/vue-app) as a starting point. -{% /callout %} - -This tutorial walked you through how Nx can improve the local development experience, but the biggest difference Nx makes is in CI. As repositories get bigger, making sure that the CI is fast, reliable and maintainable can get very challenging. Nx provides a solution. - -- Nx reduces wasted time in CI with the [`affected` command](/ci/features/affected). -- Nx Replay's [remote caching](/ci/features/remote-cache) will reuse task artifacts from different CI executions making sure you will never run the same computation twice. -- Nx Agents [efficiently distribute tasks across machines](/ci/concepts/parallelization-distribution) ensuring constant CI time regardless of the repository size. The right number of machines is allocated for each PR to ensure good performance without wasting compute. -- Nx Atomizer [automatically splits](/ci/features/split-e2e-tasks) large e2e tests to distribute them across machines. Nx can also automatically [identify and rerun flaky e2e tests](/ci/features/flaky-tasks). - -### Connect to Nx Cloud {% highlightColor="green" %} - -Nx Cloud is a companion app for your CI system that provides remote caching, task distribution, e2e tests deflaking, better DX and more. - -Now that we're working on the CI pipeline, it is important for your changes to be pushed to a GitHub repository. - -1. Commit your existing changes with `git add . && git commit -am "updates"` -2. [Create a new GitHub repository](https://github.com/new) -3. Follow GitHub's instructions to push your existing code to the repository - -Now connect your repository to Nx Cloud with the following command: - -```shell -npx nx connect -``` - -A browser window will open to register your repository in your [Nx Cloud](https://cloud.nx.app) account. The link is also printed to the terminal if the windows does not open, or you closed it before finishing the steps. The app will guide you to create a PR to enable Nx Cloud on your repository. - -![](/shared/tutorials/nx-cloud-github-connect.avif) - -Once the PR is created, merge it into your main branch. - -![](/shared/tutorials/github-cloud-pr-merged.avif) - -And make sure you pull the latest changes locally: - -```shell -git pull -``` - -You should now have an `nxCloudId` property specified in the `nx.json` file. - -### Create a CI Workflow {% highlightColor="green" %} - -Use the following command to generate a CI workflow file. - -```shell -npx nx generate ci-workflow --ci=github -``` - -This generator creates a `.github/workflows/ci.yml` file that contains a CI pipeline that will run the `lint`, `test`, `build` and `e2e` tasks for projects that are affected by any given PR. If you would like to also distribute tasks across multiple machines to ensure fast and reliable CI runs, uncomment the `nx-cloud start-ci-run` line and have the `nx affected` line run the `e2e-ci` task instead of `e2e`. - -The key lines in the CI pipeline are: - -```yml {% fileName=".github/workflows/ci.yml" highlightLines=["10-14", "21-23"] %} -name: CI -# ... -jobs: - main: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - # This enables task distribution via Nx Cloud - # Run this command as early as possible, before dependencies are installed - # Learn more at https://nx.dev/ci/reference/nx-cloud-cli#npx-nxcloud-startcirun - # Uncomment this line to enable task distribution - # - run: npx nx-cloud start-ci-run --distribute-on="3 linux-medium-js" --stop-agents-after="e2e-ci" - - uses: actions/setup-node@v3 - with: - node-version: 20 - cache: 'npm' - - run: npm ci --legacy-peer-deps - - uses: nrwl/nx-set-shas@v4 - # Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected - # When you enable task distribution, run the e2e-ci task instead of e2e - - run: npx nx affected -t lint test build e2e -``` - -### Open a Pull Request {% highlightColor="green" %} - -Commit the changes and open a new PR on GitHub. - -```shell -git add . -git commit -m 'add CI workflow file' -git push origin add-workflow -``` - -When you view the PR on GitHub, you will see a comment from Nx Cloud that reports on the status of the CI run. - -![Nx Cloud report](/shared/tutorials/github-pr-cloud-report.avif) - -The `See all runs` link goes to a page with the progress and results of tasks that were run in the CI pipeline. - -![Run details](/shared/tutorials/nx-cloud-run-details.avif) - -For more information about how Nx can improve your CI pipeline, check out one of these detailed tutorials: - -- [Circle CI with Nx](/ci/intro/tutorials/circle) -- [GitHub Actions with Nx](/ci/intro/tutorials/github-actions) - -## Next Steps - -Connect with the rest of the Nx community with these resources: - -- ⭐️ [Star us on GitHub](https://github.com/nrwl/nx) to show your support and stay updated on new releases! -- [Join the Official Nx Discord Server](https://go.nx.dev/community) to ask questions and find out the latest news about Nx. -- [Follow Nx on Twitter](https://twitter.com/nxdevtools) to stay up to date with Nx news -- [Read our Nx blog](/blog) -- [Subscribe to our Youtube channel](https://www.youtube.com/@nxdevtools) for demos and Nx insights diff --git a/nx-dev/nx-dev/redirect-rules.js b/nx-dev/nx-dev/redirect-rules.js index c9c04273f2..9f51e504f9 100644 --- a/nx-dev/nx-dev/redirect-rules.js +++ b/nx-dev/nx-dev/redirect-rules.js @@ -613,29 +613,29 @@ const standaloneTutorialRedirects = { '/react-tutorial/5-summary': '/getting-started/tutorials/react-monorepo-tutorial', '/react-standalone-tutorial': - '/getting-started/tutorials/react-standalone-tutorial', + '/getting-started/tutorials/react-monorepo-tutorial', '/react-standalone-tutorial/1-code-generation': - '/getting-started/tutorials/react-standalone-tutorial', + '/getting-started/tutorials/react-monorepo-tutorial', '/react-standalone-tutorial/2-project-graph': - '/getting-started/tutorials/react-standalone-tutorial', + '/getting-started/tutorials/react-monorepo-tutorial', '/react-standalone-tutorial/3-task-running': - '/getting-started/tutorials/react-standalone-tutorial', + '/getting-started/tutorials/react-monorepo-tutorial', '/react-standalone-tutorial/4-task-pipelines': - '/getting-started/tutorials/react-standalone-tutorial', + '/getting-started/tutorials/react-monorepo-tutorial', '/react-standalone-tutorial/5-summary': - '/getting-started/tutorials/react-standalone-tutorial', + '/getting-started/tutorials/react-monorepo-tutorial', '/angular-standalone-tutorial': - '/getting-started/tutorials/angular-standalone-tutorial', + '/getting-started/tutorials/angular-monorepo-tutorial', '/angular-standalone-tutorial/1-code-generation': - '/getting-started/tutorials/angular-standalone-tutorial', + '/getting-started/tutorials/angular-monorepo-tutorial', '/angular-standalone-tutorial/2-project-graph': - '/getting-started/tutorials/angular-standalone-tutorial', + '/getting-started/tutorials/angular-monorepo-tutorial', '/angular-standalone-tutorial/3-task-running': - '/getting-started/tutorials/angular-standalone-tutorial', + '/getting-started/tutorials/angular-monorepo-tutorial', '/angular-standalone-tutorial/4-task-pipelines': - '/getting-started/tutorials/angular-standalone-tutorial', + '/getting-started/tutorials/angular-monorepo-tutorial', '/angular-standalone-tutorial/5-summary': - '/getting-started/tutorials/angular-standalone-tutorial', + '/getting-started/tutorials/angular-monorepo-tutorial', }; const packagesIndexes = { @@ -782,9 +782,9 @@ const conceptUrls = { '/getting-started/tutorials/integrated-repo-tutorial': '/getting-started/tutorials/react-monorepo-tutorial', '/getting-started/react-standalone-tutorial': - '/getting-started/tutorials/react-standalone-tutorial', + '/getting-started/tutorials/react-monorepo-tutorial', '/getting-started/angular-standalone-tutorial': - '/getting-started/tutorials/angular-standalone-tutorial', + '/getting-started/tutorials/angular-monorepo-tutorial', '/concepts/more-concepts/micro-frontend-architecture': '/concepts/module-federation/micro-frontend-architecture', '/concepts/more-concepts/faster-builds-with-module-federation': @@ -806,9 +806,15 @@ const nested5minuteTutorialUrls = { '/tutorials/integrated-repo-tutorial': '/getting-started/tutorials/integrated-repo-tutorial', '/tutorials/react-standalone-tutorial': - '/getting-started/tutorials/react-standalone-tutorial', + '/getting-started/tutorials/react-monorepo-tutorial', + '/getting-started/tutorials/react-standalone-tutorial': + '/getting-started/tutorials/react-monorepo-tutorial', '/tutorials/angular-standalone-tutorial': - '/getting-started/tutorials/angular-standalone-tutorial', + '/getting-started/tutorials/angular-monorepo-tutorial', + '/getting-started/tutorials/angular-standalone-tutorial': + '/getting-started/tutorials/angular-monorepo-tutorial', + '/getting-started/tutorials/vue-standalone-tutorial': + '/getting-started/tutorials', '/tutorials/node-server-tutorial': '/getting-started/tutorials', '/angular-tutorial': '/getting-started/tutorials/angular-monorepo-tutorial', '/angular-tutorial/1-code-generation': diff --git a/nx-dev/nx-dev/redirect-rules.spec.js b/nx-dev/nx-dev/redirect-rules.spec.js index 249e5d52a6..6006c2c104 100644 --- a/nx-dev/nx-dev/redirect-rules.spec.js +++ b/nx-dev/nx-dev/redirect-rules.spec.js @@ -66,11 +66,7 @@ describe('Redirect rules configuration', () => { }); test('old tutorial links', () => { - const oldTutorialUrls = [ - '/tutorials/integrated-repo-tutorial', - '/tutorials/react-standalone-tutorial', - '/tutorials/angular-standalone-tutorial', - ]; + const oldTutorialUrls = ['/tutorials/integrated-repo-tutorial']; for (const url of oldTutorialUrls) { expect(redirectRules.nested5minuteTutorialUrls[url]).toEqual( diff --git a/nx-dev/ui-home/src/lib/smarter-tools-for-monorepos.tsx b/nx-dev/ui-home/src/lib/smarter-tools-for-monorepos.tsx index c21f6693ac..0310107965 100644 --- a/nx-dev/ui-home/src/lib/smarter-tools-for-monorepos.tsx +++ b/nx-dev/ui-home/src/lib/smarter-tools-for-monorepos.tsx @@ -390,20 +390,6 @@ export function SmarterToolsForMonorepos(): JSX.Element { /> - - - - - - - -