Как создать Monorepo с Nx, Next.js и TypeScript

Для чего используется TypeScript Изучение

В этой статье мы узнаем, что такое монорепозиторий, и как монорепозиторий помогает быстрее разрабатывать приложения с лучшим опытом разработки. Мы обсудим преимущества использования инструментов разработки Nx для управления монорепозиторием и узнаем, как использовать эти инструменты для создания приложения Next.js.

Что такое Monorepo и почему нам следует подумать об его использовании

Monorepo является единственным хранилищем, который содержит программы и приложения, инструменты и конфигурации нескольких проектов или компонентов проекта. Это альтернатива созданию отдельных репозиториев для каждого проекта или части проекта.

Рассмотрим сценарий, в котором мы создаем приложение приборной панели с использованием некоторой интерфейсной библиотеки или фреймворка. Код этого интерфейсного приложения может храниться в dashboardрепозитории. Компоненты пользовательского интерфейса, которые использует этот репозиторий, могут храниться в другом репозитории с именем components. Теперь, каждый раз, когда мы обновляем componentsрепозиторий, нам нужно будет заходить в dashboardрепозиторий и обновлять componentsзависимость.

Рассмотрим сценарий, в котором мы создаем приложение

Чтобы решить эту проблему, мы могли бы объединить componentsрепо с dashboardрепо.

Однако может существовать другое внешнее приложение для маркетингового сайта

Однако может существовать другое внешнее приложение для маркетингового сайта, которое хранится в marketingрепозитории и зависит от componentsрепозитория. Итак, нам придется продублировать componentsи объединить его marketing. Однако из-за этого любые изменения, связанные с components, придется вносить в двух местах, что не идеально.

Выше проблема может быть решена с помощью monorepo

Выше проблема может быть решена с помощью monorepo, где dashboard, componentsи marketingкомпоненты находятся в одном хранилище.

Использование монорепозитория дает различные преимущества

Использование монорепозитория дает различные преимущества:

  • Обновлять пакеты намного проще, поскольку все приложения и библиотеки находятся внутри единого репозитория. Поскольку все приложения и пакеты находятся в одном репозитории, добавление нового кода или изменение существующего кода можно легко протестировать и отправить.
  • Рефакторинг кода намного проще, поскольку нам нужно делать это только в одном месте, а не копировать один и тот же материал в нескольких репозиториях.
  • Монорепозиторий допускает постоянную настройку конвейеров CI / CD, которые могут повторно использоваться всеми приложениями и библиотеками, присутствующими в одном репозитории.
  • Публикация пакетов также становится намного проще благодаря таким инструментам, как Nx.

Интерфейс командной строки Nx поможет нам в создании новых приложений Next.js и библиотек компонентов React. Это также поможет нам запустить веб-сервер разработки с горячей перезагрузкой модуля. Он также может выполнять множество других важных вещей, таких как линтинг, форматирование и генерация кода. Преимущество использования такого интерфейса командной строки в том, что он обеспечит чувство стандартизации в нашей кодовой базе. По мере роста нашей кодовой базы становится очень трудно управлять и понимать основные сложности. Nx CLI устраняет большинство этих сложностей, предоставляя инструменты для автоматизации генерации кода.

Требуется программное обеспечение

Для запуска нашего приложения нам понадобится следующее:

  • npm
  • Yarn (optional)

В приложении будут использоваться следующие технологии:

  • Next.js для создания нашего приложения
  • Styled Components для добавления стилей в наше приложение

Примечание: вы можете узнать больше о том, как установить несколько версий Node.js с помощью nvm, если вы хотите быстро освоить это.

Нам также понадобится учетная запись Product Hunt.

Установка и загрузка рабочей области Nx Workspace

Мы можем установить Nx CLI, используя следующую команду:

npm install nx -g

Приведенная выше команда установит Nx CLI глобально. Это полезно, потому что теперь мы можем создать новое приложение Next.js с помощью этого интерфейса командной строки из любого каталога.

Затем нам нужно запустить следующую команду внутри каталога, в котором мы хотим создать наше монорепо:

npx create-nx-workspace@latest nx-nextjs-monorepo

Приведенная выше команда создаст рабочее пространство Nx. Все приложения Nx могут находиться внутри рабочего пространства Nx.

