У нас в команде есть пару проектов, для которых есть старые frontend. Написаны все они на разных технологиях, но объединяет их одно: нежелание кого-либо туда лезть и что-то править. Команде там кажется страшно, непонятно и неудобно. Любая доработка превращается в головную боль. В очередном проекте нам хотелось не допустить такого развития событий, и, кажется, у нас получилось.

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

В рамках этой статьи мы построим скелет будущего frontend приложения. На основе этого скелета и наших ошибок, о которых мы рассказали далее, можно брать и пилить проект с низким порогом вхождения (опробовали на новых сотрудниках).

Мы постарались написать приложение так, чтобы backend разработчикам было наиболее привычно и комфортно. Классы, интерфейсы, наследование, типизация, вот это вот всё… И, конечно же, чтобы визуально это смотрелось красиво и современно. Для всех этих целей мы выбрали Vue и TS. Перед началом работ советую ознакомиться с документацией по vue и vue router

Итак, начнём…

1. Скелет проекта

Нам потребуются установленные Node.js и npm (диспетчер пакетов Node.js). Напоминаю, что безопаснее пользоваться дистрибутивами и пакетами, которые вышли раньше 24 февраля 22 года.

curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -
sudo apt-get install -y nodejs

Нам понадобятся:

  • Vue (документация тут);

  • TypeScript;

  • Axios (для запросов к серверу);

  • Vue Router (поддержка роутинга во Vue о которой можно почитать тут);

  • Vuex (позволяет общаться компонентам между собой. Можно ознакомиться тут);

  • CSS Pre-processors;

  • Linter / Formatter (анализ качества вашего кода). Например: eslint или tslint;

  • Пакеты vue-class-component и vue-property-decorator для того чтобы привести к классо-ориентированному виду, который все мы так любим;

  • UI Framework с которым мы будем работать, для того чтобы не было мучительно больно изобретать велосипеды, рисовать кнопочки и заниматься другими трудоемкими вещами. Мне приходилось работать со следующими фреймворками:

    • boostrap-vue (показался не удобным);

    • element-ui (большое разнообразие компонентов);

    • vuetify (достаточное количество компонентов и хорошая документация).

Для своего проекта мы выбрали element ui из-за обилия различных компонентов.

Все это можно легко поставить и развернуть при помощи vue-cli, но мы в команде выбрали другой путь. После всем нам известных событий, много библиотек стали тянуть транзитивно вредоносные зависимости. Поэтому команда приняла решение обойтись без vue-cli и использовать webpack для более очевидного управления зависимостями и более гибкой сборки проекта.

Пример получившегося package.json:
{
 "name": "hello-world",
 "version": "1.0.0",
 "scripts": {
   "build:dev": "npx webpack",
   "build:prod": "npx webpack --env production",
   "lint": "eslint . --ext .ts",
   "lint:fix": "npm run lint -- --fix",
   "serve": "npx webpack serve"
 },
 "dependencies": {
   "axios": "0.25.0",
   "element-ui": "2.15.6",
   "ts-jenum": "2.2.2",
   "vue": "2.6.14",
   "vue-axios": "3.4.0",
   "vue-cookies": "1.7.4",
   "vue-router": "3.5.3",
   "vuex": "3.6.2"
 },
 "devDependencies": {
   "@babel/core": "7.17.0",
   "@babel/preset-env": "7.16.11",
   "@babel/preset-typescript": "7.16.7",
   "@babel/runtime": "7.17.0",
   "@types/webpack-env": "1.16.3",
   "@typescript-eslint/eslint-plugin": "5.21.0",
   "@typescript-eslint/parser": "5.21.0",
   "@vue/eslint-config-typescript": "10.0.0",
   "babel-loader": "8.2.3",
   "babel-preset-vue": "2.0.2",
   "clean-webpack-plugin": "4.0.0",
   "css-loader": "6.6.0",
   "eslint": "^8.14.0",
   "eslint-plugin-vue": "^8.7.1",
   "eslint-webpack-plugin": "^3.1.1",
   "file-loader": "6.2.0",
   "html-webpack-plugin": "5.5.0",
   "mini-css-extract-plugin": "2.5.3",
   "sass": "1.49.7",
   "sass-loader": "12.4.0",
   "ts-loader": "9.2.6",
   "tsconfig-paths-webpack-plugin": "3.5.2",
   "typescript": "4.5.5",
   "url-loader": "4.1.1",
   "vue-class-component": "7.2.6",
   "vue-loader": "15.9.8",
   "vue-property-decorator": "9.1.2",
   "vue-template-compiler": "2.6.14",
   "webpack": "5.68.0",
   "webpack-cli": "4.9.2",
   "webpack-dev-server": "4.7.4"
 },
 // Избавляемся от вредоносных версий. Работает только с npm > 8.3
 "overrides": {
   "node-ipc@>9.2.1 <10": "9.2.1",
   "node-ipc@>10.1.0": "10.1.0"
 }
} 

