Третий выпуск поста из четырех частей о том, как перенести идею из каркаса в производственное развертывание с помощью ReactJS. В этом посте мы создадим приложение для фильмов, используя библиотеку компонентов, созданную в предыдущем посте.

Первоначально это было размещено здесь.

Это третий пост в серии. Вы можете найти первый пост здесь

Где мы

Хорошо, так что до сих пор у нас есть

  • Мы провели мозговой штурм над нашей блестящей идеей создать приложение для просмотра фильмов.
  • Мы решили, какие функции необходимы в рамках MVP.
  • Наша команда дизайнеров предоставила нам каркасы.
  • Мы настроили наш проект как Monorepo.
  • Мы настроили правила линтинга, средство форматирования кода и перехватчики коммитов.
  • Мы настроили нашу библиотеку компонентов
  • Мы добавили поддержку Typescript в нашу библиотеку компонентов.
  • У нас есть сборник рассказов
  • Мы добавили наши компоненты в библиотеку компонентов
  • Мы добавили модульные тесты для наших компонентов
  • Мы можем увидеть наши компоненты, продемонстрированные в Storybook.

Что мы собираемся делать сейчас

Итак, следующим шагом будет создание приложения для фильмов с использованием библиотеки компонентов. Мы будем использовать TMDB для получения сведений о нашем фильме. Мы будем поддерживать состояние нашего приложения с помощью Redux. Мы будем использовать Webpack для сборки нашего приложения. В конце этого поста мы должны были преобразовать наши каркасы в настоящий работающий веб-сайт.

TL;DR

Это пост из 4 частей

Исходный код доступен здесь

Демо-версия библиотеки компонентов доступна здесь

Демо-приложение Movie доступно здесь

Извлечение общей функциональности в core

Всегда рекомендуется извлекать общие сервисы, чтобы сохранить их СУХИМИ. Как мы извлекли общие компоненты в нашем предыдущем посте, мы извлечем общие функции в core.

Что находится в core

Определение общей функциональности очень широкое, и есть несколько способов снять шкуру с курицы 🐔 Для нашего проекта мы будем извлекать наши вызовы API в core

Настройка core

Переместить в папку packages

cd packages

Создайте новую папку для нашего core

mkdir core
cd core

Инициализировать проект пряжи

yarn init

Следуя шагам по именованию, как мы это делали в предыдущем посте, наш package.json выглядит так:

{
  "name": "@awesome-movie-app/core",
  "version": "1.0.0",
  "description": "Core Services for Awesome Movie App",
  "main": "index.js",
  "repository": "[email protected]:debojitroy/movie-app.git",
  "author": "Debojit Roy <[email protected]>",
  "license": "MIT",
  "private": true
}

Корпус core

Добавление axios

Мы будем делать много вызовов XHR для получения данных. Мы можем использовать собственные функции AJAX браузера или новый блестящий API fetch. С таким количеством браузеров и различной реализацией fetch безопаснее не использовать fetch. Если мы решим включить fetch, нам придется добавить необходимые полифиллы.

Поэтому гораздо лучше использовать axios, который обеспечит правильную работу наших сетевых вызовов независимо от браузера пользователя.

Инициализация config переменных

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

Итак, мы создадим файл bootstrap, который будет использоваться для инициализации конфигурации.

let config: { url: string; apiKey: string } = { url: "", apiKey: "" }
export const setConfig = (incomingConfig: { url: string; apiKey: string }) => {
  config = incomingConfig
}
export const getConfig = () => config

Добавление search service

Одной из первых вещей в соответствии с нашим требованием было добавить службу поиска. Мы собираемся использовать Конечную точку поиска

После сопоставления ответа функциональность выглядит примерно так

import axios from "axios"
import isNil from "lodash/isNil"
import { getConfig } from "./bootstrap"
export interface SearchResult {
  popularity: number
  vote_count: number
  video: boolean
  poster_path: string
  id: number
  adult: boolean
  backdrop_path: string
  original_language: string
  original_title: string
  genre_ids: number[]
  title: string
  vote_average: number
  overview: string
  release_date: string
}
export interface SearchResponse {
  page: number
  total_results: number
  total_pages: number
  results: SearchResult[]
}
export const searchMovie = async (
  queryString?: string
): Promise<SearchResponse> => {
  const config = getConfig()
  if (isNil(queryString) || queryString.trim() === "") {
    return new Promise(resolve => {
      resolve({
        page: 1,
        total_pages: 1,
        total_results: 0,
        results: [],
      })
    })
  }
  const encodedQuery = encodeURI(queryString)
  const result = await axios.get(
    `${config.url}/3/search/movie?api_key=${config.apiKey}&query=${encodedQuery}`
  )
  return result.data
}

Мы продолжим отображать остальной функционал, полный код доступен здесь

Настройка веб-приложения

Теперь, когда необходимые сервисы определены, мы сосредоточимся на создании веб-приложения.

Разделение кода таким образом помогает повторно использовать функциональность без многократного копирования и вставки.

Основные части нашего веб-приложения будут

  • Общедоступные файлы
  • Конфигурация веб-пакета
  • Общие части
  • Разделение по функциям

Настройка проекта веб-приложения

Перейдите в папку packages

cd packages

Создайте новую папку для нашего webapp

mkdir webapp
cd webapp

Инициализировать проект пряжи

yarn init

Следуя шагам по именованию, как мы это делали в предыдущем посте, наш package.json выглядит так:

{
  "name": "@awesome-movie-app/webapp",
  "version": "1.0.0",
  "description": "Web Application for Awesome Movie App",
  "main": "index.js",
  "repository": "[email protected]:debojitroy/movie-app.git",
  "author": "Debojit Roy <[email protected]>",
  "license": "MIT",
  "private": true
}

