В данной статье расскажу об использовании Nx для разработки веб приложений на Typescript.
Два года назад я написал статью на медиуме - Сборка Typescript приложения с помощью Webpack, где поделился своим решением сборки простого приложения на Typescript с помощью Webpack.
И все бы ничего, если бы это не устарело. Все описанное работает и до сих пор, но уже есть более продвинутые решения, об одном из которых и пойдет дальнейший разговор.
Проблемы собственных решений
Больше трех лет назад я устройся на работу в один небольшой фин. тех стартап Angular разработчиком. И по мере выполнения разных задач, появилась потребность в некоем решении, которое удовлетворяло бы следующим требованиям:
Язык разработки Typescript;
Livereload при внесении изменений;
Есть "prod" сборка, которая оптимизирует исходный код.
Одним словом все то, что есть в современных JS фреймворках.
Я просмотрел несколько проектов на github’е, но ни одно решение мне не подошло, так как либо они уже не поддерживались, либо были слишком усложнены. Мне же нужно было что-то простое.
Я был знаком с webpack и мне не составило труда набросать несколько модулей и собрать сборку, которая удовлетворяла выше описанным требованиям.
Решение отлично работало, за одним только исключением, что его трудно было обновлять. Из-за того, что это был просто шаблон приложения, который копировался и изменялся, то явного желания обновлять 5 - 10 созданных проектов не было.
Так как проекты жили параллельно, всегда приходилось мигрировать проекты, копируя куски из одного решения в другое. В один момент я даже подумывал о создании своего CLI, но сама мысль об этом явно кричала о том, что я делаю что-то не так.
Примерно в тоже время, я начал использовать Nx для своих Angular проектов.
Информация о Nx
Nx это набор утилит для создания и управления монорепозиторием. С Nx можно ознакомиться в официальной документации.
Так как статья посвящена разработке typescript приложений, интересен следующий раздел - Nx и TypeScript.
В свое время, часть Angular Team создает свою компанию Nrwl, в которой начинают делать свой инструментарий для упрощения разработки приложений на Angular. Консультации компаний из списка Fortune 500 позволили расширить инструментарий и включить в него не только Angular, а также React, Vue и чистый Typescript.
Если вы предпочитаете больше смотреть, чем читать, то команда Nx регулярно выпускает ролики с обзором и настройкой текущего инструментария. Например, ролик посвященный первой настройке приложения:
Вот несколько особенностей Nx:
вся мощь typescript вместе с eslint;
монорепозиторий, который позволяет разрабатывать несколько приложений с единой кодовой базой;
генераторы - семейство консольных команд для упрощения создания новых файлов и решений, с возможностью создания собственных шаблонов и команд;
конфигурируемые сборки - например, версии для релиза с оптимизацией исходного кода;
миграции - при изменении и обновлении системных зависимостей, Nx самостоятельно поправит конфигурационные файлы и приведет все данные к требуемому виду;
возможность использовать все современные фреймворки React, Angular, Vue и Svetle.
Полный список можно посмотреть в официальной документации по Nx.
Другими словами, NX - набор утилит, который все делает за вас, и делает это хорошо, но в разумных пределах, конечно.
Предварительная настройка
Для работы с nx вам потребуется установленная NodeJS и один из менеджеров пакетов, таких как npm
или yarn
.
Ознакомиться с NodeJS можно на сайте - https://nodejs.org/en/docs.
Перед использованием nx, можно установить nx cli глобально:
yarn global add /cli
Это позволит запускать команды nx без менеджеров пакетов (yarn или npm):
nx g lib mylib
Иначе придется писать:
yarn nx g lib mylib
Создание workspace для typescript приложения
Для того чтобы создать новый Nx workspace необходимо запустить команду:
npx create-nx-workspace@latest
Если вы используете yarn
, то тогда можете запустить следующую команду:
yarn create nx-workspace --package-manager=yarn
Введем название workspace - boobs
:
При при создании workspace можно выбрать тип проекта (angular
, react
, node
или typescript
). Данная опция определяет какие зависимости будут включены в package.json.
В качестве проекта выберем ts
:
Откажемся от использования облака:
Если вы не боитесь стать пупыркой, можете попробовать использовать облако.
Далее Nx будет устанавливать необходимые зависимости:
Nx установил зависимости, где сразу предложил пройти туториал :)
Перейдем в папку с созданным проектом:
cd boobs
Посмотрим, что создал Nx, выполнив команду ls
:
Но так как из консоли ничего непонятно, откроем проект в любимой IDE:
JetBrains, к моему большому сожалению, не хочет
подогнатьпредоставить мне бесплатную версию. Видимо скоро придется перейти на VSCode.
Формально Nx создаст следующую структуру:
boobs/
├── packages/
├── tools/
├── workspace.json
├── nx.json
├── package.json
└── tsconfig.base.json
Откроем package.json
:
{
"name": "boobs",
"version": "0.0.0",
"license": "MIT",
"scripts": {},
"private": true,
"dependencies": {},
"devDependencies": {
"@nrwl/cli": "13.8.2",
"@nrwl/js": "13.8.2",
"@nrwl/tao": "13.8.2",
"@nrwl/workspace": "13.8.2",
"@types/node": "16.11.7",
"prettier": "^2.5.1",
"typescript": "~4.5.2"
}
}
Как видно из содержания, Nx создал новый, пустой проект с минимумом зависимостей. Пакеты @nrwl
понятны из названия, где либо подключают cli
, либо js
, либо осуществляют работу самого workspace
.
Рассмотрим файл nx.json
:
{
"extends": "@nrwl/workspace/presets/core.json",
"npmScope": "boobs",
"affected": {
"defaultBase": "main"
},
"cli": {
"defaultCollection": "@nrwl/workspace"
},
"tasksRunnerOptions": {
"default": {
"runner": "@nrwl/workspace/tasks-runners/default",
"options": {
"cacheableOperations": [
"build",
"lint",
"test",
"e2e"
]
}
}
}
}
В данном случае имеем 3 секции:
affected
- ветка, с которой будут сравниваться изменения в монорепозитории. Это необходимо для того, чтобы понимать какие тесты запускать, и какие части проекта (приложения и библиотеки) были затронуты в результате последних правок.cli
- настройка CLI. Обычно, там указываются дефолтные коллекции. В данном случае используем коллекции nx. Если вы фанатик yarn, можно добавить свойство"packageManager": "yarn"
, чтобы всегдаyarn
использовался.tasksRunnerOptions
- набор правил запуска приложений и библиотек. Для базовой настройки это можно пока пропустить.
Файл workspace.json
будет содержать пути конфигураций для библиотек и приложений:
{
"version": 2,
"projects": {}
}
Последний файл это tsconfig.base.json
:
{
"compileOnSave": false,
"compilerOptions": {
"rootDir": ".",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es2015",
"module": "esnext",
"lib": ["es2017", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"baseUrl": ".",
"paths": {}
},
"exclude": ["node_modules", "tmp"]
}
Здесь все стандартно. Можно бампнуть es до последней версии.
Единственное, что бросается в глаза - это отсутствие eslint
.
Добавим eslint
в workspace:
yarn add --dev @nrwl/linter
yarn add --dev @nrwl/eslint-plugin-nx
Данный шаг можно пропустить, так как при создании приложения, Nx сделает все за вас, и сам добавит eslint и jest.
Работа с NX workspace
В монорепе можно создавать как минимум два типа пакетов:
библиотеки - обычные пакеты, которые можно разместить в npmjs
приложения - тоже что и библиотеки, только приложение имеет способы запуска отслеживания изменений.
Для примера создадим новое приложение - store:
nx generate @nrwl/js:app store
После создания приложения, глобально появилось несколько файлов:
.eslintrc.json
- настройки линтераjest.config.js
,jest.preset.js
- настройка для unit тестирования с помощью jest
Workspace.json добавился новый проект:
{
"version": 2,
"projects": {
"store": "packages/store"
}
}
Также, из-за того, что в проекте не было jest, Nx любезно его установил, обновив package.json
:
{
"name": "boobs",
"version": "0.0.0",
"license": "MIT",
"scripts": {},
"private": true,
"dependencies": {
"tslib": "^2.0.0"
},
"devDependencies": {
"@nrwl/cli": "13.8.2",
"@nrwl/eslint-plugin-nx": "^13.8.2",
"@nrwl/jest": "13.8.2",
"@nrwl/js": "13.8.2",
"@nrwl/linter": "^13.8.2",
"@nrwl/tao": "13.8.2",
"@nrwl/workspace": "13.8.2",
"@types/jest": "27.0.2",
"@types/node": "16.11.7",
"@typescript-eslint/eslint-plugin": "~5.10.0",
"@typescript-eslint/parser": "~5.10.0",
"eslint": "~8.7.0",
"eslint-config-prettier": "8.1.0",
"jest": "27.2.3",
"prettier": "^2.5.1",
"ts-jest": "27.0.5",
"typescript": "~4.5.2"
}
}
Конечно, не понятно почему нельзя было сразу добавить команду запуска приложения в package.json, ну да ладно.
Запустим проект:
nx serve store
Как видно livereload работает. Если изменять файлы в монорепозитории, то приложение будет меняться.
Если открыть созданный проект, то там следующая структура:
packages/store
├── jest.config.js
├── package.json
├── project.json
├── README.md
├── src
│ ├── app
│ │ ├── store.spec.ts
│ │ └── store.ts
│ └── index.ts
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
Tsconfig файлы переопределяют правила глобальной конфигурации TS.
Например, tsconfig.spec.ts
- создаст окружение для тестирования.
Project.json
описывает конфигурацию приложения.
{
"root": "packages/store",
"sourceRoot": "packages/store/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@nrwl/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/packages/store",
"main": "packages/store/src/index.ts",
"tsConfig": "packages/store/tsconfig.app.json",
"assets": ["packages/store/*.md"]
}
},
"serve": {
"executor": "@nrwl/js:node",
"options": {
"buildTarget": "store:build"
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["packages/store/**/*.ts"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["coverage/packages/store"],
"options": {
"jestConfig": "packages/store/jest.config.js",
"passWithNoTests": true
}
}
},
"tags": []
}
В данном случае видны правила для запуска и компиляции проекта, есть команды для проверки стилей (lint
) и тестирования (test
).
Само приложение включает в себя только один файл - store.ts
:
export function store(): string {
return 'store';
}
Теперь создадим библиотеку api:
nx generate @nrwl/js:library api
Структура библиотеки:
├── jest.config.js
├── package.json
├── project.json
├── README.md
├── src
│ ├── index.ts
│ └── lib
│ ├── api.spec.ts
│ └── api.ts
├── tsconfig.json
├── tsconfig.lib.json
└── tsconfig.spec.json
Одним из немногих отличий конфигурации библиотеки от приложения является project.json:
{
"root": "packages/api",
"sourceRoot": "packages/api/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nrwl/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/packages/api",
"main": "packages/api/src/index.ts",
"tsConfig": "packages/api/tsconfig.lib.json",
"assets": ["packages/api/*.md"]
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["packages/api/**/*.ts"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["coverage/packages/api"],
"options": {
"jestConfig": "packages/api/jest.config.js",
"passWithNoTests": true
}
}
},
"tags": []
}
Единственное отличие - это наличие блока с serve
:
{
"root": "packages/store",
"sourceRoot": "packages/store/src",
"projectType": "application",
"targets": {
...
"serve": {
"executor": "@nrwl/js:node",
"options": {
"buildTarget": "store:build"
}
},
...
}
}
Используем библиотеку api
в приложении strore
:
import { api } from "@boobs/api";
export function store(): string {
console.log(api());
return 'store';
}
Соберем проект:
nx run store:build --with-deps
Запустим проект:
nx serve store
Как видим на скриншоте выше, был вызван метод из библиотеки api
, перед запуском приложения store
.
Node и Nx
Если нужно пойти дальше и начать разрабатывать приложение на NodeJs, то у Nx есть отличный пакет - @nrwl/node
. Подробнее о разработке на Node с NX.
Добавим пакет ноды в workspace:
yarn add -D @nrwl/node
Теперь создадим приложение с node:
nx g @nrwl/node:application node-store
Структура созданного приложения:
packages/node-store
├── jest.config.js
├── project.json
├── src
│ ├── app
│ ├── assets
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ └── main.ts
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
Почти все тоже самое, но только с немного улучшенной структурой.
Как и все другие библиотеки и приложения, главным файлом является project.json:
{
"root": "packages/node-store",
"sourceRoot": "packages/node-store/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@nrwl/node:build",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/packages/node-store",
"main": "packages/node-store/src/main.ts",
"tsConfig": "packages/node-store/tsconfig.app.json",
"assets": ["packages/node-store/src/assets"]
},
"configurations": {
"production": {
"optimization": true,
"extractLicenses": true,
"inspect": false,
"fileReplacements": [
{
"replace": "packages/node-store/src/environments/environment.ts",
"with": "packages/node-store/src/environments/environment.prod.ts"
}
]
}
}
},
"serve": {
"executor": "@nrwl/node:execute",
"options": {
"buildTarget": "node-store:build"
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["packages/node-store/**/*.ts"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["coverage/packages/node-store"],
"options": {
"jestConfig": "packages/node-store/jest.config.js",
"passWithNoTests": true
}
}
},
"tags": []
}
Как видно из примера, тут уже есть environment'ы, также есть различные виды сборок, такие как development
и production
.
Внимательный читатель заметит, что чем дальше идет усложнение структуры, то все больше и больше она начинает напоминать структуру проектов на Angular.
Этот enterprise из головы уже не выкинуть.
Express и Nx
Шагнем дальше и создадим проект с express. Добавим соответствующий пакет Nx:
yarn add -D @nrwl/express
Создадим express приложение:
nx g @nrwl/express:app express-store
При запуске команд, Nx проверяет конфигурации, и в случае с добавлением express он глобально добавил несколько зависимостей в package.json
:
{
"name": "boobs",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"start": "nx serve store",
"build": "nx run store:build --with-deps"
},
"private": true,
"dependencies": {
"express": "4.17.2",
"tslib": "^2.0.0"
},
"devDependencies": {
"@nrwl/cli": "13.8.2",
"@nrwl/eslint-plugin-nx": "^13.8.2",
"@nrwl/express": "^13.8.3",
"@nrwl/jest": "13.8.2",
"@nrwl/js": "13.8.2",
"@nrwl/linter": "^13.8.2",
"@nrwl/node": "^13.8.3",
"@nrwl/tao": "13.8.2",
"@nrwl/workspace": "13.8.2",
"@types/express": "4.17.13",
"@types/jest": "27.0.2",
"@types/node": "16.11.7",
"@typescript-eslint/eslint-plugin": "~5.10.0",
"@typescript-eslint/parser": "~5.10.0",
"eslint": "~8.7.0",
"eslint-config-prettier": "8.1.0",
"jest": "27.2.3",
"prettier": "^2.5.1",
"ts-jest": "27.0.5",
"typescript": "~4.5.2"
}
}
Отсюда и ответ на вопрос, зачем в каждом из пакетов, сгенерированных Nx, есть собственный package.json
.
Запустим проект:
nx serve express-store
Откроем браузер:
Магия, ничего не скажешь.
Если сравнивать проект на node и express, то они как не казалось бы удивительным, отличаются только наличием express в main.ts
:
/**
* 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 express-store!' });
});
const port = process.env.port || 3333;
const server = app.listen(port, () => {
console.log(`Listening at http://localhost:${port}/api`);
});
server.on('error', console.error);
Web и Nx
Последним пакетом можно рассмотреть пакет для создания web приложений - @nrwl/web
Добавим пакет в workspace:
yarn add -D @nrwl/web
Пакет подключает около 300 пакетов для веб разработки.
Создадим web приложение:
nx g @nrwl/web:app web-store
Выберем SCSS:
Запустим приложение:
nx serve web-store
Откроем браузер - http://localhost:4200
Выше приведенное решение и является аналогом моего самописанного решения, которое можно теперь выкинуть на помойку.
Следующим шагом идет создание уже полноценных веб приложений на Angular и React.
Там все аналогично. Добавляете пакет, создаете приложение.
yarn add -D @nrwl/angular
nx g @nrwl/angular:app angular-store
Однако, стоит отметить, что лучше при создании workspace использовать пресеты с приложениями. Это позволит не делать много не нужной работы.
Исходники
Все описанное выше можно посмотреть в репозитории - https://github.com/Fafnur/boobs:
Резюме
В данной статье рассказал о таком инструменте как Nx и его использовании при разработке веб приложений. Сделал краткий обзор структуры приложений на Nx, а также привел команды для генерации библиотек и запуска приложений.
В заключении хочется сказать, что инструментарии для веб разработки очень сильно развиваются. Грустно видеть проекты, которые до сих пор используют gulp как основную систему сборки. Нет никаких претензий к gulp, grunt и webpack, просто уже давно много задач, которые решали сборщики - были решены и оптимизированы. И разработчику не нужно создавать и поддерживать свой инструментарий. Хорошим примером является Nx, который возьмет на себя весь front-ops и даст вам возможность заниматься тем, чем должен заниматься разработчик - разработкой приложений, а не бесконечной настройкой конфигов, и их изменением при обновлении vendors.
Комментарии (7)
SiSya
19.02.2022 17:45Не совсем в итоге понял, какие проблемы решаются в сравнении с уже существующими сборщиками. Допустим, тот же vite. Можете уточнить?
fafnur Автор
19.02.2022 17:46Он ничем не лучше. Тут скорее про то, во что эволюционировал Nx. И что его можно использовать для широкого спектра задач.
jesaiah4
20.02.2022 05:33-3в 2022 использовать вбпак а не нативный HMR и при этом писать "современные приложения"... мда.
Все кто дошел до комментариев. Советую вас начать с VITE , тогда по настоящему вы будете писать современные приложения
wendel
Хороший инструмент, удобный, развивается, пользуюсь уже не первую версию.
Не подскажите название темы для терминала? (zsh если не ошибаюсь)
fafnur Автор
Это zsh + oh my zsh, тема dracula (https://draculatheme.com/).
Если manjaro, то нужно тему именно для gnome-terminal еще поставить
https://github.com/dracula/gnome-terminal/tree/65fb7e8df668524f8b9c771429c76f675016a71a