Для установки всех пакетов на основе package.json достаточно выполнить команду npm i.

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

.eslintrc.js
module.exports = {
   root: true,
   env: {
       node: true
   },
   // Подключаем рекомендованные правила
   "extends": [
       "plugin:vue/recommended",
       'eslint:recommended',
       "@vue/typescript/recommended"
   ],
   parser: "@typescript-eslint/parser",
   parserOptions: {
       ecmaVersion: 2020,
       project: ["./tsconfig.json"],
   },
   // Дополняем рекомендованные правила своими
   rules: {
       // Отключаем вывод в консоль для прода
       "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
       // Отключаем дебаг для прода
       "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
       // Отключаем for in для массивов
       "@typescript-eslint/no-for-in-array": "warn",
       // Не ставим await в return
       "no-return-await": "warn",
       // Никаких any
       "@typescript-eslint/no-explicit-any": "warn",
       // Настраиваем отступы
       "indent": ["warn", 4],
       // Нет лишним пробелам
       "no-multi-spaces": "warn",
       // Пробелы перед/после ключевых слов
       "keyword-spacing": [2, {"before": true, "after": true}],
       // Проверка типов при сложении
       "@typescript-eslint/restrict-plus-operands": "warn",
       // Сравнение через тройное равно
       "eqeqeq": "warn",
       // Длинна строки кода
       "max-len": ["warn", { "code": 160 }],
       // Предупреждаем о забытых await
       "require-await": "warn",
       // Предупреждаем о забытых фигурных скобках
       "curly": "warn",
       // Максимальное количество классов в файле
       "max-classes-per-file": ["warn", 2],
       // Двойные кавычки
       "quotes": ["warn", "double"],
       // Проверка точек с запятой
       "semi": ["warn", "always"]
   }
}

Для проверки кода через eslint достаточно будет выполнить код: npm run lint.

2. Сборка проекта

Перейдём к самой ужасной части: сборке проекта на webpack. На самом деле это не так страшно, как выглядит на первый взгляд. Есть отличная документация по каждому используемому плагину. Поэтому я приложу код сборки проекта с небольшими комментариями. Актуально для webpack 5 версии.

webpack.config.js
const path = require("path");
const { DefinePlugin } = require("webpack");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { VueLoaderPlugin }  = require("vue-loader");
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const ESLintPlugin = require('eslint-webpack-plugin');

