Front-end Engineering Practice — Custom React Scaffolding & CLI Upgrade

Архитектура внешний интерфейс DevOps
Front-end Engineering Practice — Custom React Scaffolding & CLI Upgrade

⚠️ Эта статья является первой подписанной статьей сообщества Nuggets, и ее перепечатка без разрешения запрещена.

предисловие

ПредыдущийРазработка корпоративного интерфейса командной строкиВ 2019 году был сделан предварительный CLI для процесса построения этой части, но это только первый шаг в построении инженерной системы.

Большинство разработчиков, как правило, разрабатывают бизнес-код. Недостаточно полагаться на CLI для ограничения с конца devops, поэтому небольшая команда в целом также начнет с скаффолдинга.

В этой статье React будет использоваться в качестве примера для настройки набора настраиваемых шаблонов и обновления предыдущего интерфейса командной строки.

Пользовательские леса React

Конструкция строительных лесов обычно делится на две части: одна — инфраструктура, другая — бизнес-архитектура.

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

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

Создайте инфраструктуру

Как и CLI, скаффолдинг создается с нуля, поэтому исходный проект и конфигурация ts по-прежнему являются отправной точкой.

npm init
tsx --init

как указано вышеpackage.josnиtsconfig.jsonсгенерированный,tsconfig.jsonЭлементы конфигурации могут напрямую использовать следующую конфигурацию или переопределять ее в соответствии с вашими потребностями.

{
  "include": [
    "src"
  ],
  "compilerOptions": {
    "module": "CommonJS",
    "target": "es2018",
    "outDir": "dist",
    "noEmit": true,
    "jsx": "react-jsx",
    "esModuleInterop": true,
    "moduleResolution": "node",
    "strict": true,
    "noUnusedLocals": false,
    "noFallthroughCasesInSwitch": true,
    "baseUrl": "./",
    "keyofStringsOnly": true,
    "skipLibCheck": true,
    "paths": {
      "@/*": [
        "./src/*"
      ]
    }
  }
}

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

{
  "name": "react-tpl",
  "version": "1.0.0",
  "description": "a react tpl",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "cross-env NODE_ENV=development webpack-dev-server --config ./script/webpack.config.js",
  },
  "author": "cookieboty",
  "license": "ISC",
  "dependencies": {
    "@babel/cli": "^7.14.5",
    "@babel/core": "^7.14.6",
    "@babel/preset-env": "^7.14.7",
    "@babel/preset-react": "^7.14.5",
    "@babel/preset-typescript": "^7.14.5",
    "babel-loader": "^8.2.2",
    "clean-webpack-plugin": "^4.0.0-alpha.0",
    "cross-env": "^7.0.3",
    "css-loader": "^6.1.0",
    "file-loader": "^6.2.0",
    "html-webpack-plugin": "^5.3.2",
    "less": "^4.1.1",
    "less-loader": "^10.0.1",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "style-loader": "^3.1.0",
    "typescript": "^4.3.5",
    "webpack": "^5.45.1",
    "webpack-cli": "3.3.12",
    "webpack-dev-server": "^3.11.2"
  },
  "devDependencies": {
    "@types/react": "^17.0.14",
    "@types/react-dom": "^17.0.9"
  }
}

настроить веб-пакет

новыйscript/webpack.config.jsСкопируйте приведенную ниже конфигурацию.

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
  mode: "development",
  entry: "./src/index.tsx",
  devServer: {
    contentBase: path.resolve(__dirname, "dist"),
    hot: true,
    historyApiFallback: true,
    compress: true,
  },
  resolve: {
    alias: {
      '@': path.resolve('src')
    },
    extensions: ['.ts', '.tsx', '.js', '.json']
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        use: {
          loader: require.resolve('babel-loader')
        },
        exclude: [/node_modules/],
      },
      {
        test: /\.(css|less)$/,
        use: [
          {
            loader: "style-loader",
          },
          {
            loader: "css-loader",
            options: {
              importLoaders: 1,
            },
          },
        ],
      },
      {
        test: /\.(png|svg|jpg|gif|jpeg)$/,
        loader: 'file-loader'
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        loader: 'file-loader'
      }
    ],
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: 'tpl/index.html'
    }),
  ]
};

Здесь следует отметить, чтоwebpack-cliиwebpack-dev-serverизВерсии должны быть согласованы, вы можете использовать версию 3.0.Если версии несовместимы, будет сообщено об ошибке.

Настройка связанных с React

