Введение
Наверное, в жизни разработчика, с бэкграундом геймера, возникает мысль о разработке собственного проекта, который точно реализует все детские хотелки и будет лучшим из лучшим.
В моей голове подобный проект всегда выглядит, как несбыточная мечта, однако, на протяжении моей карьеры, где на данный момент я — Lead Full Stack Software Development Engineer, где Full Stack — это полный цикл разработки включающий Technical writing, QA, SDET, SDE, Architecture, BA, DBA, UI/UX и так далее, наконец-то сформировался концепт проекта мечты и, собственно, план по реализации, осталось дело за малым.
Подход
Суть плана по реализации — каждая статья должна приводить к построению очередного MVP, а также knowledge sharing по best practices и возможность напрямую общаться с потанцевальной аудиторией. Если это не понравится Хабру, то 42.
Каждая публикация будет иметь цель и, собственно, выполнение намеченных пунктов, что немаловажно, так как до этого я пробовал запустить видеоблог, но в какой-то момент понял, что это не моё (не потому что не получается, а потому что мне не нравится подход, где вместо контента я должен заморачиваться над подачей видео, звук, монтаж, сценарий и так далее. С текстом, к сожалению, проще. Как ни крути за буквы, но для аудитории машин, мне платят, так почему бы и не выработать подход и тут, да ещё и бесплатно делиться опытом, чему-то научить (по-совместительству преподаю, читаю лекции и помогаю войти-вАйти-и-не-выйти), потешить комплекс Бога, пошарить своё видение на многие аспекты в разработке и помочь такому же искателю приключений запустить проект мечты.
Цель данной публикации
Запустить проект в минимальном виде. Да-да. Каждый шаг — это MVP, я верю на какой-либо публикации руки дойдут до roadmaps и диаграмм Ганта, но сейчас это уже не RAD (по сугубо личному мнению, этот подход, на мой взгляд, является единственным гарантированным запуском чего-либо, нежели Agile, Waterfall, PRINCE2 и так далее, так как помогает работать в одиночку).
Все мои персональные мысли будут оформлены в цитаты, остальное всё что упрётся в форматирование редактора Хабра.
В классическом написании unit-тестов, я стараюсь придерживаться подхода AAA, разумеется с собственной призмой, следовательно, это будет использовано и здесь.
-
Arrange
-
Концепция игры, которая перевернёт весь мир
-
Все необходимые ресурсы (навыки, интернет и деньги на хостинг (про последнее думаю опционально, так как не везде это нужно, но мало ли!))
-
Отсутствие монетизации на стандартной рекламе (тут концепт раскроется ближе к финалу)
-
Желание быть «везде» (благо кросс-платформенных решений сейчас более чем достаточно)
-
-
Actual
-
Положен старта блога
-
Ноутбук
-
Несколько часов по вечерам будних/выходных дней
-
-
Assert
-
Написан кросс-платформенный hello-world
-
Проект доступен извне
-
Здесь я хочу сделать акцент на MVP сценарии, что не нужно сразу всё пытаться опубликовать в PlayMarket, AppStore, Windows Store и так далее, если решение изначально поддерживает возможность масштабироваться под платформы, то этого достаточно, главное не писать всё так, чтобы потом пришлось перелопатить всё, лишь бы выйти на мобильный рынок, не переживайте, мы завоюем всё, в том числе и корованы
Точка входа
Первым делом, что делает любой уважающий себя разработчик? Правильно, создаёт репозиторий, где будет храниться код. Площадок много, но хочется иметь зелёную статистику в профиле, который потом часто нужно шарить работодателям, думаю https://github.com уже дефакто-стандарт, а значит велосипеды можно не строить.
Однозначно создавать организацию или просто потом перекинуть owner-а репозитория из личного профиля на организационный — думаю выйдет боком уже на старте, если я буду использовать множественные репозитории, хотя сейчас, если грамотно организовать весь CI/CD, то и распилить моно-репозиторий не составит труда. Что быстрее? Свой репозиторий.
Вот тут уже и проект назвать как-то нужно, мне нравится слово «Синергия», оно излишне характеризует концепт проекта, а также отсылка к моим научным интересам, в лице генетических алгоритмов. Идём в Google Translate и copy-paste, чтобы без опечаток.
Не считаю, что название должно быть каким-то важным элементом, пусть с этим разбираться будем при маркетинге, в будущем понадобится только купить другой домен, сменить имя репозитория, имя организации и так далее, сейчас это вообще не имеет смысла, однако в коде привязываться точно к этому не стоит, мощность алфавита высокая, Латынь за глаза, значит, пока всё р(а/о)вно.
Репозиторий сразу публичный (одна из особенностей концепта), MIT License (потому что не разбираюсь, это то, что оставит за Вами авторство и влияет только на упоминание на мой взгляд)
С удовольствием бы пошёл прямо сейчас настраивать линтеры коммитов, веток и так далее, но будем честны, работа в одиночку — это хождение в production через master, нравится оно или нет, но не создавайте себе проблем с доставкой кода на раннем старте, перфекционизм — это хорошо для демонстрации своего профиля будущему работодателю, но создавать PR для того, чтобы создать PR, это чересчур, как форма ради формы
Итак, точка входа готова, нужно написать процессор и получить точку выхода.
Процессор
Для старта нужно определиться с фреймворком, сразу на ум лезут Xamarin Forms, Electron и Kotlin-решения.
Писать на каком-то движке плохая идея, если хочется получить что-то для масштабирования по Rapid Development, так как может организоваться vendor lock уже на уровне инструмента, не спорю, что привязываясь к фреймворку или языку программирования, также происходит vendor lock, но это уровень абстракции чуть ниже, чем использование движка вроде Unreal/Unity. Да и плюс, в arrange было что-то про навыки, так что тут с этим проще. Однако стоит учесть, что если опыта у Вас мало, то выбирайте из двух зол меньшее, написать физику для игры или настроить body shaping в Unity — выбор Ваш.
Конечно, Electron очень прост в портировании на что угодно, и думаю что скорее всего его и буду использовать, мне не нравится что проекты на .NET несут за собой очень много deps в виде никому не нужных конфигураций файлов.
С другой стороны, стоит учесть, что при всём желании можно Xamarin Forms скрестить с Unity и получить более гибкую разработку, но наверное оставлю решение под Electron, всё-таки TypeScript даёт возможность не писать кучу обвязки для запуска кода.
Немножко погуглил и выяснил, что есть легковесные альтернативы Electron, собственно идём устанавливать в репозиторий всё по гайду первой попавшейся библиотеки.
В будущем есть планы избавиться от зависимостей фреймворка, но сейчас просто чтобы запуститься этого достаточно. Не нужно изобретать велосипед. Мы его реверс-инженируем позже!
Судя по всему, в этом фреймворке можно писать как чисто на JS, так и используя производные вроде React, Vue и Angular, однако можно пойти дальше и использовать Kotlin, компилируя его в JavaScript, что заманчивая идея, подумаем позже, сейчас запускаем на чистом.
Пользоваться CLI не вижу смысла, чтобы не создавать зависимости на фреймворк глубже, то и ставить глобально их решение не буду, благо npx поддерживается, этого достаточно
Итого выходит следующая команда:
npx @neutralinojs/neu create synergy --template neutralinojs/neutralinojs-zero
Выполняем, запускаем (как по TDD) и сразу же коммитим
Я предпочитаю сразу использовать commit convention, оно хорошо позволяет понимать что происходит в истории коммитов, воспитывает и позволяет открыть масштабирование к semantic release, шарить changelog на основе коммитов и вообще писать коммиты осмыслено.
Пожалуй сразу напишу скрипты для запуска, чтобы не вспоминать ничего и подключу родной Makefile
Что интересно, так это структура результата фреймворка
В ней есть папка приложения synergy, в которой есть www, что напоминает типичный веб-сервер nginx
Следовательно, можно просто хранить это как DI-обёртку, а писать код проекта в другом месте, что позволяет сразу же переименовать всю папку приложения на фреймворке в какое-нибудь более-менее намекающее адаптер, так и сделаем.
Здесь я не хочу использовать React, а значит мне достаточно сборщика, который с минимальным кодом позволит собирать bundle для точки входа и подбрасывать его фреймворку как часть сборки, следует в тоже время вынести результаты сборки выше, чтобы минимизировать желание залезать в его код в принципе. Остановлюсь на Parcel в виду минималистичности
Немного повозился с тем, чтобы абстрагироваться от Parcel также, в итоге вышло следующее:
-
Создал отдельную директорию application-source
-
Инициализировал там пакет
-
Установил этот пакет из application-builder (в нём живёт Parcel)
-
И теперь могу в application-source мутировать код, который при сохранении package.json триггернёт пересборку и я увижу live changes, следовательно, жить можно
Из того, что хочется сделать прямо сейчас, чтобы двигаться дальше — это пайплайн на GitHub Actions, выполняющий следующие задачи:
-
Проект собирается (для этого добавил ещё парочку скриптов сборки)
-
Проект публикуется в npm package
Опишем YML, который выглядит следующим образом с обычной копипастой из интернета, что соответствовать следующим критериям:
-
node_modules кешируются при каждой сборке (зачем иметь головную боль в будущем с решением в 9 строк), дальше в каждую job вставляем и забываем про переустановку модулей
-
добавим сразу же проверку на наличие ключей прежде чем стартовать конвейер
-
используется semantic release, а значит нам понадобится использовать GH_TOKEN, который проще всего прокинуть в .npmrc файл и таким образом организовать публикацию пакета прямо в GitHub Registry вместо NPM Registry.
Плюсом ко всему это позволит добавлять сами артефакты сборки в GitHub binaries и автоматически генерировать патч-ноты, мы ведь ленивые, раз проще написать файл на 100 строк, чтобы больше не думать о том, как работать с точкой выхода, то так и поступим.
Будьте внимательны и всегда указывайте timeout-minutes, дважды натыкался на выедание лимитов из-за зависшего пайплайна по любой причине.
-
есть возможность исключать из триггеров по pre-path и запускается на каждый пуш в мастер или PR
-
и так как совсем скучно добавим парочку линтеро.
-
разумеется добавим шаг установки модулей через npm ci до выполнения publish, чтобы устанавливать зависимости акцентируясь на package-lock вместо package.json
-
И шаг сборки
Это приблизительная, который я несу в каждый фронтенд репозиторий, который попадается мне под руку, нужно заменить только некоторые вызовы на make и будет земля Винни-Пухом. Чаще всего добавляю всякие генераторы бейджей для покрытия тестами и так далее, думаю как доберёмся до тестов добавим и это, так как проекты легче стартовать с TDD/BDD, чем без него, так как тоже дело 10-15 минут.
Что немаловажно:
-
Токен под ключом GH_TOKEN будет Ваш персональный токен, пойдём генерить с опцией публикации пакета и получение репозитория, заодно и gist-ы включим, пусть будут
-
Теперь идём в настройки репозитория и добавляем там сгенерированный токен под названием GH_TOKEN
Для полноты осталось установить semantic-release и настроить выборку какие файлы должны попадать в сборку, а какие нет
Дальше захостим решение с помощью GitHub Pages и оставим для следующей итерации и публикации, думаю, что она начнётся с сбора обратной связи, формированию технического долга и первого построения роадмапа, с уже пройденной точкой старта.
Первый выход в production
Итак, автоматическая сборка что в себя должна включить:
-
Публикация кросс-платформенных артефактов (публиковать будем всё на GitHub)
-
Публикация веб-артефактов + npm package, где мы их будем хранить (здесь сразу же выстрел наперёд, чтобы помочь себе отказаться от GitHub Pages в будущем безболезненно)
Поэтому напишем простенький shell-скрипт, который будет вызываться на этапе вызова semantic-release в GitHub Actions
Что мы делаем:
-
Во-первых, папку application-source, которая хранит код продукта мы не трогаем, а всё делаем на уровне приложения сборщика, в него я добавил semantic release
-
Во-вторых, артефакты, что появляются от Neutralino (или что угодно в будущем), а также билд фронта, я несу в директорию dist, для удобной публикации semantic-release указав только папку desktop как артефакты, а web, как полноценный GitHub Pages
Пример того, как примерно выглядит конфигурация semantic-release:
Также стоит учесть, что при публикации в npm registry нам также понадобится часть конфигурации
И дальше пушим до тех пор пока всё не соберётся, будем откатывать коммит и экспериментировать
Внешний вид выстроенного workflow
Методом проб и ошибок доделываем пайплайны (ну а что вы хотели, это real-time разработка и написание публикации, так и фейлы должны быть реальными, уже на 17-ый запуск всё заработало, кроме авто-генерации GitHub Pages)
Как это выглядит?
Справа появились релизы, а также npm-пакет содержащий бандлы фронта
Типичный релиз содержит в себе данные базирующиеся на semantic-release и коммитах, а также сборки под разные ОС
Среднее время прохождение пайплайнов ~ 1 минута 30 секунд
Нужно доделать только генерацию CHANGELOG в репозиторий и разобраться в чём дело и куда пропали GitHub Pages.
Пока идёт 23-ий action, принял решение, что на фронте мне нужно отобразить ссылки на сборки, учитывая, что единственное отличие это номер версии, то номер можно импортировать из package.json и получить следующее:
-
ссылку на репозиторий
-
номер версии
Как итог только bundler знает о версии и ссылке, когда как source продолжает жить изолировано.
На 26-ой запуск пайплайнов выяснилось, что вся проблема была в отсутствии плагинов в cache у node_modules, поэтому если шаги дошли до релиза добавил npm ci
Итоговый CI/CD
name: CI
on:
push:
branches: [master]
paths-ignore:
- "README.md"
- "SECURITY.md"
- "CONTRIBUTING.md"
- "CODE_OF_CONDUCT.md"
- "LICENSE"
- ".gitignore"
pull_request:
branches: [master]
jobs:
check-secrets:
timeout-minutes: 5
runs-on: ubuntu-latest
outputs:
github-token: ${{ steps.github-token.outputs.defined }}
steps:
- id: github-token
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
if: "${{ env.GITHUB_TOKEN != '' }}"
run: echo "::set-output name=defined::true"
test-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: wagoid/commitlint-github-action@v4
pre-tests:
name: Install dependencies (if needed)
runs-on: ubuntu-latest
needs: [check-secrets, test-commit]
timeout-minutes: 5
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 16
- name: Manage cache
uses: actions/cache@v2.1.7
with:
path: |
./node_modules
key: ${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('.github/workflows/*.yml') }}
restore-keys: |
${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }}
${{ runner.OS }}-build
- name: Install deps
run: make dependencies
publish:
name: Publish a new version of package via Semantic Release
needs: [pre-tests]
timeout-minutes: 5
if: ${{ github.ref == 'refs/heads/master' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 16
- name: Manage cache
uses: actions/cache@v2.1.7
with:
path: |
./node_modules
key: ${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('.github/workflows/*.yml') }}
restore-keys: |
${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }}
${{ runner.OS }}-build
- name: Prepare token
working-directory: ./application-builder
run: echo "//npm.pkg.github.com/:_authToken=${GH_TOKEN}" >> .npmrc
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
- name: Build project
run: make build && make release
- name: Semantic release
working-directory: ./application-builder
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
NPM_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: npm ci && npx semantic-release
Итог и точки выхода
-
релиз включает в себя кросс-платформенные сборки, бандлы фронта в
npm package
и в отдельную веткуweb
для GitHub Pages -
3 минуты на релиз
-
автоматическая проверка коммитов
-
семантическое и автоматическое версионирование и генерация CHANGELOG на уровне релизов
-
сборка фронта существует в
application-builder
-
бизнес-логика существует в
application-source
-
сборка платформо-зависимых пакетов в
application-executor
И собственно рабочий фронт
Цели для следующей итерации
-
Построение дорожной карты
-
А это горизонт планирования на пару кварталов
-
-
Создание бизнес-плана
-
Проект должен на что-то жить, может кнопку спонсорства прибью
-
-
Идентификация пользователя
-
Сервера не будет, есть пара идей, в крайнем случае AWS Lambda
-
-
Дизайн лендинга
-
Не будем прикручивать фреймворки и библиотеки, порисуем в Figma и соберём прототип из чистого CSS, может быть используем SCSS/LESS
-
Думаю насчёт сборки через WebAssembly
-
-
Устранить самые серьёзные баги исходя из комментариев к этой публикации, ну а что?)