module.exports = env => {

 return {
   context: path.resolve(process.cwd(), "src"),
   devtool: env.production === true ? false : "eval-cheap-source-map",
   mode: env.production === true ? "production" : "development",
   performance: {
     hints: false,
   },
   // Точки входа
   entry: {
     "main": ["./ts/main.ts"]
   },

   // Что получаем на выходе
   output: {
     path: path.resolve(process.cwd(), "dist"),
     filename: "js/main.js",
     publicPath: !!process.env.WEBPACK_DEV_SERVER ? "/" : "./",
   },

   resolve: {
     plugins: [new TsconfigPathsPlugin()],
     extensions: [".ts", ".js", ".vue", ".json"],
     alias: {
       vue$: "vue/dist/vue.esm.js"
     }
   },

   // Dev сервер
   devServer: {
     devMiddleware: {
       index: true,
       publicPath: '/',
       writeToDisk: true
     },
     static: {
       directory: path.join(__dirname, 'dist')
     },
     port: 9000,
     hot: true
   },

   module: {
     rules: [
       // Загрузчик TS файлов
       {
         test: /\.tsx?$/,
         loader: "ts-loader",
         exclude: /node_modules/,
         options: {
           appendTsSuffixTo: [/\.vue$/]
         }
       },
       // Загрузчик vue файлов (хотя мы их не используем, но вдруг кому понадобится)
       {
         test: /\.vue$/,
         use: "vue-loader",
       },
       // Загрузчик изображений
       {
         test: /\.(png|jpg|gif|svg|ico)$/,
         loader: "file-loader",
         options: {
           name: "static/[name].[ext]?[hash]"
         }
       },
       // Загрузчик js файлов
       {
         test: /\.js$/,
         loader: "file-loader",
         exclude: /node_modules/,
         options: {
           name: "js/[name].[ext]"
         }
       },
       // Загрузчик стилей
       {
         test: /\.(css|sass|scss)$/,
         use: [
           // Минификатор стилей
           {
             loader: MiniCssExtractPlugin.loader,
             options: {
               publicPath: (resourcePath, context) => {
                 return path.relative(path.dirname(resourcePath), context) + "/";
               },
             },
           },
           // Загрузчик обычных css стилей
           "css-loader",
           // Sass-загрузчик
           {
             loader: "sass-loader"
           }
         ]
       }
     ]
   },

   plugins: [
     // Очищает build директорию
     new CleanWebpackPlugin(),

     // Формирует html. Подсовывает title, делает внедрение js в body
     new HtmlWebpackPlugin({
       inject: "body",
       template: "index.html",
       title: "Hello-world"
     }),

     // Запускает проверку кода через eslint
     new ESLintPlugin({
       extensions: "ts"
     }),

     new VueLoaderPlugin(),

     // Минифицирует стили
     new MiniCssExtractPlugin({
       filename: 'static/style.css'
     }),

     new DefinePlugin({
       __VUE_OPTIONS_API: JSON.stringify(true),
       VUE_PROD_DEVTOOLS: JSON.stringify(env.production !== true),
     }),
   ]
 }
};

По итогу первых двух пунктов получаем скелет нашего проекта, который можно посмотреть тут.

3. Время собирать камни…

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

3.1. Дублирование кода и шаблона.

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

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

Пример: был сложный компонент редактора документов на 2000 строк кода и на 3000 строк шаблона. В самом простом виде он выглядит так:

Пришла задача от бизнеса сделать ещё один редактор другого типа документов. Этот редактор отличается всего лишь на 25%. У нас решили эту задачу посредством наследования.

Что не так:

  1. Компонент на 2000 строк кода и 3000 строк шаблона это уже сигнал о том, что что-то не так. При открытии такого компонента хочется плакать.

  2. Наследование нас спасло от дублирования кода, но не от дублирования 3000 строк шаблона.

Решение: На рисунке выше можно уже увидеть, что блоки “Поля документов”, “История изменений”, “Статус документа”, “Связанные документы” и “Вложения” очень хорошо ложатся в отдельные компоненты. Всего лишь нужно вынести это всё в отдельный класс.

Если эти компоненты совпадают на 100% у обоих редакторов (Вложения, история изменений, связанные документы), то таким подходом мы избавились от дублирования и кода и шаблона для этой части.

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

После того, как редактор был разбит на мелкие компоненты мы получили:

  • Избавление от дублирования и кода и шаблона.

  • Маленькие и понятные компоненты, которые имеют лишь единственную обязанность. Такие компоненты легко понимать и модифицировать.

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

3.2 Используйте Single File Component

