ReactNxTutorial翻译

https://nx.dev/l/r/tutorial/02-add-e2e-test

React使用Nx框架开发官方教程

1 – 创建应用

在教程中你将会利用Nx框架使用现代化技术和常见库搭建一个全栈应用。

新建工作空间

所有的工作始于创建一个新的工作空间。

npx create-nx-workspace@latest

你将会得到以下提示:

Workspace name (e.g., org name)     myorg
What to create in the new workspace react
Application name                    todos
Default stylesheet format           CSS
ps:这个过程要花些时间,推荐翻墙后再操作。

输入上述选项后Nx将会在当前目录下创建一个全新的react工作空间。

共有两个项目被加入了新建的工作空间:

  • 一个React应用项目
  • 面向React应用的E2E测试项目

开发模式启动新建的React应用

新创建的React应用通过在项目文件夹中执行以下命令进行Serve启动:

npx nx serve todos

关于Nx CLI的Tips

如果更希望通过全局安装的Nx框架运行项目,你可以使用:

nx serve todos

取决于你的开发环境,上述命令结果可能为 Command ‘nx’ not found。

你可以通过npm包管理器全局安装nx cli解决:

npm install -g nx

yarn包管理器则使用:

yarn global add nx

当然你可以通过在每一条命令前添加’npx’运行在本地项目文件夹安装的Nx:

npx nx -- serve todos

yarn包管理器则使用:

yarn nx serve todos

2 – 添加E2E测试

默认情况下Nx使用Cypress作为ESE测试框架。

打开  apps/todos-e2e/src/support/app.po.ts文件。该文件包含用于检索页面元素的helpers(选择器)。

添加以下选择器:

export const getTodos = () => cy.get('li.todo');
export const getAddTodoButton = () => cy.get('button#add-todo');

然后更新apps/todos-e2e/src/integration/app.spec.ts

import { getAddTodoButton, getTodos } from '../support/app.po';

describe('TodoApps', () => {
  beforeEach(() => cy.visit('/'));

  it('should display todos', () => {
    getTodos().should((t) => expect(t.length).equal(2));
    getAddTodoButton().click();
    getTodos().should((t) => expect(t.length).equal(3));
  });
});

这是一个简单地E2E测试,只检测todo列表是否正确渲染。

停止 npx nx serve 命令,而后执行 npx nx e2e todos-e2e –watch

Cypress测试框架UI将会打开。点击右侧“Run 1 integration spec”按钮开始E2E测试并保持E2E测试运行。

可以看到当前测试未通过,但通过整个教程我们将逐步让这些E2E测试通过。

3 – 编写待办事项代码并展示

非常棒!~ 你的E2E 测试未通过。下面我们会让他通过。

使用Cypress的最好方法是在我们开发的时候将未通过的E2E测试保持运行。这样你可以清晰明了的得到你所做的进步。

展示待办事项

打开apps/todos文件夹,这个文件夹包含了当前React应用整个项目。

为了让第一个E2E测试断言通过,在apps/todos/src/app/app.tsx文件中添加以下代码:

import React, { useState } from 'react';

interface Todo {
  title: string;
}

export const App = () => {
  const [todos, setTodos] = useState<Todo[]>([
    { title: 'Todo 1' },
    { title: 'Todo 2' },
  ]);

  return (
    <>
      <h1>Todos</h1>
      <ul>
        {todos.map((t) => (
          <li className={'todo'}>{t.title}</li>
        ))}
      </ul>
    </>
  );
};

export default App;

点击左侧菜单栏上方最右侧按钮重新运行测试用例。现在测试用例在寻找”添加待办事项”按钮时失败了。

添加待办事项

添加 add-todo 按钮及点击事件处理函数

//apps/todos/src/app/app.tsx
import React, { useState } from 'react';

interface Todo {
  title: string;
}

