Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 11f9b2d62f | |||
| e1e3541ab6 | |||
| b3dd5daffc | |||
| fad8e9f7f0 | |||
| 4273b7af0d | |||
| 35c2d16afd | |||
| b8a022a901 | |||
| 1c3ef85340 | |||
| 8db4489680 | |||
| 8018ad299b | |||
| 8e9d12dc72 | |||
| 802ee79efc |
@@ -0,0 +1,395 @@
|
||||
---
|
||||
title: 'Modern Angular Testing with Nx'
|
||||
slug: modern-angular-testing-with-nx
|
||||
authors: ['Jack Hsu']
|
||||
tags: ['angular', 'nx']
|
||||
cover_image: /blog/images/2025-03-17/modern-angular-testing.png
|
||||
description: 'Learn how Nx enhances Angular testing by integrating modern tools like Playwright and Vitest, improving test speed, reliability, and CI scalability.'
|
||||
---
|
||||
|
||||
{% callout type="deepdive" title="Angular Week Series" expanded=true %}
|
||||
|
||||
This article is part of the Angular Week series:
|
||||
|
||||
- **Modern Angular Testing with Nx**
|
||||
- [Angular Architecture Guide To Building Maintainable Applications at Scale](/blog/architecting-angular-applications)
|
||||
- _Using Rspack with Angular_
|
||||
- _Enterprise Patterns_
|
||||
|
||||
{% /callout %}
|
||||
|
||||
Testing is a crucial part of any application to ensure correctness and guard against regression, and Angular is no exception. Here at Nx, we're big fans of Angular, and we think that we can drastically improve the unit testing and end-to-end (E2E) experience for Angular developers.
|
||||
|
||||
Modern tools have evolved to make testing faster, more reliable, and developer-friendly. The Angular team has recognized this shift in the testing landscape. They've officially announced that [Protractor is in maintenance mode](https://blog.angular.dev/the-state-of-end-to-end-testing-with-angular-d175f751cb9c) and recommend using modern alternatives like Playwright or Cypress. Similarly, while Karma is still supported, the team [acknowledges the benefits of modern test runners](https://blog.angular.dev/moving-angular-cli-to-jest-and-web-test-runner-ef85ef69ceca) like Jest and Vitest, especially in terms of performance and developer experience.
|
||||
|
||||
In this post, we'll explore how the Nx unlocks modern testing tools for Angular developers, and how Nx can help your CI scale as your team and codebase grow.
|
||||
|
||||
## **Effortless E2E Testing at Scale**
|
||||
|
||||
New Nx Angular workspaces come with Playwright as the E2E testing framework by default.
|
||||
|
||||
```shell
|
||||
npx create-nx-workspace@latest --preset=angular-monorepo --appName=my-app
|
||||
```
|
||||
|
||||
You can use Cypress by passing the `--e2eTestRunner=cypress` option. We will use Playwright for this example, but the benefits that Nx brings apply to both frameworks.
|
||||
|
||||
Once you have the workspace created, you can see the E2E tests in action by running the following:
|
||||
|
||||
```shell
|
||||
npx nx e2e my-app-e2e
|
||||
```
|
||||
|
||||
This command runs `playwright test` underneath the hood. This is just standard tooling, and there is nothing Nx-specific about the tests. Nx comes into play with the `@nx/playwright` plugin, as you see inside your `nx.json` file. The `@nx/playwright` plugin does a few things.
|
||||
|
||||
First, it makes the `e2e` command cacheable, so running it again without changes source files nor test files will replay the command from cache.
|
||||
|
||||
```shell
|
||||
npx nx e2e my-app-e2e
|
||||
```
|
||||
|
||||
You should see a message as follows the second time:
|
||||
|
||||
```{% command="npx nx e2e my-app-e2e" %}
|
||||
NX Successfully ran target e2e for project my-app-e2e (154ms)
|
||||
|
||||
Nx read the output from the cache instead of running the command for 1 out of 1 tasks.
|
||||
```
|
||||
|
||||
This is a powerful feature, especially when used in conjunction with [Remote Caching](/ci/features/remote-cache) (i.e. Nx Replay).
|
||||
|
||||
How does Nx understand when a task can be read from cache? Well, the `@nx/playwright` also autoconfigures task inputs for you. So, unless a relevant file is changed, the task can be read from cache. That means updates to `README.md` will replay E2E tests from cache rather than running the expensive task.
|
||||
|
||||
Another thing that Nx handles automatically for you is restoring outputs from cache. You'll notice that an HTML report is generated in the `dist/.playwright` folder.
|
||||
|
||||
```
|
||||
dist/.playwright/apps/my-app-e2e
|
||||
├── playwright-report
|
||||
│ └── index.html
|
||||
└── test-output
|
||||
```
|
||||
|
||||
You can remove this folder, and when Nx replays the task from cache, the test artifacts will be restored.
|
||||
|
||||
```
|
||||
rm -rf dist
|
||||
npx nx e2e my-app-e2e
|
||||
tree dist/.playwright/apps/my-app-e2e
|
||||
```
|
||||
|
||||
You should see the exact same output in `dist` even though the Playwright tests didn't actually run.
|
||||
|
||||
To see how Nx configures your project, you can use `nx show project`.
|
||||
|
||||
```shell
|
||||
npx nx show project my-app-e2e
|
||||
npx nx show project my-app
|
||||
```
|
||||
|
||||