Избавляемся от .vue, .ts и .html файлов и склеиваем их в один .ts файл. “Зачем это всё?” — спросите вы. Просто данный стиль ближе по духу разработчикам, не имевшим дело с frontend. Он менее пугающий. А также это позволяет посмотреть на все ресурсы компонента сразу в одном файле. Это просто удобнее чем, открывать три разных файла.

Если вы используете vue-cli вам потребуется в vue.config.ts проставить флаг runtimeCompiler: true.
Склеенные компоненты выглядят следующим образом:

import { Component, Vue } from 'vue-property-decorator';

@Component({
 template: `
   <div class="about">
     <h1>This is an about page</h1>
   </div>
 `
})
export default class AboutView extends Vue {
    // Code…
}

3.3 Не изобретайте велосипедов

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

У нас встречались свои велосипеды в виде каких-то таблиц и прочего. Это приводило к ужасному визуальному виду и множеству багов. В итоге свои компоненты-таблицы были удалены и прикручены таблицы из UI framework, с небольшой кастомизацией, а восторгу от красоты новых таблиц у пользователей продукта не было предела…

3.4 Взаимодействие компонентов

Основа работы Vue — однонаправленный поток данных. Это значит, что данные из компонентов верхних уровней передаются в компоненты нижних уровней через входные параметры (или props). А для обратной связи наверх используются события (дочерние компоненты уведомляют о произошедшем событии и, возможно, передают какие-то данные). А теперь рассмотрим пример приложения со следующей структурой компонентов:

Что делать, если потребуется передать данные из дочернего 1.1.1 компонента в дочерний 2.3.1? Для этого есть два подхода:

  1. Vuex;

  2. Глобальная шина событий.

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

Глобальная шина событий.

Данный подход позволяет передавать событие из любого компонента в любой. Реализуется это посредством создания пустого экземпляра Vue и его импорта.

export const bus = new Vue();
// ComponentA.ts (импортируем шину и генерируем в неё события)
import { bus } from "bus.js";
bus.$emit("my-event"[, данные]);
// ComponentB.ts (импортируем шину и отслеживаем в ней события)
import { bus } from "bus.js";
bus.$on("my-event", this.myEventHandler);

3.5. Динамические компоненты.

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

Пример: Был компонент редактора документов. Пришел бизнес и сказал, что некоторые документы нужно отображать в другом редакторе. Реализовано это было посредством разных ссылок. По одной ссылке (editor/101) открывался один компонент редактора документов. По другой ссылке (another_editor/102) открывался другой компонент редактора документов.

Проблемы: пользователи часто из одного редактора (editor/101) меняли в адресе номер документа на другой документ (у них был в наличии нужный им номер документа) и получали не тот редактор, который должен был открыться. Серверная часть, конечно, валидировала это недоразумение, но ситуация для пользователя неприятная.
Решение: Указываем роут на компонент, который будет отвечать за выбор нужного редактора на основе какого-либо признака (рабочий пример добавил вместе со скелетом приложения тут)

@Component({
   template: `
     <component v-if="document"
                :key="document.id"
                :is="component"
                :document="document"></component>
   `
})
export default class DocumentEditor extends Vue {

   /** Документ */
   private document: Document | null = null;

   . . .

   /**
    * Возвращает компонент, который требуется показать клиенту,
    * на основе типа документа или какого-либо другого признака
    */
   private get component(): VueClass<Vue> {
       if (this.document?.type === "TYPE_ONE") {
           return AnotherEditor;
       }
       return CommonEditor;
   }
}

3.6. Глобальный обработчик событий.

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

try {
  ...
} catch (e) {
  this.$notify({
    title: "Ошибка",
    type: "error",
    message: e.message
  });
  throw e;
}

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

/**
 * Глобальный обработчик ошибок Vue
 */
Vue.config.errorHandler = (err: Error & AxiosError, vm, info) => {
   Notification.error(getErrorMessage(err))
}

/**
 * Глобальный обработчик ошибок для промисов
 */
window.addEventListener("unhandledrejection", (event) => {
   Notification.error(getErrorMessage(event.reason));
});

/**
 * Извлекает сообщение об ошибке
 * @param error ошибка
 */