Возможно, вам потребуется заменить nx-nextjs-monorepoна имя вашей рабочей области. Его можно называть как угодно. Название рабочей области обычно представляет собой название организации, компании и т. Д.

Когда мы запускаем указанную выше команду, нам дается набор шагов, которые позволят создать приложения того типа, которые мы хотим создать с помощью Nx.

Шаг 1. Сначала он спросит, какой тип приложения мы хотим создать. Мы выберем Next.js из списка опций.

Сначала он спросит, какой тип приложения мы хотим создать

Шаг 2: Он запросит у нас имя приложения, которое мы хотим создать. Мы можем назвать это как угодно. В данном случае мы назовем это «охота за продуктом».

Он запросит у нас имя приложения, которое мы хотим создать

Шаг 3: Нас спросят, какой тип таблицы стилей мы хотим использовать. Мы выберем стилизованные компоненты.

Нас спросят, какой тип таблицы стилей мы хотим использовать

Шаг 4. Он спросит нас, хотим ли мы использовать Nx Cloud, платформу для ускорения сборки приложений Nx. В этом случае мы выберем Нет, но, пожалуйста, проверьте это.

Он спросит нас, хотим ли мы использовать Nx Cloud, платформу для ускорения сборки приложений Nx

Nx создаст скаффинги для всех файлов и каталогов и сгенерирует для нас следующую структуру.

В apps каталоге собраны все наши приложения

В apps каталоге собраны все наши приложения. В нашем случае этот каталог будет содержать приложение Next.js, которое мы создаем (названное product-hunt). Этот каталог также содержит приложения для сквозного тестирования (названные product-hunt-e2e), созданные с помощью Cypress.

libsКаталог содержит все библиотеки, такие как компоненты, полезные функции, и так далее. Эти библиотеки могут использоваться любым из приложений, присутствующих в appsкаталоге.

toolsКаталог содержит все пользовательские сценарии, codemods, и так далее, которые используются, чтобы сделать некоторые изменения в наш код.

Создание главной страницы Product Hunt с помощью Next.js

На этом этапе мы создадим главную страницу Producthunt. Мы будем получать данные из официального API Product Hunt. Product Hunt API предоставляет интерфейс GraphQL, который находится по адресу https://api.producthunt.com/v2/api/graphql. Доступ к нему можно получить с помощью access_token, который можно сгенерировать из панели управления Product Hunt API.

Читайте также:  Учебник RxJS: наблюдаемые, операторы и не только

Чтобы создать новое приложение, нам нужно нажать кнопку ДОБАВИТЬ ПРИЛОЖЕНИЕ.

Затем мы можем добавить имя для нашего приложения и https: // localhost: 4200 / в качестве URI перенаправления для нашего нового приложения и нажать кнопку » Создать приложение«.

Теперь мы сможем просмотреть учетные данные нашего нового приложения.

Затем нам нужно сгенерировать токен разработчика, нажав кнопку СОЗДАТЬ ТОКЕН на той же странице.

Это сгенерирует новый токен и отобразит его на странице.

Затем нам нужно сохранить эти учетные данные в нашем приложении. Мы можем создать новый.env.localфайл внутри apps/product-huntкаталога со следующим содержимым:

// apps/product-hunt/.env.local

NEXT_PUBLIC_PH_API_ENDPOINT=https://api.producthunt.com/v2/api/graphql
NEXT_PUBLIC_PH_TOKEN=<your-developer-token>

Поскольку API Product Hunt находится на GraphQL, нам придется установить несколько пакетов, чтобы наше приложение работало с GraphQL. Из корневого каталога нам нужно запустить следующую команду для установки необходимых пакетов:

yarn add graphql-hooks graphql-hooks-memcache

graphql-hooks — это минимальный клиент GraphQL, ориентированный на перехватчики. Это помогает нам запрашивать данные с сервера GraphQL.

graphql-hooks-memcache — это реализация кэширования в памяти для файловgraphql-hooks.

Далее нам нужно инициализировать клиент GraphQL из graphql-hooksпакета. Мы можем сделать это, создав новый graphql-client.tsфайл внутри apps/product-hunt/libкаталога со следующим содержимым:

// apps/product-hunt/lib/graphql-client.ts

import { GraphQLClient } from "graphql-hooks";
import memCache from "graphql-hooks-memcache";
import { useMemo } from "react";

let graphQLClient;