новыйtpl/index.htmlфайл (шаблон html), скопируйте следующий код

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8"/>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

новыйsrc/index.tsxфайл (входной файл), скопируйте следующий код

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

ReactDOM.render(
  <App />,
  document.getElementById("root")
);

новый.babelrcфайл (конфигурация парсинга babel), скопируйте следующий код

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react",
    [
      "@babel/preset-typescript",
      {
        "isTSX": true,
        "allExtensions": true
      }
    ]
  ]
}

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

image.png

браузер открытhttp://localhost:8081/, вы можете увидеть отображаемую страницу, написанную

image.png

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

Из-за размера эта статья не будет слишком много объяснять элементы конфигурации Webpack, Babel и React. Она только предоставляет полный пример, и вы можете завершить построение базовой структуры в соответствии с шагами. Если учащиеся хотят Знайте более важные детали. Рекомендуется читать документацию сразу после завершения строительства, а затем настраивать нужные функции в соответствии с документацией, больше думать и делать больше.

Оптимизация конфигурации Webpck Dev

Упрощение вывода информации о сервере

Из предыдущей диаграммы видно, чтоwebpack-dev-serverВыходная информация беспорядочна, вы можете использовать поле конфигурации Stats для фильтрации выходной информации.

Как правило, нам нужно только увидеть информацию об ошибке, и можно добавить следующие параметры:

devServer: {
    stats: 'errors-only', // 过滤信息输出
    contentBase: path.resolve(__dirname, "dist"),
    hot: true,
    historyApiFallback: true,
    compress: true,
},

Добавить вывод информации о сборке

image.png

ProgressPlugin может отслеживать процент выполнения каждого хука и выводить имя и описание каждого хука.

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

const { ProgressPlugin } = require('webpack')
plugins: [
    ...
    new ProgressPlugin(),
]

Оптимизируйте бизнес-модули

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

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

├── dist/                          // 默认的 build 输出目录
└── src/                           // 源码目录
    ├── assets/                    // 静态资源目录
    ├── config                     
        ├── config.js              // 项目内部业务相关基础配置
    ├── components/                // 公共组件目录
    ├── service/                   // 业务请求管理
    ├── store/                     // 共享 store 管理目录
    ├── util/                      // 工具函数目录
    ├── pages/                     // 页面目录
    ├── router/                    // 路由配置目录
    ├── .index.tsx                 // 依赖主入口
└── package.json

Настроить маршрутизацию

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

Преобразуйте сначалаindex.tsxВходной файл, код выглядит следующим образом:

import React from 'react'
import ReactDOM from 'react-dom'
import { HashRouter, Route, Switch } from 'react-router-dom'
import routerConfig from './router/index'
import './base.less'

ReactDOM.render(
  <React.StrictMode>
    <HashRouter>
      <Switch>
        {
          routerConfig.routes.map((route) => {
            return (
              <Route key={route.path} {...route} />
            )
          })
        }
      </Switch>
    </HashRouter>
  </React.StrictMode>,
  document.getElementById('root')
)

конфигурации файла router/index.ts код выглядит следующим образом:

import BlogsList from '@/pages/blogs/index'
import BlogsDetail from '@/pages/blogs/detail'

export default {
  routes: [
    { exact: true, path: '/', component: BlogsList },
    { exact: true, path: '/blogs/detail/:article_id', component: BlogsDetail },
  ],
}

Управление услугами

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

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

import * as information from './information'
import * as base from './base'

export {
  information,
  base
}

Это упрощает управление запросами.В качестве класса бизнес-запросов base.ts может обрабатывать некоторые специальные бизнес-обработки.

import { request } from '../until/request'

const prefix = '/api'

export const getAllInfoGzip = () => {
  return request({
    url: `${prefix}/apis/random`,
    method: 'GET'
  })
}

util/request, как унифицированный метод запроса, может быть заменен библиотеками запросов, такими как fetch, axios и т. д., и в этот метод может быть инкапсулирована общая логика перехвата.

import qs from 'qs'
import axios from "axios";

interface IRequest {
    url: string
    params?: SVGForeignObjectElement
    query?: object
    header?: object
    method?: "POST" | "OPTIONS" | "GET" | "HEAD" | "PUT" | "DELETE" | undefined
}

interface IResponse {
    count: number
    errorMsg: string
    classify: string
    data: any
    detail?: any
    img?: object
}