function getErrorMessage(error: Error & AxiosError) {
   return error.response?.data?.message ? error.response?.data?.message : error.message;
}

Больше не надо будет для этого добавлять блоки try ... catch(e) …, ведь теперь глобальный обработчик сам отловит любую ошибку и отобразит её пользователю. Приятным бонусом является то, что теперь можно отобразить пользователю текст с ошибкой просто кинув эту самую ошибку throw new Error("Не заполнен номер документа");.

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

Комментарии (18)


  1. monochromer
    06.07.2022 17:11
    +1

    Если вы используете vue-cli вам потребуется в vue.config.ts проставить флаг runtimeCompiler: true

    Не лучше было бы использовать расширение синтаксиса JSX/TSX и render-функции, чтобы они компилировались при сборке, а не в runtime браузера пользователя?


    1. belovdp Автор
      06.07.2022 21:55

      Спасибо, мы посмотрим в эту сторону


  1. skymal4ik
    06.07.2022 17:22

    Эх, непросто всё в мире фронт-энда, кучи инструментов и технологий :)

    Всегда была интересна эта область, но никогда не было достаточно свободных времени и ресурсов, чтобы нормально изучить её…

    Мастера фронтов, а подскажите, есть что-то простое, без всяких нод и компиляций, чтобы в идеале подключил чистый js файл (вроде jquery) и можешь работать с беком по api (crud, json) и динамически обновлять таблички на фронте без тонн кода?


    1. ionicman
      06.07.2022 17:42
      +3

      Мастера фронтов, а подскажите, есть что-то простое, без всяких нод и компиляций, чтобы в идеале подключил чистый js

      Если хочется реактивности - тотже vue умеет в загружаемые компоненты (ничего собирать и компилировать не нужно), если нет - чем vanilla html/js/css не угодили?


    1. ilyapirogov
      06.07.2022 19:30
      +1

      Для модульности: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
      Для общения с бекендом: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
      Для динамических табличек: https://developer.mozilla.org/en-US/docs/Web/Web_Components


    1. GothicJS
      06.07.2022 20:34

      Эх, непросто всё в мире фронт-энда, кучи инструментов и технологий :)

      А на каком стеке их не кучи ?


    1. egor_gruzdev
      08.07.2022 22:19

      alpinejs в помощь


  1. Nbx
    06.07.2022 18:02

    Используйте Single File Component
    А какой-нибудь VS code это правильно понимает? Или будет жевать как строку?


    1. belovdp Автор
      06.07.2022 18:18

      IDEA с плагином для Vue точно нормально воспринимает


  1. Djaler
    06.07.2022 18:25
    +4

    Избавляемся от .vue, .ts и .html файлов и склеиваем их в один .ts файл. “Зачем это всё?” — спросите вы.

    Действительно спрошу. SFC - это как раз про .vue файлы, в которых присутствуют и template и script и (опционально) style.
    Зачем вместо этого держать разметку компонента в строке, как в доисторические времена без сборщиков? (К тому же, так ещё и тянутся в бандл лишние килобайты компилятор шаблонов)

    И отдельный вопрос - зачем backend разработчиком руками настраивать Webpack, когда есть Vue CLI (а в 2022 году - Vite)?


  1. doomguy49
    06.07.2022 23:11
    +3

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

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

    Пакеты vue-class-component и vue-property-decorator для того чтобы привести к классо-ориентированному виду, который все мы так любим;

    Эти пакеты более не поддерживаются. Переходить с них на vue3 — это ужас. Надо уходить с них, а не использовать. И, следовательно, composables избавляют от дублирования, а не наследование классов

    Vuex (позволяет общаться компонентам между собой.

    Pinia. Типизировать Vuex грустно

    Компонент на 2000 строк кода и 3000 строк шаблона это уже сигнал о том, что что-то не так

    Это сигнал о том, что предыдущие сигналы были проигнорированы

    Вы находитесь сейчас где-то в 2018-2020 годах


    1. nin-jin
      06.07.2022 23:42
      +3

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


      1. doomguy49
        07.07.2022 13:35

        А что, до 24 февраля вредоносного кода в пакетах не встречалось? Особенно, среди всякого мусора, который в проектах-то лучше и не использовать


        1. nin-jin
          07.07.2022 14:18

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


          1. doomguy49
            07.07.2022 23:01
            -1

            Да причем тут доверие вообще? Мы же имеем дело с фактами, если пакет содержит вредоносный код, причем абсолютно неважно какого характера: политического, рекламного, волшебного — мы его не используем ни сейчас, ни в будущем, т.к. как минимум ревьюер это пропустил (опять же, нет никакой разницы почему — обиженный правый или невнимательность), а это означает, что даже если завтра настанет любовь и мир во всем мире, кто-нибудь захочет залезть к нам в карман, и к нашим клиентам, и потому мы вообще никак не защищены, гарантий нет.

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

            А что будет, если недовольные останутся и через 10 лет? Будете тянуть фреймворки от 2022 года?

            И так пишите на старом. Конечно, я понимаю, что Вам, как бэкендеру на php, с классами иметь дело приятнее, но вот просто ради интереса, взгляните на два шага вперед — как планируете мигрировать на vue3?


            1. nin-jin
              08.07.2022 06:07
              +1

              Отлично, давайте косплеить 5-летрих детей, и делать вид, что безопасность построена не на сети доверия.

              Вы перепутали меня с автором статьи. Вы и код так же внимательно ревьите?


              1. doomguy49
                08.07.2022 11:44
                -1

                Еще бы сказали, что на любви построена. Не сказать, что я сильно удивлен


  1. nin-jin
    06.07.2022 23:37
    +3

    А теперь давайте представим, что вы решили ещё сильнее упростить себе жизнь и переписали всё на $mol...

    Мы постарались написать приложение так, чтобы backend разработчикам было наиболее привычно и комфортно. Классы, интерфейсы, наследование, типизация..

    .. шаблоны вперемешку с логикой и стилями. У вас тоже PHP на бэкенде?

    Ладно, шутки в сторону. В $mol все компоненты являются обычными классами унаследованными от других компонент. При этом в полной мере поддерживается инверсия контроля (Пруф). У Vue же с нею всё плохо, как и со статической типизацией шаблонов и стилей. А вот в $mol с этим более чем хорошо (Пруф).

    Нам понадобятся:

    Да, это всё. В $mol есть все необходимые батарейки: богатая библиотека виджетов, запросы к серверу, роутинг, стейт-менеджмент и тд. И даже сам $mol нет необходимости устанавливать - он скачается автоматически, как только вы начнёт его использовать.

    Перейдём к самой ужасной части: сборке проекта

    Разворачивание рабочего окружения делается всего в несколько вызовов:

    git clone https://github.com/hyoo-ru/mam.git
    cd mam
    npm install
    npm start
    

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

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

    Хвала Ленину, что мейнтейнером $mol является мало того, что россиянин, так ещё и живёт он в России. Более того, ему хватает совести не использовать свои проекты для политической борьбы, и прагматичности не тянуть 100500 зависимостей. Благодаря чему приложения получаются куда более легковесными (Пруф).

    был сложный компонент редактора документов на 2000 строк кода и на 3000 строк шаблона

    А после переписывания на $mol он похудел до 300 строк кода и 200 строк "шаблона" (Пруф).

    Наследование нас спасло от дублирования кода, но не от дублирования 3000 строк шаблона.

    К счастью, в $mol наследование спасает и от дублирования в "шаблонах" (Пруф). Впрочем, декомпозиция, конечно, предпочтительнее.

    Что делать, если потребуется передать данные из дочернего 1.1.1 компонента в дочерний 2.3.1?

    Ну а если надо передать из 1.1 в 1.3 - тоже гонять всё через глобальную шину или глобальный стор? Всё же лучше иметь разнонаправленные потоки данных, которые позволяют хранить состояние в том компоненте, где ему место, а работать с ним из разных мест благодаря разнонаправленным потокам данных (Пруф).

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

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