export const App = () => {
  const [todos, setTodos] = useState<Todo[]>([
    { title: 'Todo 1' },
    { title: 'Todo 2' },
  ]);

  function addTodo() {
    setTodos([
      ...todos,
      {
        title: `New todo ${Math.floor(Math.random() * 1000)}`,
      },
    ]);
  }

  return (
    <>
      <h1>Todos</h1>
      <ul>
        {todos.map((t) => (
          <li className={'todo'}>{t.title}</li>
        ))}
      </ul>
      <button id={'add-todo'} onClick={addTodo}>
        Add Todo
      </button>
    </>
  );
};

export default App;

保存后重新运行测试,第二个断言也通过了。

4 – 与服务器API相交互

现实世界中的应用并不是独立存在的,他们需要与API进行数据交互。下面我们将使待办事项应用于API进行交互。

让我们修改应用,让它从API获取数据。

import React, { useEffect, useState } from 'react';

interface Todo {
  title: string;
}

const App = () => {
  const [todos, setTodos] = useState<Todo[]>([]);

  useEffect(() => {
    fetch('/api/todos')
      .then((_) => _.json())
      .then(setTodos);
  }, []);

  function addTodo() {
    fetch('/api/addTodo', {
      method: 'POST',
      body: '',
    })
      .then((_) => _.json())
      .then((newTodo) => {
        setTodos([...todos, newTodo]);
      });
  }

  return (
    <>
      <h1>Todos</h1>
      <ul>
        {todos.map((t) => (
          <li className={'todo'}>{t.title}</li>
        ))}
      </ul>
      <button id={'add-todo'} onClick={addTodo}>
        Add Todo
      </button>
    </>
  );
};

export default App;

可以看到,上述代码使用了两个API。

第一个为”base_url/api/todos”,获取数据后直接转换为json,而后使用setTodos将数据填充。

第二个为”base_url/api/addTodo”,使用POST方法传了一个空body,然后将返回的数据转为json追加到Todo列表内。

5 – 添加Node应用并实现API

因为我们还没有创建后端API,因此React应用对API的请求失败了。你可以非常容易的使用Nx在同一个repo中开发Node后端和React前端。他们可以使用同样的命令进行运行和测试。同时你可以在前后端之前共享代码。下面我们将通过Nx创建Node.js后端应用来实现APi服务。

在工作空间中添加Express

Nx是一个开放的平台,拥有针对多种现代化工具和开发框架的丰富插件,用户可以非常容易的通过Nx对项目进行方便快捷的管理。

通过npx nx list命令查看当前Nx拥有的插件:

在Nx工作空间文件夹中执行 npx nx list @nrwl/express 查看是否已安装express插件

通过以下命令安装express依赖:

//npm
npm install --save-dev @nrwl/express

//-------------------------------------//
//yarn
yarn add --dev @nrwl/express

添加@nwrl/express也会自动添加@nwrl/node插件。

生成Express应用

执行以下命令生成新的Express应用:

npx nx g @nrwl/express:app api --frontendProject=todos

当命令执行完成后,你应该能看到以下目录结构:

myorg/
├── apps/
│   ├── todos/
│   │   ├── src/
│   │   ├── project.json
│   │   └── proxy.conf.json
│   ├── todos-e2e/
│   └── api/
│       ├── src/
│       │   ├── app/
│       │   ├── assets/
│       │   ├── environments/
│       │   │   ├── environment.ts
│       │   │   └── environment.prod.ts
│       │   └── main.ts
│       ├── jest.conf.js
│       ├── project.json
│       ├── tsconfig.app.json
│       ├── tsconfig.json
│       └── tsconfig.spec.json
├── libs/
├── tools/
├── workspace.json
├── nx.json
├── package.json
└── tsconfig.base.json

Nx将任何你能运行的项目代码放在apps文件夹中:前端应用、后端应用、E2E测试集。因此api Express应用出现在apps文件夹中。

你可以执行:

  • npx nx serve api 来运行Express应用(热更新)
  • npx nx build api 来构建Express应用
  • npx nx test api 来测试Express应用