Настройка public активов

Поэтому для монтирования проекта React нам нужен элемент DOM, где React может взять на себя управление и внедрить элементы. Для этого нам нужен файл index.html, который будет обслуживаться сервером до того, как React вступит во владение.

Мы сохраним это index.html в нашей папке public, но вы можете выбрать любое другое имя.

Вы можете найти файл здесь. Не стесняйтесь называть папку и файлы по своему усмотрению, но обязательно обновите их в конфигурации веб-пакета на следующем шаге.

Настройка веб-пакета

Мы будем использовать webpack для упаковки нашего приложения. Вы можете выбрать любой другой упаковщик для своего проекта и внести соответствующие изменения.

Подготовьте папку config

mkdir config

Настройка общей конфигурации

Для нашей разработки local мы будем использовать webpack dev server и производственную сборку и минификацию для сборки production. Но некоторые шаги будут общими для обоих, мы их вынесем в наш конфиг common.

Итак, наш общий конфиг выглядит примерно так

// webpack.common.js
const path = require("path")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const { CleanWebpackPlugin } = require("clean-webpack-plugin")
const HtmlWebPackPlugin = require("html-webpack-plugin")
const isEnvDevelopment = process.env.NODE_ENV === "development"
const isEnvProduction = process.env.NODE_ENV === "production"
module.exports = {
  entry: { main: "./src/entry/index.tsx" },
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".jsx"],
  },
  node: {
    fs: "empty",
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx|mjs|ts|tsx)$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
        },
      },
      {
        test: /\.css$/,
        use: [
          "style-loader",
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              hmr: isEnvDevelopment,
            },
          },
          "css-loader",
          {
            loader: "postcss-loader",
            options: {
              ident: "postcss",
              plugins: () => [
                require("postcss-flexbugs-fixes"),
                require("postcss-preset-env")({
                  autoprefixer: {
                    flexbox: "no-2009",
                  },
                  stage: 3,
                }),
                require("postcss-normalize"),
              ],
              sourceMap: isEnvProduction,
            },
          },
        ],
        // Don't consider CSS imports dead code even if the
        // containing package claims to have no side effects.
        // Remove this when webpack adds a warning or an error for this.
        // See https://github.com/webpack/webpack/issues/6571
        sideEffects: true,
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: ["file-loader"],
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: ["file-loader"],
      },
    ],
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebPackPlugin({
      title: "Awesome Movie App",
      template: "./public/index.html",
      filename: "./index.html",
      favicon: "./public/favicon.ico",
    }),
  ],
}

Большинство вещей говорят сами за себя. Если вы новичок в webpack, я бы посоветовал ознакомиться с их потрясающей документацией.

Настройка конфига dev

С настройкой конфигурации common мы хотели бы настроить нашу конфигурацию dev. Мы хотим использовать webpack dev server и hmr с откатом маршрутизации.

Наша конфигурация разработчика выглядит так

//webpack.dev.js
const path = require("path")
const merge = require("webpack-merge")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const common = require("./webpack.common.js")
module.exports = merge(common, {
  mode: "development",
  devtool: "inline-source-map",
  output: {
    path: path.join(__dirname, "../../dist/dist-dev"),
    filename: "[name].[contenthash].js",
    publicPath: "/",
  },
  devServer: {
    contentBase: "./dist-dev",
    historyApiFallback: true,
    allowedHosts: [".debojitroy.com"],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
    }),
  ],
})

Сборка common деталей

Общие детали — это элементы, не зависящие от функций, которые имеют сквозную функциональность.

Общие — Компоненты

Это общие компоненты, которые будут использоваться во всех функциях.

Общее — Конфигурация

Конфигурации для приложений, которые определены здесь.

Общий — Редукс

Здесь будут храниться специальные файлы Redux.

Общее — Маршруты

Здесь будут храниться определенные файлы маршрутизации.

Общие — утилиты

Здесь будут добавлены общие утилиты.

Особенности здания

Функции — это место, где будут сохранены фактические функции приложения. Думайте о каждой функции как об отдельной части приложения. Каждая функция сама по себе должна выделяться. В демонстрационных целях мы рассмотрим функцию SiteHeader.

Заголовок сайта — Компоненты

Эта часть будет содержать все наши компоненты React, как следует из названия. В зависимости от требуемой функциональности мы разобьем нашу функцию на компоненты.

Заголовок сайта — Редукс

Сюда будут добавлены все файлы, связанные с Redux.

Я быстро пропускаю эти разделы, так как они являются стандартными материалами React/Redux, которые лучше объясняются во многих других местах.

Запуск веб-приложения

Добавление .env

Нам нужно объявить переменные конфигурации для запуска нашего приложения. На нашем производственном этапе мы будем делать это по-другому. Для локальной разработки добавим файл .env и добавим его в .gitignore, чтобы он не проверялся.

Go to webapp

cd packages/webapp

Создайте файл .env

vim .env

Добавьте значения конфигурации

API_URL=https://api.themoviedb.org
API_KEY=<Replace with actual key>

Подготовка сценария запуска

Теперь, когда у нас есть настройка .env, последнее, что нам нужно сделать, это добавить скрипт start.

Откройте package.json внутри webapp и добавьте это под scripts

"start": "cross-env development=true webpack-dev-server --config config/webpack.dev.js --open --port 8000"

Запуск веб-приложения локально

Как только мы закончим настройку webapp, давайте попробуем запустить его локально.

Сначала создайте свой components

cd packages/components
yarn build-js:prod

Во-вторых, создайте свой core

cd packages/core
yarn build-js:prod

Наконец начните свой webapp

cd packages/webapp
yarn start

Если все прошло хорошо, вы должны увидеть что-то вроде этого

Фу!!! Это было долго.

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