feat(nx-dev): tutorialkit project (#29992)
Adds a tutorial project under nx-dev The tutorials are run in browser and can be accessed here: [/tutorials](https://nx-dev-git-nx-dev-tutorialkit-nrwl.vercel.app/tutorials) The tutorials include: - TypeScript Packages - React Monorepo - Angular Monorepo In the future, we will link directly from the sidebar to the in-browser tutorials.
4
.gitignore
vendored
@ -22,6 +22,7 @@ jest.debug.config.js
|
|||||||
/graph/client/src/assets/generated-task-inputs
|
/graph/client/src/assets/generated-task-inputs
|
||||||
/graph/client/src/assets/generated-source-maps
|
/graph/client/src/assets/generated-source-maps
|
||||||
/nx-dev/nx-dev/public/documentation
|
/nx-dev/nx-dev/public/documentation
|
||||||
|
/nx-dev/nx-dev/public/tutorials
|
||||||
/nx-dev/nx-dev/public/images/open-graph
|
/nx-dev/nx-dev/public/images/open-graph
|
||||||
**/tests/temp-db
|
**/tests/temp-db
|
||||||
|
|
||||||
@ -38,6 +39,9 @@ out
|
|||||||
# Angular Cache
|
# Angular Cache
|
||||||
.angular
|
.angular
|
||||||
|
|
||||||
|
# Astro Cache
|
||||||
|
.astro
|
||||||
|
|
||||||
# Local dev files
|
# Local dev files
|
||||||
.env.local
|
.env.local
|
||||||
.bashrc
|
.bashrc
|
||||||
|
|||||||
@ -1,2 +1,5 @@
|
|||||||
nx-dev/**/jest.config.js
|
nx-dev/**/jest.config.js
|
||||||
.next
|
.next
|
||||||
|
_files
|
||||||
|
_solution
|
||||||
|
nx-dev/tutorial/**/templates
|
||||||
|
|||||||
@ -47,4 +47,6 @@ CODEOWNERS
|
|||||||
.pnpm-store
|
.pnpm-store
|
||||||
|
|
||||||
/.nx/workspace-data
|
/.nx/workspace-data
|
||||||
/.nx/workflows/dynamic-changesets.yaml
|
/.nx/workflows/dynamic-changesets.yaml
|
||||||
|
_files
|
||||||
|
_solution
|
||||||
|
|||||||
@ -927,7 +927,7 @@ Nx comes with a generic mechanism that allows you to assign "tags" to projects.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Then go to the `project.json` of your `products` library and assign the tags `type:feature` and `scope:products` to it.
|
Then go to the `package.json` of your `products` library and assign the tags `type:feature` and `scope:products` to it.
|
||||||
|
|
||||||
```json {% fileName="libs/products/package.json" %}
|
```json {% fileName="libs/products/package.json" %}
|
||||||
{
|
{
|
||||||
@ -938,7 +938,7 @@ Then go to the `project.json` of your `products` library and assign the tags `ty
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally, go to the `project.json` of the `shared-ui` library and assign the tags `type:ui` and `scope:shared` to it.
|
Finally, go to the `package.json` of the `shared-ui` library and assign the tags `type:ui` and `scope:shared` to it.
|
||||||
|
|
||||||
```json {% fileName="libs/shared/ui/package.json" %}
|
```json {% fileName="libs/shared/ui/package.json" %}
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
/* eslint-disable @nx/enforce-module-boundaries */
|
/* eslint-disable @nx/enforce-module-boundaries */
|
||||||
// nx-ignore-next-line
|
// nx-ignore-next-line
|
||||||
import type { TargetConfiguration } from '@nx/devkit';
|
import type { TargetConfiguration } from '@nx/devkit';
|
||||||
|
import { CopyToClipboardButton } from '@nx/graph/legacy/components';
|
||||||
import {
|
import {
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
ChevronUpIcon,
|
ChevronUpIcon,
|
||||||
EyeIcon,
|
EyeIcon,
|
||||||
PlayIcon,
|
PlayIcon,
|
||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
import { CopyToClipboardButton } from '@nx/graph/legacy/components';
|
|
||||||
import { Tooltip } from '@nx/graph/legacy/tooltips';
|
import { Tooltip } from '@nx/graph/legacy/tooltips';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
import { Pill } from '../pill';
|
import { Pill } from '../pill';
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import { FadingCollapsible } from './fading-collapsible';
|
|||||||
import { TargetConfigurationProperty } from './target-configuration-property';
|
import { TargetConfigurationProperty } from './target-configuration-property';
|
||||||
import { TooltipTriggerText } from './tooltip-trigger-text';
|
import { TooltipTriggerText } from './tooltip-trigger-text';
|
||||||
import { PropertyInfoTooltip } from '../tooltips/property-info-tooltip';
|
import { PropertyInfoTooltip } from '../tooltips/property-info-tooltip';
|
||||||
|
|
||||||
interface TargetConfigurationDetailsProps {
|
interface TargetConfigurationDetailsProps {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
targetName: string;
|
targetName: string;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { CopyToClipboardButton } from '@nx/graph/legacy/components';
|
|
||||||
import { Tooltip } from '@nx/graph/legacy/tooltips';
|
import { Tooltip } from '@nx/graph/legacy/tooltips';
|
||||||
|
import { CopyToClipboardButton } from '@nx/graph/legacy/components';
|
||||||
import { TooltipTriggerText } from '../target-configuration-details/tooltip-trigger-text';
|
import { TooltipTriggerText } from '../target-configuration-details/tooltip-trigger-text';
|
||||||
import { PropertyInfoTooltip } from '../tooltips/property-info-tooltip';
|
import { PropertyInfoTooltip } from '../tooltips/property-info-tooltip';
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { DocSearchModal, useDocSearchKeyboardEvents } from '@docsearch/react';
|
import * as docsearchReact from '@docsearch/react';
|
||||||
import {
|
import {
|
||||||
InternalDocSearchHit,
|
InternalDocSearchHit,
|
||||||
StoredDocSearchHit,
|
StoredDocSearchHit,
|
||||||
@ -10,6 +10,8 @@ import { useRouter } from 'next/navigation';
|
|||||||
import { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
|
import { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
|
const { DocSearchModal, useDocSearchKeyboardEvents } = docsearchReact;
|
||||||
|
|
||||||
const ACTION_KEY_DEFAULT = ['Ctrl ', 'Control'];
|
const ACTION_KEY_DEFAULT = ['Ctrl ', 'Control'];
|
||||||
const ACTION_KEY_APPLE = ['⌘', 'Command'];
|
const ACTION_KEY_APPLE = ['⌘', 'Command'];
|
||||||
|
|
||||||
|
|||||||
15
nx-dev/nx-dev/copy-tutorial.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
const { copySync, rmSync } = require('fs-extra');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies the tutorial kit build artifacts
|
||||||
|
*/
|
||||||
|
rmSync(path.resolve(path.join(__dirname, 'public/tutorials')), {
|
||||||
|
recursive: true,
|
||||||
|
force: true,
|
||||||
|
});
|
||||||
|
copySync(
|
||||||
|
path.resolve(path.join(__dirname, '../tutorial/dist')),
|
||||||
|
path.resolve(path.join(__dirname, 'public/tutorials')),
|
||||||
|
{ overwrite: true }
|
||||||
|
);
|
||||||
@ -13,6 +13,13 @@ module.exports = withNx({
|
|||||||
},
|
},
|
||||||
async headers() {
|
async headers() {
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
source: '/tutorials/:path*',
|
||||||
|
headers: [
|
||||||
|
{ key: 'Cross-Origin-Embedder-Policy', value: 'require-corp' },
|
||||||
|
{ key: 'Cross-Origin-Opener-Policy', value: 'same-origin' },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
source: '/:path*',
|
source: '/:path*',
|
||||||
headers: [
|
headers: [
|
||||||
@ -24,11 +31,23 @@ module.exports = withNx({
|
|||||||
{ key: 'X-XSS-Protection', value: '1; mode=block' },
|
{ key: 'X-XSS-Protection', value: '1; mode=block' },
|
||||||
{ key: 'X-Content-Type-Options', value: 'nosniff' },
|
{ key: 'X-Content-Type-Options', value: 'nosniff' },
|
||||||
{ key: 'X-Frame-Options', value: 'DENY' },
|
{ key: 'X-Frame-Options', value: 'DENY' },
|
||||||
{ key: 'Referrer-Policy', value: 'no-referrer' },
|
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
async rewrites() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: '/tutorials/:path*',
|
||||||
|
destination: '/tutorials/:path*/index.html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: '/tutorials',
|
||||||
|
destination: '/tutorials/index.html',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
async redirects() {
|
async redirects() {
|
||||||
const rules = [];
|
const rules = [];
|
||||||
|
|
||||||
|
|||||||
@ -64,11 +64,20 @@
|
|||||||
"cwd": "nx-dev/nx-dev"
|
"cwd": "nx-dev/nx-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"copy-tutorial": {
|
||||||
|
"inputs": ["{workspaceRoot}/nx-dev/tutorial/**/*"],
|
||||||
|
"outputs": ["{projectRoot}/public/tutorials"],
|
||||||
|
"command": "node ./copy-tutorial.js",
|
||||||
|
"dependsOn": ["tutorial:build"],
|
||||||
|
"options": {
|
||||||
|
"cwd": "nx-dev/nx-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
"serve-docs": {
|
"serve-docs": {
|
||||||
"executor": "nx:run-commands",
|
"executor": "nx:run-commands",
|
||||||
"options": {
|
"options": {
|
||||||
"commands": [
|
"commands": [
|
||||||
"nx watch --projects=docs -- nx run nx-dev:copy-docs",
|
"nx watch --projects=docs,tutorial -- nx run-many -t=copy-docs,copy-tutorial -p nx-dev",
|
||||||
"nx run nx-dev:serve"
|
"nx run nx-dev:serve"
|
||||||
],
|
],
|
||||||
"parallel": true
|
"parallel": true
|
||||||
@ -80,7 +89,7 @@
|
|||||||
},
|
},
|
||||||
"serve": {
|
"serve": {
|
||||||
"executor": "@nx/next:server",
|
"executor": "@nx/next:server",
|
||||||
"dependsOn": ["copy-docs"],
|
"dependsOn": ["copy-docs", "copy-tutorial"],
|
||||||
"options": {
|
"options": {
|
||||||
"buildTarget": "nx-dev:build-base",
|
"buildTarget": "nx-dev:build-base",
|
||||||
"dev": true
|
"dev": true
|
||||||
@ -111,6 +120,10 @@
|
|||||||
"command": "nx copy-docs nx-dev",
|
"command": "nx copy-docs nx-dev",
|
||||||
"description": "Copy generated docs to build output"
|
"description": "Copy generated docs to build output"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "nx copy-tutorial nx-dev",
|
||||||
|
"description": "Copy tutorial to build output"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"command": "npx ts-node -P scripts/tsconfig.scripts.json scripts/documentation/plugin-quality-indicators.ts",
|
"command": "npx ts-node -P scripts/tsconfig.scripts.json scripts/documentation/plugin-quality-indicators.ts",
|
||||||
"description": "Fetch plugin data"
|
"description": "Fetch plugin data"
|
||||||
|
|||||||
149
nx-dev/tutorial/README.md
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
# TutorialKit Starter
|
||||||
|
|
||||||
|
👋 Welcome to TutorialKit!
|
||||||
|
|
||||||
|
This README includes everything you need to start writing your tutorial content quickly.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.
|
||||||
|
├── astro.config.mjs # TutorialKit uses Astro 🚀 (https://astro.build)
|
||||||
|
├── src
|
||||||
|
│ ├── ...
|
||||||
|
│ ├── content
|
||||||
|
│ │ └── tutorial # Your tutorial content lives here
|
||||||
|
│ └── templates # Your templates (see below for more information)
|
||||||
|
├── public
|
||||||
|
│ ├── favicon.svg
|
||||||
|
│ └── logo.svg # Default logo used in top left for your tutorial
|
||||||
|
├── ...
|
||||||
|
├── theme.ts # Customize the theme of the tutorial
|
||||||
|
└── uno.config.ts # UnoCSS config (https://unocss.dev/)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Make sure you have all dependencies installed and started the dev server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## UI Structure
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ ● ● ● │
|
||||||
|
├───────────────────────────┬─────────────────────────┤
|
||||||
|
│ │ │
|
||||||
|
│ │ │
|
||||||
|
│ │ │
|
||||||
|
│ │ │
|
||||||
|
│ │ Code Editor │
|
||||||
|
│ │ │
|
||||||
|
│ │ │
|
||||||
|
│ │ │
|
||||||
|
│ │ │
|
||||||
|
│ Content ├─────────────────────────┤
|
||||||
|
│ │ │
|
||||||
|
│ │ │
|
||||||
|
│ │ Preview & Boot Screen │
|
||||||
|
│ │ │
|
||||||
|
│ │ │
|
||||||
|
│ ├─────────────────────────┤
|
||||||
|
│ │ │
|
||||||
|
│ │ Terminal │
|
||||||
|
│ │ │
|
||||||
|
└───────────────────────────┴─────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Authoring Content
|
||||||
|
|
||||||
|
A tutorial consists of parts, chapters, and lessons. For example:
|
||||||
|
|
||||||
|
- Part 1: Basics of Vite
|
||||||
|
- Chapter 1: Introduction
|
||||||
|
- Lesson 1: Welcome!
|
||||||
|
- Lesson 2: Why Vite?
|
||||||
|
- …
|
||||||
|
- Chapter 2: Your first Vite project
|
||||||
|
- Part 2: CLI
|
||||||
|
- …
|
||||||
|
|
||||||
|
Your content is organized into lessons, with chapters and parts providing a structure and defining common metadata for these lessons.
|
||||||
|
|
||||||
|
Here’s an example of how it would look like in `src/content/tutorial`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tutorial
|
||||||
|
├── 1-basics-of-vite
|
||||||
|
│ ├── 1-introduction
|
||||||
|
│ │ ├── 1-welcome
|
||||||
|
│ │ │ ├── content.md # The content of your lesson
|
||||||
|
│ │ │ ├── _files # Initial set of files
|
||||||
|
│ │ │ │ └── ...
|
||||||
|
│ │ │ └── _solution # Solution of the lesson
|
||||||
|
│ │ │ └── ...
|
||||||
|
│ │ ├── 2-why-vite
|
||||||
|
│ │ │ ├── content.md
|
||||||
|
│ │ │ └── _files
|
||||||
|
│ │ │ └── ...
|
||||||
|
│ │ └── meta.md # Metadata for the chapter
|
||||||
|
│ └── meta.md # Metadata for the part
|
||||||
|
├── 2-advanced
|
||||||
|
│ ├── ...
|
||||||
|
│ └── meta.md
|
||||||
|
└── meta.md # Metadata for the tutorial
|
||||||
|
```
|
||||||
|
|
||||||
|
### Supported Content Formats
|
||||||
|
|
||||||
|
Content can be either written as Markdown (`.md`) files or using [MDX](https://mdxjs.com/) (`.mdx`). Files have a Front Matter at the top that contains the metadata and everything that comes after is the content of your lesson.
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
type: lesson
|
||||||
|
title: Welcome!
|
||||||
|
---
|
||||||
|
|
||||||
|
# Welcome to TutorialKit!
|
||||||
|
|
||||||
|
In this tutorial we'll walk you through how to setup your environment to
|
||||||
|
write your first tutorial 🤩
|
||||||
|
```
|
||||||
|
|
||||||
|
The metadata file (`meta.md`) of parts, chapters, and lessons do not contain any content. It only contains the Front Matter for configuration.
|
||||||
|
|
||||||
|
### Metadata
|
||||||
|
|
||||||
|
Here is an overview of the properties that can be used as part of the Front Matter:
|
||||||
|
|
||||||
|
| Property | Required | Type | Inherited | Description |
|
||||||
|
| --------------- | -------- | --------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| type | ✅ | `part \| chapter \| lesson` | ❌ | The type of the metadata. |
|
||||||
|
| title | ✅ | `string` | ❌ | The title of the part, chapter, or lesson. |
|
||||||
|
| slug | | `string` | ❌ | Let’s you customize the URL pathname which is `/:partSlug/:chapterSlug/:lessonSlug`. |
|
||||||
|
| previews | | `Preview[]` | ✅ | Configure which ports should be used for the previews. If not specified, the lowest port will be used. |
|
||||||
|
| autoReload | | `boolean` | ✅ | Navigating to a lesson that specifies `autoReload` will always reload the preview. This is typically only needed if your server does not support HMR. |
|
||||||
|
| prepareCommands | | `Command[]` | ✅ | List of commands to execute sequentially. They are typically used to install dependencies or to run scripts. |
|
||||||
|
| mainCommand | | `Command` | ✅ | The main command to be executed. This command will run after the `prepareCommands`. |
|
||||||
|
|
||||||
|
A `Command` has the following shape:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
string | [command: string, title: string] | { command: string, title: string }
|
||||||
|
```
|
||||||
|
|
||||||
|
The `title` is used as part of the boot screen (see [UI Structure](#ui-structure)).
|
||||||
|
|
||||||
|
A `Preview` has the following shape:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
string | [port: number, title: string] | { port: number, title: string }
|
||||||
|
```
|
||||||
|
|
||||||
|
In most cases, metadata is inherited. For example, if you specify a `mainCommand` on a chapter without specifying it on any of its lessons, each lesson will use the `mainCommand` from its respective chapter. This extends to chapter and parts as well.
|
||||||
52
nx-dev/tutorial/astro.config.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import tutorialkit from '@tutorialkit/astro';
|
||||||
|
import { defineConfig, envField } from 'astro/config';
|
||||||
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|
||||||
|
export const config = defineConfig({
|
||||||
|
base: '/tutorials',
|
||||||
|
devToolbar: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
experimental: {
|
||||||
|
env: {
|
||||||
|
schema: {
|
||||||
|
NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA: envField.string({
|
||||||
|
context: 'client',
|
||||||
|
access: 'public',
|
||||||
|
optional: true,
|
||||||
|
}),
|
||||||
|
NEXT_PUBLIC_FARO_URL: envField.string({
|
||||||
|
context: 'client',
|
||||||
|
access: 'public',
|
||||||
|
optional: true,
|
||||||
|
}),
|
||||||
|
NEXT_PUBLIC_VERCEL_ENV: envField.string({
|
||||||
|
context: 'client',
|
||||||
|
access: 'public',
|
||||||
|
optional: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
vite: {
|
||||||
|
plugins: [nxViteTsPaths() as any],
|
||||||
|
ssr: {
|
||||||
|
noExternal: [
|
||||||
|
'@tutorialkit/astro',
|
||||||
|
'@astrojs/mdx',
|
||||||
|
'@astrojs/react',
|
||||||
|
'astro-expressive-code',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
integrations: [
|
||||||
|
tutorialkit({
|
||||||
|
components: {
|
||||||
|
HeadTags: './src/components/HeadTags.astro',
|
||||||
|
TopBar: './src/components/TopBar.astro',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export default config;
|
||||||
15
nx-dev/tutorial/package.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "tutorial",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"astro": "npx astro",
|
||||||
|
"build": "npx astro check && npx astro build",
|
||||||
|
"dev": "npx astro dev",
|
||||||
|
"preview": "npx astro preview",
|
||||||
|
"serve": "npx astro dev --port 4200"
|
||||||
|
},
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {}
|
||||||
|
}
|
||||||
17
nx-dev/tutorial/project.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "tutorial",
|
||||||
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "nx-dev/tutorial/src",
|
||||||
|
"projectType": "library",
|
||||||
|
"tags": [],
|
||||||
|
"// targets": "to see all targets run: nx show project tutorial --web",
|
||||||
|
"targets": {
|
||||||
|
"build": {
|
||||||
|
"inputs": ["{projectRoot}/src/**/**"]
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"command": "echo no linting",
|
||||||
|
"cache": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
nx-dev/tutorial/public/favicon.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
|
||||||
|
<rect width="16" height="16" rx="2" fill="#1389fd" />
|
||||||
|
<path d="M7.398 9.091h-3.58L10.364 2 8.602 6.909h3.58L5.636 14l1.762-4.909Z" fill="#fff" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 241 B |
BIN
nx-dev/tutorial/public/images/github-cloud-pr-merged.avif
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
nx-dev/tutorial/public/images/github-pr-cloud-report.avif
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
nx-dev/tutorial/public/images/nx-cloud-github-connect.avif
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
nx-dev/tutorial/public/images/nx-cloud-run-details.avif
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
nx-dev/tutorial/public/images/nx-media.png
Normal file
|
After Width: | Height: | Size: 197 KiB |
6
nx-dev/tutorial/public/logo-dark.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="#E4E6E9"
|
||||||
|
aria-hidden="true" class="h-8 w-8">
|
||||||
|
<title>Nx</title>
|
||||||
|
<path
|
||||||
|
d="M11.987 14.138l-3.132 4.923-5.193-8.427-.012 8.822H0V4.544h3.691l5.247 8.833.005-3.998 3.044 4.759zm.601-5.761c.024-.048 0-3.784.008-3.833h-3.65c.002.059-.005 3.776-.003 3.833h3.645zm5.634 4.134a2.061 2.061 0 0 0-1.969 1.336 1.963 1.963 0 0 1 2.343-.739c.396.161.917.422 1.33.283a2.1 2.1 0 0 0-1.704-.88zm3.39 1.061c-.375-.13-.8-.277-1.109-.681-.06-.08-.116-.17-.176-.265a2.143 2.143 0 0 0-.533-.642c-.294-.216-.68-.322-1.18-.322a2.482 2.482 0 0 0-2.294 1.536 2.325 2.325 0 0 1 4.002.388.75.75 0 0 0 .836.334c.493-.105.46.36 1.203.518v-.133c-.003-.446-.246-.55-.75-.733zm2.024 1.266a.723.723 0 0 0 .347-.638c-.01-2.957-2.41-5.487-5.37-5.487a5.364 5.364 0 0 0-4.487 2.418c-.01-.026-1.522-2.39-1.538-2.418H8.943l3.463 5.423-3.379 5.32h3.54l1.54-2.366 1.568 2.366h3.541l-3.21-5.052a.7.7 0 0 1-.084-.32 2.69 2.69 0 0 1 2.69-2.691h.001c1.488 0 1.736.89 2.057 1.308.634.826 1.9.464 1.9 1.541a.707.707 0 0 0 1.066.596zm.35.133c-.173.372-.56.338-.755.639-.176.271.114.412.114.412s.337.156.538-.311c.104-.231.14-.488.103-.74z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
1
nx-dev/tutorial/public/logo.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor" aria-hidden="true" class="h-8 w-8"><title>Nx</title><path d="M11.987 14.138l-3.132 4.923-5.193-8.427-.012 8.822H0V4.544h3.691l5.247 8.833.005-3.998 3.044 4.759zm.601-5.761c.024-.048 0-3.784.008-3.833h-3.65c.002.059-.005 3.776-.003 3.833h3.645zm5.634 4.134a2.061 2.061 0 0 0-1.969 1.336 1.963 1.963 0 0 1 2.343-.739c.396.161.917.422 1.33.283a2.1 2.1 0 0 0-1.704-.88zm3.39 1.061c-.375-.13-.8-.277-1.109-.681-.06-.08-.116-.17-.176-.265a2.143 2.143 0 0 0-.533-.642c-.294-.216-.68-.322-1.18-.322a2.482 2.482 0 0 0-2.294 1.536 2.325 2.325 0 0 1 4.002.388.75.75 0 0 0 .836.334c.493-.105.46.36 1.203.518v-.133c-.003-.446-.246-.55-.75-.733zm2.024 1.266a.723.723 0 0 0 .347-.638c-.01-2.957-2.41-5.487-5.37-5.487a5.364 5.364 0 0 0-4.487 2.418c-.01-.026-1.522-2.39-1.538-2.418H8.943l3.463 5.423-3.379 5.32h3.54l1.54-2.366 1.568 2.366h3.541l-3.21-5.052a.7.7 0 0 1-.084-.32 2.69 2.69 0 0 1 2.69-2.691h.001c1.488 0 1.736.89 2.057 1.308.634.826 1.9.464 1.9 1.541a.707.707 0 0 0 1.066.596zm.35.133c-.173.372-.56.338-.755.639-.176.271.114.412.114.412s.337.156.538-.311c.104-.231.14-.488.103-.74z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
125
nx-dev/tutorial/src/components/CommunityLinks.tsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
export function CommunityLinks() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<a
|
||||||
|
title="Community channel"
|
||||||
|
href="https://go.nx.dev/community"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-tk-elements-topBar-iconButton-iconColor hover:text-tk-elements-topBar-iconButton-iconColorHover bg-tk-elements-topBar-iconButton-backgroundColor hover:bg-tk-elements-topBar-iconButton-backgroundColorHover transition-theme mr-2 flex items-center rounded-md p-1 text-2xl"
|
||||||
|
>
|
||||||
|
<span className="sr-only">Community channel</span>
|
||||||
|
<svg
|
||||||
|
fill="currentColor"
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="h-4 w-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
style={{
|
||||||
|
height: '1.5rem',
|
||||||
|
width: '1.5rem',
|
||||||
|
fill: 'var(--tk-elements-topBar-iconButton-iconColor)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
title="Latest news"
|
||||||
|
href="https://x.com/NxDevTools?utm_source=nx.dev"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-tk-elements-topBar-iconButton-iconColor hover:text-tk-elements-topBar-iconButton-iconColorHover bg-tk-elements-topBar-iconButton-backgroundColor hover:bg-tk-elements-topBar-iconButton-backgroundColorHover transition-theme mr-2 flex items-center rounded-md p-1 text-2xl"
|
||||||
|
>
|
||||||
|
<span className="sr-only">Latest news</span>
|
||||||
|
<svg
|
||||||
|
fill="currentColor"
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="h-4 w-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
style={{
|
||||||
|
height: '1.5rem',
|
||||||
|
width: '1.5rem',
|
||||||
|
fill: 'var(--tk-elements-topBar-iconButton-iconColor)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<path d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
title="Latest news on Bluesky"
|
||||||
|
href="https://bsky.app/profile/nx.dev?utm_source=nx.dev"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-tk-elements-topBar-iconButton-iconColor hover:text-tk-elements-topBar-iconButton-iconColorHover bg-tk-elements-topBar-iconButton-backgroundColor hover:bg-tk-elements-topBar-iconButton-backgroundColorHover transition-theme mr-2 flex items-center rounded-md p-1 text-2xl"
|
||||||
|
>
|
||||||
|
<span className="sr-only">Latest news on Bluesky</span>
|
||||||
|
<svg
|
||||||
|
fill="currentColor"
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="h-4 w-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
style={{
|
||||||
|
height: '1.5rem',
|
||||||
|
width: '1.5rem',
|
||||||
|
fill: 'var(--tk-elements-topBar-iconButton-iconColor)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<path d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.039.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 0 1-.415-.056c.14.017.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8Z"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
title="Youtube channel"
|
||||||
|
href="https://www.youtube.com/@NxDevtools?utm_source=nx.dev"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-tk-elements-topBar-iconButton-iconColor hover:text-tk-elements-topBar-iconButton-iconColorHover bg-tk-elements-topBar-iconButton-backgroundColor hover:bg-tk-elements-topBar-iconButton-backgroundColorHover transition-theme mr-2 flex items-center rounded-md p-1 text-2xl"
|
||||||
|
>
|
||||||
|
<span className="sr-only">Youtube channel</span>
|
||||||
|
<svg
|
||||||
|
fill="currentColor"
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="h-4 w-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
style={{
|
||||||
|
height: '1.5rem',
|
||||||
|
width: '1.5rem',
|
||||||
|
fill: 'var(--tk-elements-topBar-iconButton-iconColor)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<path d="M23.5 6.19a3.02 3.02 0 0 0-2.12-2.14c-1.88-.5-9.38-.5-9.38-.5s-7.5 0-9.38.5A3.02 3.02 0 0 0 .5 6.19C0 8.07 0 12 0 12s0 3.93.5 5.81a3.02 3.02 0 0 0 2.12 2.14c1.87.5 9.38.5 9.38.5s7.5 0 9.38-.5a3.02 3.02 0 0 0 2.12-2.14C24 15.93 24 12 24 12s0-3.93-.5-5.81zM9.54 15.57V8.43L15.82 12l-6.28 3.57z"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
title="Nx is open source, check the code on GitHub"
|
||||||
|
href="https://github.com/nrwl/nx?utm_source=nx.dev"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-tk-elements-topBar-iconButton-iconColor hover:text-tk-elements-topBar-iconButton-iconColorHover bg-tk-elements-topBar-iconButton-backgroundColor hover:bg-tk-elements-topBar-iconButton-backgroundColorHover transition-theme mr-2 flex items-center rounded-md p-1 text-2xl"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="currentColor"
|
||||||
|
role="img"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
className="h-4 w-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
style={{
|
||||||
|
height: '1.5rem',
|
||||||
|
width: '1.5rem',
|
||||||
|
fill: 'var(--tk-elements-topBar-iconButton-iconColor)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<path d="M8 0a8 8 0 0 0-2.53 15.59c.4.07.55-.17.55-.38l-.01-1.49c-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82a7.42 7.42 0 0 1 4 0c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48l-.01 2.2c0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8a8 8 0 0 0-8-8z"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
19
nx-dev/tutorial/src/components/DownloadButton.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
export function DownloadButton() {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
title="Download lesson as zip-file"
|
||||||
|
className="transition-theme bg-tk-elements-topBar-iconButton-backgroundColor hover:bg-tk-elements-topBar-iconButton-backgroundColorHover border-tk-elements-navCard-borderColor hover:border-tk-elements-navCard-borderColorHover mx-auto my-5 flex items-center rounded-md border text-xl"
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<span className="text-tk-elements-topBar-iconButton-iconColor hover:text-tk-elements-topBar-iconButton-iconColorHover flex items-center p-2">
|
||||||
|
<div className="i-ph-download-simple mr-2 text-3xl" />
|
||||||
|
Download Repository
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onClick() {
|
||||||
|
const button: any = document.querySelector('#download-button button');
|
||||||
|
button.click();
|
||||||
|
}
|
||||||
313
nx-dev/tutorial/src/components/Editor.tsx
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
import type {
|
||||||
|
EditorDocument,
|
||||||
|
EditorUpdate,
|
||||||
|
ScrollPosition,
|
||||||
|
} from '@tutorialkit/react/core';
|
||||||
|
import CodeMirrorEditor from '@tutorialkit/react/core/CodeMirrorEditor';
|
||||||
|
import FileTree from '@tutorialkit/react/core/FileTree';
|
||||||
|
import type { FileSystemTree, DirectoryNode } from '@webcontainer/api';
|
||||||
|
import type { Terminal as XTerm } from '@xterm/xterm';
|
||||||
|
import { Suspense, lazy, useEffect, useState } from 'react';
|
||||||
|
import { useTheme } from './hooks/useTheme';
|
||||||
|
import { useWebContainer } from './hooks/useWebcontainer';
|
||||||
|
|
||||||
|
const Terminal = lazy(() => import('@tutorialkit/react/core/Terminal'));
|
||||||
|
|
||||||
|
export default function ExampleSimpleEditor() {
|
||||||
|
const [domLoaded, setDomLoaded] = useState(false);
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
const {
|
||||||
|
setTerminal,
|
||||||
|
previewSrc,
|
||||||
|
document,
|
||||||
|
files,
|
||||||
|
onChange,
|
||||||
|
onScroll,
|
||||||
|
selectedFile,
|
||||||
|
setSelectedFile,
|
||||||
|
} = useSimpleEditor();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDomLoaded(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-120 not-content react-example mt-4 flex flex-col overflow-hidden rounded border border-solid border-[var(--ec-brdCol)]">
|
||||||
|
<div className="flex h-1/2">
|
||||||
|
<FileTree
|
||||||
|
className="w-1/4 flex-shrink-0 text-sm"
|
||||||
|
files={files}
|
||||||
|
hideRoot
|
||||||
|
selectedFile={selectedFile}
|
||||||
|
onFileSelect={setSelectedFile}
|
||||||
|
/>
|
||||||
|
<div className="h-full w-px flex-shrink-0 bg-[var(--ec-brdCol)]" />
|
||||||
|
<div className="relative h-full max-w-[calc(75%-1px)] flex-grow bg-[var(--cm-backgroundColor)]">
|
||||||
|
<CodeMirrorEditor
|
||||||
|
theme={theme}
|
||||||
|
doc={document}
|
||||||
|
onChange={onChange}
|
||||||
|
onScroll={onScroll}
|
||||||
|
className="h-full text-[13px]"
|
||||||
|
/>
|
||||||
|
<div className="absolute bottom-0 right-0 h-4 w-4 bg-[var(--cm-backgroundColor)]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="h-px bg-[var(--ec-brdCol)]" />
|
||||||
|
<div className="m-0 flex h-1/2 p-0">
|
||||||
|
<div className="h-full w-1/2">
|
||||||
|
{domLoaded && (
|
||||||
|
<Suspense>
|
||||||
|
<Terminal
|
||||||
|
className="h-full"
|
||||||
|
readonly={false}
|
||||||
|
theme={theme}
|
||||||
|
onTerminalReady={setTerminal}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="h-full w-px flex-shrink-0 bg-[var(--ec-brdCol)]" />
|
||||||
|
<div className="h-full w-1/2">
|
||||||
|
<iframe
|
||||||
|
className="h-full w-full border-none bg-white"
|
||||||
|
src={previewSrc}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function useSimpleEditor() {
|
||||||
|
const webcontainerPromise = useWebContainer();
|
||||||
|
const [terminal, setTerminal] = useState<XTerm | null>(null);
|
||||||
|
const [selectedFile, setSelectedFile] = useState('/src/index.js');
|
||||||
|
const [documents, setDocuments] =
|
||||||
|
useState<Record<string, EditorDocument>>(FILES);
|
||||||
|
const [previewSrc, setPreviewSrc] = useState<string>('');
|
||||||
|
|
||||||
|
const document = documents[selectedFile];
|
||||||
|
|
||||||
|
async function onChange({ content }: EditorUpdate) {
|
||||||
|
setDocuments((prevDocuments) => ({
|
||||||
|
...prevDocuments,
|
||||||
|
[selectedFile]: {
|
||||||
|
...prevDocuments[selectedFile],
|
||||||
|
value: content,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const webcontainer = await webcontainerPromise;
|
||||||
|
|
||||||
|
await webcontainer.fs.writeFile(selectedFile, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onScroll(scroll: ScrollPosition) {
|
||||||
|
setDocuments((prevDocuments) => ({
|
||||||
|
...prevDocuments,
|
||||||
|
[selectedFile]: {
|
||||||
|
...prevDocuments[selectedFile],
|
||||||
|
scroll,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
const webcontainer = await webcontainerPromise;
|
||||||
|
|
||||||
|
webcontainer.on('server-ready', (_port, url) => {
|
||||||
|
setPreviewSrc(url);
|
||||||
|
});
|
||||||
|
|
||||||
|
await webcontainer.mount(toFileTree(FILES));
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!terminal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
run(terminal);
|
||||||
|
|
||||||
|
async function run(terminal: XTerm) {
|
||||||
|
const webcontainer = await webcontainerPromise;
|
||||||
|
const process = await webcontainer.spawn('jsh', ['--osc'], {
|
||||||
|
terminal: {
|
||||||
|
cols: terminal.cols,
|
||||||
|
rows: terminal.rows,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let isInteractive = false;
|
||||||
|
let resolveReady!: () => void;
|
||||||
|
|
||||||
|
const jshReady = new Promise<void>((resolve) => {
|
||||||
|
resolveReady = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
process.output.pipeTo(
|
||||||
|
new WritableStream({
|
||||||
|
write(data) {
|
||||||
|
if (!isInteractive) {
|
||||||
|
const [, osc] = data.match(/\x1b\]654;([^\x07]+)\x07/) || [];
|
||||||
|
|
||||||
|
if (osc === 'interactive') {
|
||||||
|
// wait until we see the interactive OSC
|
||||||
|
isInteractive = true;
|
||||||
|
|
||||||
|
resolveReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal.write(data);
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const shellWriter = process.input.getWriter();
|
||||||
|
|
||||||
|
terminal.onData((data) => {
|
||||||
|
if (isInteractive) {
|
||||||
|
shellWriter.write(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await jshReady;
|
||||||
|
|
||||||
|
shellWriter.write('npm install && npm start\n');
|
||||||
|
}
|
||||||
|
}, [terminal]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
setTerminal,
|
||||||
|
previewSrc,
|
||||||
|
selectedFile,
|
||||||
|
setSelectedFile,
|
||||||
|
onChange,
|
||||||
|
onScroll,
|
||||||
|
document,
|
||||||
|
files: FILE_PATHS,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const FILES: Record<string, EditorDocument> = {
|
||||||
|
'/src/index.js': {
|
||||||
|
filePath: '/src/index.js',
|
||||||
|
loading: false,
|
||||||
|
value: stripIndent(`
|
||||||
|
document.body.innerHTML = '<h1>Hello, world!</h1>';
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
'/src/index.html': {
|
||||||
|
filePath: '/src/index.html',
|
||||||
|
loading: false,
|
||||||
|
value: stripIndent(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Hello, world!</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="./index.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
'/src/assets/logo.svg': {
|
||||||
|
filePath: '/src/assets/logo.svg',
|
||||||
|
loading: false,
|
||||||
|
value: stripIndent(`
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<rect width="24" height="24" rx="15" />
|
||||||
|
</svg>
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
'/package.json': {
|
||||||
|
filePath: '/package.json',
|
||||||
|
loading: false,
|
||||||
|
value: stripIndent(`
|
||||||
|
{
|
||||||
|
"name": "hello-world",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Hello, world!",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "servor src/ --reload"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"servor": "4.0.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const FILE_PATHS = Object.keys(FILES).map(
|
||||||
|
(path) => ({ path, type: 'file' } as const)
|
||||||
|
);
|
||||||
|
|
||||||
|
function stripIndent(string: string) {
|
||||||
|
const indent = minIndent(string.slice(1));
|
||||||
|
|
||||||
|
if (indent === 0) {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const regex = new RegExp(`^[ \\t]{${indent}}`, 'gm');
|
||||||
|
|
||||||
|
return string.replace(regex, '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function minIndent(string: string) {
|
||||||
|
const match = string.match(/^[ \t]*(?=\S)/gm);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return match.reduce((acc, curr) => Math.min(acc, curr.length), Infinity);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toFileTree(
|
||||||
|
files: Record<string, EditorDocument>
|
||||||
|
): FileSystemTree {
|
||||||
|
const root: FileSystemTree = {};
|
||||||
|
|
||||||
|
for (const filePath in files) {
|
||||||
|
const segments = filePath.split('/').filter((segment) => segment);
|
||||||
|
|
||||||
|
let currentTree: FileSystemTree = root;
|
||||||
|
|
||||||
|
for (let i = 0; i < segments.length; ++i) {
|
||||||
|
const name = segments[i];
|
||||||
|
|
||||||
|
if (i === segments.length - 1) {
|
||||||
|
currentTree[name] = {
|
||||||
|
file: {
|
||||||
|
contents: files[filePath].value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
let folder = currentTree[name] as DirectoryNode;
|
||||||
|
|
||||||
|
if (!folder) {
|
||||||
|
folder = {
|
||||||
|
directory: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
currentTree[name] = folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTree = folder.directory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
5
nx-dev/tutorial/src/components/HeadTags.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<slot name="title" />
|
||||||
|
<slot name="links" />
|
||||||
|
<slot name="meta" />
|
||||||
|
{/** Add your own tags */}
|
||||||
|
<meta name="robots" content="noindex,nofollow" />
|
||||||
44
nx-dev/tutorial/src/components/Observability.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
'use client';
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
import {
|
||||||
|
getWebInstrumentations,
|
||||||
|
initializeFaro,
|
||||||
|
faro,
|
||||||
|
} from '@grafana/faro-web-sdk';
|
||||||
|
import {
|
||||||
|
NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA,
|
||||||
|
NEXT_PUBLIC_FARO_URL,
|
||||||
|
NEXT_PUBLIC_VERCEL_ENV,
|
||||||
|
} from 'astro:env/client';
|
||||||
|
|
||||||
|
export function FrontendObservability() {
|
||||||
|
const initialized = useRef(false);
|
||||||
|
useEffect(() => {
|
||||||
|
const version = NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA;
|
||||||
|
const url = NEXT_PUBLIC_FARO_URL;
|
||||||
|
// Don't initialize if we're not in a deployed environment, e.g. local development
|
||||||
|
if (initialized.current || !version) return;
|
||||||
|
initialized.current = true;
|
||||||
|
const vercelEnv = NEXT_PUBLIC_VERCEL_ENV;
|
||||||
|
const environment =
|
||||||
|
vercelEnv === 'production'
|
||||||
|
? 'prod'
|
||||||
|
: vercelEnv === 'preview'
|
||||||
|
? 'staging'
|
||||||
|
: 'development';
|
||||||
|
if (faro.api) {
|
||||||
|
faro.api.setPage({ url: document.location.href });
|
||||||
|
} else {
|
||||||
|
initializeFaro({
|
||||||
|
url,
|
||||||
|
app: {
|
||||||
|
name: 'Nx Dev',
|
||||||
|
version,
|
||||||
|
environment,
|
||||||
|
},
|
||||||
|
instrumentations: [...getWebInstrumentations()],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
31
nx-dev/tutorial/src/components/TopBar.astro
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
import { FrontendObservability } from "./Observability";
|
||||||
|
---
|
||||||
|
|
||||||
|
<nav
|
||||||
|
class="bg-tk-elements-panel-header-backgroundColor transition-theme border-b border-tk-elements-app-borderColor flex max-w-full min-h-[68px]"
|
||||||
|
>
|
||||||
|
<div class="flex flex-1 p-1 gap-4 lg:px-8 lg:py-3 text-tk-elements-app-textColor items-center">
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
class="block h-[32px] w-[32px] items-center text-tk-elements-app-textColor hover:text-tk-elements-topBar-logo-colorHover"
|
||||||
|
>
|
||||||
|
<img class="h-full w-full dark:hidden" src="/tutorials/logo.svg" />
|
||||||
|
<img class="h-full w-full hidden dark:inline-block" src="/tutorials/logo-dark.svg" />
|
||||||
|
</a>
|
||||||
|
<a class="ml-2 hidden items-center px-4 text-slate-900 lg:flex font-light lg:px-0 dark:text-white" href="/getting-started/intro"><span class="text-xl font-bold uppercase tracking-wide">Docs</span></a>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center p-3 px-4">
|
||||||
|
<div class="flex flex-1"></div>
|
||||||
|
<div class="mr-2" id="download-button">
|
||||||
|
<slot name="download-button" />
|
||||||
|
</div>
|
||||||
|
<div class="mr-2">
|
||||||
|
<slot name="open-in-stackblitz-link" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<slot name="theme-switch" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<FrontendObservability client:load />
|
||||||
|
</nav>
|
||||||
28
nx-dev/tutorial/src/components/hooks/useTheme.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
export function useTheme() {
|
||||||
|
const [theme, setTheme] = useState<'dark' | 'light'>(getThemeFromRoot());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
setTheme(getThemeFromRoot());
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(window.document.documentElement, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['data-theme'],
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getThemeFromRoot() {
|
||||||
|
return (
|
||||||
|
(globalThis.document?.documentElement.getAttribute('data-theme') as
|
||||||
|
| 'dark'
|
||||||
|
| 'light') ?? 'light'
|
||||||
|
);
|
||||||
|
}
|
||||||
23
nx-dev/tutorial/src/components/hooks/useWebcontainer.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { WebContainer } from '@webcontainer/api';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
let webcontainerBooting = false;
|
||||||
|
let resolve!: (webcontainer: WebContainer) => void;
|
||||||
|
|
||||||
|
const webcontainerPromise = new Promise<WebContainer>((_resolve) => {
|
||||||
|
resolve = _resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
export function useWebContainer() {
|
||||||
|
useEffect(() => {
|
||||||
|
if (!webcontainerBooting) {
|
||||||
|
webcontainerBooting = true;
|
||||||
|
|
||||||
|
WebContainer.boot({ workdirName: 'example' }).then((webcontainer) => {
|
||||||
|
resolve(webcontainer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return webcontainerPromise;
|
||||||
|
}
|
||||||
18
nx-dev/tutorial/src/content/config.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import {
|
||||||
|
chapterSchema,
|
||||||
|
lessonSchema,
|
||||||
|
partSchema,
|
||||||
|
tutorialSchema,
|
||||||
|
} from '@tutorialkit/types';
|
||||||
|
import { defineCollection } from 'astro:content';
|
||||||
|
|
||||||
|
const tutorial = defineCollection({
|
||||||
|
type: 'content',
|
||||||
|
schema: tutorialSchema
|
||||||
|
.strict()
|
||||||
|
.or(partSchema.strict())
|
||||||
|
.or(chapterSchema.strict())
|
||||||
|
.or(lessonSchema.strict()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const collections = { tutorial };
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../../../../templates/ts-packages/lesson-1"
|
||||||
|
}
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
---
|
||||||
|
type: lesson
|
||||||
|
title: Starting Repository
|
||||||
|
focus: /package.json
|
||||||
|
---
|
||||||
|
|
||||||
|
# TypeScript Monorepo Tutorial
|
||||||
|
|
||||||
|
In this tutorial, you'll learn how to add Nx to a repository with an existing [NPM workspaces](https://docs.npmjs.com/cli/using-npm/workspaces) setup.
|
||||||
|
|
||||||
|
What will you learn?
|
||||||
|
|
||||||
|
- how to add Nx to the repository with a single command
|
||||||
|
- how to configure caching for your tasks
|
||||||
|
- how to configure a task pipeline
|
||||||
|
- how to configure projects automatically with Nx Plugins
|
||||||
|
- how to manage your releases with `nx release`
|
||||||
|
- [how to speed up CI with Nx Cloud ⚡](/tutorials/1-ts-packages/4-fast-ci/1-welcome)
|
||||||
|
|
||||||
|
## Starting Repository
|
||||||
|
|
||||||
|
This tutorial uses the [nrwl/tuskydesign](https://github.com/nrwl/tuskydesign) GitHub repository as a starting point. If you want to follow the tutorial on your own machine instead of in the browser, you can clone the repository.
|
||||||
|
|
||||||
|
import { GithubRepository } from '@nx/nx-dev/ui-markdoc';
|
||||||
|
|
||||||
|
<GithubRepository url="https://github.com/nrwl/tuskydesign"></GithubRepository>
|
||||||
|
|
||||||
|
The repository has three TypeScript packages under `packages/animals`, `packages/names` and `packages/zoo`. The `zoo` package uses `animals` and `names` to generate a random message. The root `package.json` has a `workspaces` property that tells NPM how to find the projects in the repository.
|
||||||
|
|
||||||
|
```json title="package.json"
|
||||||
|
{
|
||||||
|
"workspaces": ["packages/*"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Because of this setting, when the install command is run at the root, the correct packages are installed for each project. NPM will create dedicated `node_modules` folders inside of each project folder where necessary. The `npm install` command has already been run for you.
|
||||||
|
|
||||||
|
Now let's try running some tasks. To build the `animals` package, use the `build` npm script:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm run build -w @tuskdesign/animals
|
||||||
|
```
|
||||||
|
|
||||||
|
The repository is set up using [TypeScript project references](https://www.typescriptlang.org/docs/handbook/project-references.html) so building the `zoo` package will automatically build all its dependencies.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm run build -w @tuskdesign/zoo
|
||||||
|
```
|
||||||
|
|
||||||
|
To run the `zoo` package use the `serve` script:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm run serve -w @tuskdesign/zoo
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see a message like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
Bo the pig says oink!
|
||||||
|
```
|
||||||
|
|
||||||
|
Now that you have a basic understanding of the repository we're working with, let's see how Nx can help us.
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
type: chapter
|
||||||
|
title: Intro
|
||||||
|
---
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../../../../templates/ts-packages/lesson-1"
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/nx/schemas/nx-schema.json",
|
||||||
|
"targetDefaults": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": ["{projectRoot}/dist"],
|
||||||
|
"cache": true
|
||||||
|
},
|
||||||
|
"typecheck": {
|
||||||
|
"cache": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultBase": "main"
|
||||||
|
}
|
||||||
1725
nx-dev/tutorial/src/content/tutorial/1-ts-packages/2-smart-monorepo/2-nx-init/_solution/package-lock.json
generated
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "@tuskdesign/source",
|
||||||
|
"workspaces": [
|
||||||
|
"packages/*"
|
||||||
|
],
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"tslib": "^2.8.0",
|
||||||
|
"typescript": "~5.5.2",
|
||||||
|
"nx": "20.6.4"
|
||||||
|
},
|
||||||
|
"dependencies": {}
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
type: lesson
|
||||||
|
title: Add Nx
|
||||||
|
focus: /package.json
|
||||||
|
---
|
||||||
|
|
||||||
|
## Smart Monorepo
|
||||||
|
|
||||||
|
<!-- {% video-link link="https://youtu.be/ZA9K4iT3ANc?t=170" /%} -->
|
||||||
|
|
||||||
|
Nx offers many features, but at its core, it is a task runner. Out of the box, it can cache your tasks and ensure those tasks are run in the correct order. After the initial set up, you can incrementally add on other features that would be helpful in your organization.
|
||||||
|
|
||||||
|
:::info
|
||||||
|
|
||||||
|
##### In-Browser Editing Tips
|
||||||
|
|
||||||
|
Instructions for each step will appear in this left-hand panel. You can edit files in the editor in the top right and execute terminal commands in the bottom right.
|
||||||
|
|
||||||
|
The file system and terminal will be reset at each step of the tutorial, so don't worry about breaking the workspace while experimenting. If you are stuck on a step, click the `Solve` button in the top right to set the file system to the solved state.
|
||||||
|
|
||||||
|
If you experience technical issues with the in-browser tutorial, try refreshing the page or use the download button in the top right to download the files for the current step to your local machine.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Add Nx
|
||||||
|
|
||||||
|
To enable Nx in your repository, run a single command:
|
||||||
|
|
||||||
|
```shell {% path="~/tuskydesigns" %}
|
||||||
|
npx nx@latest init
|
||||||
|
```
|
||||||
|
|
||||||
|
This command will download the latest version of Nx and help set up your repository to take advantage of it.
|
||||||
|
|
||||||
|
The script asks a series of questions to help set up caching for you.
|
||||||
|
|
||||||
|
- `Which scripts need to be run in order?` - Choose `build`
|
||||||
|
- `Which scripts are cacheable?` - Choose `build` and `typecheck`
|
||||||
|
- `Does the "build" script create any outputs?` - Enter `dist`
|
||||||
|
- `Does the "typecheck" script create any outputs?` - Enter nothing
|
||||||
|
- `Would you like remote caching to make your build faster?` - Choose `Skip for now`
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../../../../templates/ts-packages/lesson-2"
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
type: lesson
|
||||||
|
title: Explore Your Workspace
|
||||||
|
focus: /nx.json
|
||||||
|
previews:
|
||||||
|
- { port: 4211, title: 'Nx Graph', pathname: 'projects/%40tuskdesign%2Fzoo' }
|
||||||
|
---
|
||||||
|
|
||||||
|
### Explore Your Workspace
|
||||||
|
|
||||||
|
<!-- {% video-link link="https://youtu.be/ZA9K4iT3ANc?t=250" /%} -->
|
||||||
|
|
||||||
|
If you run `nx graph` as instructed, you'll see the dependencies between your projects.
|
||||||
|
|
||||||
|
```shell {% path="~/tuskydesigns" %}
|
||||||
|
npx nx graph --focus=@tuskdesign/zoo
|
||||||
|
```
|
||||||
|
|
||||||
|
Nx uses this graph to determine the order tasks are run and enforce module boundaries. You can also leverage this graph to gain an accurate understanding of the architecture of your codebase. Part of what makes this graph invaluable is that it is derived directly from your codebase, so it will never become out of date.
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../../../../templates/ts-packages/lesson-2"
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/nx/schemas/nx-schema.json",
|
||||||
|
"targetDefaults": {
|
||||||
|
"serve": {
|
||||||
|
"dependsOn": ["build"]
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": ["{projectRoot}/dist"],
|
||||||
|
"cache": true
|
||||||
|
},
|
||||||
|
"typecheck": {
|
||||||
|
"cache": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultBase": "main"
|
||||||
|
}
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
type: lesson
|
||||||
|
title: Create a Task Pipeline
|
||||||
|
focus: /nx.json
|
||||||
|
---
|
||||||
|
|
||||||
|
# Create a Task Pipeline
|
||||||
|
|
||||||
|
<!-- {% video-link link="https://youtu.be/ZA9K4iT3ANc?t=450" /%} -->
|
||||||
|
|
||||||
|
You may have noticed in the `packages/zoo/package.json` file, there is a `serve` script that expects the `build` task to already have created the `dist` folder. Let's set up a task pipeline that will guarantee that the project's `build` task has been run.
|
||||||
|
|
||||||
|
```json title="nx.json" {4-6}
|
||||||
|
{
|
||||||
|
"$schema": "./node_modules/nx/schemas/nx-schema.json",
|
||||||
|
"targetDefaults": {
|
||||||
|
"serve": {
|
||||||
|
"dependsOn": ["build"]
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": ["{projectRoot}/dist"],
|
||||||
|
"cache": true
|
||||||
|
},
|
||||||
|
"typecheck": {
|
||||||
|
"cache": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultBase": "main"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `serve` target's `dependsOn` line makes Nx run the `build` task for the current project before running the current project's `build` task.
|
||||||
|
|
||||||
|
Now `nx serve` will run the `build` task before running the `serve` task.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx nx serve zoo
|
||||||
|
```
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../../../../templates/ts-packages/lesson-4"
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "@tuskdesign/animals",
|
||||||
|
"version": "1.2.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"module": "./dist/index.mjs",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"dist/**"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc --build tsconfig.lib.json",
|
||||||
|
"typecheck": "tsc"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "18.16.9",
|
||||||
|
"typescript": "~5.5.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "@tuskdesign/names",
|
||||||
|
"version": "1.2.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"module": "./dist/index.mjs",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"dist/**"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc --build tsconfig.lib.json",
|
||||||
|
"typecheck": "tsc"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "18.16.9",
|
||||||
|
"typescript": "~5.5.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "@tuskdesign/zoo",
|
||||||
|
"version": "1.2.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"module": "./dist/index.mjs",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"dist/**"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"serve": "node dist/index.js",
|
||||||
|
"build": "tsc --build tsconfig.lib.json",
|
||||||
|
"typecheck": "tsc"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@tuskdesign/animals": "*",
|
||||||
|
"@tuskdesign/names": "*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "18.16.9",
|
||||||
|
"typescript": "~5.5.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/nx/schemas/nx-schema.json",
|
||||||
|
"targetDefaults": {
|
||||||
|
"serve": {
|
||||||
|
"dependsOn": ["build"]
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": ["{projectRoot}/dist"],
|
||||||
|
"cache": true
|
||||||
|
},
|
||||||
|
"typecheck": {
|
||||||
|
"cache": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultBase": "main",
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"plugin": "@nx/js/typescript",
|
||||||
|
"options": {
|
||||||
|
"typecheck": {
|
||||||
|
"targetName": "typecheck"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"targetName": "build",
|
||||||
|
"configName": "tsconfig.lib.json",
|
||||||
|
"buildDepsName": "build-deps",
|
||||||
|
"watchDepsName": "watch-deps"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
4592
nx-dev/tutorial/src/content/tutorial/1-ts-packages/2-smart-monorepo/5-add-plugin/_solution/package-lock.json
generated
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "@tuskdesign/source",
|
||||||
|
"workspaces": [
|
||||||
|
"packages/*"
|
||||||
|
],
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@nx/js": "^20.6.4",
|
||||||
|
"@swc-node/register": "~1.9.1",
|
||||||
|
"@swc/core": "~1.5.7",
|
||||||
|
"@swc/helpers": "~0.5.11",
|
||||||
|
"nx": "20.6.4",
|
||||||
|
"tslib": "^2.8.0",
|
||||||
|
"typescript": "~5.5.2"
|
||||||
|
},
|
||||||
|
"dependencies": {}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "@tuskdesign/animals",
|
||||||
|
"version": "1.2.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"module": "./dist/index.mjs",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"dist/**"
|
||||||
|
],
|
||||||
|
"scripts": {},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "18.16.9",
|
||||||
|
"typescript": "~5.5.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "@tuskdesign/names",
|
||||||
|
"version": "1.2.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"module": "./dist/index.mjs",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"dist/**"
|
||||||
|
],
|
||||||
|
"scripts": {},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "18.16.9",
|
||||||
|
"typescript": "~5.5.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "@tuskdesign/zoo",
|
||||||
|
"version": "1.2.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"module": "./dist/index.mjs",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"dist/**"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"serve": "node dist/index.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@tuskdesign/animals": "*",
|
||||||
|
"@tuskdesign/names": "*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "18.16.9",
|
||||||
|
"typescript": "~5.5.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
---
|
||||||
|
type: lesson
|
||||||
|
title: Add the @nx/js Plugin
|
||||||
|
focus: /nx.json
|
||||||
|
previews:
|
||||||
|
- {
|
||||||
|
port: 4211,
|
||||||
|
title: 'Nx Graph',
|
||||||
|
pathname: 'project-details/%40tuskdesign%2Fzoo',
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
### Use Nx Plugins to Enhance Your Workspace
|
||||||
|
|
||||||
|
We mentioned earlier that this repository is using TypeScript project references defined in the `tsconfig.json` files to incrementally build each project so that the output is available for other projects in the repository. In order for this feature to work, the `references` section in the `tsconfig.json` files for each project need to accurately reflect the actual dependencies of that project. This can be difficult to maintain, but Nx already knows the dependencies of every project and you can use the `@nx/js` plugin to automatically keep the TypeScript project references in sync with the code base.
|
||||||
|
|
||||||
|
Nx plugins can:
|
||||||
|
|
||||||
|
- automatically configure caching for you, including inputs and outputs based on the underlying tooling configuration
|
||||||
|
- infer tasks that can be run on a project because of the tooling present
|
||||||
|
- keep tooling configuration in sync with the structure of your codebase
|
||||||
|
- provide code generators to help scaffold out projects
|
||||||
|
- automatically keep the tooling versions and configuration files up to date
|
||||||
|
|
||||||
|
For this tutorial, we'll focus on inferring tasks and keeping tooling configuration in sync.
|
||||||
|
|
||||||
|
First, let's remove the existing `build` and `typecheck` scripts from each project's `package.json` files to allow the `@nx/js` plugin to infer those tasks for us.
|
||||||
|
|
||||||
|
```file:/packages/animals/package.json del={12-13} title="/packages/animals/package.json" collapse={3-10,15-21}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```file:/packages/names/package.json del={12-13} title="/packages/names/package.json" collapse={3-10,15-21}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```solution:/packages/zoo/package.json ins={12} title="/packages/zoo/package.json" collapse={3-10,14-24}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Now let's add the `@nx/js` plugin:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx nx add @nx/js
|
||||||
|
```
|
||||||
|
|
||||||
|
Your output should look like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
✔ Installing @nx/js...
|
||||||
|
✔ Initializing @nx/js...
|
||||||
|
NX Generating @nx/js:init
|
||||||
|
|
||||||
|
UPDATE nx.json
|
||||||
|
UPDATE package.json
|
||||||
|
|
||||||
|
NX Package @nx/js added successfully.
|
||||||
|
```
|
||||||
|
|
||||||
|
The `nx add` command installs the version of the plugin that matches your repo's Nx version and runs that plugin's initialization script. For `@nx/js`, the initialization script registers the plugin in the `plugins` array of `nx.json`. The registered plugin automatically infers `build` and `typecheck` tasks for any project with a `tsconfig.json` file.
|
||||||
|
|
||||||
|
Open the project details view for the `zoo` package and look at the `build` task.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx nx show project @tuskdesign/zoo
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice that the `inputs` that are inferred for the `build` task match the `include` and `exclude` settings in the `tsconfig.lib.json` file. As those settings are changed, the cache `inputs` will automatically update to the correct values.
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../../../../templates/ts-packages/lesson-5"
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/nx/schemas/nx-schema.json",
|
||||||
|
"targetDefaults": {
|
||||||
|
"serve": {
|
||||||
|
"dependsOn": ["build"]
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": ["{projectRoot}/dist"],
|
||||||
|
"cache": true
|
||||||
|
},
|
||||||
|
"typecheck": {
|
||||||
|
"cache": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultBase": "main"
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "@tuskdesign/source",
|
||||||
|
"workspaces": [
|
||||||
|
"packages/*"
|
||||||
|
],
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@nx/js": "^20.6.4",
|
||||||
|
"@swc-node/register": "~1.9.1",
|
||||||
|
"@swc/core": "~1.5.7",
|
||||||
|
"@swc/helpers": "~0.5.11",
|
||||||
|
"@types/node": "18.16.9",
|
||||||
|
"nx": "20.6.4",
|
||||||
|
"tslib": "^2.8.0",
|
||||||
|
"typescript": "~5.5.2"
|
||||||
|
},
|
||||||
|
"dependencies": {}
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import { getRandomItem } from '@tuskdesign/util';
|
||||||
|
|
||||||
|
export type Animal = {
|
||||||
|
name: string;
|
||||||
|
sound: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ANIMALS = [
|
||||||
|
{
|
||||||
|
name: "dog",
|
||||||
|
sound: "bark",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cat",
|
||||||
|
sound: "meow",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cow",
|
||||||
|
sound: "moo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "rooster",
|
||||||
|
sound: "cock-a-doodle-doo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pig",
|
||||||
|
sound: "oink",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getRandomAnimal() {
|
||||||
|
return getRandomItem(ANIMALS);
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "../util"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.lib.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"rootDir": ".",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
"outDir": "dist",
|
||||||
|
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo",
|
||||||
|
"emitDeclarationOnly": false,
|
||||||
|
"types": [
|
||||||
|
"node"
|
||||||
|
],
|
||||||
|
"declaration": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.ts"
|
||||||
|
],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "../util/tsconfig.lib.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"dist",
|
||||||
|
"src/**/*.test.ts",
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.test.tsx",
|
||||||
|
"src/**/*.spec.tsx",
|
||||||
|
"src/**/*.test.js",
|
||||||
|
"src/**/*.spec.js",
|
||||||
|
"src/**/*.test.jsx",
|
||||||
|
"src/**/*.spec.jsx"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,103 @@
|
|||||||
|
import { getRandomItem } from '@tuskdesign/util';
|
||||||
|
|
||||||
|
export const NAMES = [
|
||||||
|
"Brie",
|
||||||
|
"Vinnie",
|
||||||
|
"Romeo",
|
||||||
|
"Kid",
|
||||||
|
"Stella",
|
||||||
|
"Cody",
|
||||||
|
"Nickers",
|
||||||
|
"Lynx",
|
||||||
|
"Kramer",
|
||||||
|
"Arrow",
|
||||||
|
"Mulligan",
|
||||||
|
"Kallie",
|
||||||
|
"Dante",
|
||||||
|
"Kane",
|
||||||
|
"Klaus",
|
||||||
|
"Dolly",
|
||||||
|
"Moose",
|
||||||
|
"Bella",
|
||||||
|
"Quincy",
|
||||||
|
"Tuck",
|
||||||
|
"Jenny",
|
||||||
|
"Pirate",
|
||||||
|
"Tucker",
|
||||||
|
"Lilly",
|
||||||
|
"Monkey",
|
||||||
|
"Tessie",
|
||||||
|
"Tyler",
|
||||||
|
"Patty",
|
||||||
|
"Hammer",
|
||||||
|
"Freddy",
|
||||||
|
"Snoop",
|
||||||
|
"Maggie-moo",
|
||||||
|
"Connor",
|
||||||
|
"Erin",
|
||||||
|
"Simone",
|
||||||
|
"Buddie",
|
||||||
|
"Bobo",
|
||||||
|
"Baby Doll",
|
||||||
|
"Bugsey",
|
||||||
|
"Wally",
|
||||||
|
"Bullet",
|
||||||
|
"Bo",
|
||||||
|
"Opie",
|
||||||
|
"Alex",
|
||||||
|
"Carley",
|
||||||
|
"Mason",
|
||||||
|
"Boris",
|
||||||
|
"Duchess",
|
||||||
|
"Smoke",
|
||||||
|
"Skinny",
|
||||||
|
"Wilber",
|
||||||
|
"Georgie",
|
||||||
|
"Axel",
|
||||||
|
"Tramp",
|
||||||
|
"Cinnamon",
|
||||||
|
"Freckles",
|
||||||
|
"Jimmuy",
|
||||||
|
"Copper",
|
||||||
|
"Toto",
|
||||||
|
"Puck",
|
||||||
|
"Jojo",
|
||||||
|
"Grover",
|
||||||
|
"Grizzly",
|
||||||
|
"Tequila",
|
||||||
|
"Tipr",
|
||||||
|
"Pepper",
|
||||||
|
"Clyde",
|
||||||
|
"Morgan",
|
||||||
|
"Poppy",
|
||||||
|
"Killian",
|
||||||
|
"Odie",
|
||||||
|
"Ralphie",
|
||||||
|
"Frisky",
|
||||||
|
"Elwood",
|
||||||
|
"Flower",
|
||||||
|
"Ivy",
|
||||||
|
"Kiwi",
|
||||||
|
"Tippy",
|
||||||
|
"Sandy",
|
||||||
|
"Salem",
|
||||||
|
"Sheena",
|
||||||
|
"Flint",
|
||||||
|
"Macho",
|
||||||
|
"Moonshine",
|
||||||
|
"Jetta",
|
||||||
|
"Gabby",
|
||||||
|
"Oscar",
|
||||||
|
"Rollie",
|
||||||
|
"Cotton",
|
||||||
|
"Oakley",
|
||||||
|
"Ruby",
|
||||||
|
"Jackpot",
|
||||||
|
"Dudley",
|
||||||
|
"Fancy",
|
||||||
|
"Howie",
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getRandomName(): string {
|
||||||
|
return getRandomItem(NAMES);
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "../util"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.lib.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"rootDir": ".",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
"outDir": "dist",
|
||||||
|
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo",
|
||||||
|
"emitDeclarationOnly": false,
|
||||||
|
"declaration": true,
|
||||||
|
"types": [
|
||||||
|
"node"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.ts"
|
||||||
|
],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "../util/tsconfig.lib.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"dist",
|
||||||
|
"src/**/*.test.ts",
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.test.tsx",
|
||||||
|
"src/**/*.spec.tsx",
|
||||||
|
"src/**/*.test.js",
|
||||||
|
"src/**/*.spec.js",
|
||||||
|
"src/**/*.test.jsx",
|
||||||
|
"src/**/*.spec.jsx"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
# util
|
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev).
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
Run `nx build util` to build the library.
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "@tuskdesign/util",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"module": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
"./package.json": "./package.json",
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './lib/util.js';
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
export function getRandomItem<T>(arr: T[]): T {
|
||||||
|
return arr[Math.floor(Math.random() * arr.length)];
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"files": [],
|
||||||
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.lib.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "dist",
|
||||||
|
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo",
|
||||||
|
"emitDeclarationOnly": false,
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"],
|
||||||
|
"references": []
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "../names"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../animals"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.lib.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.base.json",
|
||||||
|
"compileOnSave": false,
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./packages/animals"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./packages/names"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./packages/zoo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./packages/util"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
type: lesson
|
||||||
|
title: Sync TypeScript References
|
||||||
|
focus: /nx.json
|
||||||
|
previews:
|
||||||
|
- {
|
||||||
|
port: 4211,
|
||||||
|
title: 'Nx Graph',
|
||||||
|
pathname: 'project-details/%40tuskdesign%2Fzoo',
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sync TypeScript References
|
||||||
|
|
||||||
|
The `build` task for the `zoo` project has a [sync generator](/concepts/sync-generators) defined. The `@nx/js:typescript-sync` generator will automatically update the `references` property in the `tsconfig.json` files across the repository to match the actual dependencies in your code.
|
||||||
|
|
||||||
|
Let's see this behavior in action by extracting some common code into a new `util` library.
|
||||||
|
|
||||||
|
First, create a library with `@nx/js:lib` generator:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
nx g @nx/js:lib packages/util
|
||||||
|
```
|
||||||
|
|
||||||
|
Set the bundler to `tsc`, the linter to `none` and the unit test runner to `none`.
|
||||||
|
|
||||||
|
Now we can move the `getRandomItem` function from `packages/names/names.ts` and `packages/animals/animals.ts` into the `packages/util/src/lib/util.ts` file.
|
||||||
|
|
||||||
|
```ts title="packages/util/src/lib/util.ts"
|
||||||
|
export function getRandomItem<T>(arr: T[]): T {
|
||||||
|
return arr[Math.floor(Math.random() * arr.length)];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts title="packages/animals/animals.ts"
|
||||||
|
import { getRandomItem } from '@tuskdesign/util';
|
||||||
|
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts title="packages/names/names.ts"
|
||||||
|
import { getRandomItem } from '@tuskdesign/util';
|
||||||
|
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Now if you run the build, Nx will notice that the TypeScript project references need to be updated and ask your permission to update them.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
nx build zoo
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
NX The workspace is out of sync
|
||||||
|
|
||||||
|
The @nx/js:typescript-sync sync generator identified 6 files in the workspace that are out of sync:
|
||||||
|
Based on the workspace project graph, some TypeScript configuration files are missing project references to the projects they depend on or contain outdated project references.
|
||||||
|
|
||||||
|
Please note that having the workspace out of sync will result in an error in CI.
|
||||||
|
|
||||||
|
? Would you like to sync the identified changes to get your workspace up to date? …
|
||||||
|
❯ Yes, sync the changes and run the tasks
|
||||||
|
No, run the tasks without syncing the changes
|
||||||
|
```
|
||||||
|
|
||||||
|
Allow the sync to happen and you'll see that the `tsconfig.json` and `tsconfig.lib.json` files have been updated to include references to the new `util` library. With this system in place, no matter how your codebase changes, the TypeScript project references will always be correct.
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../../../../templates/ts-packages/lesson-6"
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
type: lesson
|
||||||
|
title: Checkpoint
|
||||||
|
previews:
|
||||||
|
- {
|
||||||
|
port: 4211,
|
||||||
|
title: 'Nx Graph',
|
||||||
|
pathname: 'tasks/serve?projects=%40tuskdesign%2Fzoo',
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
## Checkpoint
|
||||||
|
|
||||||
|
At this point, the repository is still using all the same tools to run tasks, but now Nx runs those tasks in a smarter way. The tasks are efficiently cached so that there is no repeated work and the cache configuration settings are automatically synced with your tooling configuration files by Nx plugins. Also, any task dependencies are automatically executed whenever needed because we configured task pipelines for the projects.
|
||||||
|
|
||||||
|
Open up the task graph for `zoo` app's `serve` task again to see the changes.
|
||||||
|
|
||||||
|
```shell {% path="~/tuskydesigns" %}
|
||||||
|
npx nx run @tuskdesign/zoo:serve --graph
|
||||||
|
```
|
||||||
|
|
||||||
|
The rest of this tutorial covers managing releases and setting up CI, which both require you to have a local copy of the repository. Click the "Download Repository" button and extract the zip file on your local machine before moving to the next section.
|
||||||
|
|
||||||
|
import { DownloadButton } from '../../../../../components/DownloadButton';
|
||||||
|
|
||||||
|
<div class="my-5">
|
||||||
|
<DownloadButton client:load></DownloadButton>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
type: chapter
|
||||||
|
title: Smart Monorepo
|
||||||
|
---
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../../../../templates/ts-packages/lesson-6"
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
type: lesson
|
||||||
|
title: Manage Releases
|
||||||
|
---
|
||||||
|
|
||||||
|
import { DownloadButton } from '../../../../../components/DownloadButton';
|
||||||
|
|
||||||
|
## Manage Releases
|
||||||
|
|
||||||
|
:::info
|
||||||
|
This section of the tutorial can not be run in the browser. Please download the current state of the workspace and follow the instructions in your local environment.
|
||||||
|
|
||||||
|
<div class="my-5">
|
||||||
|
<DownloadButton client:load></DownloadButton>
|
||||||
|
</div>
|
||||||
|
:::
|
||||||
|
|
||||||
|
If you decide to publish the `animals` or `names` packages on NPM, Nx can also help you [manage the release process](/features/manage-releases). Release management involves updating the version of your package, populating a changelog, and publishing the new version to the NPM registry.
|
||||||
|
|
||||||
|
First you'll need to define which projects Nx should manage releases for by setting the `release.projects` property in `nx.json`:
|
||||||
|
|
||||||
|
```json {% fileName="nx.json" %}
|
||||||
|
{
|
||||||
|
"release": {
|
||||||
|
"projects": ["packages/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you're ready to use the `nx release` command to publish the `animals` and `names` packages. The first time you run `nx release`, you need to add the `--first-release` flag so that Nx doesn't try to find the previous version to compare against. It's also recommended to use the `--dry-run` flag until you're sure about the results of the `nx release` command, then you can run it a final time without the `--dry-run` flag.
|
||||||
|
|
||||||
|
To preview your first release, run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx nx release --first-release --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
The command will ask you a series of questions and then show you what the results would be. Once you are happy with the results, run it again without the `--dry-run` flag:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx nx release --first-release
|
||||||
|
```
|
||||||
|
|
||||||
|
After this first release, you can remove the `--first-release` flag and just run `nx release --dry-run`. There is also a [dedicated feature page](/features/manage-releases) that goes into more detail about how to use the `nx release` command.
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
type: chapter
|
||||||
|
title: Manage Releases
|
||||||
|
editor: false
|
||||||
|
previews: false
|
||||||
|
terminal: false
|
||||||
|
---
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../../../../templates/ts-packages/lesson-6"
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
type: lesson
|
||||||
|
title: About Nx Cloud
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fast CI ⚡
|
||||||
|
|
||||||
|
import { DownloadButton } from '../../../../../components/DownloadButton';
|
||||||
|
|
||||||
|
:::info
|
||||||
|
This section of the tutorial can not be run in the browser. Please download the current state of the workspace and follow the instructions in your local environment.
|
||||||
|
|
||||||
|
<div class="my-5">
|
||||||
|
<DownloadButton client:load></DownloadButton>
|
||||||
|
</div>
|
||||||
|
:::
|
||||||
|
|
||||||
|
So far in this tutorial you've seen how Nx improves the local development experience, but the biggest difference Nx makes is in CI. As repositories get bigger, making sure that the CI is fast, reliable and maintainable can get very challenging. Nx provides a solution.
|
||||||
|
|
||||||
|
- Nx reduces wasted time in CI with the [`affected` command](/ci/features/affected).
|
||||||
|
- Nx Replay's [remote caching](/ci/features/remote-cache) will reuse task artifacts from different CI executions making sure you will never run the same computation twice.
|
||||||
|
- Nx Agents [efficiently distribute tasks across machines](/ci/features/distribute-task-execution) ensuring constant CI time regardless of the repository size. The right number of machines is allocated for each PR to ensure good performance without wasting compute.
|
||||||
|
- Nx Atomizer [automatically splits](/ci/features/split-e2e-tasks) large e2e tests to distribute them across machines. Nx can also automatically [identify and rerun flaky e2e tests](/ci/features/flaky-tasks).
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
type: lesson
|
||||||
|
title: Connect to Nx Cloud
|
||||||
|
---
|
||||||
|
|
||||||
|
## Connect to Nx Cloud
|
||||||
|
|
||||||
|
Nx Cloud is a companion app for your CI system that provides remote caching, task distribution, e2e tests deflaking, better DX and more.
|
||||||
|
|
||||||
|
Now that we're working on the CI pipeline, it is important for your changes to be pushed to a GitHub repository.
|
||||||
|
|
||||||
|
1. Create a [new GitHub repository](https://github.com/new) if you don't already have one.
|
||||||
|
2. Copy the path to your repository (i.e. https://github.com/[your-user-name]/[your-repo-name].git)
|
||||||
|
3. In the terminal, add the GitHub repository as a remote with `git remote add origin https://github.com/[your-user-name]/[your-repo-name].git`
|
||||||
|
4. Commit your existing changes with `git add . && git commit -am "updates"`
|
||||||
|
5. Push your changes to your forked GitHub repository with `git push -u origin HEAD`
|
||||||
|
|
||||||
|
Now connect your repository to Nx Cloud with the following command:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx nx connect
|
||||||
|
```
|
||||||
|
|
||||||
|
A browser window will open to register your repository in your [Nx Cloud](https://cloud.nx.app) account. The link is also printed to the terminal if the windows does not open, or you closed it before finishing the steps. The app will guide you to create a PR to enable Nx Cloud on your repository.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Once the PR is created, merge it into your main branch.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
And make sure you pull the latest changes locally:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git pull
|
||||||
|
```
|
||||||
|
|
||||||
|
You should now have an `nxCloudId` property specified in the `nx.json` file.
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
type: lesson
|
||||||
|
title: Create a CI Workflow
|
||||||
|
---
|
||||||
|
|
||||||
|
## Create a CI Workflow
|
||||||
|
|
||||||
|
Use the following command to generate a CI workflow file.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx nx generate ci-workflow --ci=github
|
||||||
|
```
|
||||||
|
|
||||||
|
This generator creates a `.github/workflows/ci.yml` file that contains a CI pipeline that will run the `lint`, `test`, `build` and `e2e` tasks for projects that are affected by any given PR. If you would like to also distribute tasks across multiple machines to ensure fast and reliable CI runs, uncomment the `nx-cloud start-ci-run` line and have the `nx affected` line run the `e2e-ci` task instead of `e2e`.
|
||||||
|
|
||||||
|
The key lines in the CI pipeline are:
|
||||||
|
|
||||||
|
```yml {% fileName=".github/workflows/ci.yml" highlightLines=["10-14", "21-23"] %}
|
||||||
|
name: CI
|
||||||
|
# ...
|
||||||
|
jobs:
|
||||||
|
main:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
# This enables task distribution via Nx Cloud
|
||||||
|
# Run this command as early as possible, before dependencies are installed
|
||||||
|
# Learn more at https://nx.dev/ci/reference/nx-cloud-cli#npx-nxcloud-startcirun
|
||||||
|
# Uncomment this line to enable task distribution
|
||||||
|
# - run: npx nx-cloud start-ci-run --distribute-on="3 linux-medium-js" --stop-agents-after="e2e-ci"
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: 'npm'
|
||||||
|
- run: npm ci --legacy-peer-deps
|
||||||
|
- uses: nrwl/nx-set-shas@v4
|
||||||
|
# Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected
|
||||||
|
# When you enable task distribution, run the e2e-ci task instead of e2e
|
||||||
|
- run: npx nx affected -t lint test build e2e
|
||||||
|
```
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
type: lesson
|
||||||
|
title: Open a Pull Request
|
||||||
|
---
|
||||||
|
|
||||||
|
## Open a Pull Request
|
||||||
|
|
||||||
|
Commit the changes and open a new PR on GitHub.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git add .
|
||||||
|
git commit -m 'add CI workflow file'
|
||||||
|
git push origin add-workflow
|
||||||
|
```
|
||||||
|
|
||||||
|
When you view the PR on GitHub, you will see a comment from Nx Cloud that reports on the status of the CI run.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The `See all runs` link goes to a page with the progress and results of tasks that were run in the CI pipeline.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
For more information about how Nx can improve your CI pipeline, check out one of these detailed tutorials:
|
||||||
|
|
||||||
|
- [Circle CI with Nx](/ci/intro/tutorials/circle)
|
||||||
|
- [GitHub Actions with Nx](/ci/intro/tutorials/github-actions)
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Connect with the rest of the Nx community with these resources:
|
||||||
|
|
||||||
|
- ⭐️ [Star us on GitHub](https://github.com/nrwl/nx) to show your support and stay updated on new releases!
|
||||||
|
- [Join the Official Nx Discord Server](https://go.nx.dev/community) to ask questions and find out the latest news about Nx.
|
||||||
|
- [Follow Nx on Twitter](https://twitter.com/nxdevtools) to stay up to date with Nx news
|
||||||
|
- [Read our Nx blog](/blog)
|
||||||
|
- [Subscribe to our Youtube channel](https://www.youtube.com/@nxdevtools) for demos and Nx insights
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
type: chapter
|
||||||
|
title: Fast CI
|
||||||
|
editor: false
|
||||||
|
previews: false
|
||||||
|
terminal: false
|
||||||
|
---
|
||||||
17
nx-dev/tutorial/src/content/tutorial/1-ts-packages/meta.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
type: part
|
||||||
|
title: TypeScript Packages
|
||||||
|
meta:
|
||||||
|
title: 'Nx Tutorial: TypeScript Packages'
|
||||||
|
image: /images/nx-media.png
|
||||||
|
editor:
|
||||||
|
fileTree:
|
||||||
|
allowEdits: true
|
||||||
|
filesystem:
|
||||||
|
watch: ['/*.json', '/packages/**']
|
||||||
|
downloadAsZip: true
|
||||||
|
terminal:
|
||||||
|
panels: 'terminal'
|
||||||
|
open: true
|
||||||
|
allowRedirects: true
|
||||||
|
---
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../../../../templates/default"
|
||||||
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
type: lesson
|
||||||
|
title: Why Use a Monorepo?
|
||||||
|
---
|
||||||
|
|
||||||
|
# React Monorepo Tutorial
|
||||||
|
|
||||||
|
In this tutorial you'll learn how to use React with Nx in a monorepo setup.
|
||||||
|
|
||||||
|
What will you learn?
|
||||||
|
|
||||||
|
- how to create a new React application
|
||||||
|
- how to run a single task (i.e. serve your app) or run multiple tasks in parallel
|
||||||
|
- how to leverage code generators to scaffold components
|
||||||
|
- how to modularize your codebase and impose architectural constraints for better maintainability
|
||||||
|
- [how to speed up CI with Nx Cloud ⚡](/tutorials/2-react-monorepo/3r-fast-ci/1-welcome)
|
||||||
|
|
||||||
|
## Why Use an Nx Monorepo?
|
||||||
|
|
||||||
|
Nx works along side your existing tooling to improve your experience developing in a monorepo. You can also use code generators that Nx provides to quickly spin up a new project that is pre-configured with industry standard tooling. In this tutorial, we'll set up a monorepo that leverages the same tooling you would typically use without Nx, but Nx will enable you to focus your time on the features of your application rather than the tooling that surrounds it.
|
||||||
|
|
||||||
|
We'll use npm/yarn/pnpm workspaces to link projects and TypeScript project references to incrementally typecheck the repository. Nx works well whether you have a [single version policy](/concepts/decisions/dependency-management#single-version-policy) or have each project [separately define their dependencies](/concepts/decisions/dependency-management#independently-maintained-dependencies), but in this tutorial we'll define all dependencies in a single `package.json` at the root of the repository.
|
||||||
|
|
||||||
|
Nx generators will automatically configure new projects with useful tools like Prettier, ESLint and Jest. Those generators will also make sure that each project is referenced correctly in the root `workspaces` property and the `tsconfig` references properties accurately reflect the dependencies of each project.
|
||||||
|
|
||||||
|
Nx Plugins are optional packages that extend the capabilities of Nx, catering to various specific technologies. For instance, we have plugins tailored to React (e.g., `@nx/react`), Vite (`@nx/vite`), Cypress (`@nx/cypress`), and more. These plugins offer additional features, making your development experience more efficient and enjoyable when working with specific tech stacks.
|
||||||
|
|
||||||
|
Features we'll use in this monorepo:
|
||||||
|
|
||||||
|
- [Install dependencies at the root by default](/concepts/decisions/dependency-management#single-version-policy)
|
||||||
|
- [Scaffold new code with generators](/features/generate-code)
|
||||||
|
- [Updates dependencies with automated migrations](/features/automate-updating-dependencies)
|
||||||
|
|
||||||
|
Visit our ["Why Nx" page](/getting-started/why-nx) for more details.
|
||||||
|
|
||||||
|
## Final Code
|
||||||
|
|
||||||
|
Here's the source code of the final result for this tutorial.
|
||||||
|
|
||||||
|
import { GithubRepository } from '@nx/nx-dev/ui-markdoc';
|
||||||
|
|
||||||
|
<GithubRepository url="https://github.com/nrwl/nx-recipes/tree/main/react-monorepo"></GithubRepository>
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
type: chapter
|
||||||
|
title: Introduction
|
||||||
|
editor: false
|
||||||
|
previews: false
|
||||||
|
terminal: false
|
||||||
|
---
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../../../../templates/react-monorepo/lesson-7"
|
||||||
|
}
|
||||||
@ -0,0 +1,124 @@
|
|||||||
|
---
|
||||||
|
type: lesson
|
||||||
|
title: Run Affected Tasks
|
||||||
|
previews:
|
||||||
|
- {
|
||||||
|
port: 4211,
|
||||||
|
title: 'Affected Project Graph',
|
||||||
|
pathname: '/projects/affected',
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
## Test Affected Projects
|
||||||
|
|
||||||
|
{/* {import { VideoLink } from '@nx/nx-dev/ui-markdoc';} */}
|
||||||
|
|
||||||
|
{/* <VideoLink link="https://youtu.be/gc4N7kxiA50?t=614"></VideoLink> */}
|
||||||
|
|
||||||
|
One of the key features of Nx in a monorepo setting is that you're able to run tasks only for projects that are actually affected by the code changes that you've made. This feature relies on `git` to determine which files have been changed.
|
||||||
|
|
||||||
|
If we were developing locally, you could commit your changes so far and then make a small change to the `products` library.
|
||||||
|
|
||||||
|
```tsx title="libs/products/src/lib/products.tsx"
|
||||||
|
import styles from './products.module.css';
|
||||||
|
|
||||||
|
export function Products() {
|
||||||
|
return (
|
||||||
|
<div className={styles['container']}>
|
||||||
|
<h1>Welcome to Products!</h1>
|
||||||
|
<p>This is a change. 👋</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Products;
|
||||||
|
```
|
||||||
|
|
||||||
|
Then the following command would run the tests for only the projects affected by this change:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# This does not work in the browser webcontainer
|
||||||
|
npx nx affected -t test
|
||||||
|
```
|
||||||
|
|
||||||
|
:::info
|
||||||
|
This webcontainer does not have `git` enabled, so we have to manually tell Nx which files were touched.
|
||||||
|
:::
|
||||||
|
|
||||||
|
Use this command in the webcontainer terminal to manually specify which files have been touched.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx nx affected -t test --files=libs/products/src/lib/products.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the unit tests were run for `products`, `react-store` and `inventory`, but not for `orders` because a change to `products` can not possibly break the tests for `orders`. In a small repo like this, there isn't a lot of time saved, but as there are more tests and more projects, this quickly becomes an essential command.
|
||||||
|
|
||||||
|
You can also see what projects are affected in the graph visualizer with;
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx nx graph --affected --files=libs/products/src/lib/products.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build the Apps for Deployment
|
||||||
|
|
||||||
|
{/* <VideoLink link="https://youtu.be/gc4N7kxiA50?t=713"></VideoLink> */}
|
||||||
|
|
||||||
|
If you're ready and want to ship your applications, you can build them using
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx nx run-many -t build
|
||||||
|
```
|
||||||
|
|
||||||
|
All the required files will be placed in `/apps/react-store/dist` and `/apps/inventory/dist` and can be deployed to your favorite hosting provider.
|
||||||
|
|
||||||
|
Nx will run any script defined in `package.json`, so you can create a `deploy` task that sends the build output to your hosting provider.
|
||||||
|
|
||||||
|
```json {% fileName="apps/react-store/package.json" %}
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"deploy": "netlify deploy --dir=dist"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We want to let Nx know that the `build` task needs to be run before the `deploy` task, so we add a `dependsOn` property for that target.
|
||||||
|
|
||||||
|
```json title="apps/react-store/package.json"
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"deploy": "netlify deploy --dir=dist"
|
||||||
|
},
|
||||||
|
"nx": {
|
||||||
|
"targets": {
|
||||||
|
"deploy": {
|
||||||
|
"dependsOn": ["build"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to keep the script next to its Nx configuration, you can rewrite the above configuration like this:
|
||||||
|
|
||||||
|
```json title="apps/react-store/package.json"
|
||||||
|
{
|
||||||
|
"scripts": {},
|
||||||
|
"nx": {
|
||||||
|
"targets": {
|
||||||
|
"deploy": {
|
||||||
|
"command": "netlify deploy --dir=dist",
|
||||||
|
"dependsOn": ["build"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace the `deploy` script with whatever terminal command you use to deploy your site.
|
||||||
|
|
||||||
|
The `"dependsOn": ["build"]` setting tells Nx to make sure that the project's `build` task has been run successfully before the `deploy` task.
|
||||||
|
|
||||||
|
With the `deploy` tasks defined, you can deploy a single application with `npx nx deploy react-store` or deploy any applications affected by the current changes with:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx nx affected -t deploy
|
||||||
|
```
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../../../../templates/react-monorepo/lesson-7"
|
||||||
|
}
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
import nx from '@nx/eslint-plugin';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
...nx.configs['flat/base'],
|
||||||
|
...nx.configs['flat/typescript'],
|
||||||
|
...nx.configs['flat/javascript'],
|
||||||
|
{
|
||||||
|
ignores: ['**/dist'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||||
|
rules: {
|
||||||
|
'@nx/enforce-module-boundaries': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
enforceBuildableLibDependency: true,
|
||||||
|
allow: ['^.*/eslint(\\.base)?\\.config\\.[cm]?js$'],
|
||||||
|
depConstraints: [
|
||||||
|
{
|
||||||
|
sourceTag: "type:feature",
|
||||||
|
onlyDependOnLibsWithTags: ["type:feature", "type:ui"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: "type:ui",
|
||||||
|
onlyDependOnLibsWithTags: ["type:ui"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: "scope:orders",
|
||||||
|
onlyDependOnLibsWithTags: [
|
||||||
|
"scope:orders",
|
||||||
|
"scope:products",
|
||||||
|
"scope:shared"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: "scope:products",
|
||||||
|
onlyDependOnLibsWithTags: ["scope:products", "scope:shared"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: "scope:shared",
|
||||||
|
onlyDependOnLibsWithTags: ["scope:shared"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
'**/*.ts',
|
||||||
|
'**/*.tsx',
|
||||||
|
'**/*.cts',
|
||||||
|
'**/*.mts',
|
||||||
|
'**/*.js',
|
||||||
|
'**/*.jsx',
|
||||||
|
'**/*.cjs',
|
||||||
|
'**/*.mjs',
|
||||||
|
],
|
||||||
|
// Override or add rules here
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
];
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "orders",
|
||||||
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "libs/orders/src",
|
||||||
|
"projectType": "library",
|
||||||
|
"tags": ["type:feature", "scope:orders"],
|
||||||
|
"// targets": "to see all targets run: nx show project orders --web",
|
||||||
|
"targets": {}
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "products",
|
||||||
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "libs/products/src",
|
||||||
|
"projectType": "library",
|
||||||
|
"tags": ["type:feature", "scope:products"],
|
||||||
|
"// targets": "to see all targets run: nx show project products --web",
|
||||||
|
"targets": {}
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "ui",
|
||||||
|
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "libs/shared/ui/src",
|
||||||
|
"projectType": "library",
|
||||||
|
"tags": ["type:ui", "scope:shared"],
|
||||||
|
"// targets": "to see all targets run: nx show project ui --web",
|
||||||
|
"targets": {}
|
||||||
|
}
|
||||||
@ -0,0 +1,177 @@
|
|||||||
|
---
|
||||||
|
type: lesson
|
||||||
|
title: Module Boundary Rules
|
||||||
|
---
|
||||||
|
|
||||||
|
## Imposing Constraints with Module Boundary Rules
|
||||||
|
|
||||||
|
{/* {import { VideoLink } from '@nx/nx-dev/ui-markdoc';} */}
|
||||||
|
|
||||||
|
{/* <VideoLink link="https://youtu.be/gc4N7kxiA50?t=791"></VideoLink> */}
|
||||||
|
|
||||||
|
Once you modularize your codebase you want to make sure that the libs are not coupled to each other in an uncontrolled way. Here are some examples of how we might want to guard our small demo workspace:
|
||||||
|
|
||||||
|
- we might want to allow `orders` to import from `shared-ui` but not the other way around
|
||||||
|
- we might want to allow `orders` to import from `products` but not the other way around
|
||||||
|
- we might want to allow all libraries to import the `shared-ui` components, but not the other way around
|
||||||
|
|
||||||
|
When building these kinds of constraints you usually have two dimensions:
|
||||||
|
|
||||||
|
- **type of project:** what is the type of your library. Example: "feature" library, "utility" library, "data-access" library, "ui" library
|
||||||
|
- **scope (domain) of the project:** what domain area is covered by the project. Example: "orders", "products", "shared" ... this really depends on the type of product you're developing
|
||||||
|
|
||||||
|
Nx comes with a generic mechanism that allows you to assign "tags" to projects. "tags" are arbitrary strings you can assign to a project that can be used later when defining boundaries between projects. For example, go to the `package.json` of your `orders` library and assign the tags `type:feature` and `scope:orders` to it.
|
||||||
|
|
||||||
|
```json title="libs/orders/package.json"
|
||||||
|
{
|
||||||
|
...
|
||||||
|
"nx": {
|
||||||
|
"tags": ["type:feature", "scope:orders"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then go to the `package.json` of your `products` library and assign the tags `type:feature` and `scope:products` to it.
|
||||||
|
|
||||||
|
```json title="libs/products/package.json"
|
||||||
|
{
|
||||||
|
...
|
||||||
|
"nx": {
|
||||||
|
"tags": ["type:feature", "scope:products"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, go to the `package.json` of the `shared-ui` library and assign the tags `type:ui` and `scope:shared` to it.
|
||||||
|
|
||||||
|
```json title="libs/shared/ui/package.json"
|
||||||
|
{
|
||||||
|
...
|
||||||
|
"nx": {
|
||||||
|
"tags": ["type:ui", "scope:shared"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice how we assign `scope:shared` to our UI library because it is intended to be used throughout the workspace.
|
||||||
|
|
||||||
|
Next, let's come up with a set of rules based on these tags:
|
||||||
|
|
||||||
|
- `type:feature` should be able to import from `type:feature` and `type:ui`
|
||||||
|
- `type:ui` should only be able to import from `type:ui`
|
||||||
|
- `scope:orders` should be able to import from `scope:orders`, `scope:shared` and `scope:products`
|
||||||
|
- `scope:products` should be able to import from `scope:products` and `scope:shared`
|
||||||
|
|
||||||
|
To enforce the rules, Nx ships with a custom ESLint rule. Open the `eslint.config.mjs` at the root of the workspace and add the following `depConstraints` in the `@nx/enforce-module-boundaries` rule configuration:
|
||||||
|
|
||||||
|
```js title="eslint.config.mjs"
|
||||||
|
import nx from '@nx/eslint-plugin';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
// ...
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||||
|
rules: {
|
||||||
|
'@nx/enforce-module-boundaries': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
enforceBuildableLibDependency: true,
|
||||||
|
allow: ['^.*/eslint(\\.base)?\\.config\\.[cm]?js$'],
|
||||||
|
depConstraints: [
|
||||||
|
{
|
||||||
|
sourceTag: 'type:feature',
|
||||||
|
onlyDependOnLibsWithTags: ['type:feature', 'type:ui'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'type:ui',
|
||||||
|
onlyDependOnLibsWithTags: ['type:ui'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'scope:orders',
|
||||||
|
onlyDependOnLibsWithTags: [
|
||||||
|
'scope:orders',
|
||||||
|
'scope:products',
|
||||||
|
'scope:shared',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'scope:products',
|
||||||
|
onlyDependOnLibsWithTags: ['scope:products', 'scope:shared'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'scope:shared',
|
||||||
|
onlyDependOnLibsWithTags: ['scope:shared'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: '*',
|
||||||
|
onlyDependOnLibsWithTags: ['*'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// ...
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
To test it, go to your `libs/products/src/lib/products.tsx` file and import the `Orders` component from the `orders` project:
|
||||||
|
|
||||||
|
```tsx title="libs/products/src/lib/products.tsx"
|
||||||
|
import styles from './products.module.css';
|
||||||
|
|
||||||
|
// This import is not allowed 👇
|
||||||
|
import { Orders } from '@react-monorepo/orders';
|
||||||
|
|
||||||
|
export function Products() {
|
||||||
|
return (
|
||||||
|
<div className={styles['container']}>
|
||||||
|
<h1>Welcome to Products!</h1>
|
||||||
|
<p>This is a change. 👋</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Products;
|
||||||
|
```
|
||||||
|
|
||||||
|
If you lint your workspace you'll get an error now:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx nx run-many -t lint
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
✔ nx run @react-monorepo/orders:lint [existing outputs match the cache, left as is]
|
||||||
|
✔ nx run @react-monorepo/react-store:lint [existing outputs match the cache, left as is]
|
||||||
|
✔ nx run @react-monorepo/inventory:lint [existing outputs match the cache, left as is]
|
||||||
|
✔ nx run @react-monorepo/ui:lint [existing outputs match the cache, left as is]
|
||||||
|
✔ nx run inventory-e2e:lint [existing outputs match the cache, left as is]
|
||||||
|
✔ nx run react-store-e2e:lint (877ms)
|
||||||
|
|
||||||
|
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— ✖ nx run @react-monorepo/products:lint
|
||||||
|
> eslint .
|
||||||
|
|
||||||
|
|
||||||
|
/Users/isaac/Documents/code/nx-recipes/react-monorepo/libs/products/src/lib/products.tsx
|
||||||
|
3:1 error A project tagged with "scope:products" can only depend on libs tagged with "scope:products", "scope:shared" @nx/enforce-module-boundaries
|
||||||
|
3:10 warning 'Orders' is defined but never used @typescript-eslint/no-unused-vars
|
||||||
|
|
||||||
|
✖ 2 problems (1 error, 1 warning)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
|
||||||
|
NX Ran target lint for 7 projects (1s)
|
||||||
|
|
||||||
|
✔ 6/7 succeeded [5 read from cache]
|
||||||
|
|
||||||
|
✖ 1/7 targets failed, including the following:
|
||||||
|
|
||||||
|
- nx run @react-monorepo/products:lint
|
||||||
|
```
|
||||||
|
|
||||||
|
If you have the ESLint plugin installed in your IDE you should also immediately see an error.
|
||||||
|
|
||||||
|
Learn more about how to [enforce module boundaries](/features/enforce-module-boundaries).
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../../../../templates/default"
|
||||||
|
}
|
||||||