docs(core): node-server standalone tutorial (#14627)

This commit is contained in:
Isaac Mann 2023-02-15 10:51:05 -05:00 committed by GitHub
parent 73cd931441
commit 76d9f8bdc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1646 additions and 482 deletions

View File

@ -599,17 +599,25 @@
"disableCollapsible": false
},
{
"name": "4 - Workspace Optimization",
"path": "/node-tutorial/4-workspace-optimization",
"id": "4-workspace-optimization",
"name": "4 - Task Pipelines",
"path": "/node-tutorial/4-task-pipelines",
"id": "4-task-pipelines",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "5 - Summary",
"path": "/node-tutorial/5-summary",
"id": "5-summary",
"name": "5 - Docker Target",
"path": "/node-tutorial/5-docker-target",
"id": "5-docker-target",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "6 - Summary",
"path": "/node-tutorial/6-summary",
"id": "6-summary",
"isExternal": false,
"children": [],
"disableCollapsible": false
@ -642,17 +650,25 @@
"disableCollapsible": false
},
{
"name": "4 - Workspace Optimization",
"path": "/node-tutorial/4-workspace-optimization",
"id": "4-workspace-optimization",
"name": "4 - Task Pipelines",
"path": "/node-tutorial/4-task-pipelines",
"id": "4-task-pipelines",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "5 - Summary",
"path": "/node-tutorial/5-summary",
"id": "5-summary",
"name": "5 - Docker Target",
"path": "/node-tutorial/5-docker-target",
"id": "5-docker-target",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "6 - Summary",
"path": "/node-tutorial/6-summary",
"id": "6-summary",
"isExternal": false,
"children": [],
"disableCollapsible": false

View File

@ -742,23 +742,33 @@
"tags": []
},
{
"id": "4-workspace-optimization",
"name": "4 - Workspace Optimization",
"id": "4-task-pipelines",
"name": "4 - Task Pipelines",
"description": "",
"file": "shared/node-tutorial/4-workspace-optimization",
"file": "shared/node-tutorial/4-task-pipelines",
"itemList": [],
"isExternal": false,
"path": "/node-tutorial/4-workspace-optimization",
"path": "/node-tutorial/4-task-pipelines",
"tags": []
},
{
"id": "5-summary",
"name": "5 - Summary",
"id": "5-docker-target",
"name": "5 - Docker Target",
"description": "",
"file": "shared/node-tutorial/5-summary",
"file": "shared/node-tutorial/5-docker-target",
"itemList": [],
"isExternal": false,
"path": "/node-tutorial/5-summary",
"path": "/node-tutorial/5-docker-target",
"tags": []
},
{
"id": "6-summary",
"name": "6 - Summary",
"description": "",
"file": "shared/node-tutorial/6-summary",
"itemList": [],
"isExternal": false,
"path": "/node-tutorial/6-summary",
"tags": []
}
],
@ -796,24 +806,34 @@
"path": "/node-tutorial/3-task-running",
"tags": []
},
"/node-tutorial/4-workspace-optimization": {
"id": "4-workspace-optimization",
"name": "4 - Workspace Optimization",
"/node-tutorial/4-task-pipelines": {
"id": "4-task-pipelines",
"name": "4 - Task Pipelines",
"description": "",
"file": "shared/node-tutorial/4-workspace-optimization",
"file": "shared/node-tutorial/4-task-pipelines",
"itemList": [],
"isExternal": false,
"path": "/node-tutorial/4-workspace-optimization",
"path": "/node-tutorial/4-task-pipelines",
"tags": []
},
"/node-tutorial/5-summary": {
"id": "5-summary",
"name": "5 - Summary",
"/node-tutorial/5-docker-target": {
"id": "5-docker-target",
"name": "5 - Docker Target",
"description": "",
"file": "shared/node-tutorial/5-summary",
"file": "shared/node-tutorial/5-docker-target",
"itemList": [],
"isExternal": false,
"path": "/node-tutorial/5-summary",
"path": "/node-tutorial/5-docker-target",
"tags": []
},
"/node-tutorial/6-summary": {
"id": "6-summary",
"name": "6 - Summary",
"description": "",
"file": "shared/node-tutorial/6-summary",
"itemList": [],
"isExternal": false,
"path": "/node-tutorial/6-summary",
"tags": []
},
"/core-features": {

View File

@ -231,14 +231,19 @@
"file": "shared/node-tutorial/3-task-running"
},
{
"name": "4 - Workspace Optimization",
"id": "4-workspace-optimization",
"file": "shared/node-tutorial/4-workspace-optimization"
"name": "4 - Task Pipelines",
"id": "4-task-pipelines",
"file": "shared/node-tutorial/4-task-pipelines"
},
{
"name": "5 - Summary",
"id": "5-summary",
"file": "shared/node-tutorial/5-summary"
"name": "5 - Docker Target",
"id": "5-docker-target",
"file": "shared/node-tutorial/5-docker-target"
},
{
"name": "6 - Summary",
"id": "6-summary",
"file": "shared/node-tutorial/6-summary"
}
]
},

View File

@ -34,6 +34,14 @@ A modern Angular development experience powered by advanced generators and integ
{% /persona %}
{% persona type="node" title="Create a Standalone Node server" url="/getting-started/node-tutorial" %}
A modern Node server with scaffolding for Express, Fastify or Koa. There's also Docker support built-in.
- [Create a Standalone Node server](/getting-started/node-tutorial)
{% /persona %}
{% /personas %}
## Adopting Nx

View File