在apps/api/src/app文件夹中创建todos.ts:

import { Express } from 'express';

interface Todo {
  title: string;
}

const todos: Todo[] = [{ title: 'Todo 1' }, { title: 'Todo 2' }];

export function addTodoRoutes(app: Express) {
  app.get('/api/todos', (req, resp) => resp.send(todos));
  app.post('/api/addTodo', (req, resp) => {
    const newTodo = {
      title: `New todo ${Math.floor(Math.random() * 1000)}`,
    };
    todos.push(newTodo);
    resp.send(newTodo);
  });
}

上述代码在端点”GET /api/todo”返回常量列表Todo[],在端点”POST /api/addTodo”创建一个新的预制数据的待办事项后插入Todo列表并返回新待办事项对象。

apps/api/src/main.ts中注册上述api路由

import * as express from 'express';
import { addTodoRoutes } from './app/todos';

const app = express();

app.get('/api', (req, res) => {
  res.send({ message: 'Welcome to api!' });
});
addTodoRoutes(app);

const port = process.env.port || 3333;
const server = app.listen(port, () => {
  console.log(`Listening at http://localhost:${port}/api`);
});
server.on('error', console.error);

现在执行 npx nx serve api 运行API服务器

在浏览器中刷新页面。React应用现在能通过API获得和新建待办事项了。

ps:如果你现在还运行着Cypress,如果无法与API交互需要关闭Cypress页面后重新启动Cypress或者执行 npx nx serve todos 才能成功对接上API。

6 – 代理设置

想知道在创建node应用时传入 –frontendProject=todos参数做了什么吗?

它创建了一个允许React应用在开发环境下雨API服务器进行交互的代理配置文件。

打开 apps/todos/project.json 并找到 serve 部分:

可以看到 proxyConfig 属性指向了 apps/todos/proxy.conf.json

上述配置文件告诉 npx nx serve 将所有”/api”及其子路径下请求转发给监听在3333端口的程序。

Project.json,Targets,Executors

Nx项目配置文件为 apps/[app-name]/project.json。以apps/todos/project.json 为例,该文件包含todos React应用的配置文件。你可以在里面找到各种nx下运行的target,例如build、serve、lint和test等。这些命令意味着你可以执行npx nx build todos,npx nx serve todos等。

每一种target使用一种实际执行该命令的executor。因此这些target与npm scripts是相似的,而executor可以与shell scripts想对应。

为什么不直接使用shell scripts和npm scripts?

因为这样可以为构建工具提供许多额外的元数据信息。例如你可以使用内置的targets。npx nx serve todos –help

同时这种方式可以帮助构建非常棒的编辑器插件(editor integration)。例如VSCode Nx Console

但最重要的还是这种方式可以让Nx提供全面的开发体验而不仅仅是作为一个工具,并且使Nx能提供高级的构建特性例如分布式计算缓存(distributed computation caching)和分布式构建(distributed builds)。

7 – 前后端项目共享代码

棒极了!现在应用可以端到端运行了!但是这里还是有一个问题。前端和后端都定义了通向的Todo接口。这个接口现在是同步的,但在生存环境下,他们可能会发生随着开发的进行和时间的推移发生分歧,这样导致的结果可能是运行时错误出现。因此我们应该在前端项目和后端项目中共享这个接口代码。在Nx中,你可以通过创建一个library达到共享代码的目的。

执行以下命令生成一个library:

npx nx g @nrwl/workspace:lib data

结果可能看起来像是这样的:

myorg/
├── apps/
│   ├── todos/
│   ├── todos-e2e/
│   └── api/
├── libs/
│   └── data/
│       ├── src/
│       │   ├── lib/
│       │   │   └── data.ts
│       │   └── index.ts
│       ├── jest.conf.js
│       ├── project.json
│       ├── tsconfig.json
│       ├── tsconfig.lib.json
│       └── tsconfig.spec.json
├── tools/
├── nx.json
├── package.json
└── tsconfig.base.json