export const request = ({ url, params, query, header, method = 'POST' }: IRequest): Promise<IResponse> => {
    return new Promise((resolve, reject) => {
        axios(query ? `${url}/?${qs.stringify(query)}` : url, {
            data: params,
            headers: header,
            method: method,
        })
            .then(res => {
                resolve(res.data)
            })
            .catch(error => {
                reject(error)
            })
    })
}

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

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

import { information } from "@/service/index";

const { data } = await information.getAllInfoGzip({ id });

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

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

Обновление интерфейса командной строки

После того, как вышеупомянутые пользовательские леса React будут завершены, если мы напрямую используем CLI, созданный в предыдущей статье, для сборки проекта, построение не будет успешным.Студенты, которые все еще впечатлены, должны помнить, что входной файл предыдущего CLIsrc/index.js, шаблон html используетpublic/index.html.

Очевидно, что CLI в настоящее время далеко не соответствует требованиям, нам не нужно обновлять CLI каждый раз, когда мы разрабатываем, что нарушает принцип общности CLI.

Итак, как решить эту проблему?

пользовательский файл конфигурации

новый корневой каталогcli.config.jsonфайл, этот файл будет файлом, который необходимо прочитать конфигурацию.

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

{
  "entry": {
    "app": "./src/index.tsx"
  },
  "output": {
    "filename": "build.js",
    "path": "./dist"
  },
  "template": "tpl/index.html"
}

CLI синхронно преобразуется, и код выглядит следующим образом:

require('module-alias/register')
import webpack from 'webpack';
import { getCwdPath, loggerTiming, loggerError } from '@/util'
import { loadFile } from '@/util/file'
import { getProConfig } from './webpack.pro.config'
import ora from "ora";

export const buildWebpack = () => {

  const spinner = ora('Webpack building...')

  const rewriteConfig = loadFile(getCwdPath('./cli.config.json')) // 读取脚手架配置文件

  const compiler = webpack(getProConfig(rewriteConfig));

  return new Promise((resolve, reject) => {
    loggerTiming('WEBPACK BUILD');
    spinner.start();
    compiler.run((err: any, stats: any) => {
      console.log(err)
      if (err) {
        if (!err.message) {
          spinner.fail('WEBPACK BUILD FAILED!');
          loggerError(err);
          return reject(err);
        }
      }
    });

    spinner.succeed('WEBPACK BUILD Successful!');
    loggerTiming('WEBPACK BUILD', false);
  })
}

webpack.pro.config.tsкод показывает, как показано ниже:

import getBaseConfig from './webpack.base.config'
import { getCwdPath, } from '@/util'

interface IWebpackConfig {
  entry: {
    app: string
  }
  output: {
    filename: string,
    path: string
  }
  template: string
}

export const getProConfig = (config: IWebpackConfig) => {
  const { entry: { app }, template, output: { filename, path }, ...rest } = config

  return {
    ...getBaseConfig({
      mode: 'production',
      entry: {
        app: getCwdPath(app || './src/index.js')
      },
      output: {
        filename: filename || 'build.js',
        path: getCwdPath(path || './dist'), // 打包好之后的输出路径
      },
      template: getCwdPath(template || 'public/index.html')
    }),
    ...rest
  }
}

пройти черезloadFileфункции, прочитайте элемент пользовательской конфигурации скаффолдинга, замените начальное значение, а затем выполните сборку проекта. Результат сборки выглядит следующим образом:

image.png

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

Возьмите на себя процесс разработки

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

Сервер webpack-dev, настроенный в предыдущем скаффолде, используется на основе webpack-cli.

Поскольку интерфейс командной строки используется для управления средой разработки, нет необходимостиwebpack-dev-serverв видеwebpackПлагин используется, но вместо этого вызывает напрямуюwebpack-dev-serverизNode Api.

Извлеките конфигурацию webpack-dev-server из скаффолдинга прямо сейчас и поместите соответствующую конфигурацию в CLI.

const WebpackDevServer = require('webpack-dev-server/lib/Server')

export const devWebpack = () => {
  const spinner = ora('Webpack running dev ...')

  const rewriteConfig = loadFile(getCwdPath('./cli.config.json'))
  const webpackConfig = getDevConfig(rewriteConfig)

  const compiler = webpack(webpackConfig);

  const devServerOptions = {
    contentBase: 'dist',
    hot: true,
    historyApiFallback: true,
    compress: true,
    open: true
  };
  
  const server = new WebpackDevServer(compiler, devServerOptions);

  server.listen(8000, '127.0.0.1', () => {
    console.log('Starting server on http://localhost:8000');
  });
}