|
||||
|
||||
We also recommend that you install the Nx Console extension for VSCode, Cursor, and IntelliJ. It seamlessly integrates the [Project Details View](/recipes/nx-console/console-project-details) with your editor. To install it visit the Marketplace pages:
|
||||
|
||||
- [VSCode extension](https://marketplace.visualstudio.com/items?itemName=nrwl.angular-console)
|
||||
- [IntelliJ plugin](https://plugins.jetbrains.com/plugin/21060-nx-console)
|
||||
|
||||
Okay, the caching is already great on its own, but Nx has another very powerful feature to help you scale your CI.
|
||||
|
||||
### **Automatic E2E Test Distribution**
|
||||
|
||||
As workspaces grow, they often run into CI scaling issues that are hard to solve. E2E tests are often the main culprit of CI slowness, as a test suite can take several hours to run in large workspaces. This is just the reality for many teams, where you need the large test suites to ensure quality, but it's impossible to iterate quickly when each CI run takes hours.
|
||||
|
||||
Fortunately, Nx has a solution that does not require complicated pipeline files or a whole team of infrastructure engineers to keep CI running smoothly. That solution is [Automatic Test Splitting](/ci/features/split-e2e-tasks) (or Nx Atomizer).
|
||||
|
||||
If you view the `my-app-e2e` project (`npx nx show project my-app-e2e`), you will notice that there is an `e2e-ci` target, with additional targets created for each test file. This is the task splitting feature that `@nx/playwright` enables. Whereas the `e2e` target runs the full Playwright suite, the `e2e-ci` task runs additional tasks created from test files.
|
||||
|
||||
When run on a single machine, `e2e-ci` will be slower because it starts multiple Playwright processes, which is why we only allow it to run through distribution. To [enable distribution](/ci/features/split-e2e-tasks#enable-automated-e2e-task-splitting), you must connect your workspace to [Nx Cloud](/nx-cloud). This is easily done with the `connect` command.
|
||||
|
||||
```shell
|
||||
npx nx connect
|
||||
```
|
||||
|
||||
Follow the onboarding steps and you should be connected within five minutes. For more information, check out our [GitHub Actions Tutorial](/ci/intro/tutorials/github-actions) or our [guides](/ci/recipes/set-up) for all supported CI providers (GitHub, GitLab, Azure, etc.).
|
||||
|
||||
Now, let's take a look at a concrete example to get an idea of how much time-saving you can unlock with Nx Atomizer. I created [this repo](https://github.com/jaysoo/angular-testing-demo) that contains a simple Angular application and a UI package. It also has 40 Playwright test files.
|
||||
|
||||
```
|
||||
apps/demo-e2e/src
|
||||
├── example-1.spec.ts
|
||||
├── example-2.spec.ts
|
||||
├── example-3.spec.ts
|
||||
├── ...
|
||||
└── example-40.spec.ts
|
||||
```
|
||||
|
||||
Where each file uses `page.waitForTimeout` to artificially simulate run-running tests.
|
||||
|
||||
```ts
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('example 1 - test 1', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForTimeout(3000);
|
||||
expect(page.url()).toBe(page.url());
|
||||
});
|
||||
|
||||
test('example 1 - test 2', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForTimeout(3000);
|
||||
expect(page.url()).toBe(page.url());
|
||||
});
|
||||
|
||||
// ...
|
||||
|
||||
test('example 1 - test 10', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForTimeout(3000);
|
||||
expect(page.url()).toBe(page.url());
|
||||
});
|
||||
```
|
||||
|
||||
Using this [workflow file](https://github.com/jaysoo/angular-testing-demo/blob/main/.github/workflows/ci.yml) for GitHub Actions, we see that running unit tests and E2E tests take roughly 23 minutes in total.
|
||||
|
||||
- CI without distribution: [https://github.com/jaysoo/angular-testing-demo/actions/runs/13677108651](https://github.com/jaysoo/angular-testing-demo/actions/runs/13677108651)
|
||||
|
||||
In the [`feat/nx-cloud/setup`](https://github.com/jaysoo/angular-testing-demo/tree/feat/nx-cloud/setup) branch and PR, you can see that the [workflow file](https://github.com/jaysoo/angular-testing-demo/blob/feat/nx-cloud/setup/.github/workflows/ci.yml) is updated to enable distribution via Nx Agents.
|
||||
|
||||
```yaml
|
||||
name: CI
|
||||
# ...
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# ...
|
||||
- run: npx nx-cloud start-ci-run --distribute-on="8 linux-medium-js" --stop-agents-after="e2e-ci"
|
||||
# ...
|
||||
- run: npx nx affected -t test e2e-ci
|
||||
```
|
||||
|
||||
With distribution enabled, the total CI time went from 23 minutes to 6 minutes, which is over 70% reduction in total duration.
|
||||
|
||||
- CI with distribution: [https://github.com/jaysoo/angular-testing-demo/actions/runs/13677116398](https://github.com/jaysoo/angular-testing-demo/actions/runs/13677116398)
|
||||
|
||||
You can see that the CI pipeline distributes individual test files on multiple agents.
|
||||
|
||||

|
||||
|
||||
That is significant time-saving! As your codebase grows, you have the option to increase distribution by changing the `--distribute-on` option to ensure that CI still runs fast.
|
||||
|
||||
```yaml
|
||||
# Use 12 agents
|
||||
npx nx-cloud start-ci-run --distribute-on="12 linux-medium-js" --stop-agents-after="e2e-ci"
|
||||
```
|
||||
|
||||
### Dynamic Agent Allocation
|
||||
|
||||
You can even [dynamically allocate agents](/ci/features/dynamic-agents#dynamically-allocate-agents) depending on how big the changeset is. To do this, add an YAML file as follows:
|
||||
|
||||
```yaml
|
||||
# .nx/workflows/distribution-config.yaml
|
||||
distribute-on:
|
||||
small-changeset: 3 linux-medium-js
|
||||
medium-changeset: 8 linux-medium-js
|
||||
large-changeset: 12 linux-medium-js
|
||||
```
|
||||
|
||||
Then, use that file in the `--distribute-on` option.
|
||||
|
||||
```yaml
|
||||
npx nx-cloud start-ci-run --distribute-on=".nx/workflows/distribution-config.yaml" --stop-agents-after="e2e-ci"
|
||||
```
|
||||
|
||||
### Flakiness Detection and Automatic Re-runs
|
||||
|
||||
One last thing I want to mention, is that Nx can also help with flaky tests. Nx Cloud can reliably detect flaky tests and automatically re-run them. See the documentation on the [Re-run Flaky Tests](/ci/features/flaky-tasks) for more detail.
|
||||
|
||||
All of this power comes at very little complexity and maintenance burden. Nx allows you to declaratively describe what you want to run, and how you want to distribute tasks, and we do all the heavy lifting for you.
|
||||
|
||||
As we've seen, Nx is the easiest way for you to scale your E2E tests as your team and codebase grow, without dealing with complicated CI pipelines yourself. CI times can be drastically cut down by more than 70%, as seen in the example above. This enables teams to move faster and deliver value without being slowed down by CI.
|
||||
|
||||
Next, let's take a look at how Nx helps with modernizing unit tests.
|
||||
|
||||
## **Modern Unit Testing: Beyond Karma**
|
||||
|
||||
While Angular CLI traditionally uses [Karma for unit testing](https://angular.dev/guide/testing/test-environment), Nx enables you to leverage more modern testing frameworks like [Vitest](https://vitest.dev/) and [Jest](https://jestjs.io/). The Angular team has been [exploring modern alternatives to Karma](https://blog.angular.dev/moving-angular-cli-to-jest-and-web-test-runner-ef85ef69ceca), recognizing the benefits these tools bring. Let's explore why this matters by comparing Vitest with Karma. Many of these benefits also apply to Jest, which the Angular CLI team has plans to officially support. There are currently no plan for Angular CLI to support Vitest.
|
||||
|
||||
### **Why Choose Vitest over Karma?**
|
||||
|
||||
The benefits of modern test runners like Vitest are well-documented in the [official Vitest documentation](https://vitest.dev/guide/why.html) and the [Angular blog](https://blog.angular.dev/moving-angular-cli-to-jest-and-web-test-runner-ef85ef69ceca). Here's why they're a compelling choice:
|
||||
|
||||
### **Performance Features**
|
||||
|
||||
- **Native ESM Support**: Tests run directly without bundling, unlike Karma
|
||||
- **Parallel Test Execution**: Tests run concurrently by default
|
||||
- **Efficient Resource Usage**: Modern architecture reduces memory usage
|
||||
- **Watch Mode with HMR**: Near-instant feedback during development
|
||||
|
||||
### **Developer Experience**
|
||||
|
||||
- **Better Debugging**: Improved error messages and stack traces
|
||||
- **Rich Plugin Ecosystem**: Wide range of tools and extensions
|
||||
- **Snapshot Testing**: Easily test UI components
|
||||
- **Modern API**: Intuitive, promise-based test writing
|
||||
- **Active Community**: Regular updates and improvements
|
||||
|
||||
While actual performance gains vary by project size, teams consistently report faster test execution and improved developer experience when switching from Karma to modern test runners. This improvement in speed is can be largely attributed to not relying on real browsers and running tests in a simulated environment using pure-JavaScript DOM implementations such as `jsdom`.
|
||||
|
||||
### **Setting Up Modern Unit Tests**
|
||||
|
||||
Setting up Vitest for your Angular project with Nx is straightforward:
|
||||
|
||||
```bash
|
||||
# Add the Vite plugin
|
||||
npx nx add @nx/vite
|
||||
|
||||
# Configure Vitest for a project
|
||||
npx nx g @nx/vite:vitest
|
||||
```
|
||||
|
||||
This should generate a Vitest configuration file like the following:
|
||||
|
||||
```tsx
|
||||
// vite.config.mts
|
||||
/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
import angular from '@analogjs/vite-plugin-angular';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [angular()],
|
||||
test: {
|
||||
watch: false,
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||
setupFiles: ['src/test-setup.ts'],
|
||||
reporters: ['default'],
|
||||
coverage: {
|
||||
reportsDirectory: './coverage/my-app',
|
||||
provider: 'v8',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Now, you can run your unit tests through Vitest:
|
||||
|
||||
```shell
|
||||
npx nx test my-app --watch
|
||||
```
|
||||
|
||||

|
||||
|
||||
Note, that you may have a conflicting `test` target, which can be resolved by removing or renaming the old target in `project.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"targets": {
|
||||
"karma:test": {
|
||||
"executor": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"polyfills": ["zone.js", "zone.js/testing"],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": ["src/styles.css"],
|
||||
"scripts": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Big shout-out to Brandon Roberts and the Analog team for bringing [Angular-support to Vitest](https://analogjs.org/docs/features/testing/vitest).
|
||||
|
||||
Try it out yourself by cloning the example repo:
|
||||
|
||||
```shell
|
||||
# Set up
|
||||
git clone https://github.com/jaysoo/angular-testing-demo.git
|
||||
cd angular-testing-demo
|
||||
npm install
|
||||
|
||||
# Run test for each project
|
||||
npx nx test demo
|
||||
npx nx test ui
|
||||
|
||||
# Run in interactive mode (try updating files to see how fast HMR is)
|
||||
npx nx test ui --watch
|
||||
|
||||
# Run all tests
|
||||
npx nx run-many -t test
|
||||
```
|
||||
|
||||
The improvements for local development is night and day. Receive instant feedback when you run in watch mode, leverage modern [debugging tools integration](https://vitest.dev/guide/debugging), better error messages and stack traces, and more. CI is also improved by reducing flakiness and inconsistencies across environments, by not relying on real browsers.
|
||||
|
||||
### Generate New Projects with Vitest
|
||||
|
||||
The application and library generators that come with `@nx/angular` both support using Vitest as the unit test runner.
|
||||
|
||||
```shell
|
||||
npx nx g @nx/angular:app apps/demo --unitTestRunner=vitest
|
||||
npx nx g @nx/angular:lib packages/ui --unitTestRunner=vitest
|
||||
```
|
||||
|
||||
Note that Jest is also supported if that is preferred.
|
||||
|
||||
```shell
|
||||
npx nx g @nx/angular:app apps/demo --unitTestRunner=jest
|
||||
npx nx g @nx/angular:lib packages/ui --unitTestRunner=jest
|
||||
```
|
||||
|
||||
This means you can easily create new projects and be productive immediately.
|
||||
|
||||
## Getting Started with Nx
|
||||
|
||||
Now that you’re convinced to give Nx a try, let's go over a few ways to install Nx to your workspace.
|
||||
|
||||
For a new workspaces, which is great to get started with Nx for the first time, use the `create-nx-workspace` command.
|
||||
|
||||
```shell
|
||||
npx create-nx-workspace@latest --preset=angular-monorepo --appName=my-app
|
||||
```
|
||||
|
||||
Follow, the prompts and you're good to go!
|
||||
|
||||
For existing Angular CLI projects, you can run `nx init` inside the workspace. This will keep the file structure as is and minimally add Nx to the workspace.
|
||||
|
||||
```shell
|
||||
ng new my-app
|
||||
cd my-app
|
||||
npx nx@latest init
|
||||
```
|
||||
|
||||
Alternatively, you can convert your project into a monorepo using the `--integrated` flag. A monorepo gives you the ability to integrate more projects to it, such as other webapps or backends.
|
||||
|
||||
```shell
|
||||
ng new my-app
|
||||
cd my-app
|
||||
npx nx@latest init --integrated
|
||||
```
|
||||
|
||||
Run a few Nx commands to try it out!
|
||||
|
||||
```shell
|
||||
npx nx show project my-app
|
||||
npx nx graph
|
||||
npx nx serve my-app
|
||||
npx nx test my-app
|
||||
```
|
||||
|
||||
For more information, check out our [Getting Started](/getting-started/intro) docs.
|
||||
|
||||
## **Conclusion**
|
||||
|
||||
Modern testing tools have evolved significantly, offering better developer experience and faster execution times. By leveraging Nx's capabilities with Playwright for E2E testing and modern frameworks like Vitest for unit testing, you can create a more efficient and enjoyable testing workflow for your Angular teams.
|
||||
|
||||
CI reliability and duration are improved through:
|
||||
|
||||
- Automatic and effortless E2E test distribution
|
||||
- Flaky task detection and automatic retries
|
||||
- Faster and more consistent unit tests through modern tools like Vitest
|
||||
@@ -0,0 +1,550 @@
|
||||
---
|
||||
title: 'Angular Architecture Guide To Building Maintainable Applications at Scale'
|
||||
slug: architecting-angular-applications
|
||||
authors: ['Juri Strumpflohner']
|
||||
tags: ['nx']
|
||||
description: 'Learn how to build scalable Angular applications using domain-driven design, clear boundaries, and Nx tooling for better maintainability and team collaboration.'
|
||||
cover_image: /blog/images/articles/architecting-angular-apps-bg.jpg
|
||||
---
|
||||
|
||||
{% callout type="deepdive" title="Angular Week Series" expanded=true %}
|
||||
|
||||
This article is part of the Angular Week series:
|
||||
|
||||
- [Modern Angular Testing with Nx](/blog/modern-angular-testing-with-nx)
|
||||
- **Angular Architecture Guide To Building Maintainable Applications at Scale**
|
||||
- _Using Rspack with Angular_
|
||||
- _Enterprise Patterns_
|
||||
|
||||
{% /callout %}
|
||||
|
||||
Software Architecture consists of a variety of aspects that need to be considered. One key aspect though is to maximize the ability to remain flexible and adaptable to new customer requirements. Maybe you've already come across the "Project Paradox":
|
||||
|
||||

|
||||
|
||||
A good software architecture helps mitigate the Project Paradox by enabling reversible decisions, progressive evolution, and delaying commitments until more knowledge is available. This can be achieved by aiming for a modular design that facilitates encapsulation, allowing incremental changes, and decoupling dependencies with clearly defined boundaries.
|
||||
|
||||
In this article we focus mostly on:
|
||||
|
||||
- how to implement a scalable architecture, not only at runtime, but at development time
|
||||
- how to structure your codebase and establish boundaries
|
||||
- how to encode and automate best practices to ensure their longevity
|
||||
|
||||
{% toc /%}
|
||||
|
||||
## Pizza-boxes, Onions and Hexagons - How to organize code
|
||||
|
||||
Probably the simplest and most widespread (and probably most straightforward) approach for separating different aspects of an application is the **layered architecture** (in Italy we call them Pizza-box architecture).
|
||||
|
||||

|
||||
|
||||
If you ever looked into software architecture and structuring of projects, this is probably what you've come across. There are variations of this such as the hexagonal and onion architecture, which differ mostly in how dependencies are wired up.
|
||||
The common denominator of these architectures (often also denoted as **horizontal approaches**) is that they are mostly focused on dividing the system based on technical responsibilities.
|
||||
|
||||
On the other hand, **vertical architecture approaches** organize the application into functional segments or domains focusing on the business capabilities. This is a common approach in microservices architectures.
|
||||
|
||||

|
||||
|
||||
## Breaking up the Monolith - How to identify where boundaries are
|
||||
|
||||
{% video-player src="/documentation/blog/media/layered-to-domain-areas.mp4" alt="Moving from a layered architecture to domain oriented" showDescription=true showControls=false autoPlay=true loop=true /%}
|
||||
|
||||
Instead of organizing code by technical types (components, services, directives) we want to structure our codebase around business domains. Good candidates for domains are areas that:
|
||||
|
||||
- have distinct business capabilities (e.g. in the example of an online shop: orders, products, payments)
|
||||
- reflect team structure within the organization; domain boundaries often mirror organizational structure (Conway's Law)
|
||||
- can evolve independently from each other, at different speeds
|
||||
- have clear responsibilities and boundaries
|
||||
|
||||
To use the example of an e-commerce application, we might have:
|
||||
|
||||
- Products: Product catalog, inventory, categorization
|
||||
- Orders: Order processing, history, fulfillment
|
||||
- Checkout: Payment processing, cart management
|
||||
- User Management: Authentication, profiles, preferences
|
||||
- Shipping & Logistics: Delivery options, tracking, address management
|
||||
|
||||
All of these need to work together for fulfilling the business requirements, but they can evolve independently and responsibilities can clearly be associated. In general, start broad and then refine over time as you gain more insights.
|
||||
|
||||
Having such boundaries clearly separated not only helps with the longer-term maintainability of the application, but also helps assign teams and minimizes cross-team dependencies.
|
||||
|
||||
## Start Small, Grow as you Need It
|
||||
|
||||
A common mistake is to think too much about the ideal end goal and prepare things "just in case". Yep, over-engineering. Exactly, you might not need a monorepo (at least not yet). However, you want to make sure to not add roadblocks in your way.
|
||||
|
||||
A lot of our Angular users don't necessarily start to use Nx because they need a monorepo, but because they want to be able to modularize their monolithic codebase. Hetzner Cloud - one of [our customers](/customers) - is a good example for that. Their main goal for initially adopting Nx was to [break apart their monolith](/blog/hetzner-cloud-success-story).
|
||||
|
||||
If you want to start building a single Angular application with Nx, you can use the `--preset=angular-standalone` flag:
|
||||
|
||||
```shell
|
||||
npx create-nx-workspace myshop --preset=angular-standalone
|
||||
```
|
||||
|
||||
This creates a new Angular workspace (not a monorepo) with a single application located in the `src` folder.
|
||||
|
||||
```text
|
||||
└─ myshop
|
||||
├─ e2e
|
||||
│ ├─ ...
|
||||
│ ├─ playwright.config.ts
|
||||
│ └─ tsconfig.json
|
||||
├─ public/
|
||||
├─ src
|
||||
│ ├─ app
|
||||
│ │ ├─ ...
|
||||
│ │ ├─ app.component.ts
|
||||
│ │ ├─ app.config.ts
|
||||
│ │ └─ app.routes.ts
|
||||
│ ├─ index.html
|
||||
│ ├─ ...
|
||||
│ └─ main.ts
|
||||
├─ eslint.config.mjs
|
||||
├─ jest.config.ts
|
||||
├─ jest.preset.js
|
||||
├─ nx.json
|
||||
├─ project.json
|
||||
├─ tsconfig.app.json
|
||||
├─ tsconfig.editor.json
|
||||
├─ tsconfig.json
|
||||
└─ tsconfig.spec.json
|
||||
```
|
||||
|
||||
It uses Nx for running and building your project. Nx relies on the Angular Devkit builders but might also add in its own to fill in gaps (e.g. adding Jest/Vitest support). Have a look at the `project.json`:
|
||||
|
||||
```json {% fileName="project.json" %}
|
||||
{
|
||||
"name": "myshop",
|
||||
"sourceRoot": "./src",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@angular-devkit/build-angular:browser",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/myshop",
|
||||
"index": "./src/index.html",
|
||||
"main": "./src/main.ts",
|
||||
...
|
||||
},
|
||||
"configurations": {...},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"executor": "@angular-devkit/build-angular:dev-server",
|
||||
...
|
||||
},
|
||||
...
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
"options": {
|
||||
"lintFilePatterns": ["./src"]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectName}"],
|
||||
"options": {
|
||||
"jestConfig": "jest.config.ts"
|
||||
}
|
||||
},
|
||||
"serve-static": {
|
||||
"executor": "@nx/web:file-server",
|
||||
"options": {
|
||||
"buildTarget": "myshop:build",
|
||||
"port": 4200,
|
||||
"spa": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you have an existing Angular CLI project, you can also [add Nx support to it](/recipes/angular/migration/angular) by running:
|
||||
|
||||
```shell
|
||||
npx nx init
|
||||
```
|
||||
|
||||
If you already know you want to go straight to an Nx monorepo, you can add the `--integrated` flag to the `nx init` command.
|
||||
|
||||
## Modularize your Code into Projects Following Your Domain Areas
|
||||
|
||||
When starting with Angular, you might structure your application like this:
|
||||
|
||||
```text
|
||||
src/
|
||||
├── app/
|
||||
│ ├── auth/ # Authentication feature
|
||||
│ ├── products/ # Product management feature
|
||||
│ ├── cart/ # Shopping cart feature
|
||||
│ └── checkout/ # Checkout feature
|
||||
├── assets/
|
||||
└── styles/
|
||||
```
|
||||
|
||||
This feature-based organization is already an improvement over the traditional "type-based" structure (where code is organized by technical type like components/, services/, etc.). However, it still has limitations:
|
||||
|
||||
- Boundaries are purely folder-based with no real enforcement
|
||||
- Easy to create unwanted dependencies between features
|
||||
- Hard to maintain as the application grows
|
||||
- No clear rules about what can depend on what
|
||||
|
||||
Instead of relying on folder-based separation, we can create dedicated projects (also called libraries) for different parts of our application. Looking at our workspace structure, we have organized our code into domain-specific projects:
|
||||
|
||||
```text
|
||||
myshop/
|
||||
├── src/ # Main application
|
||||
└── packages/ # Library projects
|
||||
├── products/ # Product domain
|
||||
├── orders/ # Order management
|
||||
├── checkout/ # Checkout process
|
||||
├── user-management/
|
||||
├── shipping-logistics/
|
||||
└── ...
|
||||
```
|
||||
|
||||
These projects aren't necessarily meant to be published as npm packages - their main purpose is to create clear boundaries in your codebase. The application still builds everything together, but the project structure helps maintain clear separation of concerns.
|
||||
|
||||
As domains grow more complex, you might want to split them further into more specialized libraries. For example, our products domain is organized as:
|
||||
|
||||
```text
|
||||
packages/products/
|
||||
├── data-access/ # API and state management
|
||||
├── feat-product-list/ # Product listing feature
|
||||
├── feat-product-detail/ # Product detail feature
|
||||
├── feat-product-reviews/ # Product reviews feature
|
||||
├── ui-product-card/ # Reusable product card component
|
||||
└── ui-product-carousel/ # Product carousel component
|
||||
```
|
||||
|
||||
This structure follows a pattern where each domain can have:
|
||||
|
||||
- **Feature libraries** (`feat-*`): Implement specific business features or pages
|
||||
- **UI libraries** (`ui-*`): Contain presentational components
|
||||
- **Data-access libraries**: Handle API communication and state management
|
||||
|
||||
Since this is a standalone application (not a monorepo), we use TypeScript path mappings to link these projects together. In our `tsconfig.base.json`, you can see how each project is mapped:
|
||||
|
||||
```json {% fileName="tsconfig.base.json" %}
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@myshop/products-data-access": [
|
||||
"packages/products/data-access/src/index.ts"
|
||||
],
|
||||
"@myshop/products-feat-product-list": [
|
||||
"packages/products/feat-product-list/src/index.ts"
|
||||
],
|
||||
"@myshop/products-ui-product-card": [
|
||||
"packages/products/ui-product-card/src/index.ts"
|
||||
],
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
These mappings allow you to have clear imports in your code:
|
||||
|
||||
```typescript
|
||||
// Clear imports showing the domain and type of code you're using
|
||||
import { ProductListComponent } from '@myshop/products-feat-product-list';
|
||||
import { ProductCardComponent } from '@myshop/products-ui-product-card';
|
||||
import { ProductService } from '@myshop/products-data-access';
|
||||
```
|
||||
|
||||
This makes it immediately obvious:
|
||||
|
||||
1. Which domain the code belongs to (`products`)
|
||||
2. What type of code it is (`feat-*`, `ui-*`, `data-access`)
|
||||
3. What specific feature or component you're importing
|
||||
|
||||
## The Application is Your Linking and Deployment Container
|
||||
|
||||

|
||||
|
||||
In a well-modularized architecture, your application shell should be surprisingly thin. Think of your main application as primarily a composition layer: it imports and coordinates the various domain libraries but contains minimal logic itself.
|
||||
|
||||
The ideal application structure has:
|
||||
|
||||
- **Thin application shell** - Contains mainly routing configuration, bootstrap logic, and layout composition
|
||||
- **Domain libraries** - All business logic, UI components, and data access code
|
||||
|
||||
At the Angular router level you then import the various feature libraries:
|
||||
|
||||
```typescript
|
||||
...
|
||||
export const appRoutes: Route[] = [
|
||||
{
|
||||
path: 'products',
|
||||
loadComponent: () =>
|
||||
import('@myshop/products-feat-product-list').then(
|
||||
(m) => m.ProductsFeatProductListComponent
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'product/:id',
|
||||
loadComponent: () =>
|
||||
import('@myshop/products-feat-product-detail').then(
|
||||
(m) => m.ProductsFeatProductDetailComponent
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'reviews',
|
||||
loadComponent: () =>
|
||||
import('@myshop/products-feat-product-reviews').then(
|
||||
(m) => m.ProductsFeatProductReviewsComponent
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'orders',
|
||||
loadComponent: () =>
|
||||
import('@myshop/orders-feat-order-history').then(
|
||||
(m) => m.OrdersFeatOrderHistoryComponent
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'create-order',
|
||||
loadComponent: () =>
|
||||
import('@myshop/orders-feat-create-order').then(
|
||||
(m) => m.OrdersFeatCreateOrderComponent
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'checkout',
|
||||
loadComponent: () =>
|
||||
import('@myshop/checkout-feat-checkout-flow').then(
|
||||
(m) => m.CheckoutFeatCheckoutFlowComponent
|
||||
),
|
||||
},
|
||||
...
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'products',
|
||||
pathMatch: 'full',
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
> This routing configuration is just an example to convey the idea. You can go even further by having the top level domain-entry routing at the application and then domain specific routing is added by the various domain libraries themselves.
|
||||
|
||||
When building, Nx compiles all the imported libraries together with your application code, creating a single deployable bundle. The libraries themselves don't produce deployable artifacts, they are implementation details that are consumed by the application.
|
||||
|
||||
This pattern makes it much easier to move features between applications later if needed, as your business logic isn't tied to any specific application shell. It also provides a clear mental model: applications are for deployment, libraries are for code organization and reuse.
|
||||
|
||||
## When to Create a New Library
|
||||
|
||||
There is really no correct or wrong answer here. You should not just go and create a library for each component. That's probably too much. It really depends on how closely related various components or use cases are.
|
||||
|
||||
Let's have a look at our current product domain example:
|
||||
|
||||
```text
|
||||
packages/products/
|
||||
├── data-access/ # API and state management
|
||||
├── feat-product-list/ # Product listing feature
|
||||
├── feat-product-detail/ # Product detail feature
|
||||
├── feat-product-reviews/ # Product reviews feature
|
||||
├── ui-product-card/ # Reusable product card component
|
||||
└── ui-product-carousel/ # Product carousel component
|
||||
```
|
||||
|
||||
We could easily just have a single `feat-product-list` which contains both, the list as well as detail view navigation because they might be closely connected. Similarly we could group `ui-product-card` and `ui-product-carousel` into a single `ui-product` library.
|
||||
|
||||
A good rule of thumb is to understand and see how often various parts change over time. As your application grows, watch for these signs that your library boundaries might need adjustment:
|
||||
|
||||
- **Frequent Cross-Library Changes** - You consistently need to modify multiple libraries for a single feature change
|
||||
- **Circular Dependencies** - Libraries depend on each other in ways that create circular references
|
||||
- **Unclear Ownership** - Multiple teams frequently need to coordinate to modify the same library
|
||||
- **Complex Dependencies** - Simple features require importing from many different libraries or domains
|
||||
- **Excessive Shared Code** - You find yourself duplicating types and utilities across domains
|
||||
|
||||
Remember that library boundaries aren't set in stone - they should evolve with your application. Start with broader boundaries and refine them as you gain insights into how your code changes together.
|
||||
|
||||
## Guard Your Boundaries - Automatically Enforcing Clear Dependencies
|
||||
|
||||
Once you've established your domain boundaries and architectural layers, you need to ensure they remain intact as your codebase grows. Nx provides powerful tools to enforce these boundaries through module boundary rules that can be configured in your ESLint configuration.
|
||||
|
||||
In our example, we use a dual-tagging approach:
|
||||
|
||||
1. **Scope tags** (`scope:<name>`): These reflect our domain boundaries, representing different business capabilities like `products`, `orders`, `checkout` etc. They encode our vertical slicing approach.
|
||||
2. **Type tags** (`type:<name>`): These represent our horizontal architectural layers such as `feature`, `ui`, `data-access`, and `util`.
|
||||
|
||||
Here's how the rules are configured:
|
||||
|
||||
```typescript
|
||||
// Type-based rules
|
||||
{
|
||||
sourceTag: 'type:feature',
|
||||
onlyDependOnLibsWithTags: ['type:feature', 'type:ui', 'type:data-access']
|
||||
},
|
||||
{
|
||||
sourceTag: 'type:ui',
|
||||
onlyDependOnLibsWithTags: ['type:ui', 'type:util', 'type:data-access']
|
||||
},
|
||||
|
||||
// Domain-based rules
|
||||
{
|
||||
sourceTag: 'scope:orders',
|
||||
onlyDependOnLibsWithTags: ['scope:orders', 'scope:products', 'scope:shared']
|
||||
},
|
||||
{
|
||||
sourceTag: 'scope:products',
|
||||
onlyDependOnLibsWithTags: ['scope:products', 'scope:shared']
|
||||
}
|
||||
```
|
||||
|
||||
The rules enforce a clear dependency structure:
|
||||
|
||||
- **Type rules** ensure architectural layering. For instance, UI components can only depend on other UI components, utilities, and data-access libraries. This prevents circular dependencies and maintains a clean architecture.
|
||||
- **Domain rules** control which domains can talk to each other. For example, the `orders` domain can depend on `products` (since orders contain products), but `products` cannot depend on `orders`.
|
||||
- Every domain can depend on `shared` code, but `shared` code can only depend on other shared code, preventing it from becoming a source of circular dependencies.
|
||||
|
||||
These rules are enforced at build time through ESLint. If a developer tries to import from a forbidden domain or layer, they'll receive an immediate error, helping maintain the architectural integrity of your application. This allows you to get feedback as early as possible when you run your PR checks on CI.
|
||||
|
||||
Note that this tagging structure is just a suggestion - you can adapt it to your specific needs. The key is to have clear, enforceable boundaries that reflect both your technical architecture and your business domains.
|
||||
|
||||
Read more about [Nx boundary rules in our documentation](/features/enforce-module-boundaries).
|
||||
|
||||
## Automate Your Standards
|
||||
|
||||
As your workspace grows, it becomes increasingly important to automate and enforce your team's standards and best practices.
|
||||
|
||||
The key to successful automation is finding the right balance. Start with automating the most common patterns that need standardization, and gradually add more automation as patterns emerge. Focus on the standards that provide the most value to your team. These automation capabilities are really here to ensure that your team's standards are not just documented but actively enforced through tooling, making it easier for developers to do the right thing by default.
|
||||
|
||||
Nx provides powerful mechanisms to achieve this.
|
||||
|
||||
### Custom Generators for Consistent Code Generation
|
||||
|
||||
Nx is extensible. As such it allows you to create custom code generators that you can use to encode your organization's standards and best practices.
|
||||
|
||||
The generator itself is just a function that manipulates files:
|
||||
|
||||
```typescript
|
||||
import { Tree, formatFiles } from '@nx/devkit';
|
||||
|
||||
export default async function (tree: Tree, schema: any) {
|
||||
// Add your generator logic here
|
||||
// For example, create files, modify configurations, etc.
|
||||
|
||||
await formatFiles(tree);
|
||||
}
|
||||
```
|
||||
|
||||
Your team can then run these generators through the Nx CLI (via the `nx generate ...` command) or [Nx Console](/getting-started/editor-setup):
|
||||
|
||||
For more detailed information about creating custom generators, including how to add options, create files, and modify existing ones, check out the [Local Generators documentation](/extending-nx/recipes/local-generators).
|
||||
|
||||
### Leverage Nx Console AI Integration
|
||||
|
||||
If you use [Nx Console](/getting-started/editor-setup), Nx's editor extension for VSCode and IntelliJ, then you should already have the latest AI capabilities enabled.
|
||||
|
||||
Nx Console [just got some enhancements](/blog/nx-made-cursor-smarter) with the goal of providing contextual information to editor integrated LLMs such as Copilot and Cursor. By providing Nx workspace metadata to these models they are able to provide much more valuable, context specific information and perform actions via the Nx CLI.
|
||||
|
||||
You can find more detailed information [in our documentation](/features/enhance-AI) about how to enable and use the capabilities.
|
||||
|
||||
## Single-app vs Multiple App Deployment
|
||||
|
||||
Until now we didn't really talk about a monorepo at all. We have a single Angular application and modularized its features into dedicated projects. The projects themselves are really just to encapsulate their logic and structure our application. While we could make them buildable by themselves (mostly for leveraging speed gains from incremental building) most of them do not have build targets. You can test and lint them independently but with a standalone Angular application, all your features are bundled and deployed together as a single unit.
|
||||
|
||||
While this approach is simple and works well for smaller applications, there are several scenarios where you might want to split your application:
|
||||
|
||||
- **Different scaling requirements**: Your customer-facing store might need high availability and scalability to handle thousands of concurrent users, while your admin interface serves a much smaller number of internal users
|
||||
- **Resource optimization**: Not all users need all features. For example, administrative features like inventory management are only needed by staff members
|
||||
- **Independent deployment cycles**: Different parts of your application might need to evolve at different speeds. Your admin interface might need frequent updates for internal tools, while your customer-facing store remains more stable
|
||||
- **Security considerations**: Keeping administrative features in a separate application can reduce the attack surface of your customer-facing application
|
||||
|
||||
As your application grows, you might want to split it into multiple applications - perhaps separating your customer-facing storefront from your administrative interface. The first step is to convert your standalone application into a monorepo structure. Nx comes with a `convert-to-monorepo` command to do exactly that:
|
||||
|
||||
```shell
|
||||
nx g convert-to-monorepo
|
||||
```
|
||||
|
||||
This command moves your existing application into an `apps` directory and adjusts any configuration that needs to be adjusted for supporting multiple side-by-side applications in a monorepo setup.
|
||||
|
||||
> Note: If you're looking for a NPM/Yarn/PNPM workspaces based monorepo setup, then make sure to read our article about the [New Nx Experience for TypeScript Monorepos](/blog/new-nx-experience-for-typescript-monorepos).
|
||||
|
||||
### Creating Multiple Applications
|
||||
|
||||
Once you have a monorepo structure, you can create additional applications that share code with your original app. For example, you might want to create an admin application:
|
||||
|
||||
```shell
|
||||
nx g @nx/angular:app admin
|
||||
```
|
||||
|
||||
Now you can move administrative features (like inventory management) to the new admin app by importing libraries relevant to the new app (such as for example the inventory management libraries).
|
||||
|
||||
Here's what the new structure could look like:
|
||||
|
||||
```text
|
||||
myshop/
|
||||
├── apps/
|
||||
│ ├── shop/ # Customer-facing storefront (high availability needed)
|
||||
│ └── admin/ # Administrative interface (internal users only)
|
||||
└── packages/
|
||||
├── products/ # Shared product domain
|
||||
├── orders/ # Shared order management
|
||||
├── checkout/ # Shop-specific checkout process
|
||||
└── shared/ # Common utilities and components
|
||||
```
|
||||
|
||||
You can see how the already modular structure allows you to adjust your application structure and re-link some of the packages into the new application. Something that would have otherwise been a major undertaking.
|
||||
|
||||
Similarly to how we now have two applications that can be deployed and scaled independently, we could go even further and convert it into a microfrontend approach. But more on that in another article.
|
||||
|
||||
## Scaling Development
|
||||
|
||||
Obviously as your codebase keeps growing you need to have the tooling support that helps keep it sustainable. In particular CI might become a concern as the number of projects grows. For that purpose Nx has several features to keep your CI fast and efficient:
|
||||
|
||||
- **[Remote Caching (Nx Replay)](/ci/features/remote-cache)** ensures your code is never rebuilt or retested unnecessarily.
|
||||
- **[Distributed Task Execution (Nx Agents)](/ci/features/distribute-task-execution)** intelligently allocates tasks across multiple machines.
|
||||
- **[Atomizer](/ci/features/split-e2e-tasks)** helps manage growing test suites by automatically splitting them into more fine-grained runs and by leveraging Nx Agents to parallelize them across machines.
|
||||
- **[Flaky Task Detection](/ci/features/flaky-tasks)** identifies flaky tasks (often automated unit or e2e tests) and re-runs them automatically for you.
|
||||
|
||||
One of the key advantages of using Nx is that it's not limited to just Angular either. As your application grows, you might need to:
|
||||
|
||||
- Add a documentation site using static site generators like [Analog](https://analogjs.org/)
|
||||
- Create landing pages with Next.js or Astro
|
||||
- Build backend services with NestJS or Express
|
||||
- Add specialized tools for specific business needs
|
||||
|
||||
Nx supports all these scenarios while maintaining the ability to share code between different technologies. For example, you could:
|
||||
|
||||
```text
|
||||
myshop/
|
||||
├── apps/
|
||||
│ ├── shop/ # Main Angular application
|
||||
│ ├── admin/ # Angular admin interface
|
||||
│ ├── docs/ # Analog documentation site
|
||||
│ ├── landing/ # Next.js marketing site
|
||||
│ └── api/ # NestJS backend
|
||||
└── packages/
|
||||
├── products/ # Shared product domain (used by both front and backend)
|
||||
├── orders/ # Shared order management
|
||||
└── shared/ # Common utilities and types
|
||||
```
|
||||
|
||||
The modular structure we established earlier makes it easy to share types and interfaces between frontend and backend and reuse business logic across different applications.
|
||||
|
||||
## Wrapping up
|
||||
|
||||
Building maintainable Angular applications at scale requires thoughtful architecture decisions and proper tooling support. We've covered several key aspects:
|
||||
|
||||
1. **Domain-Driven Structure**: Moving from traditional layered architectures to organizing code around business domains creates clearer boundaries and better maintainability.
|
||||
|
||||
2. **Incremental Adoption**: Starting small with a standalone application and growing into a more complex structure as needed, rather than over-engineering from the start.
|
||||
|
||||
3. **Clear Boundaries**: Using projects/libraries to create explicit boundaries between different parts of your application, with automated enforcement through module boundary rules.
|
||||
|
||||
4. **Automation & Standards**: Leveraging custom generators and AI-enhanced tooling to maintain consistency and best practices across your codebase.
|
||||
|
||||
5. **Scalability Options**: Understanding when and how to evolve from a single application to multiple applications or even microfrontends, while maintaining code sharing and reusability.
|
||||
|
||||
Remember that architecture is not a one-time decision but an evolving process - start with clear boundaries and good practices, then adapt as your application and team's needs grow. The key to success lies in having the proper tooling and automation in place to support you as your application grows.
|
||||
|
||||
---
|
||||
|
||||
Learn more:
|
||||
|
||||
- 🧠 [Nx AI Docs](/features/enhance-AI)
|
||||
- 👩💻 [Nx GitHub](https://github.com/nrwl/nx)
|
||||
- 💬 [Nx Official Discord Server](https://go.nx.dev/community)
|
||||
- 📹 [Nx Youtube Channel](https://www.youtube.com/@nxdevtools)
|
||||
@@ -0,0 +1,226 @@
|
||||
---
|
||||
title: 'Using Rspack with Angular'
|
||||
date: 2025-03-19
|
||||
slug: using-rspack-with-angular
|
||||
authors: [Colum Ferry]
|
||||
tags: [angular, webpack, rspack, nx]
|
||||
cover_image: /blog/images/2025-03-19/rspack.avif
|
||||
description: Learn about how and why to use Rspack with Angular thanks to Nx's efforts for supporting Rspack for Angular.
|
||||
---
|
||||
|
||||
Configuring your build tooling for [Angular](https://angular.dev) applications has always been a lesser concern for most Angular developers due to the abstractions that Angular created called `builders`. The underlying implementation details were hidden from the developer who only needed to run either `ng build` or `nx build`.
|
||||
Despite this, most Angular developers knew that it was originally [Webpack](https://webpack.js.org/) that was used to build their applications. This was a great solution at the time and it was even possible to extend their builds by leveraging custom webpack configurations and plugins.
|
||||
|
||||
Over time, as applications grew in size and complexity, it became clear that the inherit slowness with Webpack build speeds was becoming more and more of an issue for Angular developers.
|
||||
|
||||
The Angular Team decided to address this issue by creating a new build pipeline that leveraged [Esbuild](https://esbuild.github.io/).
|
||||
|
||||
> Esbuild brought much-needed performance improvements to Angular builds, but existing Webpack-based applications were left with either a difficult migration path or no clear upgrade strategy.
|
||||
|
||||
To this day, many existing Angular applications still rely on Webpack because they cannot readily replace their Webpack configurations and plugins with equivalent Esbuild plugins. Thus, they are continuing to fight with slow builds reducing their productivity.
|
||||
|
||||
## What about Rspack?
|
||||
|
||||
[Rspack](https://rspack.dev) is a high performance JavaScript bundler written in Rust. It offers strong compatibility with the Webpack ecosystem, allowing for almost seamless replacement of webpack, and provides lightning fast build speeds.
|
||||
|
||||
Because it supports the existing Webpack ecosystem, it provides an answer to teams that maintain Angular applications using Webpack and want to migrate to a faster build pipeline.
|
||||
|
||||
However, it is crucial to understand that Rspack is not _completely_ compatible with the Webpack ecosystem with some slight nuances and low-level api differences that prevent certain plugins and loaders from working out of the box.
|
||||
|
||||
One such example is the `AngularIvyPlugin` which is a Webpack plugin that is used to support Angular's Ivy compiler. Therefore, it was not possible to simply drop in Rpsack and expect it to work with Angular applications.
|
||||
Many people have tried to get Rspack working with Angular, but it has proven to be a challenge, with partial support and partial success being reported in the past but with a lot of limitations or actual performance degradations over the Webpack approach.
|
||||
|
||||
Previous attempts to get Rspack working with Angular focused on porting the Webpack-specific plugins, loaders and configurations to Rspack, either as-is or by reproducing them. This approach was never fully successful.
|
||||
|
||||
Instead, a new approach was needed to support Rspack with Angular. A closer examination of how Angular compiles and bundles the application was required.
|
||||
|
||||
## Introducing Angular Rspack
|
||||
|
||||

|
||||
|
||||
Angular Rspack started in September 2024 after I spent way too long investigating and researching how exactly Angular compiles and bundles for both their Webpack support and Esbuild support.
|
||||
|
||||
Something that I kept coming back to was that any Rspack solution that relied too much on Angular's Webpack support had the chance of being dropped by the Angular team as they continue to build out incredible new features with Esbuild. Instead, I decided that replicating and utilizing the abstractions the Angular team provided for their Esbuild support had a much stronger chance of longevity.
|
||||
|
||||
But before we dive into the technical details, I am excited to announce that Angular Rspack is now being maintained by the Nx team and is available for use with Nx. This means that you can now use Rspack with Angular and Nx and enjoy the benefits of both.
|
||||
The new package is called [@nx/angular-rspack](https://www.npmjs.com/package/@nx/angular-rspack) and it is available on npm while the repository has been moved to [nrwl/angular-rspack](https://github.com/nrwl/angular-rspack).
|
||||
|
||||
Nx already supports and integrates with a wide variety of build tooling across the ecosystem - filling in the gaps where developer experience (DX) needs to be improved - which makes Nx the perfect fit for continuing to maintain and build out Angular Rspack.
|
||||
|
||||
The support is still currently experimental, however, it is in a state that may be sufficient for your current needs. We invite you to try it out and to let us know if you run into issues by raising issues on the [angular-rspack repo](https://github.com/nrwl/angular-rspack/issues/new).
|
||||
|
||||
There are some limitations and missing features that are currently being worked on and on the roadmap to support. They have been listed at the bottom of this article.
|
||||
|
||||
## Migrating from Angular Webpack to Angular Rspack
|
||||
|
||||
To make the migration process as smooth as possible, a new generator has been added to the `@nx/angular` package called `convert-to-rspack` which will help you migrate your Angular applications from Webpack to Rspack.
|
||||
|
||||
The steps are very simple:
|
||||
|
||||
1. Run `nx migrate latest` to update your workspace to the latest version of Nx.
|
||||
2. Run `nx g @nx/angular:convert-to-rspack` to migrate your Angular application to Rspack.
|
||||
|
||||
There is also a [guide in our documentation](/recipes/angular/rspack/migrate-from-webpack) that walks you through the process step-by-step.
|
||||
Even if you're currently using the Angular CLI, it's as simple as first running `npx nx init` in your workspace and then running `npx nx g convert-to-rspack`.
|
||||
|
||||
## Using Angular Rspack
|
||||
|
||||
You'll notice that after migrating to Angular Rspack your `build` and `serve` targets have been removed from your project, and are instead [inferred](/concepts/inferred-tasks) by the `@nx/rspack/plugin`.
|
||||
In addition, a new `rspack.config.ts` file has been created in your project which looks something like this:
|
||||
|
||||
```ts
|
||||
import { createConfig } from '@nx/angular-rspack';
|
||||
export default createConfig(
|
||||
{
|
||||
options: {
|
||||
root: __dirname,
|
||||
outputPath: {
|
||||
base: '../../dist/apps/app',
|
||||
},
|
||||
index: './src/index.html',
|
||||
browser: './src/main.ts',
|
||||
polyfills: ['zone.js'],
|
||||
tsConfig: './tsconfig.app.json',
|
||||
assets: [
|
||||
'./src/favicon.ico',
|
||||
'./src/assets',
|
||||
{
|
||||
input: './public',
|
||||
glob: '**/*',
|
||||
},
|
||||
],
|
||||
styles: ['./src/styles.scss'],
|
||||
scripts: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
production: {
|
||||
options: {
|
||||
outputPath: {
|
||||
base: '../../dist/apps/app-prod',
|
||||
},
|
||||
index: './src/index.prod.html',
|
||||
browser: './src/main.prod.ts',
|
||||
tsConfig: './tsconfig.prod.json',
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
{% callout type="deepdive" title="createConfig Information" %}
|
||||
The `createConfig` function is used to create an Rspack configuration object setup for Angular applications.
|
||||
You can read more about it [here](/nx-api/angular-rspack/documents/create-config).
|
||||
{% /callout %}
|
||||
|
||||
### Building and Serving your Application
|
||||
|
||||
You can now run `nx build app` and `nx serve app` to build and serve your application via Rspack.
|
||||
|
||||
```{% command="nx build app" %}
|
||||
> nx run app:build
|
||||
|
||||
> rspack build --node-env=production
|
||||
|
||||
● ━━━━━━━━━━━━━━━━━━━━━━━━━ (100%) emitting after emit browser:
|
||||
|
||||
|
||||
browser compiled successfully in 2.32 s
|
||||
```
|
||||
|
||||
```{% command="nx serve app" %}
|
||||
> nx run app:serve
|
||||
|
||||
> rspack serve --node-env=development
|
||||
|
||||
<i> [webpack-dev-server] [HPM] Proxy created: /api -> http://localhost:3000
|
||||
<i> [webpack-dev-server] Project is running at:
|
||||
<i> [webpack-dev-server] Loopback: http://127.0.0.1:8080/
|
||||
<i> [webpack-dev-server] Content not from webpack is served from '/Users/columferry/dev/nrwl/issues/rspack-angular/ng-rspack/e2e/fixtures/rspack-csr-css/public' directory
|
||||
<i> [webpack-dev-server] 404s will fallback to '/index.html'
|
||||
Listening on port: 8080
|
||||
● ━━━━━━━━━━━━━━━━━━━━━━━━━ (100%) emitting after emit browser:
|
||||
browser compiled successfully in 2.33 s
|
||||
```
|
||||
|
||||
### Using a configuration
|
||||
|
||||
To run the build with the `production` configuration:
|
||||
|
||||
```bash
|
||||
NGRS_CONFIG=production nx build app
|
||||
```
|
||||
|
||||
`NGRS_CONFIG` is an environment variable that you can use to specify which configuration to use. If the environment variable is not set, the `production` configuration is used by default.
|
||||
|
||||
## Creating a new Angular Rspack application
|
||||
|
||||
We currently do not have a generator for creating a new Angular Rspack application, but we will soon and it will be available under a `--bundler=rspack` option on the `@nx/angular:application` generator.
|
||||
|
||||
However, you can still create a new Angular Rspack application by running the following commands:
|
||||
|
||||
```shell
|
||||
nx g @nx/angular:application myapp --bundler=webpack
|
||||
nx g @nx/angular:convert-to-rspack myapp
|
||||
```
|
||||
|
||||
## Benchmarks
|
||||
|
||||

|
||||
|
||||
Below is a table of benchmarks for the different bundlers available, run against an application consisting of ~800 lazy loaded routes with ~10 components each - totaling ~8000 components.
|
||||
|
||||
**System Info**
|
||||
|
||||
- MacBook Pro (macOS 15.3.1)
|
||||
- Processor: M2 Max
|
||||
- Memory: 96 GB
|
||||
- `@nx/angular-rspack` version: 20.6.2
|
||||
- Angular version: ~19.2.0
|
||||
|
||||
| Build/Bundler | Prod SSR (s) | Prod (s) | Dev (s) |
|
||||
| ------------- | ------------ | -------- | ------- |
|
||||
| Webpack | 198.614 | 154.339 | 159.436 |
|
||||
| esbuild | 23.701 | 19.569 | 15.358 |
|
||||
| Rsbuild | 23.949 | 20.490 | 18.209 |
|
||||
| Rspack | 30.589 | 19.269 | 19.940 |
|
||||
|
||||
> You can find the benchmarks and run them yourself: [https://github.com/nrwl/ng-bundler-benchmark](https://github.com/nrwl/ng-bundler-benchmark)
|
||||
|
||||
As can be seen by the benchmarks above, Rspack is significantly faster than Webpack and very close to Esbuild.
|
||||
|
||||
Given that the primary goal for Angular Rspack is to provide a faster build system for Angular Webpack applications while supporting their existing Webpack configurations and plugins, we are confident that Angular Rspack will be a great choice for teams that want to migrate to a faster build system.
|
||||
|
||||
## Known Limitations and Missing Features
|
||||
|
||||
The following are known limitations and missing features of Angular Rspack:
|
||||
|
||||
- Static Site Generation (SSG) is not supported.
|
||||
- Angular's built-in support for Internationalization (i18n) is not supported.
|
||||
- Server Routing is not supported - still experimental in Angular currently.
|
||||
- App Engine APIs are not supported - still experimental in Angular currently.
|
||||
- Optimization is not currently 1:1 with Angular's optimization - however, there are still great optimizations that are made.
|
||||
- Styles optimization for `inline-critical` and `remove-special-comments` are not yet implemented.
|
||||
- Inlining of fonts is not yet implemented.
|
||||
- Web Workers are not fully supported.
|
||||
- Hot Module Replacement (HMR) is partially supported.
|
||||
|
||||
If you have any other missing features or limitations, please [let us know](https://github.com/nrwl/angular-rspack/issues/new).
|
||||
|
||||
## What's Next?
|
||||
|
||||
We are actively working on improving the experience, stability and performance of Angular Rspack. Our next steps will revolve around getting to feature parity with Angular's build system - addressing the items listed above to achieve this.
|
||||
|
||||
The `@nx/angular` plugin will also be updated to support generating new Angular Rspack applications as well as supporting Rspack Module Federation with Angular.
|
||||
|
||||
Exciting times ahead! You can follow our progress by starring the [Angular Rspack repository](https://github.com/nrwl/angular-rspack) and following us on [X](https://X.com/nxdevtools).
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [Nx Angular Rspack](/recipes/angular/rspack/introduction)
|
||||
- [Angular](https://angular.dev)
|
||||
- [Rspack](https://rspack.dev)
|
||||
- 🧠 [Nx Docs](/getting-started/intro)
|
||||
- 👩💻 [Nx GitHub](https://github.com/nrwl/nx)
|
||||
- 💬 [Nx Official Discord Server](https://go.nx.dev/community)
|
||||
- 📹 [Nx Youtube Channel](https://www.youtube.com/@nxdevtools)
|
||||
|
After Width: | Height: | Size: 755 KiB |
|
After Width: | Height: | Size: 923 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 850 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 37 KiB |
@@ -1455,6 +1455,14 @@
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "React Router with Nx",
|
||||
"path": "/recipes/react/react-router",
|
||||
"id": "react-router",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Use Environment Variables in React",
|
||||
"path": "/recipes/react/use-environment-variables-in-react",
|
||||
@@ -2837,6 +2845,14 @@
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "React Router with Nx",
|
||||
"path": "/recipes/react/react-router",
|
||||
"id": "react-router",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Use Environment Variables in React",
|
||||
"path": "/recipes/react/use-environment-variables-in-react",
|
||||
@@ -2904,6 +2920,14 @@
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "React Router with Nx",
|
||||
"path": "/recipes/react/react-router",
|
||||
"id": "react-router",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Use Environment Variables in React",
|
||||
"path": "/recipes/react/use-environment-variables-in-react",
|
||||
|
||||
@@ -1993,6 +1993,17 @@
|
||||
"path": "/recipes/react/remix",
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"id": "react-router",
|
||||
"name": "React Router with Nx",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/guides/react-router",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/recipes/react/react-router",
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"id": "use-environment-variables-in-react",
|
||||
"name": "Use Environment Variables in React",
|
||||
@@ -3887,6 +3898,17 @@
|
||||
"path": "/recipes/react/remix",
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"id": "react-router",
|
||||
"name": "React Router with Nx",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/guides/react-router",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/recipes/react/react-router",
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"id": "use-environment-variables-in-react",
|
||||
"name": "Use Environment Variables in React",
|
||||
@@ -3980,6 +4002,17 @@
|
||||
"path": "/recipes/react/remix",
|
||||
"tags": []
|
||||
},
|
||||
"/recipes/react/react-router": {
|
||||
"id": "react-router",
|
||||
"name": "React Router with Nx",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/guides/react-router",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/recipes/react/react-router",
|
||||
"tags": []
|
||||
},
|
||||
"/recipes/react/use-environment-variables-in-react": {
|
||||
"id": "use-environment-variables-in-react",
|
||||
"name": "Use Environment Variables in React",
|
||||
|
||||
@@ -46,3 +46,11 @@ We provide a recommended version, and it is usually the latest minor version of
|
||||
| ^8.0.0 | **8.12.2** | >=8.7.0 <=8.12.2 |
|
||||
|
||||
Additionally, you can check the supported versions of Node and Typescript for the version of Angular you are using in the [Angular docs](https://angular.dev/reference/versions#actively-supported-versions).
|
||||
|
||||
## Nx and Angular Rspack Version Compatibility Matrix
|
||||
|
||||
Below is a reference table that matches versions of [Angular Rspack](/recipes/angular/rspack/introduction) to the versions of Angular and Nx that is compatible with it.
|
||||
|
||||
| Angular Rspack | Angular | Nx |
|
||||
| -------------- | ----------- | ------------------- |
|
||||
| ~20.6.0 | **~19.2.0** | >= 20.6.0 <= latest |
|
||||
|
||||
@@ -659,6 +659,11 @@
|
||||
"id": "remix",
|
||||
"file": "shared/guides/remix"
|
||||
},
|
||||
{
|
||||
"name": "React Router with Nx",
|
||||
"id": "react-router",
|
||||
"file": "shared/guides/react-router"
|
||||
},
|
||||
{
|
||||
"name": "Use Environment Variables in React",
|
||||
"id": "use-environment-variables-in-react",
|
||||
|
||||
|
After Width: | Height: | Size: 21 KiB |
@@ -36,3 +36,40 @@ This makes it a great solution for teams that want to migrate to a faster build
|
||||
Please not that Angular Rspack support is still experimental and is not yet considered production ready. We are actively working on improving the experience and stability of Angular Rspack, and we will continue to update this page as we make progress.
|
||||
|
||||
{% /callout %}
|
||||
|
||||
## Known Limitations and Missing Features
|
||||
|
||||
The following are known limitations and missing features of Angular Rspack:
|
||||
|
||||
- Static Site Generation (SSG) is not supported.
|
||||
- Angular's built-in support for Internationalization (i18n) is not supported.
|
||||
- Server Routing is not supported - still experimental in Angular currently.
|
||||
- App Engine APIs are not supported - still experimental in Angular currently.
|
||||
- Optimization is not currently 1:1 with Angular's optimization - however, there are still great optimizations that are made.
|
||||
- Styles optimization for `inline-critical` and `remove-special-comments` are not yet implemented.
|
||||
- Inlining of fonts is not yet implemented.
|
||||
- Web Workers are not fully supported.
|
||||
- Hot Module Replacement (HMR) is partially supported.
|
||||
|
||||
If you have any other missing features or limitations, please [let us know](https://github.com/nrwl/angular-rspack/issues/new).
|
||||
|
||||
## Benchmarks
|
||||
|
||||

|
||||
|
||||
Below is a table of benchmarks for different bundlers, tested on an application with ~800 lazy-loaded routes and ~10 components per route—totaling ~8000 components.
|
||||
|
||||
**System Info**
|
||||
|
||||
- MacBook Pro (macOS 15.3.1)
|
||||
- Processor: M2 Max
|
||||
- Memory: 96 GB
|
||||
|
||||
| Build/Bundler | Prod SSR (s) | Prod (s) | Dev (s) |
|
||||
| ------------- | ------------ | -------- | ------- |
|
||||
| Webpack | 198.614 | 154.339 | 159.436 |
|
||||
| esbuild | 23.701 | 19.569 | 15.358 |
|
||||
| Rsbuild | 23.949 | 20.490 | 18.209 |
|
||||
| Rspack | 30.589 | 19.269 | 19.940 |
|
||||
|
||||
You can find the benchmarks and run them yourself: [https://github.com/nrwl/ng-bundler-benchmark](https://github.com/nrwl/ng-bundler-benchmark)
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
---
|
||||
title: React Router with Nx
|
||||
description: Learn how to create, build, serve, and test React Router applications within an Nx workspace, leveraging Nx's powerful tooling for modern web development.
|
||||
---
|
||||
|
||||
## React Router with Nx
|
||||
|
||||
React Router is the successor of Remix and is the recommended routing library for new projects that may require Server-Side Routing (SSR).
|
||||
|
||||
There are three modes when using React Router: `framework`, `declarative` and `data`. `Framework` mode is the most comprehensive and is what will be covered in this recipe.
|
||||
|
||||
We'll show you how to create a [React Router](https://reactrouter.com/home) application with Nx.
|
||||
|
||||
## Create Nx Workspace
|
||||
|
||||
```{% command="npx create-nx-workspace@latest acme --preset=apps" path="~/" %}
|
||||
|
||||
✔ Which stack do you want to use? · react
|
||||
✔ What framework would you like to use? · none
|
||||
✔ Application name · acme
|
||||
✔ Would you like to use React Router for server-side rendering [https://reactrouter.com/]? · Yes
|
||||
```
|
||||
|
||||
## Have an existing App?
|
||||
|
||||
If you already have an existing React Router application and want to add Nx to it you can do so by running the following command:
|
||||
|
||||
```shell
|
||||
npx nx init
|
||||
```
|
||||
|
||||
## Generate a React Router Application
|
||||
|
||||
If you would like to generate a new React Router application, you can do so by running the following command in your Nx workspace
|
||||
|
||||
```{% command="nx g @nx/react:app apps/happynrwl --routing --use-react-router" path="~/acme" %}
|
||||
|
||||
```
|
||||
|
||||
There is no need to install any additional plugins to use React Router with Nx. The `@nx/react` plugin already includes support for React Router.
|
||||
|
||||
## Running the Application
|
||||
|
||||
Now that you have created your React Router application with Nx you can build, serve and test with the following commands:
|
||||
|
||||
### Build
|
||||
|
||||
To build your application run the following command:
|
||||
|
||||
```{% command="nx build happynrwl" path="~/acme" %}
|
||||
|
||||
> nx run acme:happynrwl
|
||||
|
||||
> react-router build
|
||||
|
||||
vite v6.2.1 building for production...
|
||||
✓ 45 modules transformed.
|
||||
build/client/.vite/manifest.json 1.40 kB │ gzip: 0.32 kB
|
||||
build/client/assets/about-D7ArdXr1.js 0.20 kB │ gzip: 0.18 kB
|
||||
build/client/assets/with-props-CQyfqcsx.js 0.22 kB │ gzip: 0.19 kB
|
||||
build/client/assets/root-BKqDrCrU.js 0.99 kB │ gzip: 0.54 kB
|
||||
build/client/assets/app-DnLbn-a2.js 24.50 kB │ gzip: 6.26 kB
|
||||
build/client/assets/chunk-K6CSEXPM-DXuCqE6i.js 111.05 kB │ gzip: 37.47 kB
|
||||
build/client/assets/entry.client-CkXnIIWp.js 179.95 kB │ gzip: 56.93 kB
|
||||
✓ built in 576ms
|
||||
vite v6.2.1 building SSR bundle for production...
|
||||
✓ 9 modules transformed.
|
||||
build/server/.vite/manifest.json 0.17 kB
|
||||
build/server/index.js 43.12 kB
|
||||
✓ built in 35ms
|
||||
|
||||
|
||||
|
||||
NX Successfully ran target build for project happynrwl (1s)
|
||||
```
|
||||
|
||||
### Serve (Development)
|
||||
|
||||
To serve your application for development use the following command:
|
||||
|
||||
```{% command="nx dev happynrwl" path="~/acme" %}
|
||||
> nx run happynrwl:dev
|
||||
|
||||
> react-router dev
|
||||
|
||||
1:30:42 p.m. [vite] (client) Re-optimizing dependencies because lockfile has changed
|
||||
➜ Local: http://localhost:4200/
|
||||
➜ press h + enter to show help
|
||||
```
|
||||
|
||||
### Serve (Production)
|
||||
|
||||
To serve your application's production build use the following command:
|
||||
|
||||
```{% command="nx start happynrwl" path="~/acme" %}
|
||||
> nx run happynrwl:build
|
||||
|
||||
> react-router build
|
||||
|
||||
vite v6.2.1 building for production...
|
||||
✓ 45 modules transformed.
|
||||
build/client/.vite/manifest.json 1.40 kB │ gzip: 0.32 kB
|
||||
build/client/assets/about-D7ArdXr1.js 0.20 kB │ gzip: 0.18 kB
|
||||
build/client/assets/with-props-CQyfqcsx.js 0.22 kB │ gzip: 0.19 kB
|
||||
build/client/assets/root-BKqDrCrU.js 0.99 kB │ gzip: 0.54 kB
|
||||
build/client/assets/app-DnLbn-a2.js 24.50 kB │ gzip: 6.26 kB
|
||||
build/client/assets/chunk-K6CSEXPM-DXuCqE6i.js 111.05 kB │ gzip: 37.47 kB
|
||||
build/client/assets/entry.client-CkXnIIWp.js 179.95 kB │ gzip: 56.93 kB
|
||||
✓ built in 576ms
|
||||
vite v6.2.1 building SSR bundle for production...
|
||||
✓ 9 modules transformed.
|
||||
build/server/.vite/manifest.json 0.17 kB
|
||||
build/server/index.js 43.12 kB
|
||||
✓ built in 35ms
|
||||
|
||||
> nx run happynrwl:start
|
||||
|
||||
> react-router-serve build/server/index.js
|
||||
|
||||
[react-router-serve] http://localhost:3000 (http://192.168.0.112:3000)
|
||||
```
|
||||
|
||||
{% callout type="note" title="PORT" %}
|
||||
The default port for `production` is 3000 if you want to change it use the PORT environment variable
|
||||
{% /callout %}
|
||||
|
||||
### Unit Test
|
||||
|
||||
To unit test the application run the following command:
|
||||
|
||||
```{% command="nx test happynrwl" path="~/acme" %}
|
||||
> nx run happynrwl:test
|
||||
|
||||
> vitest
|
||||
|
||||
|
||||
RUN v3.0.8 /private/tmp/projects/acme/apps/happynrwl
|
||||
|
||||
✓ tests/routes/_index.spec.tsx (1 test) 45ms
|
||||
✓ renders loader data
|
||||
|
||||
Test Files 1 passed (1)
|
||||
Tests 1 passed (1)
|
||||
Start at 13:40:29
|
||||
Duration 807ms (transform 59ms, setup 0ms, collect 168ms, tests 45ms, environment 372ms, prepare 52ms)
|
||||
```
|
||||
|
||||
## Generate a Route
|
||||
|
||||
By default a route is generated for you when you create a new React Router application. If you would like to generate a new route you can do so by running the following command:
|
||||
|
||||
```{% command="nx g @nx/react:component --path=apps/happynrwl/app/routes/contact" path="~/happynrwl" %}
|
||||
CREATE apps/happynrwl/app/routes/contact.tsx
|
||||
CREATE apps/happynrwl/app/routes/contact.module.scss
|
||||
CREATE apps/happynrwl/app/routes/contact.spec.tsx
|
||||
```
|
||||
|
||||
Now we have create a new route called `contact` in our application. Let's add that route to our `routes.tsx` file.
|
||||
|
||||
```tsx
|
||||
import { type RouteConfig, index, route } from "@react-router/dev/routes";
|
||||
|
||||
export default [
|
||||
index('./app.tsx'),
|
||||
route('about', './routes/about.tsx')
|
||||
route('contact', './routes/contact.tsx')
|
||||
] satisfies RouteConfig;
|
||||
```
|
||||
|
||||
Now if we serve or app and navigate to `https://localhost:4200/contact` we will see our new route.
|
||||
|
||||
## GitHub Repository with Example
|
||||
|
||||
You can find an example of an Nx Workspace using React Router by clicking below
|
||||
|
||||
{% github-repository url="<https://github.com/nrwl/nx-recipes/tree/main/react-router>" /%}
|
||||
@@ -3,6 +3,12 @@ title: Remix with Nx
|
||||
description: Learn how to create, build, serve, and test Remix applications within an Nx workspace, leveraging Nx's powerful tooling for modern web development.
|
||||
---
|
||||
|
||||
{% callout type="warning" title="The future of Remix is React Router" %}
|
||||
Remix has announced its transition to React Router. The `@nx/remix` plugin is based on Remix v2 and is incompatible with the latest version of React Router unless you [upgrade](https://reactrouter.com/upgrading/remix) your Remix application. In the future, the `@nx/remix` plugin will be deprecated in favor of the `@nx/react/router` plugin. If you are starting a new project, we recommend using the `@nx/react/router` plugin instead.
|
||||
|
||||
For an example of how to use React Router with Nx, see the [React Router with Nx](/recipes/react/react-router) recipe.
|
||||
{% /callout %}
|
||||
|
||||
# Remix with Nx
|
||||
|
||||
In this recipe, we'll show you how to create a [Remix](https://remix.run) application with Nx.
|
||||
|
||||
@@ -46,3 +46,11 @@ We provide a recommended version, and it is usually the latest minor version of
|
||||
| ^8.0.0 | **8.12.2** | >=8.7.0 <=8.12.2 |
|
||||
|
||||
Additionally, you can check the supported versions of Node and Typescript for the version of Angular you are using in the [Angular docs](https://angular.dev/reference/versions#actively-supported-versions).
|
||||
|
||||
## Nx and Angular Rspack Version Compatibility Matrix
|
||||
|
||||
Below is a reference table that matches versions of [Angular Rspack](/recipes/angular/rspack/introduction) to the versions of Angular and Nx that is compatible with it.
|
||||
|
||||
| Angular Rspack | Angular | Nx |
|
||||
| -------------- | ----------- | ------------------- |
|
||||
| ~20.6.0 | **~19.2.0** | >= 20.6.0 <= latest |
|
||||
|
||||
@@ -33,32 +33,19 @@ For this recipe, we'll assume that the root-level app is named `my-app`. The hig
|
||||
|
||||
## Steps
|
||||
|
||||
1. Update the `workspaceLayout` property in `nx.json` to be:
|
||||
|
||||
```jsonc {% fileName="nx.json" %}
|
||||
{
|
||||
"workspaceLayout": {
|
||||
"appsDir": "apps",
|
||||
"libsDir": "libs"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This will make sure that new apps are created under `apps` and new libraries are created under `libs`.
|
||||
|
||||
2. If there is a `tsconfig.json` file in the root, rename it to `tsconfig.old.json`
|
||||
1. If there is a `tsconfig.json` file in the root, rename it to `tsconfig.old.json`
|
||||
|
||||
This step is to make sure that a `tsconfig.base.json` file is generated by the app generator in the next step.
|
||||
|
||||
3. Create a new app using the appropriate plugin under `apps/temp`
|
||||
2. Create a new app using the appropriate plugin under `apps/temp`
|
||||
|
||||
```shell
|
||||
nx g app apps/temp
|
||||
```
|
||||
|
||||
4. Move the `/src` (and `/public`, if present) folders to `apps/temp/`, overwriting the folders already there.
|
||||
3. Move the `/src` (and `/public`, if present) folders to `apps/temp/`, overwriting the folders already there.
|
||||
|
||||
5. For each config file in `apps/temp`, copy over the corresponding file from the root of the repo.
|
||||
4. For each config file in `apps/temp`, copy over the corresponding file from the root of the repo.
|
||||
|
||||
It can be difficult to know which files are root-level config files and which files are project-specific config files. Here is a non-exhaustive list of config files to help distinguish between the two.
|
||||
|
||||
@@ -71,7 +58,7 @@ For this recipe, we'll assume that the root-level app is named `my-app`. The hig
|
||||
`jest.config.app.ts` in the root should be renamed to `jest.config.ts` when moved to `apps/temp`. Also update the `jestConfig` option in `project.json` to point to `jest.config.ts` instead of `jest.config.app.ts`.
|
||||
{% /callout %}
|
||||
|
||||
6. Update the paths of the project-specific config files that were copied into `apps/temp`.
|
||||
5. Update the paths of the project-specific config files that were copied into `apps/temp`.
|
||||
|
||||
Here is a non-exhaustive list of properties that will need to be updated to have the correct path:
|
||||
|
||||
@@ -83,7 +70,7 @@ For this recipe, we'll assume that the root-level app is named `my-app`. The hig
|
||||
| `jest.config.ts` | `preset`, `coverageDirectory` |
|
||||
| `vite.config.ts` | `cacheDir`, `root`, `dir` |
|
||||
|
||||
7. Doublecheck that all the tasks defined in the `apps/temp/project.json` file still work.
|
||||
6. Doublecheck that all the tasks defined in the `apps/temp/project.json` file still work.
|
||||
|
||||
```shell
|
||||
nx build temp
|
||||
@@ -91,23 +78,23 @@ For this recipe, we'll assume that the root-level app is named `my-app`. The hig
|
||||
nx lint temp
|
||||
```
|
||||
|
||||
8. Move the `/e2e/src` folder to `/apps/temp-e2e`, overwriting the folder already there
|
||||
9. For each config file in `apps/temp-e2e`, copy over the corresponding file from the root of the repo. Update the paths for these files in the same way you did for the `my-app` config files.
|
||||
10. Update the `/apps/temp-e2e/project.json` `implicitDependencies` to be `temp` instead of `my-app`
|
||||
11. Doublecheck that all the tasks defined in the `apps/temp-e2e/project.json` file still work.
|
||||
7. Move the `/e2e/src` folder to `/apps/temp-e2e`, overwriting the folder already there
|
||||
8. For each config file in `apps/temp-e2e`, copy over the corresponding file from the root of the repo. Update the paths for these files in the same way you did for the `my-app` config files.
|
||||
9. Update the `/apps/temp-e2e/project.json` `implicitDependencies` to be `temp` instead of `my-app`
|
||||
10. Doublecheck that all the tasks defined in the `apps/temp-e2e/project.json` file still work.
|
||||
|
||||
```shell
|
||||
nx lint temp-e2e
|
||||
nx e2e temp-e2e
|
||||
```
|
||||
|
||||
12. Delete all the project specific config files in the root and under `e2e`
|
||||
13. Once the `project.json` file has been deleted in the root, rename `temp-e2e` to `my-app-e2e` and rename `temp` to `my-app`
|
||||
11. Delete all the project specific config files in the root and under `e2e`
|
||||
12. Once the `project.json` file has been deleted in the root, rename `temp-e2e` to `my-app-e2e` and rename `temp` to `my-app`
|
||||
|
||||
```shell
|
||||
nx g move --projectName=temp-e2e --destination=my-app-e2e
|
||||
nx g move --projectName=temp --destination=my-app
|
||||
```
|
||||
|
||||
14. Update the `defaultProject` in `nx.json` if needed
|
||||
15. Check again that all the tasks still work and that the `nx graph` displays what you expect.
|
||||
13. Update the `defaultProject` in `nx.json` if needed
|
||||
14. Check again that all the tasks still work and that the `nx graph` displays what you expect.
|
||||
|
||||
@@ -101,6 +101,7 @@
|
||||
- [React](/recipes/react)
|
||||
- [React Native with Nx](/recipes/react/react-native)
|
||||
- [Remix with Nx](/recipes/react/remix)
|
||||
- [React Router with Nx](/recipes/react/react-router)
|
||||
- [Use Environment Variables in React](/recipes/react/use-environment-variables-in-react)
|
||||
- [Using Tailwind CSS in React](/recipes/react/using-tailwind-css-in-react)
|
||||
- [Adding Images, Fonts, and Files](/recipes/react/adding-assets-react)
|
||||
|
||||
@@ -354,7 +354,6 @@
|
||||
"cliui": "^8.0.1",
|
||||
"core-js": "3.36.1",
|
||||
"enquirer": "~2.3.6",
|
||||
"fast-glob": "3.2.7",
|
||||
"framer-motion": "^11.3.0",
|
||||
"front-matter": "^4.0.2",
|
||||
"glob": "7.1.4",
|
||||
@@ -377,6 +376,7 @@
|
||||
"tailwind-merge": "^2.4.0",
|
||||
"tailwindcss": "3.4.4",
|
||||
"three": "^0.166.1",
|
||||
"tinyglobby": "^0.2.12",
|
||||
"tslib": "^2.3.0",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"dependencies": {
|
||||
"@nx/devkit": "file:../devkit",
|
||||
"@nx/js": "file:../js",
|
||||
"tinyglobby": "^0.2.10",
|
||||
"tinyglobby": "^0.2.12",
|
||||
"picocolors": "^1.1.0",
|
||||
"tsconfig-paths": "^4.1.2",
|
||||
"tslib": "^2.3.0"
|
||||
|
||||
@@ -37,6 +37,8 @@ export async function getProjectReportLines(
|
||||
projectReportBuffer = await execGradleAsync(gradlewFile, [
|
||||
'projectReportAll',
|
||||
process.env.NX_VERBOSE_LOGGING === 'true' ? '--info' : '',
|
||||
'--exclude-task',
|
||||
'htmlDependencyReport',
|
||||
]);
|
||||
} catch (e: Buffer | Error | any) {
|
||||
if (e.toString()?.includes('ERROR: JAVA_HOME')) {
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
"picomatch": "4.0.2",
|
||||
"semver": "^7.5.3",
|
||||
"source-map-support": "0.5.19",
|
||||
"tinyglobby": "^0.2.10",
|
||||
"tinyglobby": "^0.2.12",
|
||||
"ts-node": "10.9.1",
|
||||
"tsconfig-paths": "^4.1.2",
|
||||
"tslib": "^2.3.0"
|
||||
|
||||
@@ -175,7 +175,7 @@ impl NxCache {
|
||||
&self,
|
||||
hash: String,
|
||||
result: CachedResult,
|
||||
outputs: Vec<String>,
|
||||
outputs: Option<Vec<String>>,
|
||||
) -> anyhow::Result<()> {
|
||||
trace!(
|
||||
"applying remote cache results: {:?} ({})",
|
||||
@@ -184,9 +184,12 @@ impl NxCache {
|
||||
);
|
||||
let terminal_output = result.terminal_output.clone();
|
||||
let mut size = terminal_output.len() as i64;
|
||||
if outputs.len() > 0 && result.code == 0 {
|
||||
size += try_and_retry(|| self.copy_files_from_cache(result.clone(), outputs.clone()))?;
|
||||
};
|
||||
if let Some(outputs) = outputs {
|
||||
if outputs.len() > 0 && result.code == 0 {
|
||||
size +=
|
||||
try_and_retry(|| self.copy_files_from_cache(result.clone(), outputs.clone()))?;
|
||||
};
|
||||
}
|
||||
write(self.get_task_outputs_path(hash.clone()), terminal_output)?;
|
||||
|
||||
let code: i16 = result.code;
|
||||
|
||||
@@ -40,7 +40,7 @@ export declare class NxCache {
|
||||
constructor(workspaceRoot: string, cachePath: string, dbConnection: ExternalObject<NxDbConnection>, linkTaskDetails?: boolean | undefined | null, maxCacheSize?: number | undefined | null)
|
||||
get(hash: string): CachedResult | null
|
||||
put(hash: string, terminalOutput: string, outputs: Array<string>, code: number): void
|
||||
applyRemoteCacheResults(hash: string, result: CachedResult, outputs: Array<string>): void
|
||||
applyRemoteCacheResults(hash: string, result: CachedResult, outputs?: Array<string> | undefined | null): void
|
||||
getTaskOutputsPath(hash: string): string
|
||||
getCacheSize(): number
|
||||
copyFilesFromCache(cachedResult: CachedResult, outputs: Array<string>): number
|
||||
|
||||
@@ -87,9 +87,6 @@ importers:
|
||||
enquirer:
|
||||
specifier: ~2.3.6
|
||||
version: 2.3.6
|
||||
fast-glob:
|
||||
specifier: 3.2.7
|
||||
version: 3.2.7
|
||||
framer-motion:
|
||||
specifier: ^11.3.0
|
||||
version: 11.5.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
@@ -156,6 +153,9 @@ importers:
|
||||
three:
|
||||
specifier: ^0.166.1
|
||||
version: 0.166.1
|
||||
tinyglobby:
|
||||
specifier: ^0.2.12
|
||||
version: 0.2.12
|
||||
tslib:
|
||||
specifier: ^2.3.0
|
||||
version: 2.7.0
|
||||
@@ -961,9 +961,6 @@ importers:
|
||||
terser-webpack-plugin:
|
||||
specifier: ^5.3.3
|
||||
version: 5.3.10(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4))
|
||||
tinyglobby:
|
||||
specifier: ^0.2.10
|
||||
version: 0.2.10
|
||||
tmp:
|
||||
specifier: ~0.2.1
|
||||
version: 0.2.3
|
||||
@@ -10924,16 +10921,16 @@ packages:
|
||||
fd-slicer@1.1.0:
|
||||
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
|
||||
|
||||
fdir@6.3.0:
|
||||
resolution: {integrity: sha512-QOnuT+BOtivR77wYvCWHfGt9s4Pz1VIMbD463vegT5MLqNXy8rYFT/lPVEqf/bhYeT6qmqrNHhsX+rWwe3rOCQ==}
|
||||
fdir@6.4.2:
|
||||
resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==}
|
||||
peerDependencies:
|
||||
picomatch: ^3 || ^4
|
||||
peerDependenciesMeta:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fdir@6.4.2:
|
||||
resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==}
|
||||
fdir@6.4.3:
|
||||
resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==}
|
||||
peerDependencies:
|
||||
picomatch: ^3 || ^4
|
||||
peerDependenciesMeta:
|
||||
@@ -16853,8 +16850,8 @@ packages:
|
||||
tinyexec@0.3.2:
|
||||
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
|
||||
|
||||
tinyglobby@0.2.10:
|
||||
resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==}
|
||||
tinyglobby@0.2.12:
|
||||
resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tinyglobby@0.2.6:
|
||||
@@ -23269,7 +23266,7 @@ snapshots:
|
||||
semver: 7.7.1
|
||||
simple-git: 3.27.0
|
||||
sirv: 2.0.4
|
||||
tinyglobby: 0.2.10
|
||||
tinyglobby: 0.2.12
|
||||
unimport: 3.12.0(rollup@4.22.0)(webpack-sources@3.2.3)
|
||||
vite: 6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1)
|
||||
vite-plugin-inspect: 0.8.7(@nuxt/kit@3.13.2(magicast@0.3.5)(rollup@4.22.0)(webpack-sources@3.2.3))(rollup@4.22.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1))
|
||||
@@ -23533,7 +23530,7 @@ snapshots:
|
||||
'@nx/devkit': 20.5.0-rc.4(nx@20.5.0-rc.4(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))
|
||||
'@nx/js': 20.5.0-rc.4(@babel/traverse@7.26.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(nx@20.5.0-rc.4(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))
|
||||
picocolors: 1.1.1
|
||||
tinyglobby: 0.2.10
|
||||
tinyglobby: 0.2.12
|
||||
tsconfig-paths: 4.2.0
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
@@ -23668,7 +23665,7 @@ snapshots:
|
||||
picomatch: 4.0.2
|
||||
semver: 7.7.1
|
||||
source-map-support: 0.5.19
|
||||
tinyglobby: 0.2.10
|
||||
tinyglobby: 0.2.12
|
||||
ts-node: 10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(typescript@5.7.3)
|
||||
tsconfig-paths: 4.2.0
|
||||
tslib: 2.8.1
|
||||
@@ -30859,11 +30856,11 @@ snapshots:
|
||||
dependencies:
|
||||
pend: 1.2.0
|
||||
|
||||
fdir@6.3.0(picomatch@4.0.2):
|
||||
fdir@6.4.2(picomatch@4.0.2):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.2
|
||||
|
||||
fdir@6.4.2(picomatch@4.0.2):
|
||||
fdir@6.4.3(picomatch@4.0.2):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.2
|
||||
|
||||
@@ -38169,14 +38166,14 @@ snapshots:
|
||||
|
||||
tinyexec@0.3.2: {}
|
||||
|
||||
tinyglobby@0.2.10:
|
||||
tinyglobby@0.2.12:
|
||||
dependencies:
|
||||
fdir: 6.4.2(picomatch@4.0.2)
|
||||
fdir: 6.4.3(picomatch@4.0.2)
|
||||
picomatch: 4.0.2
|
||||
|
||||
tinyglobby@0.2.6:
|
||||
dependencies:
|
||||
fdir: 6.3.0(picomatch@4.0.2)
|
||||
fdir: 6.4.2(picomatch@4.0.2)
|
||||
picomatch: 4.0.2
|
||||
|
||||
tinypool@1.0.2: {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as fg from 'fast-glob';
|
||||
import * as globby from 'tinyglobby';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as octokit from 'octokit';
|
||||
@@ -35,7 +35,7 @@ async function main() {
|
||||
|
||||
for (const pattern of patternsToCheck) {
|
||||
foundMatchingFiles ||=
|
||||
fg.sync(pattern, {
|
||||
globby.globSync(pattern, {
|
||||
ignore: ['node_modules', 'dist', 'build', '.git'],
|
||||
cwd: path.join(__dirname, '..'),
|
||||
onlyFiles: false,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//@ts-check
|
||||
const fs = require('fs');
|
||||
const glob = require('fast-glob');
|
||||
const glob = require('tinyglobby');
|
||||
|
||||
const p = process.argv[2];
|
||||
|
||||
const nativeFiles = glob.sync(`packages/${p}/**/*.{node,wasm,js,mjs,cjs}`);
|
||||
const nativeFiles = glob.globSync(`packages/${p}/**/*.{node,wasm,js,mjs,cjs}`);
|
||||
|
||||
console.log({ nativeFiles });
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//@ts-check
|
||||
const { mkdirSync, copySync } = require('fs-extra');
|
||||
const glob = require('fast-glob');
|
||||
const glob = require('tinyglobby');
|
||||
const { join, basename } = require('path');
|
||||
|
||||
const p = process.argv[2];
|
||||
@@ -15,7 +15,7 @@ try {
|
||||
});
|
||||
} catch {}
|
||||
for (const f of from) {
|
||||
const matchingFiles = glob.sync(f, {
|
||||
const matchingFiles = glob.globSync(f, {
|
||||
cwd: process.cwd(),
|
||||
onlyDirectories: true,
|
||||
});
|
||||
|
||||