将Todo接口代码复制进 libs/data/src/lib/data.ts

export interface Todo {
  title: string;
}

VS Code用户需要注意:

如果你是用VS Code进行开发,你可以需要重启TypeScript服务器才能使新添加的library被识别。每次新的工作空间library被添加时这样的操作都可能需要执行。

重构API服务器代码

在 /apps/api/src/app/todos.ts 中import在library中的接口:

import { Express } from 'express';
import { Todo } from '@myorg/data';

const todos: Todo[] = [{ title: 'Todo 1' }, { title: 'Todo 2' }];

export function addTodoRoutes(app: Express) {
  app.get('/api/todos', (req, resp) => resp.send(todos));
  app.post('/api/addTodo', (req, resp) => {
    const newTodo = {
      title: `New todo ${Math.floor(Math.random() * 1000)}`,
    };
    todos.push(newTodo);
    resp.send(newTodo);
  });
}

ps:注意引入Todo接口时需要将后面’@myorg/data’中myorg更换为自己工作空间名称。

更新React应用代码

apps/todos/src/app/app.tsx 引入library中的todo接口:

import React, { useEffect, useState } from 'react';
import { Todo } from '@myorg/data';

export const App = () => {
  ...
};

export default App;

每次添加一个新的library都需要重启 npx nx serve

然后重启 npx nx serve api 和 npx nx serve todos 前后端应用将会在共享统一的Todo接口的情况下运行。

创建Libs

Nx中共享代码并不是Libraries的唯一用途。在将代码进行重构成API友好小单元(组件)时Library也是非常有用的。

Public API(公共API)

每一个Library都有一份定义了公共API的index.ts文件。其他应用和libraries应只能使用index.ts导出的接口。任何index.ts未暴露出的API都应该是Library私有的。

UI Libraries

下面我们将创建一个React组件Library来展现它的方便性。执行以下命令:

npx nx g @nrwl/react:lib ui

你应该会得到以下结构:

myorg/
├── apps/
│   ├── todos/
│   ├── todos-e2e/
│   └── api/
├── libs/
│   ├── data/
│   └── ui/
│       ├── src/
│       │   ├── lib/
│       │   │   ├── ui.css
│       │   │   ├── ui.spec.tsx
│       │   │   └── ui.tsx
│       │   └── index.ts
│       ├── jest.conf.js
│       ├── project.json
│       ├── tsconfig.json
│       ├── tsconfig.lib.json
│       └── tsconfig.spec.json
├── tools/
├── nx.json
├── workspace.json
├── package.json
└── tsconfig.base.json

打开 libs/ui/src/lib/ui.tsx 文件:

import './ui.module.css';

/* eslint-disable-next-line */
export interface UiProps {}

export function Ui(props: UiProps) {
  return (
    <div>
      <h1>Welcome to Ui!</h1>
    </div>
  );
}

export default Ui;

添加一个React组件

在这里你可以修改和生成一个React UI组件。

执行以下命令在新建的UI Library中添加新组件:

npx nx g @nrwl/react:component todos --project=ui --export

执行结果:

实现 Todos 组件:

import { Todo } from '@myorg/data';
import './todos.module.css';

export interface TodosProps {
  todos: Todo[];
}

export function Todos(props: TodosProps) {
  return (
    <ul>
      {props.todos.map((t) => (
        <li className={'todo'}>{t.title}</li>
      ))}
    </ul>
  );
}

export default Todos;

使用 UI Library

现在将 UI库中Todos组件引入 apps/todos/src/app/app.tsx

import React, { useEffect, useState } from 'react';
import { Todo } from '@myorg/data';
import { Todos } from '@myorg/ui';