const createClient = (initialState) => {
  return new GraphQLClient({
    ssrMode: typeof window === "undefined",
    url: process.env.NEXT_PUBLIC_PH_API_ENDPOINT, // Server URL (must be absolute)
    cache: memCache({ initialState }),
    headers: {
      Authorization: `Bearer ${process.env.NEXT_PUBLIC_PH_TOKEN}`,
    },
  });
};

export const initializeGraphQL = (initialState = null) => {
  const _graphQLClient = graphQLClient ?? createClient(initialState);

  // After navigating to a page with an initial GraphQL state, create a new
  // cache with the current state merged with the incoming state and set it to
  // the GraphQL client. This is necessary because the initial state of
  // `memCache` can only be set once
  if (initialState && graphQLClient) {
    graphQLClient.cache = memCache({
      initialState: Object.assign(
        graphQLClient.cache.getInitialState(),
        initialState
      ),
    });
  }

  // For SSG and SSR always create a new GraphQL Client
  if (typeof window === "undefined") {
    return _graphQLClient;
  }

  // Create the GraphQL Client once in the client
  if (!graphQLClient) {
    graphQLClient = _graphQLClient;
  }

  return _graphQLClient;
};

export const useGraphQLClient = (initialState) => {
  const store = useMemo(() => initializeGraphQL(initialState), [initialState]);

  return store;
};

Приведенный выше код похож на официальный пример Next.js GraphQL. Основная идея вышеупомянутого файла — создать клиента GraphQL, который поможет нам запрашивать данные с сервера GraphQL.

createClientФункция отвечает за создание клиента GraphQL с использованием graphql-hooksпакета.

initializeGraphQLФункция отвечает за инициализацию нашего клиента GraphQL использования createClient, а также увлажняющий наш клиента GraphQL на стороне клиента. Это необходимо, потому что мы используем Next.js, который позволяет нам получать данные как на стороне клиента, так и на стороне сервера. Таким образом, если данные извлекаются на стороне сервера, клиентская сторона также должна быть заполнена теми же данными без выполнения каких-либо дополнительных запросов к серверу GraphQL.

Это useGraphQLClientловушка, которую можно использовать для создания клиента GraphQL.

Затем нам также нужно создать еще один файл graphql-request.tsвнутри apps/product-hunt/libкаталога со следующим содержимым:

// apps/product-hunt/lib/graphql-request.ts

const defaultOpts = {
  useCache: true,
};

// Returns the result of a GraphQL query. It also adds the result to the
// cache of the GraphQL client for better initial data population in pages.

// Note: This helper tries to imitate what the query hooks of `graphql-hooks`
// do internally to make sure we generate the same cache key
const graphQLRequest = async (client, query, options = defaultOpts) => {
  const operation = {
    query,
  };
  const cacheKey = client.getCacheKey(operation, options);
  const cacheValue = await client.request(operation, options);

  client.saveCache(cacheKey, cacheValue);

  return cacheValue;
};

export default graphQLRequest;

graphQLRequestФункция отвечает за возврат результата запроса GraphQL, а также добавить результат в кэше клиента GraphQL в.

Приведенный выше код похож на официальный пример Next.js GraphQL.

Далее нам нужно обновить apps/product-hunt/pages/_app.tsxфайл следующим содержанием:

// apps/product-hunt/pages/_app.tsx

import { ClientContext } from "graphql-hooks";
import { AppProps } from "next/app";
import Head from "next/head";
import React from "react";
import { useGraphQLClient } from "../lib/graphql-client";

const NextApp = ({ Component, pageProps }: AppProps) => {
  const graphQLClient = useGraphQLClient(pageProps.initialGraphQLState);

  return (
    <ClientContext.Provider value={graphQLClient}>
      <Head>
        <title>Welcome to product-hunt!</title>
      </Head>
      <Component {...pageProps} />
    </ClientContext.Provider>
  );
};

export default NextApp;

Приведенный выше код гарантирует, что все наше приложение будет иметь доступ к поставщику контекста GraphQL, заключив наше приложение в оболочкуClientContext.Provider.

Затем нам нужно создать еще один файл all-posts.tsвнутри apps/product-hunt/queriesкаталога со следующим содержимым:

// apps/product-hunt/queries/all-posts.ts

