构建工具是任何网络开发者的宝贵资产。目前,许多伟大的构建工具可用于所有领先的JavaScript框架。Nx是一个构建框架,它允许你在原地构建单体和脚手架完整的应用程序,就是这样一个最近越来越受欢迎的例子。
Nx将开发人员在构建应用程序时通常必须手动执行的许多步骤自动化。它包括计算缓存、依赖图和构建自动化等功能,还包括与Cypress的插件集成。
在这篇文章中,我们将通过使用Nx和React来构建一个简单的待办事项列表应用程序。要跟上进度,请查看我在GitHub上的示例项目。
让我们开始吧!
设置一个工作区
为了开始,你需要创建一个Nx工作区。你可以使用npx
,通过运行以下代码来完成。
npx create-nx-workspace@latest
你也可以全局安装Nx。
当CLI运行时,它会创建一个工作空间,这基本上是你项目的一个容器。当我们完成后,这个容器将成为一个monorepo。
CLI会问你一系列的问题,然后把你的项目的主要骨架搭出来。我们正在构建一个React应用程序,所以文件结构看起来像下面的代码块。
├── README.md
├── apps
│ ├── first-project
│ └── first-project-e2e
├── babel.config.json
├── jest.config.js
├── jest.preset.js
├── libs
├── nx.json
├── package-lock.json
├── package.json
├── tools
│ ├── generators
│ └── tsconfig.tools.json
├── tsconfig.base.json
└── workspace.json
文件结构包括一个apps
,该目录有两个文件夹。一个是项目本身,另一个是用Cypress运行端到端测试。
现在你有了一个脚手架式的应用程序,你可以使用npx nx serve first-project
来运行它。
你将收到以下输出。
创建一个API
让我们在React项目旁边创建一个API,我们可以用它来执行任何REST调用。Nx的一个很酷的功能是它能够在一个命令中为你的项目添加一个API。
我们将使用Express创建一个API。首先,你需要用Nx CLI安装express
插件。
npm install --save-dev @nrwl/express
现在,你可以用以下命令创建API。
npx nx g @nrwl/express:app api --frontendProject=first-project
文件夹结构应该看起来像下面的代码。
├── README.md
├── apps
│ ├── api
│ ├── first-project
│ └── first-project-e2e
├── babel.config.json
├── jest.config.js
├── jest.preset.js
├── libs
├── nx.json
├── package-lock.json
├── package.json
├── tools
│ ├── generators
│ └── tsconfig.tools.json
├── tsconfig.base.json
└── workspace.json
构建应用程序的脚手架
现在我们已经有了构件,让我们来看看到目前为止我们已经建立了什么如果你看一下apps
文件夹,你会看到下面的代码。
├── api
│ ├── jest.config.js
│ ├── src
│ │ ├── app
│ │ ├── assets
│ │ ├── environments
│ │ └── main.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.json
│ └── tsconfig.spec.json
├── first-project
│ ├── jest.config.js
│ ├── proxy.conf.json
│ ├── src
│ │ ├── app
│ │ ├── assets
│ │ ├── environments
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── main.tsx
│ │ ├── polyfills.ts
│ │ └── styles.scss
│ ├── tsconfig.app.json
│ ├── tsconfig.json
│ └── tsconfig.spec.json
└── first-project-e2e
├── cypress.json
├── src
│ ├── fixtures
│ ├── integration
│ ├── plugins
│ └── support
├── tsconfig.e2e.json
└── tsconfig.json
api
项目是一个完整的 Express 应用程序,它由main.ts
, anentrypoint
文件构成。
/**
* This is not a production server yet!
* This is only a minimal backend to get started.
*/
import * as express from 'express';
const app = express();
app.get('/api', (req, res) => {
res.send({ message: 'Welcome to api!' });
});
const port = process.env.port || 3333;
const server = app.listen(port, () => {
console.log(`Listening at http://localhost:${port}/api`);
});
server.on('error', console.error);
进入first-project
文件夹。你会看到用传统的文件夹结构构建的React应用,常见的有assets
,styles
,index
,main
, 和app
文件。
import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom';
import App from './app/app';
ReactDOM.render(
<StrictMode>
<App />
</StrictMode>,
document.getElementById('root')
);
最后,如果你查看first-project-e2e
文件夹,你会看到Cypress项目,具有任何Cypress测试套件的传统结构。
├── cypress.json
├── src
│ ├── fixtures
│ ├── integration
│ ├── plugins
│ └── support
├── tsconfig.e2e.json
└── tsconfig.json
调用API
到目前为止,我们已经建立了一个脚手架式的应用程序。现在,让我们添加一个功能,展示CLI的作用我将用React和Nx建立一个简单的待办事项应用程序。
首先,将以下代码复制到apps/first-project/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;
如果你从项目根部运行npm run start
,你会看到以下内容。
到目前为止,一切都很好!我们可以通过整合对API项目的调用来改进我们的代码。首先,创建一个名为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);
});
}
上面的代码创建了许多我们的API需要的路由。现在,我们将通过修改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
,然后点击 [http://localhost:3333/api/todos](http://localhost:3333/api/todos)
.你会看到以下内容。
[{"title":"Todo 1"},{"title":"Todo 2"}]
现在,让我们从我们的应用程序调用API。我们将设置一个代理,以便从我们的React应用程序的API调用直接调用API。
请看位于项目根部的workspace.json
文件,在那里你可以找到整个应用程序的配置。
接下来,我们需要在我们的first-project
React应用程序的serve
目标中找到proxyConfig
,它应该看起来像下面的代码块。
{
"serve": {
"builder": "@nrwl/web:dev-server",
"options": {
"buildTarget": "first-project:build",
"proxyConfig": "apps/first-project/proxy.conf.json"
},
"configurations": {
"production": {
"buildTarget": "first-projects:build:production"
}
}
}
}
打开proxyConifg
设置的文件,在apps/first-project/proxy.conf.json
,并添加以下代码。它将代理所有的API调用来调用你的API项目。
{
"/api": {
"target": "http://localhost:3333",
"secure": false
}
}
现在,如果我们修改first-rproject
文件夹中的main.ts
文件,我们就可以调用API,而不是使用我们最初设置的本地状态。修改apps/first-project/src/app/app.tsx
,看起来像下面的代码块。
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;
现在,我们的React应用程序正在调用API,而不是依赖本地状态。
让我们运行API和frontendProject
!你可以使用npx nx serve first-project
和npx nx serve api
。你也可以使用一个npm包,比如Concurrently和 [start-server-and-test](https://www.npmjs.com/package/start-server-and-test)
,使用脚本来自动运行API。
用Cypress测试
如前所述,Nx在应用被架设时内置了Cypress测试。
为了看到测试的实际效果,修改apps/first-project-e2e/src/support/app.po.ts
的测试文件,以包括以下内容。
export const getTodos = () => cy.get('li.todo');
export const getAddTodoButton = () => cy.get('button#add-todo');
现在,修改位于apps/first-project-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));
});
});
运行npx nx e2e first-project-e2e --watch
,看看你的测试在运行中
额外的Nx功能
Nx CLI有许多功能,超出了我们到目前为止所涉及的范围。其中最有用的是为你的项目生成一个依赖关系图的能力,它通过数据可视化简化了复杂的项目。我们简单的待办事项应用程序不需要这个,但看到它也是很不错的
从运行下面的代码开始。
npx nx dep-graph
你会收到一个看起来像下面图片的输出。
另一个非常酷的功能是Nx有能力检测变化,并且只重建受项目最新修改影响的部分。下面是一些利用这个功能的命令样本,来自Nx的文档。
nx affected:apps # prints affected apps
nx affected:libs # prints affected libs
nx affected:build # builds affected apps and libs
nx affected:lint # lints affected apps and libs
nx affected:test # tests affected apps and libs
nx affected:e2e # e2e tests affected apps
最后,Nx允许你创建库和计算缓存以提高性能。我强烈建议阅读更多关于这些和其他功能的资料
接下来的步骤
在本教程中,我们通过搭建一个简单的待办事项应用程序的脚手架,介绍了使用React和Nx的基础知识。我们项目的下一步将是构建资产并部署它们。Nx可以为小项目自动构建,也可以扩展到企业应用的大项目。
我希望你喜欢读这篇文章!我希望它能激励你去看看Nx,并将它与React一起使用。我绝对推荐你查看Nx的文档,并阅读他们的一些指南,以了解更多信息。
在andrewevans.dev上关注我,在Twitter上与我联系:@AndrewEvans0102。
The postBuilding an application with React and Nxappeared first onLogRocket Blog.