const App = () => {
  const [todos, setTodos] = useState<Todo[]>([]);

  useEffect(() => {
    fetch('/api/todos')
      .then((_) => _.json())
      .then(setTodos);
  }, []);

  function addTodo() {
    fetch('/api/addTodo', {
      method: 'POST',
      body: '',
    })
      .then((_) => _.json())
      .then((newTodo) => {
        setTodos([...todos, newTodo]);
      });
  }

  return (
    <>
      <h1>Todos</h1>
      <Todos todos={todos} />
      <button id={'add-todo'} onClick={addTodo}>
        Add Todo
      </button>
    </>
  );
};

export default App;

重启 api服务器和todos React应用,然后应用应该正常运行。

Nx命令帮助你探索代码生成选项。执行 npx nx g @nrwl/react:component –help 可以查看所有可用选项。传递 –dry-run 参数可以在不改变、增删代码的情况下查看该命令将会生成、改变的文件。例如 npx nx g @nrwl/react:component mycmp --project=ui --dry-run

9 – 依赖关系图

一个Nx工作空间可能包含成百上千的应用和库。随着代码量的增加,如何有效的理解他们之间的相互依赖关系、了解进行某些改动将会影响那些代码成为一个巨大的挑战。

在这之前,一些高级工程师将会创建一个临时的依赖关系图并上传到公司wiki来解决这个问题。这个图表在第一天可能和实际有巨大差异,但随着每一天的修改它将一步一步接近项目实际情况。

但随着Nx的到来,你可以通过执行一条命令更简单、方便的生成一个依赖关系图:

npx nx dep-graph

依赖关系图将会展现在一个新的浏览器窗口中。点击“Show all projects”查看工作空间中所有应用和库的依赖关系。

使用计算缓存(Computation Caching)

Nx拥有内建的计算缓存可以帮助极大的提升命令执行速度。

我们可以通过执行 npx nx build todos 来查看计算缓存的效果:

第一次运行这条命令花费了一定时间,现在我们在此运行该命令,你可以看到命令执行结果相较于第一次运行快了许多:

基于当前开发环境和源码状态,Nx发现已经执行过这条命令。它将从本地缓存中读取构建信息并重新展示到输出,同时将必要的文件存储起来。

构建多个项目

为了构建多个项目,我们将执行npx nx run-many --target=build --projects=todos,api,这将构建api和todos两个项目。

可以看到Nx构建了api项目,但直接从计算缓存中取出了todos项目缓存文件。在这里你可以找到更多关于缓存的知识。

添加 –parallel 参数到任何Nx指令可以并行执行,默认情况下Nx大部分使用并行执行模式。

测试受影响的项目(Test Affected Projects)

为了支持计算缓存,Nx通过分析特定pull请求对项目的影响来方便您的开发。

Commit 至今为止项目所有变动:

git add .
git commit -am 'init'
git checkout -b testbranch

打开 libs/ui/src/lib/todos/todos.tsx文件,并将Todo组件<li>标签内容更改为 {t.title}!!:

执行 npx nx affected:apps,你应该会看到todos被输出。该命令通过依赖关系图得出那些apps将会应该这次改动而受到影响。

执行 npx nx affected:libs,ui组件库应该会被输出。这条命令与上一条相似,只不过是受影响的库。

测试受影响的项目

输出这些受影响的项目是容易的,但是通常情况下你可能想对这些受影响的位置做一些事情。例如你可能想测试这些受影响的位置。

执行 npx nx affected:test 来对受改动影响的项目进行重新测试。

可以看到Nx只是尝试重新测试了ui和todos。而没有对api和data进行测试。

Affected:*

你可以通过添加affected参数对受影响项目执行任何target。

# The following are equivalent
npx nx affected --target=build
npx nx affected:build

12 – 总结

在这个教程中:

  • 使用React和Express构建了一个全栈应用
  • 让前端和后端共享代码
  • 创建了一个UI库
  • 使用Nx affected指令将受影响的项目进行了重新测试和构建

v1.0wep 创建文章并翻译Nx React教程

发表评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注