const ALL_POSTS_QUERY = `
  query allPosts {
    posts {
      edges {
        node {
          id
          name
          description
          votesCount
          website
          thumbnail {
            url
          }
        }
      }
    }
  }
`;

export default ALL_POSTS_QUERY;

Вышеупомянутый запрос GraphQL позволит нам получить все сообщения из конечной точки ProductHunt GraphQL API.

Давайте также создадим новый product.tsфайл внутри apps/product-hunt/typesкаталога со следующим содержимым для определения Productтипа:

// apps/product-hunt/types/product.ts

export default interface Product {
  id: number;
  name: string;
  tagline: string;
  slug: string;
  thumbnail: {
    image_url: string;
  };
  user: {
    avatar_url: string;
    name: string;
  };
}

Приведенный выше код добавляет типы TypeScript для Product. Продукт может иметь идентификатор, имя, слоган, ярлык, миниатюру и пользователя. Вот как Product Hunt GraphQL возвращает данные.

Читайте также:  Как Google использует искусственный интеллект?

Далее нам нужно обновить apps/product-hunt/pages/index.tsxфайл следующим содержанием:

// apps/product-hunt/pages/index.tsx

import { useQuery } from "graphql-hooks";
import { GetStaticProps, NextPage } from "next";
import Image from "next/image";
import React from "react";
import { initializeGraphQL } from "../lib/graphql-client";
import graphQLRequest from "../lib/graphql-request";
import {
  StyledCard,
  StyledCardColumn,
  StyledCardLink,
  StyledCardRow,
  StyledCardTagline,
  StyledCardThumbnailContainer,
  StyledCardTitle,
  StyledContainer,
  StyledGrid,
} from "../public/styles";
import ALL_POSTS_QUERY from "../queries/all-posts";
import Product from "../types/product";

interface IProps {
  hits: Product[];
}

const ProductsIndexPage: NextPage<IProps> = () => {
  const { data } = useQuery(ALL_POSTS_QUERY);

  return (
    <StyledContainer>
      <StyledGrid>
        {data.posts.edges.map(({ node }) => {
          return (
            <StyledCardLink key={node.id} href={node.website} target="_blank">
              <StyledCard>
                <StyledCardColumn>
                  <StyledCardThumbnailContainer>
                    <Image src={node.thumbnail.url} layout="fill" />
                  </StyledCardThumbnailContainer>
                </StyledCardColumn>
                <StyledCardColumn>
                  <StyledCardRow>
                    <StyledCardTitle>{node.name}</StyledCardTitle>
                    <StyledCardTagline>{node.description}</StyledCardTagline>
                  </StyledCardRow>
                </StyledCardColumn>
              </StyledCard>
            </StyledCardLink>
          );
        })}
      </StyledGrid>
    </StyledContainer>
  );
};

export const getStaticProps: GetStaticProps = async () => {
  const client = initializeGraphQL();

  await graphQLRequest(client, ALL_POSTS_QUERY);

  return {
    props: {
      initialGraphQLState: client.cache.getInitialState(),
    },
    revalidate: 60,
  };
};

export default ProductsIndexPage;

В приведенном выше фрагменте кода мы делаем две вещи:

  1. Мы получаем данные с помощью ALL_POSTS_QUERYзапроса GraphQL, а затем сопоставляем dataмассив, возвращаемый API ProductHunt.
  2. Мы получаем данные во время сборки спомощью getStaticProps, который является функцией Next.js. Однако, если мы получим данные во время сборки, они могут устареть. Итак, воспользуемся revalidateопцией. Revalidate необязательное количество (в секундах), после которого может произойти повторное создание страницы. Это также известно как инкрементная статическая регенерация.

Давайте также добавим стили, добавив в apps/product-hunt/public/styles.tsфайл следующий контент :

// apps/product-hunt/public/styles.ts

import styled from "styled-components";

export const StyledContainer = styled.div`
  padding: 24px;
  max-width: 600px;
  margin:  auto;
  font-family: sans-serif;
`;

export const StyledGrid = styled.div`
  display: grid;
  grid-template-columns: repeat(1, minmax(, 1fr));
  grid-gap: 24px;
`;

export const StyledCardLink = styled.a`
  text-decoration: none;
  color: #000;
`;

export const StyledCard = styled.div`
  display: flex;
  gap: 12px;
  padding: 12px;
  background-color: #f7f7f7;
`;