@ -1,5 +1,5 @@
---
title: 'Node Tutorial - Part 1: Code Generation'
title: 'Node Standalone Tutorial - Part 1: Code Generation'
description: In this tutorial you'll create a backend-focused workspace with Nx.
---
@ -18,98 +18,67 @@ In this tutorial you'll create a backend-focused workspace with Nx.
- [1 - Code Generation](/node-tutorial/1-code-generation)
- [2 - Project Graph](/node-tutorial/2-project-graph)
- [3 - Task Running](/node-tutorial/3-task-running)
- [4 - Workspace Optimization](/node-tutorial/4-workspace-optimization)
- [5 - Summary](/node-tutorial/5-summary)
- [4 - Task Pipelines](/node-tutorial/4-task-pipelines)
- [5 - Docker Target](/node-tutorial/5-docker-target)
- [6 - Summary](/node-tutorial/6-summary)
## Your Objective
For this tutorial, you'll create an Express API application, a CLI (command-line interface) application, and a library for a data client that these two applications will use to interact with a data-source.
![Our Workspace Requirements](/shared/node-tutorial/requirements-diagram.svg)
For this tutorial, you'll create an Express API application, a library that the API can reference to handle authentication and a suite of e2e tests.
## Creating an Nx Workspace
Run the command `npx create-nx-workspace@latest` and when prompted, provide the following responses:
```{% command="npx create-nx-workspace@latest" path="~" %}
✔ Choose your style · integrated
✔ What to create in the new workspace · ts
✔ Repository name · my-products
✔ Choose your style · node-server
✔ What framework should be used? · express
✔ Repository name · products-api
✔ Enable distributed caching to make your CI faster · Yes
```
{% card title="Opting into Nx Cloud" description="You will also be prompted whether to add Nx Cloud to your workspace. We won't address this in this tutorial, but you can see the introduction to Nx Cloud for more details." url="/nx-cloud/intro/what-is-nx-cloud" /%}
## Install the Node Plugin
The `node-server` preset automatically creates a `products-api` application at the root of the workspace and an `e2e` project that runs against it.
Open the folder that was created and install the `@nrwl/node` plugin.
```shell
cd my-products
npm i -D @nrwl/node
```
## Add Two Application to Your Workspace
```{% command="nx g @nrwl/node:app products-api" path="~/my-products" %}
> NX Generating @nrwl/node:application
CREATE packages/products-api/src/app/.gitkeep
CREATE packages/products-api/src/assets/.gitkeep
CREATE packages/products-api/src/main.ts
CREATE packages/products-api/tsconfig.app.json
CREATE packages/products-api/tsconfig.json
CREATE packages/products-api/project.json
CREATE packages/products-api/.eslintrc.json
CREATE packages/products-api/jest.config.ts
CREATE packages/products-api/tsconfig.spec.json
```
Run this command to create your `products-cli` app:
```{% command="npx nx g @nrwl/node:app products-cli" path="~/my-products" %}
> NX Generating @nrwl/node:application
CREATE packages/products-cli/src/app/.gitkeep
CREATE packages/products-cli/src/assets/.gitkeep
CREATE packages/products-cli/src/main.ts
CREATE packages/products-cli/tsconfig.app.json
CREATE packages/products-cli/tsconfig.json
CREATE packages/products-cli/project.json
CREATE packages/products-cli/.eslintrc.json
CREATE packages/products-cli/jest.config.ts
CREATE packages/products-cli/tsconfig.spec.json
```
![Nx Generator Syntax](/shared/node-tutorial/generator-syntax.svg)
{% callout type="note" title="Framework Options" description="This tutorial uses the `express` framework. The `node-server` preset also provides starter files for `koa` and `fastify`." /%}
## Generating Libraries
To create the `products-data-client` library, use the `@nrwl/js:lib` generator:
To create the `auth` library, use the `@nrwl/node:lib` generator:
```{% command="npx nx g @nrwl/js:lib products-data-client" path="~/my-products" %}
![Nx Generator Syntax](/shared/node-tutorial/generator-syntax.svg)
> NX Generating @nrwl/js:library
```{% command="npx nx g @nrwl/node:lib auth --buildable" path="~/products-api" %}
> NX Generating @nrwl/node:library
CREATE libs/products-data-client/README.md
CREATE libs/products-data-client/package.json
CREATE libs/products-data-client/src/index.ts
CREATE libs/products-data-client/src/lib/products-data-client.spec.ts
CREATE libs/products-data-client/src/lib/products-data-client.ts
CREATE libs/products-data-client/tsconfig.json
CREATE libs/products-data-client/tsconfig.lib.json
CREATE libs/products-data-client/project.json
UPDATE tsconfig.base.json
CREATE libs/products-data-client/.eslintrc.json
CREATE libs/products-data-client/jest.config.ts
CREATE libs/products-data-client/tsconfig.spec.json
CREATE auth/README.md
CREATE auth/.babelrc
CREATE auth/package.json
CREATE auth/src/index.ts
CREATE auth/src/lib/auth.spec.ts
CREATE auth/src/lib/auth.ts
CREATE auth/tsconfig.json
CREATE auth/tsconfig.lib.json
UPDATE tsconfig.json
UPDATE package.json
CREATE auth/project.json
CREATE .eslintrc.base.json
UPDATE .eslintrc.json
UPDATE e2e/.eslintrc.json
CREATE auth/.eslintrc.json
CREATE jest.config.app.ts
UPDATE jest.config.ts
UPDATE project.json
CREATE auth/jest.config.ts
CREATE auth/tsconfig.spec.json
```
You have now created all three projects from the design:
You have now created three projects:
- `products-api` in `apps/products-api`
- `products-cli` in `apps/products-cli`
- `products-data-client` in `libs/products-data-client`
- `products-api` in `/`
- `e2e` in `/e2e`
- `auth` in `/auth`
## What's Next

View File

@ -1,126 +1,95 @@
# Node Tutorial - Part 2: Project Graph
---
title: 'Node Standalone Tutorial - Part 2: Project Graph'
description: In this tutorial you'll create a backend-focused workspace with Nx.
---
# Node Standalone Tutorial - Part 2: Project Graph
Run the command: `npx nx graph`. A browser should open up with the following contents:
![Initial Project Graph](/shared/node-tutorial/initial-project-graph.png)
{% graph height="200px" type="project" jsonFile="shared/node-tutorial/initial-project-graph.json" %}
{% /graph %}
This is still different from the design from the start of Part 1:
You'll notice that there is no dependency drawn from `products-api` to the `auth` library. The project graph is derived from the source code of your workspace. Once we actually wire everything up, the project graph will update accordingly.
![Our Workspace Requirements](/shared/node-tutorial/requirements-diagram.svg)
### `auth`
The Project Graph is derived from the source code of your workspace. Make the following adjustments to your existing projects, so that our Project Graph will match the design:
Update the contents of the generated `auth.ts` file:
### `products-data-client`
Update the contents of the generated `products-data-client.ts` file:
```typescript {% fileName="libs/products-data-client/src/lib/products-data-client.ts" %}
export interface Product {
id: string;
```typescript {% fileName="auth/src/lib/auth.ts" %}
export type AuthResponse = AuthSuccessResponse | AuthFailureResponse;
export interface AuthSuccessResponse {
success: true;
name: string;
price: number;
}
export interface AuthFailureResponse {
success: false;
}
export interface ProductsDataClient {
getProducts(): Promise<Product[]>;
getProductById(id: string): Promise<Product | undefined>;
}
export const exampleProducts: Record<string, Product> = {
'1': { id: '1', name: 'Product 1', price: 100 },
'2': { id: '2', name: 'Product 2', price: 200 },
};
export function createProductsDataClient(): ProductsDataClient {
return {
getProducts() {
return Promise.resolve(Object.values(exampleProducts));
},
getProductById(id) {
return Promise.resolve(exampleProducts[id]);
},
};
}
```
### `products-cli`
Update the generated `main.ts` file of this project to import the `createProductsDataClient()` function.
Use the data client to print the product matching the id provided at the command-line. If no id was provided, print all products as an array:
```typescript {% fileName="apps/products-cli/src/main.ts" %}
import { createProductsDataClient } from '@my-products/products-data-client';
main();
async function main() {
const productsDataClient = createProductsDataClient();
const id = getProvidedId();
if (id != null) {
const product = await productsDataClient.getProductById(id);
console.log(JSON.stringify(product, null, 2));
} else {
const products = await productsDataClient.getProducts();
console.log(JSON.stringify(products, null, 2));
}
}
function getProvidedId() {
return process.argv[2];
export function doAuth(): AuthResponse {
return { success: true, name: 'Cheddar' };
}
```
### `products-api`
Update the generated `main.ts` file of this project to also import the `createProductsDataClient()` function.
Add a post endpoint to the `main.ts` file of the root project that uses the `doAuth()` function.
Use the data client and Express to create an Express app with 2 GET request handlers:
```typescript {% fileName="src/main.ts" %}
import express from 'express';
import { doAuth } from '@products-api/auth';
```javascript {% fileName="apps/products-api/src/main.ts" %}
/**
* This is not a production server yet!
* This is only a minimal backend to get started.
*/
import * as express from 'express';
import { createProductsDataClient } from '@my-products/products-data-client';
const port = process.env.PORT ? Number(process.env.PORT) : 3000;
const app = express();
const productsDataClient = createProductsDataClient();
app.get('/products', async (_req, res) => {
const products = await productsDataClient.getProducts();
res.send(products);
app.get('/', (req, res) => {
res.send({ message: 'Hello API' });
});
app.get('/products/:id', async (req, res) => {
const id = req.params.id;
const product = await productsDataClient.getProductById(id);
if (product == null) {
res.status(404).send();
return;
}
res.send(product);
app.post('/auth', (req, res) => {
res.send({ success: true, name: 'Cheddar' });
});
const port = process.env.port || 3333;
const server = app.listen(port, () => {
console.log(`Listening at http://localhost:${port}`);
app.listen(port, () => {
console.log(`[ ready ] http://localhost:${port}`);
});
```
### `e2e`
Update the e2e tests to check the new `/auth` endpoint.
```javascript {% fileName="e2e/src/server/server.spec.ts" %}
import axios from 'axios';
describe('GET /', () => {
it('should return a message', async () => {
const res = await axios.get(`/`);
expect(res.status).toBe(200);
expect(res.data).toEqual({ message: 'Hello API' });
});
});
describe('POST /auth', () => {
it('should return a status and a name', async () => {
const res = await axios.post(`/auth`, {});
expect(res.status).toBe(200);
expect(res.data).toEqual({ success: true, name: 'Cheddar' });
});
});
server.on('error', console.error);
```
Now run `npx nx graph` again:
{% side-by-side %}
![Matching Graph](/shared/node-tutorial/matching-graph.png)
{% graph height="200px" type="project" jsonFile="shared/node-tutorial/final-project-graph.json" %}
{% /graph %}
![Our Workspace Requirements](/shared/node-tutorial/requirements-diagram.svg)
{% /side-by-side %}
The graph now shows the dependency between `products-api` and `auth`.
Your graph now matches the original design.
The Project Graph is more than just a visualization - Nx provides tooling to optimize your task-running and even automate your CI based on this graph. This will be covered in more detail in: [4: Workspace Optimization](/node-tutorial/4-workspace-optimization).
The project graph is more than just a visualization - Nx provides tooling to optimize your task-running and even automate your CI based on this graph. This will be covered in more detail in: [4: Task Pipelines](/node-tutorial/4-task-pipelines).
## What's Next

View File

@ -1,4 +1,9 @@
# Node Tutorial - Part 3: Task-Running
---
title: 'Node Standalone Tutorial - Part 3: Task-Running'
description: In this tutorial you'll create a backend-focused workspace with Nx.
---
# Node Standalone Tutorial - Part 3: Task-Running
Common tasks include:
@ -11,38 +16,45 @@ When you ran your generators in Part 1, you already set up these more common tas
## Defining Targets
Here's the `project.json` file for your `products-data-client` project:
Here's the `project.json` file for the `auth` project:
```json {% fileName="libs/products-data-client/project.json" %}
```json {% fileName="/auth/project.json" %}
{
"name": "products-data-client",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/products-data-client/src",
"name": "auth",
"$schema": "../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "auth/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nrwl/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/products-data-client",
"main": "libs/products-data-client/src/index.ts",
"tsConfig": "libs/products-data-client/tsconfig.lib.json",
"assets": ["libs/products-data-client/*.md"]
"outputPath": "dist/./auth",
"tsConfig": "auth/tsconfig.lib.json",
"packageJson": "auth/package.json",
"main": "auth/src/index.ts",
"assets": ["auth/*.md"]
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/products-data-client/**/*.ts"]
"lintFilePatterns": ["auth/**/*.ts"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/libs/products-data-client"],
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/products-data-client/jest.config.ts",
"jestConfig": "auth/jest.config.ts",
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
}
},
@ -50,96 +62,93 @@ Here's the `project.json` file for your `products-data-client` project:
}
```
You can see that three targets are defined here: `build`, `lint` and `test`.
You can see that three targets are defined here: `build`, `test` and `lint`.
The properties inside each of these these targets is defined as follows:
The properties of these targets are defined as follows:
- `executor` - which Nx executor to run. The syntax here is: `<plugin name>:<executor name>`
- `outputs` - this is an array of files that would be created by running this target. (This informs Nx on what to save for it's caching mechanisms you'll learn about in [4 - Workspace Optimizations](/node-tutorial/4-workspace-optimization)).
- `outputs` - this is an array of files that would be created by running this target. (This informs Nx on what to save for it's caching mechanisms you'll learn about in [4 - Task Pipelines](/node-tutorial/4-task-pipelines)).
- `options` - this is an object defining which executor options to use for the given target. Every Nx executor allows for options as a way to parameterize it's functionality.
## Running Tasks
![Syntax for Running Tasks in Nx](/shared/node-tutorial/run-target-syntax.svg)
Run the `build` target for your `products-data-client` project:
Run the `build` target for your `auth` project:
```{% command="npx nx build products-data-client" path="~/my-products" %}
```{% command="npx nx build auth" path="~/products-api" %}
> nx run products-data-client:build
> nx run auth:build
Compiling TypeScript files for project "products-data-client"...
Done compiling TypeScript files for project "products-data-client".
Compiling TypeScript files for project "auth"...
Done compiling TypeScript files for project "auth".
———————————————————————————————————————————————————————————————————————————————————————————————
———————————————————————————————————————————————————————————————————————————————————————————————————————————————————
> NX Successfully ran target build for project products-data-client (780ms)
> NX Successfully ran target build for project auth (1s)
```
You can now find your built `products-data-client` distributable in your `dist/libs/products-data-client` directory, as specified in the `outputPath` property of the `build` target options in your `project.json` file.
You can now find your built `auth` distributable in your `dist/auth/` directory, as specified in the `outputPath` property of the `build` target options in your `project.json` file.
Next, run a lint check on `products-data-client`:
Next, run a lint check on `auth`:
```{% command="npx nx lint products-data-client" path="~/my-products" %}
```{% command="npx nx lint auth" path="~/products-api" %}
> nx run products-data-client:lint
> nx run auth:lint
Linting "products-data-client"...
Linting "auth"...
All files pass linting.
———————————————————————————————————————————————————————————————————————————————————————————————
> NX Successfully ran target lint for project products-data-client (777ms)
> NX Successfully ran target lint for project products-api (777ms)
```
Next, add some tests to your data client in the `products-data-client.spec.ts` file:
## Run e2e Tests
```typescript {% fileName="libs/products-data-client/src/lib/products-data-client.spec.ts" %}
import {
createProductsDataClient,
exampleProducts,
} from './products-data-client';
To run the e2e tests, you first need to serve the root `products-api` project:
describe('productsDataClient', () => {
it('should get all example products', async () => {
const productsDataClient = createProductsDataClient();
const products = await productsDataClient.getProducts();
expect(products).toEqual(Object.values(exampleProducts));
});
```{% command="npx nx serve products-api" path="~/products-api" %}
> nx run products-api:serve
it('should get example product by id', async () => {
const productsDataClient = createProductsDataClient();
const product = await productsDataClient.getProductById('1');
expect(product).toEqual(exampleProducts['1']);
});
});
Debugger listening on ws://localhost:9229/5ee3e454-1e38-4d9b-a5de-64a4cb1e21b9
Debugger listening on ws://localhost:9229/5ee3e454-1e38-4d9b-a5de-64a4cb1e21b9
For help, see: https://nodejs.org/en/docs/inspector
[ ready ] http://localhost:3000
[ watch ] build succeeded, watching for changes...
```
And then run your `test` target:
Then you can run the e2e tests from the `e2e` project in a separate terminal:
```{% command="npx nx test products-data-client" path="~/my-products" %}
```{% command="npx nx e2e e2e" path="~/products-api" %}
> nx run e2e:e2e
> nx run products-data-client:test
Determining test suites to run...
Setting up...
PASS products-data-client libs/products-data-client/src/lib/products-data-client.spec.ts
productsDataClient
✓ should get all example products (1 ms)
✓ should get example product by id
PASS e2e e2e/src/server/server.spec.ts
GET /
✓ should return a message (39 ms)
POST /auth
✓ should return a status and a name (19 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 0.843 s
Time: 0.263 s, estimated 1 s
Ran all test suites.
———————————————————————————————————————————————————————————————————————————————————————————————
Tearing down...
> NX Successfully ran target test for project products-data-client (2s)
———————————————————————————————————————————————————————————————————————————————————————————————————————————————————
> NX Successfully ran target e2e for project e2e (2s)
```
## What's Next
- Continue to [4: Workspace Optimization](/node-tutorial/4-workspace-optimization)
- Continue to [4: Task Pipelines](/node-tutorial/4-task-pipelines)

View File

@ -0,0 +1,240 @@
---
title: 'Node Standalone Tutorial - Part 4: Task Pipelines'
description: In this tutorial you'll create a backend-focused workspace with Nx.
---
# Node Standalone Tutorial - Part 4: Task Pipelines
## Running Dependent Tasks
Let's build the `products-api` application:
```{% command="npx nx build products-api" path="~/products-api" %}
✔ 1/1 dependent project tasks succeeded [0 read from cache]
Hint: you can run the command with --verbose to see the full dependent project outputs
—————————————————————————————————————————————————————————————————————————————————————————————————————————————————
> nx run products-api:build:production
✔ Browser application bundle generation complete.
✔ Copying assets complete.
✔ Index html generation complete.
Initial Chunk Files | Names | Raw Size | Estimated Transfer Size
main.dc68f58360ec52f7.js | main | 203.69 kB | 55.81 kB
polyfills.19459ef8805e51da.js | polyfills | 33.04 kB | 10.64 kB
runtime.639feb9584ec9047.js | runtime | 2.62 kB | 1.23 kB
styles.ef46db3751d8e999.css | styles | 0 bytes | -
| Initial Total | 239.35 kB | 67.68 kB
Lazy Chunk Files | Names | Raw Size | Estimated Transfer Size
967.25ab9a0a8950995f.js | store-cart | 719 bytes | 395 bytes
Build at: 2022-11-30T16:44:43.171Z - Hash: 9850ece7cc7c6b7c - Time: 6527ms
—————————————————————————————————————————————————————————————————————————————————————————————————————————————————
> NX Successfully ran target build for project products-api and 1 task(s) they depend on (9s)
```
Notice this line:
```shell
✔ 1/1 dependent project tasks succeeded [0 read from cache]
```
When you run a task, Nx will run all the task's dependencies before running the task you specified. This ensures all the needed artifacts are in place before the task is run.
## Configuring Task Pipelines
Nx can infer how projects depend on each other by examining the source code, but Nx doesn't know which tasks depend on each other.
In the `nx.json` file you can see the default set up:
```json
{
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
}
}
}
```
The `"dependsOn": ["^build"]` line says that every `build` task depends on the `build` tasks for its project dependencies. You can override the `dependsOn` setting for individual projects in the `project.json` files.
{% card title="More On The Task Pipeline Configuration" description="See the Task Pipeline Configuration Guide for more details on how to configure your Task Graph." url="/concepts/task-pipeline-configuration" /%}
## Skip Repeated Tasks
Why does Nx always run the dependent tasks? Doesn't that waste time repeating the same work?
It would, if Nx didn't have a robust caching mechanism to take care of that problem for you. Let's build the `products-api` app again.
```{% command="npx nx build products-api" path="~/products-api" %}
✔ 1/1 dependent project tasks succeeded [1 read from cache]
Hint: you can run the command with --verbose to see the full dependent project outputs
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
> nx run store:build:production [existing outputs match the cache, left as is]
Initial Chunk Files | Names | Raw Size | Estimated Transfer Size
main.dc68f58360ec52f7.js | main | 203.69 kB | 55.81 kB
polyfills.19459ef8805e51da.js | polyfills | 33.04 kB | 10.64 kB
runtime.639feb9584ec9047.js | runtime | 2.62 kB | 1.23 kB
styles.ef46db3751d8e999.css | styles | 0 bytes | -
| Initial Total | 239.35 kB | 67.68 kB
Lazy Chunk Files | Names | Raw Size | Estimated Transfer Size
967.25ab9a0a8950995f.js | store-cart | 719 bytes | 395 bytes
Build at: 2022-11-30T16:44:43.171Z - Hash: 9850ece7cc7c6b7c - Time: 6527ms
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
> NX Successfully ran target build for project store and 1 task(s) they depend on (13ms)
Nx read the output from the cache instead of running the command for 2 out of 2 tasks.
```
This time the build only took 13 ms. Also, if you delete the `dist` folder and run the command again, the build output will be recreated.
{% card title="More Task Caching Details" description="See the documentation for more information on caching." url="/core-features/cache-task-results" /%}
## Cache Inputs and Outputs
How does Nx know when to replace a cached task result? And how does Nx know what should be cached?
Nx determines if a project has been modified by looking at the task's defined `inputs`. And then when the task is completed, it caches the terminal output and all the defined file `outputs`.
### Inputs
Inputs for your task caching includes by default any environment details and all the source code of the projects and dependencies affecting your project.
When you run a task, Nx uses the inputs for your task to create a hash that is used as an index for the task results. If the task has already been run with the same inputs, Nx replays the results stored in the cache.
If this index does not exist, Nx runs the command and if the command succeeds, it stores the result in the cache.
{% card title="More On Customizing Inputs" description="See the Customizing Inputs Guide for more details on how to set inputs for your tasks." url="/concepts/task-pipeline-configuration" /%}
### Outputs
Outputs of the cache include the terminal output created by the task, as well as any files created by the task - for example: the artifact created by running a `build` task.
Here are the outputs defined for the `auth` project:
```json {% fileName="auth/project.json" %}
{
"name": "shared-ui",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "library",
"sourceRoot": "shared/ui/src",
"prefix": "store",
"targets": {
"build": {
"executor": "@nrwl/angular:ng-packagr-lite",
"outputs": ["{workspaceRoot}/dist/{projectRoot}"],
"options": {
"project": "shared/ui/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "shared/ui/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "shared/ui/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "shared/ui/jest.config.ts",
"passWithNoTests": true
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"options": {
"lintFilePatterns": ["shared/ui/**/*.ts", "shared/ui/**/*.html"]
}
}
},
"tags": []
}
```
Outputs are stored in the cache so that terminal output can be replayed, and any created files can be pulled from your cache, and placed where they were created the original time the task was run.
## Testing Affected Projects
Another way that Nx saves you from unnecessary work is the `affected` command. `affected` is a mechanism that relies on your git metadata to determine the projects in your workspace that were affected by a given commit.
Run the command:
```shell
git add . && git commit -m "commiting to test affected"
```
Then make a change to an endpoint of your `producst-api` project:
```ts {% fileName="src/main.ts" %}
import express from 'express';
import { doAuth } from '@products-api/auth';
const port = process.env.PORT ? Number(process.env.PORT) : 3000;
const app = express();
app.get('/', (req, res) => {
res.send({ message: 'Hello modified API' });
});
app.post('/auth', (req, res) => {
return doAuth();
});
app.listen(port, () => {
console.log(`[ ready ] http://localhost:${port}`);
});
```
You can visualize how our workspace is affected by this change using the command:
```shell
npx nx affected:graph
```
{% graph height="450px" jsonFile="shared/node-tutorial/affected-project-graph.json" %}
{% /graph %}
The change made to the `products-api` project is also affecting the `e2e` project. This can be leveraged to run tasks only on the projects that were affected by this commit.
To run the `lint` targets only for affected projects, run the command:
```shell
npx nx affected --target=lint
```
This can be particularly helpful in CI pipelines for larger repos, where most commits only affect a small subset of the entire workspace.
{% card title="Affected Documentation" description="Checkout Affected documentation for more details" url="/nx/affected" /%}
## What's Next
- Continue to [5: Docker Target](/node-tutorial/5-docker-target)

View File

@ -1,213 +0,0 @@
# Node Tutorial - Part 4: Workspace Optimization
## Testing Affected Projects
`affected` is a mechanism that relies on your git metadata to determine the projects in your workspace that were affected by a given commit.
Run the command:
```shell
git add . && git commit -m "commiting to test affected"
```
Then make a change to your example products in your `products-data-client` project:
```typescript {% fileName="libs/products-data-client/src/lib/products-data-client.ts" %}
export const exampleProducts: Record<string, Product> = {
'1': { id: '1', name: 'Product 1', price: 100 },
'2': { id: '2', name: 'Product 2', price: 400 }, // changed here
};
```
Run the following command to visualize how our workspace is affected by this change:
```shell
npx nx affected:graph
```
![Project Graph with All Affected](/shared/node-tutorial/project-graph-with-all-affected.png)
The change made to the `products-data-client` project is also affecting the `products-api` and `products-cli` projects, since both of those projects import from the `products-data-client` project.
Next, stash your changes since the commit:
```shell
git stash
```
And then make a minor adjustment to the `products-cli` project:
```typescript {% fileName="apps/products-cli/src/main.ts" %}
import { createProductsDataClient } from '@my-products/products-data-client';
main();
async function main() {
const productsDataClient = createProductsDataClient();
const id = getProvidedId();
if (id != null) {
const product = await productsDataClient.getProductById(id);
if (!product) {
throw new Error(`Product with id ${id} not found`);
}
console.log(JSON.stringify(product, null, 2));
} else {
const products = await productsDataClient.getProducts();
console.log(JSON.stringify(products, null, 2));
}
}
function getProvidedId() {
return process.argv[2];
}
```
Now run the command to visualize the affected graph again:
```shell
npx nx affected:graph
```
![Project Graph with One Affected](/shared/node-tutorial/project-graph-with-one-affected.png)
This can be leveraged to run tasks only on the projects that were affected by this commit.
To run the `test` targets only for affected projects, run the command:
```shell
npx nx affected --target=test
```
This can be particularly helpful in CI pipelines for larger repos, where most commits only affect a small subset of the entire workspace.
{% card title="Affected Documentation" description="Checkout Affected documentation for more details" url="/nx/affected" /%}
## Task Caching
`affected` allows you to "skip" tasks that couldn't possibly be affected by your changes. Task Caching allows you to "replay" tasks that have already been run.
Task Caching is informed by "inputs" and "outputs":
### Inputs
Inputs for your task caching includes by default any environment details and all the source code of the projects and dependencies affecting your project.
When running a task, Nx will determine all the inputs for your task and create a hash that will be used to index your cache. If you've already run this task with the same inputs, your cache will already be populated at this index, and Nx will replay the results stored in the cache.
If this index does not exist, Nx will run the command and if the command succeeds, it will store the result in the cache.
### Outputs
Outputs of the cache include the terminal output created by the task, as well as any files created by the task - for example: the artifact created by running a `build` task.
Outputs are defined for every target in your workspace:
```json {% fileName="libs/products-data-client/project.json" %}
{
"name": "products-data-client",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/products-data-client/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nrwl/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/products-data-client",
"main": "libs/products-data-client/src/index.ts",
"tsConfig": "libs/products-data-client/tsconfig.lib.json",
"assets": ["libs/products-data-client/*.md"]
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/products-data-client/**/*.ts"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/libs/products-data-client"],
"options": {
"jestConfig": "libs/products-data-client/jest.config.ts",
"passWithNoTests": true
}
}
},
"tags": []
}
```
Outputs are stored in the cache so that terminal output can be replayed, and any created files can be pulled from your cache and placed where they were created the original time the task was run.
### Example
To see caching in action, first clear your `dist` directory:
```shell
rm -rf dist/"
```
And run the command `npx nx build products-data-client`. (Recall that you had already run this target in [3- Task Running](/node-tutorial/3-task-running))
```{% command="npx nx build products-data-client" path="~/my-products" %}
> nx run products-data-client:build [local cache]
Compiling TypeScript files for project "products-data-client"...
Done compiling TypeScript files for project "products-data-client".
———————————————————————————————————————————————————————————————————————————————————————————————
> NX Successfully ran target build for project products-data-client (32ms)
Nx read the output from the cache instead of running the command for 1 out of 1 tasks.
```
Notice that `[local cache]` is mentioned in the terminal output, and that this time the command only took 32ms to run.
Also notice that the result of your build has been added back to the `dist/libs/products-data-client` directory.
{% card title="More Task Caching Details" description="See the documentation for more information on caching." url="/core-features/cache-task-results" /%}
## Configuring Task Pipelines
Next, run the command `npx nx build products-cli`:
```{% command="npx nx build products-cli" path="~/my-products" %}
✔ 1/1 dependent project tasks succeeded [1 read from cache]
Hint: you can run the command with --verbose to see the full dependent project outputs
———————————————————————————————————————————————————————————————————————————————————————————————
> nx run products-cli:build
chunk (runtime: main) main.js (main) 1.71 KiB [entry] [rendered]
webpack compiled successfully (bafa37be9890ecb2)
———————————————————————————————————————————————————————————————————————————————————————————————
> NX Successfully ran target build for project products-cli and 1 task(s) they depend on (2s)
Nx read the output from the cache instead of running the command for 1 out of 2 tasks.
```
Notice the line here:
```text
✔ 1/1 dependent project tasks succeeded [1 read from cache]
```
This is because your `products-cli` project depends on your `products-data-client` project, which also has a `build` target. By default Nx is configured to run (or read from cache) the `build` target for any dependencies that have a `build` target, before running the `build` on the original project.
This feature allows the Nx graph to dynamically maintain task dependencies, rather than having to manually maintain those task dependencies as your workspace continues to grow.
{% card title="More On The Task Pipeline Configuration" description="See the Task Pipeline Configuration Guide for more details on how to configure your Task Graph." url="/concepts/task-pipeline-configuration" /%}
## What's Next
- Continue to [5: Summary](/node-tutorial/5-summary)

View File

@ -0,0 +1,132 @@
---
title: 'Node Standalone Tutorial - Part 5: Docker Target'
description: In this tutorial you'll create a backend-focused workspace with Nx.
---
# Node Standalone Tutorial - Part 5: Docker Target
## Generate a Micro-service with a Docker File
Let's create a new `orders-api` application with a docker file included:
```{% command="npx nx g @nrwl/node:app orders-api --docker" path="~/products-api" %}
> NX Generating @nrwl/node:application
✔ Which framework do you want to use? · express
CREATE orders-api/src/assets/.gitkeep
CREATE orders-api/src/main.ts
CREATE orders-api/tsconfig.app.json
CREATE orders-api/tsconfig.json
CREATE orders-api/project.json
CREATE orders-api/.eslintrc.json
CREATE orders-api/jest.config.ts
CREATE orders-api/tsconfig.spec.json
CREATE orders-api-e2e/project.json
CREATE orders-api-e2e/jest.config.ts
CREATE orders-api-e2e/src/orders-api/orders-api.spec.ts
CREATE orders-api-e2e/src/support/global-setup.ts
CREATE orders-api-e2e/src/support/global-teardown.ts
CREATE orders-api-e2e/src/support/test-setup.ts
CREATE orders-api-e2e/tsconfig.json
CREATE orders-api-e2e/tsconfig.spec.json
CREATE orders-api-e2e/.eslintrc.json
CREATE orders-api/Dockerfile
```
This generator has created a basic Dockerfile for the new microservice.
```{% filename="orders-api/Dockerfile" %}
# This file is generated by Nx.
#
# Build the docker image with `npx nx docker-build orders-api`.
# Tip: Modify "docker-build" options in project.json to change docker build args.
#
# Run the container with `docker run -p 3000:3000 -t orders-api`.
FROM docker.io/node:lts-alpine
ENV HOST=0.0.0.0
ENV PORT=3000
WORKDIR /app
RUN addgroup --system orders-api && \
adduser --system -G orders-api orders-api
COPY dist/orders-api orders-api
RUN chown -R orders-api:orders-api .
CMD [ "node", "orders-api" ]
```
You also have an Nx target to build your Docker image.
```{% command="npx nx docker-build orders-api" path="~/products-api" %}
> nx run orders-api:build
> nx run orders-api:docker-build
#1 [internal] load build definition from Dockerfile
#1 sha256:4c99d8269ea9b513bd4dc776dba71aa66d5829ea8e590b8aeb803a2067f59cd7
#1 transferring dockerfile: 37B done
#1 DONE 0.0s
#2 [internal] load .dockerignore
#2 sha256:e71d5f0270d20785d8ae5f235f0abefd0806a3001ce09bbd5fd6f34cb8b1ca81
#2 transferring context: 2B done
#2 DONE 0.0s
#3 [internal] load metadata for docker.io/library/node:lts-alpine
#3 sha256:e161ecf2e6f1cf45a4881933800e629a1213e55a987b539a70bb5826846509fd
#3 DONE 0.2s
#8 [1/5] FROM docker.io/library/node:lts-alpine@sha256:fda98168118e5a8f4269efca4101ee51dd5c75c0fe56d8eb6fad80455c2f5827
#8 sha256:00cf67cfc27afade2e1236f1196ec1d784e6c26792e57b580683350c09199e48
#8 DONE 0.0s
#9 [internal] load build context
#9 sha256:9103d257e071bd890d889058da8bff61c78e8bb01b7ed24337b78f75e8830218
#9 transferring context: 1.70MB 0.1s done
#9 DONE 0.1s
#4 [2/5] WORKDIR /app
#4 sha256:17db46c2fd7998a5902ae01d80def26aa254289bbab2c6fc5aacc55252ac84b0
#4 CACHED
#5 [3/5] RUN addgroup --system orders-api && adduser --system -G orders-api orders-api
#5 sha256:b44659fc59b4a2b2d6e4ab5e87ab46bcef11185d06154f4b1ec6d7a1753379f2
#5 CACHED
#6 [4/5] COPY dist/orders-api orders-api
#6 sha256:b903d3e7efcc38acf17f87fc8de482eb267fe0269156e8862cf149cdee04c2df
#6 CACHED
#7 [5/5] RUN chown -R orders-api:orders-api .
#7 sha256:250b9a198f6002246bab3725d205af49d6327990451320d8642f56b9882f4f0a
#7 CACHED
#10 exporting to image
#10 sha256:e8c613e07b0b7ff33893b694f7759a10d42e180f2b4dc349fb57dc6b71dcab00
#10 exporting layers done
#10 writing image sha256:e3543d878821de18de83201719f1f333bb7072a50e42216ff5c253db9081ce71 done
#10 naming to docker.io/library/orders-api done
#10 DONE 0.0s
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
————————————————————————————————————————————————————————————————————————————————————————————————————————————
> NX Successfully ran target docker-build for project orders-api and 1 task it depends on (2s)
View logs and investigate cache misses at https://nx.app/runs/NrNdfzx12g
```
The `docker-build` command is defined as a target in the `orders-api` `project.json` file. If you need to make any modifications to the command, you can make them there. Note that this target is set up so that the `build` target will always be run first.
```json {% filename="/orders-api/project.json" %}
{
"targets": {
"docker-build": {
"dependsOn": ["build"],
"executor": "nx:run-commands",
"options": {
"commands": ["docker build -f orders-api/Dockerfile . -t orders-api"]
}
}
}
}
```
## What's Next
- Continue to [6: Summary](/node-tutorial/6-summary)

View File

@ -1,4 +1,9 @@
# Node Tutorial - Part 5: Summary
---
title: 'Node Standalone Tutorial - Part 6: Summary'
description: In this tutorial you'll create a backend-focused workspace with Nx.
---
# Node Standalone Tutorial - Part 6: Summary
In this tutorial you:
@ -6,6 +11,7 @@ In this tutorial you:
- Learned how Nx determines a graph of your workspace
- Learned how to configure and run tasks in your workspace
- Learned how Nx's built-in optimizations work, and how to apply those to your own workspace
- Learned how Nx can create a simple Dockerfile for you
## Learn More

View File

@ -0,0 +1,346 @@
{
"hash": "69d706f56987672f5650b3b4007076d431d26232d3c1a09f4fe5af46daf8301b",
"projects": [
{
"name": "auth",
"type": "lib",
"data": {
"tags": [],
"root": "auth",
"files": [
{
"file": "auth/.babelrc",
"hash": "cf7ddd99c615a064ac18eb3109eee4f394ab1faf"
},
{
"file": "auth/.eslintrc.json",
"hash": "2ecc0fb3287e0c1e27691b519c3a058a3248ab44"
},
{
"file": "auth/jest.config.ts",
"hash": "9a572192bd6837978590302b51b89644e075ed71"
},
{
"file": "auth/package.json",
"hash": "802cad57c0ab5b838834efc500b28f342cc33fd0"
},
{
"file": "auth/project.json",
"hash": "c93aa4cc32972ac2dcfeacbffee3b73d8a529383"
},
{
"file": "auth/README.md",
"hash": "e895849f0d4f455473302f8dd795da5f2ff2dfab"
},
{
"file": "auth/src/index.ts",
"hash": "2953bfc1d2b036689276d7f95bd6653b2a487993"
},
{
"file": "auth/src/lib/auth.spec.ts",
"hash": "6b29b9cb382148b049b80a7a1ba6ec98193f90a6"
},
{
"file": "auth/src/lib/auth.ts",
"hash": "87694589f307699e63ba8041cc774165851f359e"
},
{
"file": "auth/tsconfig.json",
"hash": "f1a0ff5ec6a6f796fa26b4a8c82e74632b92a031"
},
{
"file": "auth/tsconfig.lib.json",
"hash": "53c4ac8d833637e0dca7b03920b04cae26d9c5bb"
},
{
"file": "auth/tsconfig.spec.json",
"hash": "fdb45891ed0c135fa82a503b4f115e31fe36bf91"
}
],
"targets": {
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"],
"executor": "@nrwl/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/./auth",
"tsConfig": "auth/tsconfig.lib.json",
"packageJson": "auth/package.json",
"main": "auth/src/index.ts",
"assets": ["auth/*.md"]
},
"configurations": {}
},
"lint": {
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": { "lintFilePatterns": ["auth/**/*.ts"] },
"configurations": {}
},
"test": {
"inputs": [
"default",
"^production",
"{workspaceRoot}/jest.preset.js"
],
"executor": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "auth/jest.config.ts",
"passWithNoTests": true
},
"configurations": {}
}
}
}
},
{
"name": "e2e",
"type": "lib",
"data": {
"tags": [],
"root": "e2e",
"files": [
{
"file": "e2e/.eslintrc.json",
"hash": "c9038d6e5ca20e9fa5cd35c9e3a6c99a5a7feb8b"
},
{
"file": "e2e/jest.config.ts",
"hash": "f9477b3d4a1ff3d9604277e734c2225999a83b0e"
},
{
"file": "e2e/project.json",
"hash": "699de53c0ad89a54bc901260b28c34fc9e4f4132"
},
{
"file": "e2e/src/server/server.spec.ts",
"hash": "61ea782c168679416bc726b0d8c91b34f6503127",
"deps": ["npm:axios"]
},
{
"file": "e2e/src/support/global-setup.ts",
"hash": "c1f514446d8c15b5f2ef8eb1f6bf232a9b3b6cae"
},
{
"file": "e2e/src/support/global-teardown.ts",
"hash": "32ea345c47f1533607149a609aac15010d8c68e9"
},
{
"file": "e2e/src/support/test-setup.ts",
"hash": "07f2870393f1e3001ccdcdba4d7857aa5a36f03a",
"deps": ["npm:axios"]
},
{
"file": "e2e/tsconfig.json",
"hash": "9f8b535226f17c8a0597e41249116f0de7b2cf08"
},
{
"file": "e2e/tsconfig.spec.json",
"hash": "2ac21b50bb8d3c742fe5058d8ec0ae43e668d2dd"
}
],
"targets": {
"e2e": {
"executor": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{e2eProjectRoot}"],
"options": {
"jestConfig": "e2e/jest.config.ts",
"passWithNoTests": true
}
},
"lint": {
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": { "lintFilePatterns": ["e2e/**/*.{js,ts}"] },
"configurations": {}
}
}
}
},
{
"name": "products-api",
"type": "app",
"data": {
"tags": [],
"root": ".",
"files": [
{
"file": ".eslintrc.base.json",
"hash": "7c52faa3cc08c835904b5313d738d8d9781e6997"
},
{
"file": ".eslintrc.json",
"hash": "53d540eebe0a66d946cc7e8e963fc94033742beb"
},
{
"file": ".gitignore",
"hash": "51b9af5269c1df38f3252250d5b46665822374cc"
},
{
"file": ".prettierignore",
"hash": "d0b804da2a462044bb1c63364440b2c2164e86ad"
},
{
"file": ".prettierrc",
"hash": "544138be45652abc7bc3873341deacd3f4f90c61"
},
{
"file": ".vscode/extensions.json",
"hash": "64553b175b1109d40227087360bba86cbaa738fb"
},
{
"file": "jest.config.app.ts",
"hash": "2ff66d3d84af8b60b6b296eee248e086f99bd542"
},
{
"file": "jest.config.ts",
"hash": "2a738f7746dcbefabfa363e4e0a9ed10024ca942",
"deps": ["npm:@nrwl/jest"]
},
{
"file": "jest.preset.js",
"hash": "e6c8ebea00cfb845c55c897266534e9386904de0",
"deps": ["npm:@nrwl/jest"]
},
{
"file": "nx.json",
"hash": "fe0b6e421b0c7e03b5c3cc571ea256069ad054b1"
},
{
"file": "package-lock.json",
"hash": "24bbcf950e8435a6f83295321c5d8c3c67df9582"
},
{
"file": "package.json",
"hash": "4b8184bd2510cf4ed177b63d225f6ada51a3316e",
"deps": [
"npm:axios",
"npm:tslib",
"npm:@nrwl/esbuild",
"npm:@nrwl/eslint-plugin-nx",
"npm:@nrwl/jest",
"npm:@nrwl/js",
"npm:@nrwl/linter",
"npm:@nrwl/node",
"npm:@nrwl/nx-cloud",
"npm:@nrwl/workspace",
"npm:@types/express",
"npm:@types/jest",
"npm:@types/node",
"npm:@typescript-eslint/eslint-plugin",
"npm:@typescript-eslint/parser",
"npm:esbuild",
"npm:eslint",
"npm:eslint-config-prettier",
"npm:express",
"npm:jest",
"npm:jest-environment-jsdom",
"npm:nx",
"npm:prettier",
"npm:ts-jest",
"npm:ts-node",
"npm:typescript"
]
},
{
"file": "project.json",
"hash": "e848e5ca90d075791876a4f54f57de789e212a4b"
},
{
"file": "README.md",
"hash": "11b2d2170208c891b85335735fd609a57fcf9af8"
},
{
"file": "src/app/.gitkeep",
"hash": "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
},
{
"file": "src/assets/.gitkeep",
"hash": "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
},
{
"file": "src/main.ts",
"hash": "7be55ba18caf4cea1a68a8f0d8a396f1674d8244",
"deps": ["npm:express", "auth"]
},
{
"file": "tsconfig.app.json",
"hash": "c300cd67e29c6755eabb25a7529dd2e9de82177d"
},
{
"file": "tsconfig.json",
"hash": "e8e698fbde0725b5d2c6769947a458797d4495b3"
},
{
"file": "tsconfig.spec.json",
"hash": "2fa7e96d8d7f2598d83b187fbe3eaebd654f667b"
}
],
"targets": {
"start": {
"executor": "nx:run-script",
"options": { "script": "start" }
},
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"],
"executor": "@nrwl/esbuild:esbuild",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist",
"format": ["cjs"],
"main": "src/main.ts",
"tsConfig": "tsconfig.app.json",
"assets": ["src/assets"]
},
"configurations": {}
},
"test": {
"inputs": [
"default",
"^production",
"{workspaceRoot}/jest.preset.js"
],
"executor": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectName}"],
"options": {
"jestConfig": "jest.config.app.ts",
"passWithNoTests": true
},
"configurations": {}
},
"serve": {
"executor": "@nrwl/js:node",
"options": { "buildTarget": "products-api:build" },
"configurations": {
"production": { "buildTarget": "products-api:build:production" }
}
},
"lint": {
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": { "lintFilePatterns": ["./**/*.ts"] },
"configurations": {}
}
}
}
}
],
"dependencies": {
"auth": [],
"e2e": [{ "source": "e2e", "target": "products-api", "type": "implicit" }],
"products-api": [
{ "source": "products-api", "target": "auth", "type": "static" }
]
},
"workspaceLayout": { "appsDir": "apps", "libsDir": "libs" },
"affectedProjectIds": ["products-api", "e2e"],
"focus": null,
"groupByFolder": false,
"exclude": ""
}

View File

@ -0,0 +1,327 @@
{
"hash": "1828ed9ae8ba437cd57a41182e400677f0ff28bda80f3b9f1541c0fd7cd81ddf",
"projects": [
{
"name": "auth",
"type": "lib",
"data": {
"tags": [],
"root": "auth",
"files": [
{
"file": "auth/.babelrc",
"hash": "cf7ddd99c615a064ac18eb3109eee4f394ab1faf"
},
{
"file": "auth/.eslintrc.json",
"hash": "2ecc0fb3287e0c1e27691b519c3a058a3248ab44"
},
{
"file": "auth/jest.config.ts",
"hash": "9a572192bd6837978590302b51b89644e075ed71"
},
{
"file": "auth/project.json",
"hash": "947e2a7cfb1d11fc0edb398bb9cbabf05f3eed21"
},
{
"file": "auth/README.md",
"hash": "e895849f0d4f455473302f8dd795da5f2ff2dfab"
},
{
"file": "auth/src/index.ts",
"hash": "2953bfc1d2b036689276d7f95bd6653b2a487993"
},
{
"file": "auth/src/lib/auth.spec.ts",
"hash": "6b29b9cb382148b049b80a7a1ba6ec98193f90a6"
},
{
"file": "auth/src/lib/auth.ts",
"hash": "87694589f307699e63ba8041cc774165851f359e"
},
{
"file": "auth/tsconfig.json",
"hash": "f1a0ff5ec6a6f796fa26b4a8c82e74632b92a031"
},
{
"file": "auth/tsconfig.lib.json",
"hash": "53c4ac8d833637e0dca7b03920b04cae26d9c5bb"
},
{
"file": "auth/tsconfig.spec.json",
"hash": "fdb45891ed0c135fa82a503b4f115e31fe36bf91"
}
],
"targets": {
"lint": {
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": { "lintFilePatterns": ["auth/**/*.ts"] },
"configurations": {}
},
"test": {
"inputs": [
"default",
"^production",
"{workspaceRoot}/jest.preset.js"
],
"executor": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "auth/jest.config.ts",
"passWithNoTests": true
},
"configurations": {}
}
}
}
},
{
"name": "e2e",
"type": "lib",
"data": {
"tags": [],
"root": "e2e",
"files": [
{
"file": "e2e/.eslintrc.json",
"hash": "c9038d6e5ca20e9fa5cd35c9e3a6c99a5a7feb8b"
},
{
"file": "e2e/jest.config.ts",
"hash": "f9477b3d4a1ff3d9604277e734c2225999a83b0e"
},
{
"file": "e2e/project.json",
"hash": "67123c12436c550477c2788ef4eb5870a3adb3f0"
},
{
"file": "e2e/src/server/server.spec.ts",
"hash": "61ea782c168679416bc726b0d8c91b34f6503127",
"deps": ["npm:axios"]
},
{
"file": "e2e/src/support/global-setup.ts",
"hash": "c1f514446d8c15b5f2ef8eb1f6bf232a9b3b6cae"
},
{
"file": "e2e/src/support/global-teardown.ts",
"hash": "32ea345c47f1533607149a609aac15010d8c68e9"
},
{
"file": "e2e/src/support/test-setup.ts",
"hash": "07f2870393f1e3001ccdcdba4d7857aa5a36f03a",
"deps": ["npm:axios"]
},
{
"file": "e2e/tsconfig.json",
"hash": "9f8b535226f17c8a0597e41249116f0de7b2cf08"
},
{
"file": "e2e/tsconfig.spec.json",
"hash": "2ac21b50bb8d3c742fe5058d8ec0ae43e668d2dd"
}
],
"targets": {
"e2e": {
"executor": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{e2eProjectRoot}"],
"options": {
"jestConfig": "e2e/jest.config.ts",
"passWithNoTests": true
}
},
"lint": {
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": { "lintFilePatterns": ["e2e/**/*.{js,ts}"] },
"configurations": {}
}
}
}
},
{
"name": "products-api",
"type": "app",
"data": {
"tags": [],
"root": ".",
"files": [
{
"file": ".eslintrc.base.json",
"hash": "7c52faa3cc08c835904b5313d738d8d9781e6997"
},
{
"file": ".eslintrc.json",
"hash": "53d540eebe0a66d946cc7e8e963fc94033742beb"
},
{
"file": ".gitignore",
"hash": "51b9af5269c1df38f3252250d5b46665822374cc"
},
{
"file": ".prettierignore",
"hash": "d0b804da2a462044bb1c63364440b2c2164e86ad"
},
{
"file": ".prettierrc",
"hash": "544138be45652abc7bc3873341deacd3f4f90c61"
},
{
"file": ".vscode/extensions.json",
"hash": "64553b175b1109d40227087360bba86cbaa738fb"
},
{
"file": "jest.config.app.ts",
"hash": "33759b382d178de6c65ff4f26b34c192de5573cd"
},
{
"file": "jest.config.ts",
"hash": "2a738f7746dcbefabfa363e4e0a9ed10024ca942",
"deps": ["npm:@nrwl/jest"]
},
{
"file": "jest.preset.js",
"hash": "e6c8ebea00cfb845c55c897266534e9386904de0",
"deps": ["npm:@nrwl/jest"]
},
{
"file": "nx.json",
"hash": "af988c9133568995e00c8392e641ae3d5f5c7f9f"
},
{
"file": "package-lock.json",
"hash": "c673e6ed166924761428c2fa0c6406327870e420"
},
{
"file": "package.json",
"hash": "de7138436eaf8100c6de4cddddd50ccb881ad5df",
"deps": [
"npm:axios",
"npm:tslib",
"npm:@nrwl/esbuild",
"npm:@nrwl/eslint-plugin-nx",
"npm:@nrwl/jest",
"npm:@nrwl/linter",
"npm:@nrwl/node",
"npm:@nrwl/nx-cloud",
"npm:@nrwl/workspace",
"npm:@types/express",
"npm:@types/jest",
"npm:@types/node",
"npm:@typescript-eslint/eslint-plugin",
"npm:@typescript-eslint/parser",
"npm:esbuild",
"npm:eslint",
"npm:eslint-config-prettier",
"npm:express",
"npm:jest",
"npm:jest-environment-jsdom",
"npm:nx",
"npm:prettier",
"npm:ts-jest",
"npm:ts-node",
"npm:typescript"
]
},
{
"file": "project.json",
"hash": "d808a6dfae9220eaecb9804da9f9186e580dacaa"
},
{
"file": "README.md",
"hash": "be383295d307d4db7bb3b773bf72acb4243c226b"
},
{
"file": "src/app/.gitkeep",
"hash": "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
},
{
"file": "src/assets/.gitkeep",
"hash": "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
},
{
"file": "src/main.ts",
"hash": "6cab50d279bc6ce17e8a35112ec854d68e0579c1",
"deps": ["npm:express", "auth"]
},
{
"file": "tsconfig.app.json",
"hash": "c300cd67e29c6755eabb25a7529dd2e9de82177d"
},
{
"file": "tsconfig.json",
"hash": "10390542b24ec4b22c9358c82d0a858f10e9a7ec"
},
{
"file": "tsconfig.spec.json",
"hash": "2fa7e96d8d7f2598d83b187fbe3eaebd654f667b"
}
],
"targets": {
"start": {
"executor": "nx:run-script",
"options": { "script": "start" }
},
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"],
"executor": "@nrwl/esbuild:esbuild",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist",
"format": ["cjs"],
"main": "src/main.ts",
"tsConfig": "tsconfig.app.json",
"assets": ["src/assets"]
},
"configurations": {}
},
"test": {
"inputs": [
"default",
"^production",
"{workspaceRoot}/jest.preset.js"
],
"executor": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectName}"],
"options": {
"jestConfig": "jest.config.app.ts",
"passWithNoTests": true
},
"configurations": {}
},
"serve": {
"executor": "@nrwl/js:node",
"options": { "buildTarget": "products-api:build" },
"configurations": {
"production": { "buildTarget": "products-api:build:production" }
}
},
"lint": {
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": { "lintFilePatterns": ["./**/*.ts"] },
"configurations": {}
}
}
}
}
],
"dependencies": {
"auth": [],
"e2e": [{ "source": "e2e", "target": "products-api", "type": "implicit" }],
"products-api": [
{ "source": "products-api", "target": "auth", "type": "static" }
]
},
"workspaceLayout": { "appsDir": "apps", "libsDir": "libs" },
"affectedProjectIds": [],
"focus": null,
"groupByFolder": false,
"exclude": []
}

View File

@ -0,0 +1,325 @@
{
"hash": "2243ecc815a9b5c726fa59f3c2eb8d15a36c9f467eed41cd231dd9980a737a4d",
"projects": [
{
"name": "auth",
"type": "lib",
"data": {
"tags": [],
"root": "auth",
"files": [
{
"file": "auth/.babelrc",
"hash": "cf7ddd99c615a064ac18eb3109eee4f394ab1faf"
},
{
"file": "auth/.eslintrc.json",
"hash": "2ecc0fb3287e0c1e27691b519c3a058a3248ab44"
},
{
"file": "auth/jest.config.ts",
"hash": "9a572192bd6837978590302b51b89644e075ed71"
},
{
"file": "auth/project.json",
"hash": "947e2a7cfb1d11fc0edb398bb9cbabf05f3eed21"
},
{
"file": "auth/README.md",
"hash": "e895849f0d4f455473302f8dd795da5f2ff2dfab"
},
{
"file": "auth/src/index.ts",
"hash": "2953bfc1d2b036689276d7f95bd6653b2a487993"
},
{
"file": "auth/src/lib/auth.spec.ts",
"hash": "6b29b9cb382148b049b80a7a1ba6ec98193f90a6"
},
{
"file": "auth/src/lib/auth.ts",
"hash": "dcd6088b201a8159a82243b2d04034a7023a0087"
},
{
"file": "auth/tsconfig.json",
"hash": "f1a0ff5ec6a6f796fa26b4a8c82e74632b92a031"
},
{
"file": "auth/tsconfig.lib.json",
"hash": "53c4ac8d833637e0dca7b03920b04cae26d9c5bb"
},
{
"file": "auth/tsconfig.spec.json",
"hash": "fdb45891ed0c135fa82a503b4f115e31fe36bf91"
}
],
"targets": {
"lint": {
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": { "lintFilePatterns": ["auth/**/*.ts"] },
"configurations": {}
},
"test": {
"inputs": [
"default",
"^production",
"{workspaceRoot}/jest.preset.js"
],
"executor": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "auth/jest.config.ts",
"passWithNoTests": true
},
"configurations": {}
}
}
}
},
{
"name": "e2e",
"type": "lib",
"data": {
"tags": [],
"root": "e2e",
"files": [
{
"file": "e2e/.eslintrc.json",
"hash": "c9038d6e5ca20e9fa5cd35c9e3a6c99a5a7feb8b"
},
{
"file": "e2e/jest.config.ts",
"hash": "f9477b3d4a1ff3d9604277e734c2225999a83b0e"
},
{
"file": "e2e/project.json",
"hash": "67123c12436c550477c2788ef4eb5870a3adb3f0"
},
{
"file": "e2e/src/server/server.spec.ts",
"hash": "51717c797954a51eb0495dd069d018268f1921de",
"deps": ["npm:axios"]
},
{
"file": "e2e/src/support/global-setup.ts",
"hash": "c1f514446d8c15b5f2ef8eb1f6bf232a9b3b6cae"
},
{
"file": "e2e/src/support/global-teardown.ts",
"hash": "32ea345c47f1533607149a609aac15010d8c68e9"
},
{
"file": "e2e/src/support/test-setup.ts",
"hash": "07f2870393f1e3001ccdcdba4d7857aa5a36f03a",
"deps": ["npm:axios"]
},
{
"file": "e2e/tsconfig.json",
"hash": "9f8b535226f17c8a0597e41249116f0de7b2cf08"
},
{
"file": "e2e/tsconfig.spec.json",
"hash": "2ac21b50bb8d3c742fe5058d8ec0ae43e668d2dd"
}
],
"targets": {
"e2e": {
"executor": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{e2eProjectRoot}"],
"options": {
"jestConfig": "e2e/jest.config.ts",
"passWithNoTests": true
}
},
"lint": {
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": { "lintFilePatterns": ["e2e/**/*.{js,ts}"] },
"configurations": {}
}
}
}
},
{
"name": "products-api",
"type": "app",
"data": {
"tags": [],
"root": ".",
"files": [
{
"file": ".eslintrc.base.json",
"hash": "7c52faa3cc08c835904b5313d738d8d9781e6997"
},
{
"file": ".eslintrc.json",
"hash": "53d540eebe0a66d946cc7e8e963fc94033742beb"
},
{
"file": ".gitignore",
"hash": "51b9af5269c1df38f3252250d5b46665822374cc"
},
{
"file": ".prettierignore",
"hash": "d0b804da2a462044bb1c63364440b2c2164e86ad"
},
{
"file": ".prettierrc",
"hash": "544138be45652abc7bc3873341deacd3f4f90c61"
},
{
"file": ".vscode/extensions.json",
"hash": "64553b175b1109d40227087360bba86cbaa738fb"
},
{
"file": "jest.config.app.ts",
"hash": "33759b382d178de6c65ff4f26b34c192de5573cd"
},
{
"file": "jest.config.ts",
"hash": "2a738f7746dcbefabfa363e4e0a9ed10024ca942",
"deps": ["npm:@nrwl/jest"]
},
{
"file": "jest.preset.js",
"hash": "e6c8ebea00cfb845c55c897266534e9386904de0",
"deps": ["npm:@nrwl/jest"]
},
{
"file": "nx.json",
"hash": "af988c9133568995e00c8392e641ae3d5f5c7f9f"
},
{
"file": "package-lock.json",
"hash": "c673e6ed166924761428c2fa0c6406327870e420"
},
{
"file": "package.json",
"hash": "de7138436eaf8100c6de4cddddd50ccb881ad5df",
"deps": [
"npm:axios",
"npm:tslib",
"npm:@nrwl/esbuild",
"npm:@nrwl/eslint-plugin-nx",
"npm:@nrwl/jest",
"npm:@nrwl/linter",
"npm:@nrwl/node",
"npm:@nrwl/nx-cloud",
"npm:@nrwl/workspace",
"npm:@types/express",
"npm:@types/jest",
"npm:@types/node",
"npm:@typescript-eslint/eslint-plugin",
"npm:@typescript-eslint/parser",
"npm:esbuild",
"npm:eslint",
"npm:eslint-config-prettier",
"npm:express",
"npm:jest",
"npm:jest-environment-jsdom",
"npm:nx",
"npm:prettier",
"npm:ts-jest",
"npm:ts-node",
"npm:typescript"
]
},
{
"file": "project.json",
"hash": "d808a6dfae9220eaecb9804da9f9186e580dacaa"
},
{
"file": "README.md",
"hash": "be383295d307d4db7bb3b773bf72acb4243c226b"
},
{
"file": "src/app/.gitkeep",
"hash": "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
},
{
"file": "src/assets/.gitkeep",
"hash": "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
},
{
"file": "src/main.ts",
"hash": "664465562bdb3a29608d9eac73f05e633615fa02",
"deps": ["npm:express"]
},
{
"file": "tsconfig.app.json",
"hash": "c300cd67e29c6755eabb25a7529dd2e9de82177d"
},
{
"file": "tsconfig.json",
"hash": "10390542b24ec4b22c9358c82d0a858f10e9a7ec"
},
{
"file": "tsconfig.spec.json",
"hash": "2fa7e96d8d7f2598d83b187fbe3eaebd654f667b"
}
],
"targets": {
"start": {
"executor": "nx:run-script",
"options": { "script": "start" }
},
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"],
"executor": "@nrwl/esbuild:esbuild",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist",
"format": ["cjs"],
"main": "src/main.ts",
"tsConfig": "tsconfig.app.json",
"assets": ["src/assets"]
},
"configurations": {}
},
"test": {
"inputs": [
"default",
"^production",
"{workspaceRoot}/jest.preset.js"
],
"executor": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectName}"],
"options": {
"jestConfig": "jest.config.app.ts",
"passWithNoTests": true
},
"configurations": {}
},
"serve": {
"executor": "@nrwl/js:node",
"options": { "buildTarget": "products-api:build" },
"configurations": {
"production": { "buildTarget": "products-api:build:production" }
}
},
"lint": {
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": { "lintFilePatterns": ["./**/*.ts"] },
"configurations": {}
}
}
}
}
],
"dependencies": {
"auth": [],
"e2e": [{ "source": "e2e", "target": "products-api", "type": "implicit" }],
"products-api": []
},
"workspaceLayout": { "appsDir": "apps", "libsDir": "libs" },
"affectedProjectIds": [],
"focus": null,
"groupByFolder": false,
"exclude": []
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 720 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 679 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 695 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 44 KiB

View File

@ -373,6 +373,7 @@ const oldNodeTutorialPaths = [
'/node-tutorial/05-dep-graph',
'/node-tutorial/06-computation-caching',
'/node-tutorial/07-test-affected-projects',
'/node-tutorial/4-workspace-optimization',
'/node-tutorial/08-summary',
];
const nodeRedirectDestination = '/getting-started/node-tutorial';

View File

@ -23,6 +23,7 @@ type PersonaType =
| 'distribute'
| 'javascript'
| 'lerna'
| 'node'
| 'react'
| 'angular'
| 'integrated';
@ -114,6 +115,22 @@ const typeMap: Record<
</div>
),
},
node: {
image: (
<div className="space-y-4">
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
className="h-6 w-6"
>
{/*NODEJS*/}
<path d="M11.998,24c-0.321,0-0.641-0.084-0.922-0.247l-2.936-1.737c-0.438-0.245-0.224-0.332-0.08-0.383 c0.585-0.203,0.703-0.25,1.328-0.604c0.065-0.037,0.151-0.023,0.218,0.017l2.256,1.339c0.082,0.045,0.197,0.045,0.272,0l8.795-5.076 c0.082-0.047,0.134-0.141,0.134-0.238V6.921c0-0.099-0.053-0.192-0.137-0.242l-8.791-5.072c-0.081-0.047-0.189-0.047-0.271,0 L3.075,6.68C2.99,6.729,2.936,6.825,2.936,6.921v10.15c0,0.097,0.054,0.189,0.139,0.235l2.409,1.392 c1.307,0.654,2.108-0.116,2.108-0.89V7.787c0-0.142,0.114-0.253,0.256-0.253h1.115c0.139,0,0.255,0.112,0.255,0.253v10.021 c0,1.745-0.95,2.745-2.604,2.745c-0.508,0-0.909,0-2.026-0.551L2.28,18.675c-0.57-0.329-0.922-0.945-0.922-1.604V6.921 c0-0.659,0.353-1.275,0.922-1.603l8.795-5.082c0.557-0.315,1.296-0.315,1.848,0l8.794,5.082c0.57,0.329,0.924,0.944,0.924,1.603 v10.15c0,0.659-0.354,1.273-0.924,1.604l-8.794,5.078C12.643,23.916,12.324,24,11.998,24z M19.099,13.993 c0-1.9-1.284-2.406-3.987-2.763c-2.731-0.361-3.009-0.548-3.009-1.187c0-0.528,0.235-1.233,2.258-1.233 c1.807,0,2.473,0.389,2.747,1.607c0.024,0.115,0.129,0.199,0.247,0.199h1.141c0.071,0,0.138-0.031,0.186-0.081 c0.048-0.054,0.074-0.123,0.067-0.196c-0.177-2.098-1.571-3.076-4.388-3.076c-2.508,0-4.004,1.058-4.004,2.833 c0,1.925,1.488,2.457,3.895,2.695c2.88,0.282,3.103,0.703,3.103,1.269c0,0.983-0.789,1.402-2.642,1.402 c-2.327,0-2.839-0.584-3.011-1.742c-0.02-0.124-0.126-0.215-0.253-0.215h-1.137c-0.141,0-0.254,0.112-0.254,0.253 c0,1.482,0.806,3.248,4.655,3.248C17.501,17.007,19.099,15.91,19.099,13.993z" />
</svg>
</div>
),
},
react: {
image: (
<svg