Затем добавьте соответствующую команду в сценарии package.json скаффолдинга, чтобы завершить захват среды разработки.Команда выглядит следующим образом:

"scripts": {
     "dev": "cross-env NODE_ENV=development fe-cli webpack",
     "build": "cross-env NODE_ENV=production fe-cli webpack"
 }

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

Оптимизация конфигурации сборки веб-пакета

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

mini-css-extract-plugin

mini-css-extract-pluginЭто плагин для извлечения стилей, который может извлекать css отдельно и упаковывать его в один файл.Он создает файл css для каждого файла js, содержащего css. Также поддерживается загрузка по требованию css и sourceMaps. Код конфигурации выглядит следующим образом:

{
    rules: [
        test: /\.(css|less)$/,
            use: [MiniCssExtractPlugin.loader],
          }
    ]
}
  
plugins: [
      new MiniCssExtractPlugin({
        filename: '[name].[contenthash].css',
        chunkFilename: '[id].[contenthash].css',
        ignoreOrder: true,
      })
    ]

Извлечь общие модули

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

 optimization: {
      splitChunks: {
        cacheGroups: {
          commons: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendors',
            chunks: 'all',
          },
        },
      },
},

image.png

Как показано на рисунке, продукт, созданный сейчас, стал намного понятнее в одно мгновение?

Оптимизация пути продукта сборки

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

image.png

На самом деле это очень просто, добавьте пути всех продуктов сборки равномерно перед ними.static/js, чтобы продукт, полученный во время строительства, был таким, как показано на следующем рисунке.

image.png

Настройка инкрементных сборок (постоянный кеш)

Это новая функция веб-пакета 5. В веб-пакете 4 наш общий метод оптимизации сборки заключается в использованииhard-source-webpack-pluginЭтот плагин будетзависимости модуляОн кэшируется, и кэш будет считываться напрямую во время второй сборки, чтобы ускорить сборку.

Этот процесс заменен кэшем в webpack 5. Официальная встроенная функция постоянного кэша тоже очень удобна в настройке, достаточно добавить следующий код:

import { getCwdPath } from '@/util'

export default {
  cache: {
    type: 'filesystem',  //  'memory' | 'filesystem'
    cacheDirectory: getCwdPath('./temp_cache'), // 默认将缓存存储在 当前运行路径/.cache/webpack
    // 缓存依赖,当缓存依赖修改时,缓存失效
    buildDependencies: {
      // 将你的配置添加依赖,更改配置时,使得缓存失效
      config: [__filename]
    },
    allowCollectingMemory: true,
    profile: true,
  },
}

Затем при запуске сборки или разработки файл кеша будет создан в текущем рабочем каталоге следующим образом:

image.png

Теперь посмотрим, насколько увеличилась скорость сборки:

image.png

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

Здесь следует отметить один момент, поскольку мы вызываем Node Api веб-пакета для сборки, поэтому нам нужно явно закрыть компилятор для нормального создания файлов кеша.

const compiler = webpack(webpackConfig);

  try {
    compiler.run((err: any, stats: any) => {

      if (err) {
        loggerError(err);
      } else {
        loggerSuccess('WEBPACK SUCCESS!');
      }
      compiler.close(() => {
        loggerInfo('WEBPACK GENERATE CACHE'); // 显示调用 compiler 关闭,生成缓存
      });
      loggerTiming('WEBPACK BUILD', false);
    });
  } catch (error) {
    loggerError(error)
  }

Заинтересованные студенты могут попробовать среду разработки, и скорость запуска также будет снижена до второго уровня.

Специальная благодарность

image.png

Это сообщение читателя из предыдущей статьи, здесь@сайтама, поблагодарите этого одноклассника за его предложение. В дополнение к представлению идей кодирование и шаги будут более подробно описаны в следующей серии сообщений в блоге, а демонстрационные версии проекта также будут своевременно предоставлены для справки. Более подходящие предложения от других учащихся также могут быть сообщается в области комментариев. Я надеюсь, что помимо завершения этой серии я смогу писать лучше, чтобы учиться друг у друга и расти вместе с другими одноклассниками.

напиши в конце

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

Весь код проекта загружен наадрес проекта, Заинтересованные студенты могут вытащить ссылку, и соответствующие коды всех последующих столбцов будут размещены в единомBOTY DESIGNсередина.