export const StyledCardColumn = styled.div`
  display: flex;
  flex-direction: column;
  gap: 4px;
  justify-content: space-between;
`;

export const StyledCardRow = styled.div`
  display: flex;
  flex-direction: column;
  gap: 4px;
`;

export const StyledCardThumbnailContainer = styled.div`
  object-fit: cover;

  width: 150px;
  height: 150px;
  position: relative;
`;

export const StyledCardTitle = styled.div`
  font-size: 18px;
  font-weight: bold;
`;

export const StyledCardTagline = styled.div`
  font-size: 14px;
  line-height: 1.5;
`;

Теперь, если мы запустим yarn startкоманду в новом окне терминала, мы увидим следующий экран на http: // localhost: 4200 /.

Чтобы исправить указанную выше проблему, нам нужно обновить наш apps/product-hunt/next.config.jsфайл следующим содержанием:

// apps/product-hunt/next.config.js

// eslint-disable-next-line @typescript-eslint/no-var-requires
const withNx = require("@nrwl/next/plugins/with-nx");

module.exports = withNx({
  nx: {
    // Set this to false if you do not want to use SVGR
    // See: https://github.com/gregberge/svgr
    svgr: true,
  },
  images: {
    domains: ["ph-files.imgix.net", "ph-avatars.imgix.net"],
  },
});

Мы добавили домены, из которых API Product Hunt извлекает изображения. Это необходимо, потому что мы используем компонент Next Image.

Теперь, если мы перезапустим наш сервер, мы сможем увидеть следующий экран на http: // localhost: 4200 /.

Создание библиотеки компонентов многократного использования

Мы успешно создали первую страницу Product Hunt. Однако мы видим, что все наши стили относятся к одному приложению. Итак, если мы хотим повторно использовать те же стили при создании другого приложения, нам придется скопировать эти стили в новое приложение.

Один из способов решить эту проблему — создать отдельную библиотеку компонентов и сохранить там эти стили. Эта библиотека компонентов может быть повторно использована несколькими приложениями.

Чтобы создать новую библиотеку React в Nx, мы можем запустить следующую команду из корня нашего проекта:

nx generate @nrwl/react:library components

Приведенная выше команда даст нам подсказку, изображенную ниже.

Приведенная выше команда даст нам подсказку, изображенную ниже

Поскольку мы используем стилизованные компоненты, мы выберем этот вариант в предложении выше. Выбрав эту опцию, мы увидим следующие изменения в нашем терминале.

оскольку мы используем стилизованные компоненты, мы выберем этот вариант в предложении выше

Затем мы скопируем все стили из apps/product-hunt/public/styles.tsв libs/components/src/lib/components.tsxфайл.

Также нам нужно импортировать все стили из этой библиотеки. Для этого нам нужно изменить наш apps/product-hunt/pages/index.tsxфайл:

// apps/product-hunt/pages/index.tsx

import {
  StyledCard,
  StyledCardColumn,
  StyledCardLink,
  StyledCardRow,
  StyledCardTagline,
  StyledCardThumbnailContainer,
  StyledCardTitle,
  StyledContainer,
  StyledGrid,
} from "@nx-nextjs-monorepo/components";

Если мы посмотрим на наш tsconfig.base.jsonфайл, мы увидим следующую строку:

// tsconfig.base.json

"paths": {
  "@nx-nextjs-monorepo/components": ["libs/components/src/index.ts"]
}

@nx-nextjs-monorepo/componentsэто имя нашей библиотеки компонентов. Следовательно, мы импортировали все стили из этой библиотеки в apps/product-hunt/pages/index.tsxфайл.

Мы можем удалить apps/product-hunt/public/styles.tsфайл, так как он нам больше не нужен.

Теперь, если мы перезапустим наш сервер Nx, мы увидим следующий экран на http: // localhost: 4200 /.

Заключение

В этой статье мы узнали, как мы можем использовать Nx для создания монорепозитория с Next.js и стилизованными компонентами. Мы также узнали, как использование monorepos может улучшить опыт разработки и скорость создания приложений. Мы создали приложение Next.js и библиотеку стилизованных компонентов, но с помощью Nx можно создавать приложения Angular, Cypress, Nest, Gatsby, Express и Storybook, используя их генераторы.

Оцените статью
bestprogrammer.ru
Добавить комментарий