[{"data":1,"prerenderedAt":3123},["ShallowReactive",2],{"page-ru-\u002Fposts":3,"posts-ru":27},{"id":4,"title":5,"body":6,"date":16,"description":17,"duration":16,"extension":18,"meta":19,"navigation":20,"path":21,"robots":16,"seo":22,"sitemap":23,"stem":24,"tags":16,"toc":25,"__hash__":26},"content_ru\u002Fposts\u002Findex.md","Блог - Растен Ремизов",{"type":7,"value":8,"toc":12},"minimark",[9],[10,11],"posts-list",{},{"title":13,"searchDepth":14,"depth":14,"links":15},"",2,[],null,"Блог Растена Ремизова — статьи о программировании, жизни и всяких интересных вещах","md",{},true,"\u002Fposts",{"title":5,"description":17},{"loc":21},"posts\u002Findex",false,"FTiqKzMqF8nkV1wdf1HjGN1r-buAkJBn5hG24IdqLjA",[28,1673,1718,2224,2922],{"id":29,"title":30,"body":31,"date":1662,"description":1663,"duration":1664,"extension":18,"meta":1665,"navigation":20,"path":1666,"robots":16,"seo":1667,"sitemap":1668,"stem":1669,"tags":1670,"toc":20,"__hash__":1672},"content_ru\u002Fposts\u002Fessential-frontend-automations.md","Необходимые фронтенд автоматизации",{"type":7,"value":32,"toc":1647},[33,40,47,52,56,59,63,66,82,86,89,94,105,111,125,139,142,169,173,186,189,193,201,260,274,285,288,308,311,314,317,321,324,333,355,367,379,383,386,399,422,428,432,435,438,447,451,466,475,484,487,808,920,927,1102,1105,1109,1112,1115,1130,1133,1373,1505,1512,1604,1633,1636,1640,1643],[34,35,36],"h1",{"id":13},[37,38],"binding",{"value":39},"title",[41,42],"posts-date-duration",{":duration":43,":date":44,"className":45},"duration","date",[46],"-mt-6",[48,49,51],"h2",{"id":50},"введение","Введение",[53,54,55],"p",{},"За годы работы с фронтендом я перепробовал кучу инструментов автоматизации — часть оказалась временным хайпом, а часть настоящими находками, которые я использую и по сей день.",[53,57,58],{},"Сегодня расскажу про свои любимые автоматизации, которые считаю полезными (а некоторые — прямо необходимыми). Все их я испытывал на проде, и кое-что из этого отличается от типичного пайплайна.",[48,60,62],{"id":61},"дисклеймер","Дисклеймер",[53,64,65],{},"Намеренно не трогаю LLM-автоматизации по нескольким причинам:",[67,68,69,73,76,79],"ul",{},[70,71,72],"li",{},"Их уже столько, что хватит еще на 1–2 статьи",[70,74,75],{},"У меня пока мало опыта с LLM-автоматизациями именно на проде",[70,77,78],{},"Они постоянно появляются, меняются и эволюционируют",[70,80,81],{},"Текущие автоматизации можно использовать внутри LLM-агентов — они хорошо дополняют друг друга",[48,83,85],{"id":84},"автоматизации","Автоматизации",[53,87,88],{},"Статья идет от более известных инструментов к менее раскрученным. Можно пользоваться оглавлением слева и сразу прыгать к интересующей области.",[90,91,93],"h3",{"id":92},"линтинг-и-форматирование-eslint-eslint-stylistic","Линтинг и форматирование = ESLint + ESLint Stylistic",[53,95,96,97,104],{},"Самая очевидная автоматизация — конечно же ",[98,99,103],"a",{"href":100,"rel":101},"https:\u002F\u002Feslint.org\u002F",[102],"nofollow","ESLint",".",[106,107,108],"blockquote",{},[53,109,110],{},"А где Prettier?",[53,112,113,114,119,120,104],{},"Да, ",[98,115,118],{"href":116,"rel":117},"https:\u002F\u002Fprettier.io\u002F",[102],"Prettier"," здесь нет. На мой взгляд, лучше использовать ",[98,121,124],{"href":122,"rel":123},"https:\u002F\u002Feslint.style\u002F",[102],"ESLint Stylistic",[53,126,127,128,133,134,138],{},"Подробнее об этом можно почитать в ",[98,129,132],{"href":130,"rel":131},"https:\u002F\u002Fantfu.me\u002Fposts\u002Fwhy-not-prettier",[102],"Why I don't use Prettier",", но мой главный аргумент такой: ",[135,136,137],"strong",{},"если что-то из стиля Prettier тебе не нравится, ты будешь с ним бороться и городить костыли",". Часть поведения нельзя тонко настроить, а что-то вообще нельзя переопределить. ESLint дает куда больше гибкости там, где она нужна, и при этом не жертвует полезными фичами.",[53,140,141],{},"Если вы еще не пробовали такой подход — очень советую.",[53,143,144,145,150,151,156,157,162,163,168],{},"Также стоит внимательно наблюдать за ",[98,146,149],{"href":147,"rel":148},"https:\u002F\u002Foxc.rs\u002Fdocs\u002Fguide\u002Fusage\u002Flinter",[102],"Oxlint"," и ",[98,152,155],{"href":153,"rel":154},"https:\u002F\u002Foxc.rs\u002Fdocs\u002Fguide\u002Fusage\u002Fformatter",[102],"Oxfmt",". На момент написания статьи Oxlint еще не поддерживает многие правила ESLint (",[98,158,161],{"href":159,"rel":160},"https:\u002F\u002Fgithub.com\u002Foxc-project\u002Foxc\u002Fissues\u002F481",[102],"общие",", ",[98,164,167],{"href":165,"rel":166},"https:\u002F\u002Fgithub.com\u002Foxc-project\u002Foxc\u002Fissues\u002F15761",[102],"vue","), но на момент прочтения все может измениться.",[90,170,172],{"id":171},"тестирование-playwright-ночные-смоки","Тестирование = Playwright + ночные смоки",[53,174,175,176,179,180,185],{},"В тестировании автоматизаций полно, но одну недооценивают особенно сильно: ",[135,177,178],{},"ночные E2E-смоки",". Смоук-тесты — это небольшой набор E2E-кейсов, который покрывает самые используемые сценарии продукта. Если хотите, чтобы продукт был максимально стабильным — запускайте их каждую ночь. Для E2E сейчас лучше всего, на мой взгляд, подходит ",[98,181,184],{"href":182,"rel":183},"https:\u002F\u002Fplaywright.dev\u002F",[102],"Playwright"," — сильная поддержка от Microsoft, читаемее Cypress и есть все основные интеграции.",[53,187,188],{},"Самый крутой и недооцененный плюс — ловить регрессии в типовых сценариях до следующего рабочего дня, часто раньше, чем кто-то успеет заметить. Если никто не заметил, можно считать, что этого и не было. Чистая история ночных прогонов еще и хороший аргумент, когда речь заходит о надежности или повышении.",[90,190,192],{"id":191},"качество-кода-knip-husky","Качество кода = Knip + Husky",[53,194,195,200],{},[98,196,199],{"href":197,"rel":198},"https:\u002F\u002Fknip.dev\u002F",[102],"Knip"," помогает найти мертвый код. Минус Knip в том, что при auto-imports нужна дополнительная настройка — такие файлы по умолчанию выглядят неиспользуемыми. Минимальный фикс — пометить их как entry points или игнорировать:",[202,203,207],"pre",{"className":204,"code":205,"language":206,"meta":13,"style":13},"language-json shiki shiki-themes github-light github-dark","{\n  \"entry\": [\"src\u002Fmain.ts\", \"src\u002Fcomponents\u002F**\u002F*.vue\"],\n  \"ignore\": [\"src\u002Fauto-imports.d.ts\"]\n}\n","json",[208,209,210,219,240,254],"code",{"__ignoreMap":13},[211,212,215],"span",{"class":213,"line":214},"line",1,[211,216,218],{"class":217},"sVt8B","{\n",[211,220,221,225,228,232,234,237],{"class":213,"line":14},[211,222,224],{"class":223},"sj4cs","  \"entry\"",[211,226,227],{"class":217},": [",[211,229,231],{"class":230},"sZZnC","\"src\u002Fmain.ts\"",[211,233,162],{"class":217},[211,235,236],{"class":230},"\"src\u002Fcomponents\u002F**\u002F*.vue\"",[211,238,239],{"class":217},"],\n",[211,241,243,246,248,251],{"class":213,"line":242},3,[211,244,245],{"class":223},"  \"ignore\"",[211,247,227],{"class":217},[211,249,250],{"class":230},"\"src\u002Fauto-imports.d.ts\"",[211,252,253],{"class":217},"]\n",[211,255,257],{"class":213,"line":256},4,[211,258,259],{"class":217},"}\n",[53,261,262,267,268,273],{},[98,263,266],{"href":264,"rel":265},"https:\u002F\u002Ftypicode.github.io\u002Fhusky\u002F",[102],"Husky"," позволяет запускать команды на git-хуках — чаще всего на pre-commit. Типичные сценарии: запуск ",[98,269,272],{"href":270,"rel":271},"https:\u002F\u002Fgithub.com\u002Flint-staged\u002Flint-staged",[102],"lint-staged",", чтобы линтить только staged-файлы, или проверка формата commit-сообщений.",[53,275,276,277,280,281,284],{},"Но мое мнение: ",[135,278,279],{},"я бы не использовал Husky",", или как минимум ",[135,282,283],{},"не блокировал бы им коммиты",". Те же проверки можно реализовать в CI, и там есть контролируемый способ их обойти, когда это реально нужно.",[53,286,287],{},"Блокирующие pre-commit хуки в приватных командах почти никогда не стоят того:",[289,290,291,302,305],"ol",{},[70,292,293,294,297,298,301],{},"Эти правила легко отключить через ",[208,295,296],{},"HUSKY=0"," (или ",[208,299,300],{},"--no-verify",") без какого-либо следа или отчета.",[70,303,304],{},"Если обход все же перекрыт (кастомные git-обертки, жесткие политики хуков и т.д.) — удачи с хотфиксами, которые надо выкатить ASAP. Попробуйте объяснить менеджеру, что фикс не вышел из-за ошибки ESLint. Плюс становятся недоступны временные коммиты (лично я ими часто пользуюсь).",[70,306,307],{},"Даже это не гарантирует отключения правил — пользователь может просто удалить их или сам Husky у себя на машине.",[53,309,310],{},"В итоге поздравляю: вы сильно ухудшили DX, получив наполовину «защищенный» пайплайн.",[53,312,313],{},"Считать локальные хуки Husky настоящей границей безопасности — примерно как хранить JWT-секрет в браузере. На собеседовании присутствие блокирующих Husky-хуков в команде я бы серьезно считал желто-красным флагом.",[53,315,316],{},"Пожалуйста, используйте Husky только если понимаете, что делаете.",[90,318,320],{"id":319},"версии-зависимостей-taze-renovate","Версии зависимостей = taze + Renovate",[53,322,323],{},"Управлять зависимостями относительно просто, но делать это нужно регулярно. Если автоматизировать хотя бы часть процесса — в долгую выигрыш будет заметным.",[53,325,326,327,332],{},"Можно проверять каждый пакет вручную, а можно взять ",[98,328,331],{"href":329,"rel":330},"https:\u002F\u002Fgithub.com\u002Fantfu-collective\u002Ftaze",[102],"taze",". Taze показывает, у каких пакетов есть обновления, раскладывает их по semver (major, minor, patch) и умеет применять их автоматически:",[202,334,338],{"className":335,"code":336,"language":337,"meta":13,"style":13},"language-bash shiki shiki-themes github-light github-dark","pnpm dlx taze -w\n","bash",[208,339,340],{"__ignoreMap":13},[211,341,342,346,349,352],{"class":213,"line":214},[211,343,345],{"class":344},"sScJk","pnpm",[211,347,348],{"class":230}," dlx",[211,350,351],{"class":230}," taze",[211,353,354],{"class":223}," -w\n",[53,356,357,358,363,364,104],{},"Как альтернатива — бот ",[98,359,362],{"href":360,"rel":361},"https:\u002F\u002Fdocs.renovatebot.com\u002F",[102],"Renovate"," (или любой похожий). Он сам создает PR и апдейтит ",[208,365,366],{},"package.json",[53,368,369,370,372,373,378],{},"Честно говоря, мне ближе ",[135,371,331],{},": он работает с ",[98,374,377],{"href":375,"rel":376},"https:\u002F\u002Fpnpm.io\u002Fcatalogs",[102],"pnpm catalogs",", и мне нравится после каждого обновления пакета локально проверять сборку — просто чтобы быть уверенным. Но оба варианта, на мой взгляд, вполне ок для прода.",[90,380,382],{"id":381},"управление-монорепой-turborepo-pnpm","Управление монорепой = Turborepo + pnpm",[53,384,385],{},"У монорепозиториев темная история — долгое время это был набор костылей, чтобы заставить все репозитории жить вместе. Сейчас монорепы заметно проще, но все равно сложнее обычного репозитория.",[53,387,388,389,150,394,398],{},"Я создавал и поддерживал много монорепозиториев и считаю ",[98,390,393],{"href":391,"rel":392},"https:\u002F\u002Fturbo.build\u002F",[102],"Turborepo",[98,395,345],{"href":396,"rel":397},"https:\u002F\u002Fpnpm.io\u002F",[102]," необходимыми для этого.",[53,400,401,402,405,406,162,411,162,416,421],{},"Честно думаю, что ",[135,403,404],{},"pnpm стоит использовать вместо npm по умолчанию"," в любом репозитории — он просто быстрее и экономнее по диску. Несколько источников: ",[98,407,410],{"href":408,"rel":409},"https:\u002F\u002Fdev.to\u002Fpockit_tools\u002Fpnpm-vs-npm-vs-yarn-vs-bun-the-2026-package-manager-showdown-51dc",[102],"pnpm vs npm vs yarn vs Bun",[98,412,415],{"href":413,"rel":414},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=ZIKDJBrk56k",[102],"I Finally Changed Package Managers",[98,417,420],{"href":418,"rel":419},"https:\u002F\u002Fantfu.me\u002Fposts\u002Fcategorize-deps",[102],"Categorize Your Dependencies",". А для монореп pnpm уже давно стал стандартом, тогда как npm только догоняет (например, workspaces появились только в 2020, а catalogs до сих пор нет).",[53,423,424,427],{},[98,425,393],{"href":391,"rel":426},[102]," — настраиваемая система сборки с кучей фич: кэш сборки (чтобы не пересобирать пакеты воркспейса каждый раз, когда пересобираете фасадное приложение) и центральный оркестратор команд (чтобы, например, прогнать lint по всем воркспейсам).",[90,429,431],{"id":430},"версии-пакетов-changesets","Версии пакетов = Changesets",[53,433,434],{},"Управлять версиями пакетов сложнее, чем кажется на первый взгляд. Нужно внедрить изменение, протестировать, понять, какой это semver-бамп, собрать и зарелизить пакет, обновить changelog. Потом то же самое для следующей версии.",[53,436,437],{},"Все усложняется еще сильнее, если хочется батчить изменения перед релизом, автоматически обновлять changelog, корректно считать semver-бамп (когда накопилось несколько изменений разного уровня) — и в итоге сделать это совместной работой команды.",[53,439,440,441,446],{},"Для этого кейса лучшим инструментом считаю ",[98,442,445],{"href":443,"rel":444},"https:\u002F\u002Fgithub.com\u002Fchangesets\u002Fchangesets",[102],"Changesets",". Он автоматизирует весь этот процесс. Достаточно на каждое изменение заполнить простую CLI-форму — а дальше релизный пайплайн можно автоматизировать так тонко, как захотите.",[90,448,450],{"id":449},"генерация-бэкенд-эндпоинтов-openapi-orval","Генерация бэкенд-эндпоинтов = OpenAPI + Orval",[53,452,453,454,459,460,465],{},"А что, если хочется полной синхронизации типов и эндпоинтов между бэкендом и фронтендом? В SSR-фреймворках можно взять ",[98,455,458],{"href":456,"rel":457},"https:\u002F\u002Ftrpc.io\u002F",[102],"tRPC"," или автотипизированный composable ",[98,461,464],{"href":462,"rel":463},"https:\u002F\u002Fnuxt.com\u002Fdocs\u002Fapi\u002Fcomposables\u002Fuse-fetch",[102],"useFetch"," из Nuxt. Но те же приемы не сработают, если у вас классическая SPA: отдельная фронтенд-команда и бэкенд на Java\u002FPHP\u002Fчем угодно еще.",[53,467,468,469,474],{},"Есть ",[98,470,473],{"href":471,"rel":472},"https:\u002F\u002Fgraphql.org\u002F",[102],"GraphQL",", но что, если нужен просто типизированный REST-клиент без перехода на GraphQL? Скорее всего, у бэкенда уже есть OpenAPI-документация под Swagger. Через codegen можно взять те же доки и сгенерировать Zod-схемы, типы и даже fetch-сервисы.",[53,476,477,478,483],{},"Лучший вариант, на мой взгляд, — ",[98,479,482],{"href":480,"rel":481},"https:\u002F\u002Forval.dev\u002F",[102],"Orval",". Это codegen, который берет OpenAPI-доки и генерирует все вышеперечисленное, плюс удобная документация и простая конфигурация.",[53,485,486],{},"Конкретный before \u002F after. На входе — небольшой OpenAPI-спек и конфиг Orval:",[202,488,492],{"className":489,"code":490,"language":491,"meta":13,"style":13},"language-yaml shiki shiki-themes github-light github-dark","# openapi.yaml\nopenapi: 3.0.3\ninfo:\n  title: Users API\n  version: 1.0.0\npaths:\n  \u002Fusers\u002F{id}:\n    get:\n      operationId: getUser\n      parameters:\n        - name: id\n          in: path\n          required: true\n          schema:\n            type: string\n      responses:\n        '200':\n          description: OK\n          content:\n            application\u002Fjson:\n              schema:\n                $ref: '#\u002Fcomponents\u002Fschemas\u002FUser'\ncomponents:\n  schemas:\n    User:\n      type: object\n      required: [id, email]\n      properties:\n        id:\n          type: string\n        email:\n          type: string\n          format: email\n","yaml",[208,493,494,500,512,520,530,541,549,557,565,576,584,598,609,620,628,639,647,655,666,674,682,690,701,709,717,725,736,754,762,770,780,788,797],{"__ignoreMap":13},[211,495,496],{"class":213,"line":214},[211,497,499],{"class":498},"sJ8bj","# openapi.yaml\n",[211,501,502,506,509],{"class":213,"line":14},[211,503,505],{"class":504},"s9eBZ","openapi",[211,507,508],{"class":217},": ",[211,510,511],{"class":223},"3.0.3\n",[211,513,514,517],{"class":213,"line":242},[211,515,516],{"class":504},"info",[211,518,519],{"class":217},":\n",[211,521,522,525,527],{"class":213,"line":256},[211,523,524],{"class":504},"  title",[211,526,508],{"class":217},[211,528,529],{"class":230},"Users API\n",[211,531,533,536,538],{"class":213,"line":532},5,[211,534,535],{"class":504},"  version",[211,537,508],{"class":217},[211,539,540],{"class":223},"1.0.0\n",[211,542,544,547],{"class":213,"line":543},6,[211,545,546],{"class":504},"paths",[211,548,519],{"class":217},[211,550,552,555],{"class":213,"line":551},7,[211,553,554],{"class":504},"  \u002Fusers\u002F{id}",[211,556,519],{"class":217},[211,558,560,563],{"class":213,"line":559},8,[211,561,562],{"class":504},"    get",[211,564,519],{"class":217},[211,566,568,571,573],{"class":213,"line":567},9,[211,569,570],{"class":504},"      operationId",[211,572,508],{"class":217},[211,574,575],{"class":230},"getUser\n",[211,577,579,582],{"class":213,"line":578},10,[211,580,581],{"class":504},"      parameters",[211,583,519],{"class":217},[211,585,587,590,593,595],{"class":213,"line":586},11,[211,588,589],{"class":217},"        - ",[211,591,592],{"class":504},"name",[211,594,508],{"class":217},[211,596,597],{"class":230},"id\n",[211,599,601,604,606],{"class":213,"line":600},12,[211,602,603],{"class":504},"          in",[211,605,508],{"class":217},[211,607,608],{"class":230},"path\n",[211,610,612,615,617],{"class":213,"line":611},13,[211,613,614],{"class":504},"          required",[211,616,508],{"class":217},[211,618,619],{"class":223},"true\n",[211,621,623,626],{"class":213,"line":622},14,[211,624,625],{"class":504},"          schema",[211,627,519],{"class":217},[211,629,631,634,636],{"class":213,"line":630},15,[211,632,633],{"class":504},"            type",[211,635,508],{"class":217},[211,637,638],{"class":230},"string\n",[211,640,642,645],{"class":213,"line":641},16,[211,643,644],{"class":504},"      responses",[211,646,519],{"class":217},[211,648,650,653],{"class":213,"line":649},17,[211,651,652],{"class":230},"        '200'",[211,654,519],{"class":217},[211,656,658,661,663],{"class":213,"line":657},18,[211,659,660],{"class":504},"          description",[211,662,508],{"class":217},[211,664,665],{"class":230},"OK\n",[211,667,669,672],{"class":213,"line":668},19,[211,670,671],{"class":504},"          content",[211,673,519],{"class":217},[211,675,677,680],{"class":213,"line":676},20,[211,678,679],{"class":504},"            application\u002Fjson",[211,681,519],{"class":217},[211,683,685,688],{"class":213,"line":684},21,[211,686,687],{"class":504},"              schema",[211,689,519],{"class":217},[211,691,693,696,698],{"class":213,"line":692},22,[211,694,695],{"class":504},"                $ref",[211,697,508],{"class":217},[211,699,700],{"class":230},"'#\u002Fcomponents\u002Fschemas\u002FUser'\n",[211,702,704,707],{"class":213,"line":703},23,[211,705,706],{"class":504},"components",[211,708,519],{"class":217},[211,710,712,715],{"class":213,"line":711},24,[211,713,714],{"class":504},"  schemas",[211,716,519],{"class":217},[211,718,720,723],{"class":213,"line":719},25,[211,721,722],{"class":504},"    User",[211,724,519],{"class":217},[211,726,728,731,733],{"class":213,"line":727},26,[211,729,730],{"class":504},"      type",[211,732,508],{"class":217},[211,734,735],{"class":230},"object\n",[211,737,739,742,744,747,749,752],{"class":213,"line":738},27,[211,740,741],{"class":504},"      required",[211,743,227],{"class":217},[211,745,746],{"class":230},"id",[211,748,162],{"class":217},[211,750,751],{"class":230},"email",[211,753,253],{"class":217},[211,755,757,760],{"class":213,"line":756},28,[211,758,759],{"class":504},"      properties",[211,761,519],{"class":217},[211,763,765,768],{"class":213,"line":764},29,[211,766,767],{"class":504},"        id",[211,769,519],{"class":217},[211,771,773,776,778],{"class":213,"line":772},30,[211,774,775],{"class":504},"          type",[211,777,508],{"class":217},[211,779,638],{"class":230},[211,781,783,786],{"class":213,"line":782},31,[211,784,785],{"class":504},"        email",[211,787,519],{"class":217},[211,789,791,793,795],{"class":213,"line":790},32,[211,792,775],{"class":504},[211,794,508],{"class":217},[211,796,638],{"class":230},[211,798,800,803,805],{"class":213,"line":799},33,[211,801,802],{"class":504},"          format",[211,804,508],{"class":217},[211,806,807],{"class":230},"email\n",[202,809,813],{"className":810,"code":811,"language":812,"meta":13,"style":13},"language-ts shiki shiki-themes github-light github-dark","\u002F\u002F orval.config.ts\nimport { defineConfig } from 'orval'\n\nexport default defineConfig({\n  api: {\n    input: '.\u002Fopenapi.yaml',\n    output: {\n      target: '.\u002Fsrc\u002Fapi\u002Fendpoints.ts',\n      client: 'fetch',\n      schemas: '.\u002Fsrc\u002Fapi\u002Fmodels',\n    },\n  },\n})\n","ts",[208,814,815,820,835,840,854,859,870,875,885,895,905,910,915],{"__ignoreMap":13},[211,816,817],{"class":213,"line":214},[211,818,819],{"class":498},"\u002F\u002F orval.config.ts\n",[211,821,822,826,829,832],{"class":213,"line":14},[211,823,825],{"class":824},"szBVR","import",[211,827,828],{"class":217}," { defineConfig } ",[211,830,831],{"class":824},"from",[211,833,834],{"class":230}," 'orval'\n",[211,836,837],{"class":213,"line":242},[211,838,839],{"emptyLinePlaceholder":20},"\n",[211,841,842,845,848,851],{"class":213,"line":256},[211,843,844],{"class":824},"export",[211,846,847],{"class":824}," default",[211,849,850],{"class":344}," defineConfig",[211,852,853],{"class":217},"({\n",[211,855,856],{"class":213,"line":532},[211,857,858],{"class":217},"  api: {\n",[211,860,861,864,867],{"class":213,"line":543},[211,862,863],{"class":217},"    input: ",[211,865,866],{"class":230},"'.\u002Fopenapi.yaml'",[211,868,869],{"class":217},",\n",[211,871,872],{"class":213,"line":551},[211,873,874],{"class":217},"    output: {\n",[211,876,877,880,883],{"class":213,"line":559},[211,878,879],{"class":217},"      target: ",[211,881,882],{"class":230},"'.\u002Fsrc\u002Fapi\u002Fendpoints.ts'",[211,884,869],{"class":217},[211,886,887,890,893],{"class":213,"line":567},[211,888,889],{"class":217},"      client: ",[211,891,892],{"class":230},"'fetch'",[211,894,869],{"class":217},[211,896,897,900,903],{"class":213,"line":578},[211,898,899],{"class":217},"      schemas: ",[211,901,902],{"class":230},"'.\u002Fsrc\u002Fapi\u002Fmodels'",[211,904,869],{"class":217},[211,906,907],{"class":213,"line":586},[211,908,909],{"class":217},"    },\n",[211,911,912],{"class":213,"line":600},[211,913,914],{"class":217},"  },\n",[211,916,917],{"class":213,"line":611},[211,918,919],{"class":217},"})\n",[53,921,922,923,926],{},"После ",[208,924,925],{},"orval"," получаем типизированные модели и готовый fetch-клиент — без ручного маппинга DTO:",[202,928,930],{"className":810,"code":929,"language":812,"meta":13,"style":13},"\u002F\u002F src\u002Fapi\u002Fmodels\u002Fuser.ts (сгенерировано)\nexport interface User {\n  id: string\n  email: string\n}\n\n\u002F\u002F src\u002Fapi\u002Fendpoints.ts (сгенерировано, упрощено)\nexport const getUser = async (id: string, options?: RequestInit): Promise\u003CUser> => {\n  const res = await fetch(`\u002Fusers\u002F${id}`, { ...options, method: 'GET' })\n  return res.json()\n}\n",[208,931,932,937,950,962,971,975,979,984,1043,1085,1098],{"__ignoreMap":13},[211,933,934],{"class":213,"line":214},[211,935,936],{"class":498},"\u002F\u002F src\u002Fapi\u002Fmodels\u002Fuser.ts (сгенерировано)\n",[211,938,939,941,944,947],{"class":213,"line":14},[211,940,844],{"class":824},[211,942,943],{"class":824}," interface",[211,945,946],{"class":344}," User",[211,948,949],{"class":217}," {\n",[211,951,952,956,959],{"class":213,"line":242},[211,953,955],{"class":954},"s4XuR","  id",[211,957,958],{"class":824},":",[211,960,961],{"class":223}," string\n",[211,963,964,967,969],{"class":213,"line":256},[211,965,966],{"class":954},"  email",[211,968,958],{"class":824},[211,970,961],{"class":223},[211,972,973],{"class":213,"line":532},[211,974,259],{"class":217},[211,976,977],{"class":213,"line":543},[211,978,839],{"emptyLinePlaceholder":20},[211,980,981],{"class":213,"line":551},[211,982,983],{"class":498},"\u002F\u002F src\u002Fapi\u002Fendpoints.ts (сгенерировано, упрощено)\n",[211,985,986,988,991,994,997,1000,1003,1005,1007,1010,1012,1015,1018,1021,1024,1026,1029,1032,1035,1038,1041],{"class":213,"line":559},[211,987,844],{"class":824},[211,989,990],{"class":824}," const",[211,992,993],{"class":344}," getUser",[211,995,996],{"class":824}," =",[211,998,999],{"class":824}," async",[211,1001,1002],{"class":217}," (",[211,1004,746],{"class":954},[211,1006,958],{"class":824},[211,1008,1009],{"class":223}," string",[211,1011,162],{"class":217},[211,1013,1014],{"class":954},"options",[211,1016,1017],{"class":824},"?:",[211,1019,1020],{"class":344}," RequestInit",[211,1022,1023],{"class":217},")",[211,1025,958],{"class":824},[211,1027,1028],{"class":344}," Promise",[211,1030,1031],{"class":217},"\u003C",[211,1033,1034],{"class":344},"User",[211,1036,1037],{"class":217},"> ",[211,1039,1040],{"class":824},"=>",[211,1042,949],{"class":217},[211,1044,1045,1048,1051,1053,1056,1059,1062,1065,1067,1070,1073,1076,1079,1082],{"class":213,"line":567},[211,1046,1047],{"class":824},"  const",[211,1049,1050],{"class":223}," res",[211,1052,996],{"class":824},[211,1054,1055],{"class":824}," await",[211,1057,1058],{"class":344}," fetch",[211,1060,1061],{"class":217},"(",[211,1063,1064],{"class":230},"`\u002Fusers\u002F${",[211,1066,746],{"class":217},[211,1068,1069],{"class":230},"}`",[211,1071,1072],{"class":217},", { ",[211,1074,1075],{"class":824},"...",[211,1077,1078],{"class":217},"options, method: ",[211,1080,1081],{"class":230},"'GET'",[211,1083,1084],{"class":217}," })\n",[211,1086,1087,1090,1093,1095],{"class":213,"line":578},[211,1088,1089],{"class":824},"  return",[211,1091,1092],{"class":217}," res.",[211,1094,206],{"class":344},[211,1096,1097],{"class":217},"()\n",[211,1099,1100],{"class":213,"line":586},[211,1101,259],{"class":217},[53,1103,1104],{},"Единственная проблема, которую стоит упомянуть: нужно аккуратно выстраивать совместную работу — иначе типы на фронте могут внезапно сломаться (например, вы пишете хотфикс, а бэкенд-команда уже задеплоила фичу на dev, и ваши типы разъехались). Один из вариантов — вынести OpenAPI-схемы в npm-пакет, который обе команды обновляют и версионируют вместе.",[90,1106,1108],{"id":1107},"генерация-токенов-из-figma-tokens-studio-style-dictionary","Генерация токенов из Figma = Tokens Studio + Style Dictionary",[53,1110,1111],{},"Это одновременно самая полезная и самая сложная в настройке автоматизация — потому что придется менять то, как работает дизайн-команда.",[53,1113,1114],{},"Главная боль при готовом UI — держать его в синхроне с изменениями в Figma. Слышал кучу страшных историй, как команды тратят даже 1\u002F5 спринта только на синхронизацию с Figma. Версионирование в Figma до сих пор слабое, поэтому появляется куча локальных костылей.",[53,1116,1117,1118,1123,1124,1129],{},"Эту проблему можно закрыть через ",[98,1119,1122],{"href":1120,"rel":1121},"https:\u002F\u002Ftokens.studio\u002F",[102],"Tokens Studio",". По сути это плагин для Figma для управления дизайн-токенами. Когда дизайнеры работают через него, токены можно экспортировать как схему (что-то вроде OpenAPI, но для токенов), а дальше прогнать через ",[98,1125,1128],{"href":1126,"rel":1127},"https:\u002F\u002Fstyledictionary.com\u002F",[102],"Style Dictionary"," и получить ваш конкретный CSS-сетап — Tailwind, CSS-переменные, Sass и так далее.",[53,1131,1132],{},"Конкретный before \u002F after. Дизайнеры экспортируют схему Tokens Studio, а вы указываете на неё Style Dictionary:",[202,1134,1136],{"className":204,"code":1135,"language":206,"meta":13,"style":13},"\u002F\u002F tokens\u002Fcolors.json (экспорт из Tokens Studio)\n{\n  \"color\": {\n    \"brand\": {\n      \"primary\": { \"value\": \"#2563eb\", \"type\": \"color\" },\n      \"secondary\": { \"value\": \"#7c3aed\", \"type\": \"color\" }\n    },\n    \"surface\": {\n      \"bg\": { \"value\": \"#ffffff\", \"type\": \"color\" },\n      \"fg\": { \"value\": \"#0f172a\", \"type\": \"color\" }\n    }\n  },\n  \"spacing\": {\n    \"sm\": { \"value\": \"8\", \"type\": \"spacing\" },\n    \"md\": { \"value\": \"16\", \"type\": \"spacing\" },\n    \"lg\": { \"value\": \"24\", \"type\": \"spacing\" }\n  }\n}\n",[208,1137,1138,1143,1147,1155,1162,1191,1216,1220,1227,1251,1275,1280,1284,1291,1316,1340,1364,1369],{"__ignoreMap":13},[211,1139,1140],{"class":213,"line":214},[211,1141,1142],{"class":498},"\u002F\u002F tokens\u002Fcolors.json (экспорт из Tokens Studio)\n",[211,1144,1145],{"class":213,"line":14},[211,1146,218],{"class":217},[211,1148,1149,1152],{"class":213,"line":242},[211,1150,1151],{"class":223},"  \"color\"",[211,1153,1154],{"class":217},": {\n",[211,1156,1157,1160],{"class":213,"line":256},[211,1158,1159],{"class":223},"    \"brand\"",[211,1161,1154],{"class":217},[211,1163,1164,1167,1170,1173,1175,1178,1180,1183,1185,1188],{"class":213,"line":532},[211,1165,1166],{"class":223},"      \"primary\"",[211,1168,1169],{"class":217},": { ",[211,1171,1172],{"class":223},"\"value\"",[211,1174,508],{"class":217},[211,1176,1177],{"class":230},"\"#2563eb\"",[211,1179,162],{"class":217},[211,1181,1182],{"class":223},"\"type\"",[211,1184,508],{"class":217},[211,1186,1187],{"class":230},"\"color\"",[211,1189,1190],{"class":217}," },\n",[211,1192,1193,1196,1198,1200,1202,1205,1207,1209,1211,1213],{"class":213,"line":543},[211,1194,1195],{"class":223},"      \"secondary\"",[211,1197,1169],{"class":217},[211,1199,1172],{"class":223},[211,1201,508],{"class":217},[211,1203,1204],{"class":230},"\"#7c3aed\"",[211,1206,162],{"class":217},[211,1208,1182],{"class":223},[211,1210,508],{"class":217},[211,1212,1187],{"class":230},[211,1214,1215],{"class":217}," }\n",[211,1217,1218],{"class":213,"line":551},[211,1219,909],{"class":217},[211,1221,1222,1225],{"class":213,"line":559},[211,1223,1224],{"class":223},"    \"surface\"",[211,1226,1154],{"class":217},[211,1228,1229,1232,1234,1236,1238,1241,1243,1245,1247,1249],{"class":213,"line":567},[211,1230,1231],{"class":223},"      \"bg\"",[211,1233,1169],{"class":217},[211,1235,1172],{"class":223},[211,1237,508],{"class":217},[211,1239,1240],{"class":230},"\"#ffffff\"",[211,1242,162],{"class":217},[211,1244,1182],{"class":223},[211,1246,508],{"class":217},[211,1248,1187],{"class":230},[211,1250,1190],{"class":217},[211,1252,1253,1256,1258,1260,1262,1265,1267,1269,1271,1273],{"class":213,"line":578},[211,1254,1255],{"class":223},"      \"fg\"",[211,1257,1169],{"class":217},[211,1259,1172],{"class":223},[211,1261,508],{"class":217},[211,1263,1264],{"class":230},"\"#0f172a\"",[211,1266,162],{"class":217},[211,1268,1182],{"class":223},[211,1270,508],{"class":217},[211,1272,1187],{"class":230},[211,1274,1215],{"class":217},[211,1276,1277],{"class":213,"line":586},[211,1278,1279],{"class":217},"    }\n",[211,1281,1282],{"class":213,"line":600},[211,1283,914],{"class":217},[211,1285,1286,1289],{"class":213,"line":611},[211,1287,1288],{"class":223},"  \"spacing\"",[211,1290,1154],{"class":217},[211,1292,1293,1296,1298,1300,1302,1305,1307,1309,1311,1314],{"class":213,"line":622},[211,1294,1295],{"class":223},"    \"sm\"",[211,1297,1169],{"class":217},[211,1299,1172],{"class":223},[211,1301,508],{"class":217},[211,1303,1304],{"class":230},"\"8\"",[211,1306,162],{"class":217},[211,1308,1182],{"class":223},[211,1310,508],{"class":217},[211,1312,1313],{"class":230},"\"spacing\"",[211,1315,1190],{"class":217},[211,1317,1318,1321,1323,1325,1327,1330,1332,1334,1336,1338],{"class":213,"line":630},[211,1319,1320],{"class":223},"    \"md\"",[211,1322,1169],{"class":217},[211,1324,1172],{"class":223},[211,1326,508],{"class":217},[211,1328,1329],{"class":230},"\"16\"",[211,1331,162],{"class":217},[211,1333,1182],{"class":223},[211,1335,508],{"class":217},[211,1337,1313],{"class":230},[211,1339,1190],{"class":217},[211,1341,1342,1345,1347,1349,1351,1354,1356,1358,1360,1362],{"class":213,"line":641},[211,1343,1344],{"class":223},"    \"lg\"",[211,1346,1169],{"class":217},[211,1348,1172],{"class":223},[211,1350,508],{"class":217},[211,1352,1353],{"class":230},"\"24\"",[211,1355,162],{"class":217},[211,1357,1182],{"class":223},[211,1359,508],{"class":217},[211,1361,1313],{"class":230},[211,1363,1215],{"class":217},[211,1365,1366],{"class":213,"line":649},[211,1367,1368],{"class":217},"  }\n",[211,1370,1371],{"class":213,"line":657},[211,1372,259],{"class":217},[202,1374,1378],{"className":1375,"code":1376,"language":1377,"meta":13,"style":13},"language-js shiki shiki-themes github-light github-dark","\u002F\u002F style-dictionary.config.js\nexport default {\n  source: ['tokens\u002F**\u002F*.json'],\n  platforms: {\n    css: {\n      transformGroup: 'css',\n      buildPath: 'build\u002F',\n      files: [\n        {\n          destination: 'theme.css',\n          format: 'css\u002Fvariables',\n          options: {\n            selector: '@theme',\n          },\n        },\n      ],\n    },\n  },\n}\n","js",[208,1379,1380,1385,1393,1403,1408,1413,1423,1433,1438,1443,1453,1463,1468,1478,1483,1488,1493,1497,1501],{"__ignoreMap":13},[211,1381,1382],{"class":213,"line":214},[211,1383,1384],{"class":498},"\u002F\u002F style-dictionary.config.js\n",[211,1386,1387,1389,1391],{"class":213,"line":14},[211,1388,844],{"class":824},[211,1390,847],{"class":824},[211,1392,949],{"class":217},[211,1394,1395,1398,1401],{"class":213,"line":242},[211,1396,1397],{"class":217},"  source: [",[211,1399,1400],{"class":230},"'tokens\u002F**\u002F*.json'",[211,1402,239],{"class":217},[211,1404,1405],{"class":213,"line":256},[211,1406,1407],{"class":217},"  platforms: {\n",[211,1409,1410],{"class":213,"line":532},[211,1411,1412],{"class":217},"    css: {\n",[211,1414,1415,1418,1421],{"class":213,"line":543},[211,1416,1417],{"class":217},"      transformGroup: ",[211,1419,1420],{"class":230},"'css'",[211,1422,869],{"class":217},[211,1424,1425,1428,1431],{"class":213,"line":551},[211,1426,1427],{"class":217},"      buildPath: ",[211,1429,1430],{"class":230},"'build\u002F'",[211,1432,869],{"class":217},[211,1434,1435],{"class":213,"line":559},[211,1436,1437],{"class":217},"      files: [\n",[211,1439,1440],{"class":213,"line":567},[211,1441,1442],{"class":217},"        {\n",[211,1444,1445,1448,1451],{"class":213,"line":578},[211,1446,1447],{"class":217},"          destination: ",[211,1449,1450],{"class":230},"'theme.css'",[211,1452,869],{"class":217},[211,1454,1455,1458,1461],{"class":213,"line":586},[211,1456,1457],{"class":217},"          format: ",[211,1459,1460],{"class":230},"'css\u002Fvariables'",[211,1462,869],{"class":217},[211,1464,1465],{"class":213,"line":600},[211,1466,1467],{"class":217},"          options: {\n",[211,1469,1470,1473,1476],{"class":213,"line":611},[211,1471,1472],{"class":217},"            selector: ",[211,1474,1475],{"class":230},"'@theme'",[211,1477,869],{"class":217},[211,1479,1480],{"class":213,"line":622},[211,1481,1482],{"class":217},"          },\n",[211,1484,1485],{"class":213,"line":630},[211,1486,1487],{"class":217},"        },\n",[211,1489,1490],{"class":213,"line":641},[211,1491,1492],{"class":217},"      ],\n",[211,1494,1495],{"class":213,"line":649},[211,1496,909],{"class":217},[211,1498,1499],{"class":213,"line":657},[211,1500,914],{"class":217},[211,1502,1503],{"class":213,"line":668},[211,1504,259],{"class":217},[53,1506,1507,1508,1511],{},"С ",[208,1509,1510],{},"selector: '@theme'"," Style Dictionary сразу генерирует theme-файл для Tailwind v4:",[202,1513,1517],{"className":1514,"code":1515,"language":1516,"meta":13,"style":13},"language-css shiki shiki-themes github-light github-dark","\u002F**\n * Do not edit directly, this file was auto-generated.\n *\u002F\n\n@theme {\n  --color-brand-primary: #2563eb;\n  --color-brand-secondary: #7c3aed;\n  --color-surface-bg: #ffffff;\n  --color-surface-fg: #0f172a;\n  --spacing-sm: 8;\n  --spacing-md: 16;\n  --spacing-lg: 24;\n}\n","css",[208,1518,1519,1524,1529,1534,1538,1545,1557,1567,1575,1585,1590,1595,1600],{"__ignoreMap":13},[211,1520,1521],{"class":213,"line":214},[211,1522,1523],{"class":498},"\u002F**\n",[211,1525,1526],{"class":213,"line":14},[211,1527,1528],{"class":498}," * Do not edit directly, this file was auto-generated.\n",[211,1530,1531],{"class":213,"line":242},[211,1532,1533],{"class":498}," *\u002F\n",[211,1535,1536],{"class":213,"line":256},[211,1537,839],{"emptyLinePlaceholder":20},[211,1539,1540,1543],{"class":213,"line":532},[211,1541,1542],{"class":824},"@theme",[211,1544,949],{"class":217},[211,1546,1547,1550,1554],{"class":213,"line":543},[211,1548,1549],{"class":217},"  --color-brand-primary: ",[211,1551,1553],{"class":1552},"s7hpK","#2563eb",[211,1555,1556],{"class":217},";\n",[211,1558,1559,1562,1565],{"class":213,"line":551},[211,1560,1561],{"class":217},"  --color-brand-secondary: ",[211,1563,1564],{"class":1552},"#7c3aed",[211,1566,1556],{"class":217},[211,1568,1569,1572],{"class":213,"line":559},[211,1570,1571],{"class":217},"  --color-surface-bg: ",[211,1573,1574],{"class":1552},"#ffffff;\n",[211,1576,1577,1580,1583],{"class":213,"line":567},[211,1578,1579],{"class":217},"  --color-surface-fg: ",[211,1581,1582],{"class":1552},"#0f172a",[211,1584,1556],{"class":217},[211,1586,1587],{"class":213,"line":578},[211,1588,1589],{"class":217},"  --spacing-sm: 8;\n",[211,1591,1592],{"class":213,"line":586},[211,1593,1594],{"class":217},"  --spacing-md: 16;\n",[211,1596,1597],{"class":213,"line":600},[211,1598,1599],{"class":217},"  --spacing-lg: 24;\n",[211,1601,1602],{"class":213,"line":611},[211,1603,259],{"class":217},[202,1605,1607],{"className":1514,"code":1606,"language":1516,"meta":13,"style":13},"\u002F* app.css *\u002F\n@import \"tailwindcss\";\n@import \".\u002Fbuild\u002Ftheme.css\";\n",[208,1608,1609,1614,1624],{"__ignoreMap":13},[211,1610,1611],{"class":213,"line":214},[211,1612,1613],{"class":498},"\u002F* app.css *\u002F\n",[211,1615,1616,1619,1622],{"class":213,"line":14},[211,1617,1618],{"class":824},"@import",[211,1620,1621],{"class":230}," \"tailwindcss\"",[211,1623,1556],{"class":217},[211,1625,1626,1628,1631],{"class":213,"line":242},[211,1627,1618],{"class":824},[211,1629,1630],{"class":230}," \".\u002Fbuild\u002Ftheme.css\"",[211,1632,1556],{"class":217},[53,1634,1635],{},"С версионированием токенов та же история, но Tokens Studio умеет интегрироваться с Git — дизайнеры могут нормально версионировать изменения. Дальше можно сделать npm-пакет, где эта генерация применяется, и закрепить фронтенд на конкретной версии.",[48,1637,1639],{"id":1638},"заключение","Заключение",[53,1641,1642],{},"Я постарался собрать любимые автоматизации с реальным импактом — те, которыми продолжал пользоваться после всех экспериментов. Надеюсь, вы нашли для себя что-то интересное и новое!",[1644,1645,1646],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}",{"title":13,"searchDepth":14,"depth":14,"links":1648},[1649,1650,1651,1661],{"id":50,"depth":14,"text":51},{"id":61,"depth":14,"text":62},{"id":84,"depth":14,"text":85,"children":1652},[1653,1654,1655,1656,1657,1658,1659,1660],{"id":92,"depth":242,"text":93},{"id":171,"depth":242,"text":172},{"id":191,"depth":242,"text":192},{"id":319,"depth":242,"text":320},{"id":381,"depth":242,"text":382},{"id":430,"depth":242,"text":431},{"id":449,"depth":242,"text":450},{"id":1107,"depth":242,"text":1108},{"id":1638,"depth":14,"text":1639},"2026-07-21 00:00:00 UTC","Полезные автоматизации, которые заметно улучшают DX","10 мин",{},"\u002Fposts\u002Fessential-frontend-automations",{"title":30,"description":1663},{"loc":1666},"posts\u002Fessential-frontend-automations",[1671],"dev","iEvSmCbRSlXOKkGQPdj4lsEer0xSmIz29hJICcc18bE",{"id":1674,"title":1675,"body":1676,"date":1710,"description":13,"duration":1711,"extension":18,"meta":1712,"navigation":20,"path":1713,"robots":16,"seo":1714,"sitemap":1715,"stem":1716,"tags":16,"toc":25,"__hash__":1717},"content_ru\u002Fposts\u002Fhello-blog.md","Привет, блог!",{"type":7,"value":1677,"toc":1708},[1678,1682,1685,1688,1696,1699,1702,1705],[34,1679,1680],{"id":13},[37,1681],{"value":39},[41,1683],{":duration":43,":date":44,"className":1684},[46],[53,1686,1687],{},"В какой-то момент я заметил, что самые интересные рассуждения — о технологиях, о жизни, о том, почему все устроено так, а не иначе — исчезают. Мне захотелось место, где можно сохранять эти мысли, хотя бы для себя, и возвращаться к ним, когда понадобится.",[53,1689,1690,1691,1695],{},"Что касается блога, я искренне считаю, что он поможет мне двигаться вперед. Я хочу улучшить навык письма, научиться лучше структурировать мысли и собирать их в одном месте. Не думаю, что блог будет привязан к какой-то конкретной теме. Буду писать обо всем, что мне интересно. Но вы всегда можете использовать ",[1692,1693,1694],"em",{},"теги",", чтобы отфильтровать то, что вам интереснее всего.",[53,1697,1698],{},"Так как мой родной язык — русский, я буду писать посты и на английском, и на русском. Переключаться между ними можно в любой момент. У каждой версии будут свои статьи, потому что не все темы одинаково хорошо заходят на обе аудитории.",[53,1700,1701],{},"В любом случае, этот сайт будет развиваться со временем. Я планирую добавить раздел о железе и софте, которым пользуюсь, список любимых медиа, а может и что-то, о чем я еще даже не думал.",[53,1703,1704],{},"Надеюсь, вы найдете здесь что-то интересное. Кстати, любые предложения по улучшению приветствуются!",[53,1706,1707],{},"Увидимся в следующем посте!",{"title":13,"searchDepth":14,"depth":14,"links":1709},[],"2026-03-13 00:00:00 UTC","1 min",{},"\u002Fposts\u002Fhello-blog",{"title":1675,"description":13},{"loc":1713},"posts\u002Fhello-blog","XfwtkQnpAQ-KYfcjCVYbGFlkgAbukUCgLHsSfzbihSA",{"id":1719,"title":1720,"body":1721,"date":2214,"description":2215,"duration":2216,"extension":18,"meta":2217,"navigation":20,"path":2218,"robots":16,"seo":2219,"sitemap":2220,"stem":2221,"tags":2222,"toc":20,"__hash__":2223},"content_ru\u002Fposts\u002Fstop-using-html-for-your-resume.md","Не используйте HTML для резюме",{"type":7,"value":1722,"toc":2200},[1723,1727,1730,1732,1740,1805,1809,1812,1816,1819,1833,1836,1840,1850,1875,1878,1882,1885,1916,1920,1923,1926,1930,1936,1951,1961,1970,2002,2006,2010,2020,2034,2037,2046,2058,2084,2087,2090,2100,2103,2107,2110,2115,2142,2147,2165,2173,2175,2188,2197],[34,1724,1725],{"id":13},[37,1726],{"value":39},[41,1728],{":duration":43,":date":44,"className":1729},[46],[48,1731,62],{"id":61},[53,1733,1734,1735,1739],{},"Эта статья — логическое продолжение ",[98,1736,1738],{"href":1737},"\u002Fposts\u002Fstop-using-word-for-your-resume","Не используйте Word для резюме",". Настоятельно рекомендую сначала прочитать ее для полного контекста, но я все же кратко напомню основные тезисы:",[289,1741,1742,1753,1791,1797],{},[70,1743,1744,1745,1748,1749,1752],{},"Резюме должно иметь ",[135,1746,1747],{},"единый локальный"," источник истины в легко редактируемом ",[135,1750,1751],{},"сыром"," формате.",[70,1754,1755,1756,1759,1760,162,1763,150,1766,1769,1770,162,1773,162,1776,150,1779,1782,1783,1786,1787,1790],{},"Этот сырой формат должен ",[135,1757,1758],{},"конвертироваться в PDF",", быть ",[135,1761,1762],{},"дружелюбным к LLM и ATS",[135,1764,1765],{},"версионируемым",[135,1767,1768],{},"портативным",", с ",[135,1771,1772],{},"точным контролем верстки",[135,1774,1775],{},"богатым и гибким оформлением",[135,1777,1778],{},"удобством написания контента",[135,1780,1781],{},"разделением контента и стиля",". Мы предполагаем, что пользователь — ",[135,1784,1785],{},"разработчик"," и\u002Fили ",[135,1788,1789],{},"может или хочет освоить"," этот формат.",[70,1792,1793,1796],{},[135,1794,1795],{},"HTML"," подходит на эту роль, но не имел нужного инструментария.",[70,1798,1799,1804],{},[98,1800,1803],{"href":1801,"rel":1802},"https:\u002F\u002Fcv-html-dev.netlify.app",[102],"CV.html"," был создан, чтобы закрыть этот пробел.",[48,1806,1808],{"id":1807},"проблемы","Проблемы",[53,1810,1811],{},"С тех пор я много работал со своим инструментом, экспериментируя со стилями, контентом, визуальным дизайном и прочим. На бумаге все звучит отлично, но на практике проявились неожиданные проблемы.",[90,1813,1815],{"id":1814},"повторения","Повторения",[53,1817,1818],{},"Структура резюме по своей природе повторяема — записи о работе, пункты списков, навыки — но чистый HTML не дает способа это выразить. Например, достижения внутри записи об опыте работы или сами записи об опыте.",[53,1820,1821,1822,1827,1828,104],{},"Технически это двухуровневый список, и в повседневной работе мы бы использовали циклы и компонентную абстракцию, но чистый HTML не предоставляет таких инструментов. Можно вынести часть разметки в ",[98,1823,1826],{"href":1824,"rel":1825},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FWeb_components\u002FUsing_custom_elements",[102],"custom element",", но все равно придется повторять его много раз в коде, раздувая файл и натыкаясь на все, о чем говорит принцип ",[98,1829,1832],{"href":1830,"rel":1831},"https:\u002F\u002Fru.wikipedia.org\u002Fwiki\u002FDon%E2%80%99t_repeat_yourself",[102],"DRY",[53,1834,1835],{},"К тому же кто захочет писать веб компоненты для своего резюме?",[90,1837,1839],{"id":1838},"изображения","Изображения",[53,1841,1842,1843,1846,1847,958],{},"В некоторых странах принято добавлять фото в резюме, и самый портативный способ встроить его в HTML — конвертировать в base64-строку. Если вы хоть раз видели закодированное изображение, знаете, что оно может быть очень большим. В текущей реализации пользователю приходится вставлять его в тег ",[208,1844,1845],{},"img"," как атрибут ",[208,1848,1849],{},"src",[202,1851,1855],{"className":1852,"code":1853,"language":1854,"meta":13,"style":13},"language-html shiki shiki-themes github-light github-dark","\u003Cimg src=\"data:image\u002F[format];base64,[your_base64_string]\" \u002F>\n","html",[208,1856,1857],{"__ignoreMap":13},[211,1858,1859,1861,1863,1866,1869,1872],{"class":213,"line":214},[211,1860,1031],{"class":217},[211,1862,1845],{"class":504},[211,1864,1865],{"class":344}," src",[211,1867,1868],{"class":217},"=",[211,1870,1871],{"class":230},"\"data:image\u002F[format];base64,[your_base64_string]\"",[211,1873,1874],{"class":217}," \u002F>\n",[53,1876,1877],{},"Это идеально сочетается с предыдущей проблемой, делая HTML еще более раздутым. А теперь представьте, что одно и то же изображение нужно отрендерить несколько раз (как в случае со списком опыта работы). Одна мысль об этом — и уже болит голова.",[90,1879,1881],{"id":1880},"мультиязычность","Мультиязычность",[53,1883,1884],{},"Один из моих сценариев — создание двуязычных резюме с одинаковым внешним видом. В процессе оказлось что это гораздо трудозатратнее из-за проблем выше плюс из-за смешения тегов, классов и самого контента. Можно вставить HTML файл в LLM и попросить перевести, но:",[289,1886,1887,1890,1893],{},[70,1888,1889],{},"Все равно придется проверять результат и вносить изменения.",[70,1891,1892],{},"Закодированные изображения съедают лишние токены.",[70,1894,1895,1896,1912,1915],{},"Сравните китайскую и английскую версии одного предложения:",[202,1897,1900],{"className":1898,"code":1899,"language":18,"meta":13,"style":13},"language-md shiki shiki-themes github-light github-dark","> London is the capital of the United Kingdom.\n> 伦敦是英国的首都。\n",[208,1901,1902,1907],{"__ignoreMap":13},[211,1903,1904],{"class":213,"line":214},[211,1905,1906],{"class":504},"> London is the capital of the United Kingdom.\n",[211,1908,1909],{"class":213,"line":14},[211,1910,1911],{"class":504},"> 伦敦是英国的首都。\n",[1913,1914],"br",{},"Очевидно, что китайская версия короче английской. Если просто перевести тот же HTML-шаблон, блоки фиксированного размера и разрывы страниц поплывут. Например, двухстраничное китайское резюме может превратиться в трех- или четырехстраничное английское, что уже может считаться перегруженным. Значит, придется подстраивать стили и структуру шаблона. Помните пример со списком опыта работы? Страшно!",[48,1917,1919],{"id":1918},"решение","Решение",[53,1921,1922],{},"Не используйте HTML для резюме. Или нет?",[53,1924,1925],{},"Когда я столкнулся с этими проблемами, начал думать, как сохранить плюсы HTML и убрать минусы. И нашел очень хорошее решение.",[90,1927,1929],{"id":1928},"концепция","Концепция",[53,1931,1932,1933,104],{},"Нужно понять настоящую суть проблемы. Вернемся к нашим требованиям — одно из них было ",[135,1934,1935],{},"разделение контента и стиля",[53,1937,1938,1939,1942,1943,1946,1947,1950],{},"CV.html имеет три вкладки: ",[135,1940,1941],{},"Template",", где пользователь пишет HTML; ",[135,1944,1945],{},"Styles",", где пишет CSS; и ",[135,1948,1949],{},"Head"," для дополнительных внешних импортов (Tailwind CSS, шрифты и т.д.). Формально разделение контента и стиля через вкладки Template и Styles существует, но на практике все было наоборот.",[53,1952,1953,1954,104],{},"Потому что этот пункт нужно сформулировать чуть иначе: ",[135,1955,1956,1957,1960],{},"разделение контента, ",[1692,1958,1959],{},"шаблона"," и стиля",[53,1962,1963,1964,150,1967,1969],{},"Сначала нужно разложить HTML на части ",[135,1965,1966],{},"контента",[135,1968,1959],{},". Определим их:",[67,1971,1972,1982],{},[70,1973,1974,1977,1978,1981],{},[135,1975,1976],{},"Контент"," — вся ",[1692,1979,1980],{},"информация"," в резюме: текст, изображения и любые другие метаданные, нужные шаблону.",[70,1983,1984,1987,1988,1991,1992,1995,1996,150,1998,2001],{},[135,1985,1986],{},"Шаблон"," — ",[1692,1989,1990],{},"структура"," резюме. Его также можно описать как ",[1692,1993,1994],{},"компиляцию"," ",[135,1997,1966],{},[135,1999,2000],{},"стилей"," по правилам пользователя.",[90,2003,2005],{"id":2004},"реализация","Реализация",[2007,2008,1986],"h4",{"id":2009},"шаблон",[53,2011,2012,2013,2015,2016,2019],{},"Главная техническая проблема — раздел ",[135,2014,1941],{},". Озвученные проблемы HTML не новы — их уже решили frontend-",[1692,2017,2018],{},"фреймворки",". Но создавать React-приложение для резюме — оверинжиниринг: оно будет тяжелым, пользователю не понадобится большинство возможностей, усложнится экспорт в PDF, а экспорт сырого HTML станет недоступен. Для комфортной работы нужны только следующие возможности фреймворка:",[289,2021,2022,2025,2031],{},[70,2023,2024],{},"Циклы и условный рендеринг, чтобы убрать повторения.",[70,2026,2027,2028,104],{},"Привязка к динамическим переменным, передаваемым отдельным ",[135,2029,2030],{},"контентом",[70,2032,2033],{},"Статический HTML на выходе.",[53,2035,2036],{},"Это не самый частый сценарий, но вполне валидный — и идеальный инструмент уже существует.",[53,2038,2039,2040,2045],{},"Встречайте, ",[98,2041,2044],{"href":2042,"rel":2043},"https:\u002F\u002Fhandlebarsjs.com",[102],"Handlebars","!",[53,2047,2048,2049,2051,2052,2057],{},"Коротко, ",[135,2050,2044],{}," — простой язык шаблонов, который принимает шаблон и входной объект и генерирует HTML или другие текстовые форматы. Подробнее о возможностях — в ",[98,2053,2056],{"href":2054,"rel":2055},"https:\u002F\u002Fhandlebarsjs.com\u002Fguide",[102],"документации",", но я остановлюсь только на важном для нас:",[289,2059,2060,2073,2081],{},[70,2061,468,2062,150,2067,2072],{},[98,2063,2066],{"href":2064,"rel":2065},"https:\u002F\u002Fhandlebarsjs.com\u002Fguide\u002Fbuiltin-helpers.html#each",[102],"циклы",[98,2068,2071],{"href":2069,"rel":2070},"https:\u002F\u002Fhandlebarsjs.com\u002Fguide\u002Fbuiltin-helpers.html#if",[102],"условный рендеринг"," из коробки.",[70,2074,468,2075,2080],{},[98,2076,2079],{"href":2077,"rel":2078},"https:\u002F\u002Fhandlebarsjs.com\u002Fguide\u002Fexpressions.html#basic-usage",[102],"привязки"," к переменным-объектам.",[70,2082,2083],{},"Можно скомпилировать в HTML в рантайме.",[53,2085,2086],{},"К тому же синтаксис очень похож на современные frontend-фреймворки, так что бонусом моим коллегам-фронтендерам его будет легко освоить.",[2007,2088,1976],{"id":2089},"контент",[53,2091,2092,2093,2095,2096,2099],{},"Простая часть — извлечь ",[135,2094,2089],{}," из HTML в JS-объект. Можно создать JSON или YAML файл, где пользователь определяет свой ",[208,2097,2098],{},"cv"," — все текстовые данные, атрибуты вроде base64-строки изображения и т.д. — и затем распарсить его.",[53,2101,2102],{},"Потом передаем этот объект в Handlebars и даем пользователю обращаться к нему в шаблоне. Я выбрал YAML, потому что он более читаем для человека, чем JSON.",[90,2104,2106],{"id":2105},"результат","Результат",[53,2108,2109],{},"Я уже реализовал это решение, протестировал — работает отлично и дает огромную гибкость. Вот как это выглядит на практике:",[53,2111,2112,2113,104],{},"Хотите двуязычное резюме? Или тот же шаблон для другого человека? Достаточно заменить ",[135,2114,2089],{},[2116,2117,2123,2124,2123,2131,2123,2136,2141],"div",{"className":2118},[2119,2120,2121,2122],"flex","gap-2","md:flex-row","flex-col","\n    ",[2125,2126],"posts-image",{"src":2127,"alt":2128,"caption":2128,"className":2129},"\u002Fimages\u002Fcvhtml-original.png","Оригинальное резюме CV.html",[2130],"h-70",[2125,2132],{"src":2133,"alt":2134,"caption":2134,"className":2135},"\u002Fimages\u002Fcvhtml-russian.png","То же резюме, переведенное на русский",[2130],[2125,2137],{"src":2138,"alt":2139,"caption":2139,"className":2140},"\u002Fimages\u002Fcvhtml-johndoe.png","Тот же шаблон для другого пользователя",[2130],"\n  ",[53,2143,2144,2145,104],{},"Или хотите другой дизайн? Без проблем — просто замените ",[135,2146,2009],{},[2116,2148,2123,2150,2123,2155,2123,2160,2141],{"className":2149},[2119,2120,2121,2122],[2125,2151],{"src":2152,"alt":2153,"caption":2153,"className":2154},"\u002Fimages\u002Fcvhtml-pinkycute.png","Шаблон Pinky-cute",[2130],[2125,2156],{"src":2157,"alt":2158,"caption":2158,"className":2159},"\u002Fimages\u002Fcvhtml-newspaper.png","Шаблон Newspaper",[2130],[2125,2161],{"src":2162,"alt":2163,"caption":2163,"className":2164},"\u002Fimages\u002Fcvhtml-terminal.png","Шаблон Terminal",[2130],[53,2166,2167,2168,104],{},"Вариантов кастомизации так много, что я до сих пор нахожу новые идеи использования и дизайны. При желании, вы тоже можете поэксперементировать в ",[2169,2170],"cv-html-badge",{"className":2171},[2172],"ml-1",[48,2174,1639],{"id":1638},[53,2176,2177,2178,2181,2182,150,2184,2187],{},"HTML все еще лучший ",[1692,2179,2180],{},"сырой"," формат? Технически нет — мы заменили его двумя независимыми файлами ",[208,2183,491],{},[208,2185,2186],{},"hbs"," (Handlebars). Но он по-прежнему служит промежуточным форматом между исходными файлами и PDF, и вы по-прежнему можете использовать плюсы HTML — например, отрендерить результат на сайте.",[53,2189,2190,2191,2193,2194,2196],{},"Поэтому я бы все же сказал: относитесь к HTML как к выходному формату, а резюме пишите в абстракциях ",[135,2192,1966],{}," \u002F ",[135,2195,1959],{},". Чистый HTML не дает такого разделения, но в экосистеме уже существуют все нужные инструменты, чтобы этого достичь.",[1644,2198,2199],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":13,"searchDepth":14,"depth":14,"links":2201},[2202,2203,2208,2213],{"id":61,"depth":14,"text":62},{"id":1807,"depth":14,"text":1808,"children":2204},[2205,2206,2207],{"id":1814,"depth":242,"text":1815},{"id":1838,"depth":242,"text":1839},{"id":1880,"depth":242,"text":1881},{"id":1918,"depth":14,"text":1919,"children":2209},[2210,2211,2212],{"id":1928,"depth":242,"text":1929},{"id":2004,"depth":242,"text":2005},{"id":2105,"depth":242,"text":2106},{"id":1638,"depth":14,"text":1639},"2026-06-13 00:00:00 UTC","Продолжение темы резюме и крупные улучшения в CV.html","8 мин",{},"\u002Fposts\u002Fstop-using-html-for-your-resume",{"title":1720,"description":2215},{"loc":2218},"posts\u002Fstop-using-html-for-your-resume",[1671],"sD49shdqr640DIBITIiJBDqqdhL7iwKLvi3Ddb-d2ns",{"id":2225,"title":1738,"body":2226,"date":2914,"description":2915,"duration":1664,"extension":18,"meta":2916,"navigation":20,"path":1737,"robots":16,"seo":2917,"sitemap":2918,"stem":2919,"tags":2920,"toc":20,"__hash__":2921},"content_ru\u002Fposts\u002Fstop-using-word-for-your-resume.md",{"type":7,"value":2227,"toc":2899},[2228,2232,2235,2242,2245,2254,2265,2268,2273,2276,2279,2283,2289,2346,2350,2354,2364,2382,2385,2388,2419,2430,2433,2437,2440,2457,2460,2496,2499,2503,2506,2530,2533,2559,2570,2581,2585,2588,2590,2592,2599,2602,2606,2609,2649,2652,2660,2663,2667,2670,2801,2803,2807,2810,2813,2816,2822,2825,2864,2878,2885,2887,2890,2893,2896],[34,2229,2230],{"id":13},[37,2231],{"value":39},[41,2233],{":duration":43,":date":44,"className":2234},[46],[53,2236,2237,2238,2241],{},"Каждому проффесионалу нужно резюме. Это самый важный документ — именно он формирует первое ",[1692,2239,2240],{},"впечатление"," о вас у рекрутера.",[53,2243,2244],{},"Когда я начал работать в IT, самым простым и очевидным вариантом были сайты по поиску работы. Все популярные площадки предлагают форму, заполнив которую, появляется возможность скачать готовый документ любому пользователю.",[53,2246,2247,2248,2251,2252,104],{},"Но что если вы используете ",[1692,2249,2250],{},"несколько"," таких платформ? Поддерживать их быстро становится утомительно. Рано или поздно одно изменение в резюме превращается в серию одинаковых правок на разных сайтах. Если говорить на языке разработчиков — это нарушает принцип ",[135,2253,1832],{},[53,2255,2256,2257,2260,2261,2264],{},"Одно из решений — хранить резюме в одном редактируемом файле, обновлять его по мере необходимости, а затем синхронизировать со всеми платформами. Это не ",[1692,2258,2259],{},"полностью"," решает проблему — копировать и вставлять контент все равно приходится вручную — но создает ",[135,2262,2263],{},"единый источник истины",", из которого формируются все копии. К тому же экспорт резюме с таких платформ обычно выглядит одинаково и невзрачно, из-за чего их легко пролистать, не заметив.",[53,2266,2267],{},"Так я пришел к простому вопросу:",[106,2269,2270],{},[53,2271,2272],{},"В каком формате лучше всего создавать и хранить исходное резюме?",[53,2274,2275],{},"Я потратил на это куда больше времени, чем ожидал — и даже создал небольшой инструмент по ходу дела (об этом позже).",[2277,2278],"hr",{},[48,2280,2282],{"id":2281},"определяем-требования","Определяем требования",[53,2284,2285,2286,958],{},"Прежде чем сравнивать форматы, нужно определить, что именно мне важно. Вот мой список, упорядоченный по ",[135,2287,2288],{},"приоритету",[289,2290,2291,2301,2307,2313,2319,2325,2334,2340],{},[70,2292,2293,2296,2297,2300],{},[135,2294,2295],{},"Конвертация в PDF"," — PDF является универсальным конечным форматом для отправки и чтения резюме, поэтому исходный формат ",[135,2298,2299],{},"обязан"," легко в него конвертироваться.",[70,2302,2303,2306],{},[135,2304,2305],{},"Точный контроль над версткой"," — отступы, колонки, интервалы и выравнивание должны легко настраиваться.",[70,2308,2309,2312],{},[135,2310,2311],{},"Богатое и гибкое оформление"," — шрифты, цвета, размеры и визуальная иерархия должны применяться просто.",[70,2314,2315,2318],{},[135,2316,2317],{},"Удобство написания контента"," — писать и редактировать текст должно быть естественно, без борьбы с форматом.",[70,2320,2321,2324],{},[135,2322,2323],{},"Дружелюбность к LLM и ATS"," — LLM могут ускорить создание, изменение и проверку резюме. Совместимость с ATS менее критична, так как финальный результат — PDF, но если исходный формат тоже легко парсится — это плюс.",[70,2326,2327,2330,2331,104],{},[135,2328,2329],{},"Контроль версий"," — формат должен быть текстовым, чтобы изменения можно было отслеживать, в идеале с помощью ",[135,2332,2333],{},"Git",[70,2335,2336,2339],{},[135,2337,2338],{},"Портативность"," — никакой привязки к конкретному инструменту, редактору или платформе.",[70,2341,2342,2345],{},[135,2343,2344],{},"Разделение контента и стиля"," — чтобы можно было изменить внешний вид, не переписывая содержание и наоборот.",[48,2347,2349],{"id":2348},"оцениваем-варианты","Оцениваем варианты",[90,2351,2353],{"id":2352},"microsoft-word-docx","Microsoft Word (.docx)",[53,2355,2356,2357,2359,2360,2363],{},"Самый популярный ",[1692,2358,2180],{}," формат для резюме — безусловно ",[135,2361,2362],{},".docx",". Его сильные стороны:",[67,2365,2366,2371,2377],{},[70,2367,2368],{},[135,2369,2370],{},"Легко конвертируется в PDF",[70,2372,2373,2376],{},[135,2374,2375],{},"Богатое оформление"," — шрифты, цвета и т.д. меняются без проблем.",[70,2378,2379],{},[135,2380,2381],{},"Удобно писать контент",[53,2383,2384],{},"И... нет, это все.",[53,2386,2387],{},"Теперь слабые стороны:",[67,2389,2390,2396,2402,2408,2414],{},[70,2391,2392,2395],{},[135,2393,2394],{},"Контроль над версткой есть",", но ИМХО работать с ним неудобно.",[70,2397,2398,2401],{},[135,2399,2400],{},"Совершенно не дружелюбен к LLM"," из-за бинарного формата. Нельзя вставить сырое содержимое в LLM. LLM не могут генерировать .docx файлы. С другой стороны с ATS все отлично.",[70,2403,2404,2407],{},[135,2405,2406],{},"Контроль версий невозможен"," — бинарный формат означает бессмысленные диффы.",[70,2409,2410,2413],{},[135,2411,2412],{},"Не портативен",". Вы ограничены Word (платный, только десктоп), Google Docs или бесплатными редакторами, которые часто работают нестабильно.",[70,2415,2416,104],{},[135,2417,2418],{},"Разделение контента и стиля практически отсутствует",[53,2420,2421,2422,2425,2426,2429],{},"Итого: самый популярный формат для резюме удовлетворяет лишь ",[135,2423,2424],{},"3 из 8"," требований. То же самое относится к ",[135,2427,2428],{},".gdoc"," — по сути аналогу от Google.",[53,2431,2432],{},"Больше всего меня раздражает зависимость от сторонних продуктов и ограниченная, трудно настраиваемая верстка. Точно не мой вариант.",[90,2434,2436],{"id":2435},"latex-tex","LaTeX (.tex)",[53,2438,2439],{},"Формат для математиков. Сильные стороны:",[67,2441,2442,2447,2452],{},[70,2443,2444,104],{},[135,2445,2446],{},"Исключительно качественный PDF-вывод",[70,2448,2449,104],{},[135,2450,2451],{},"Точный контроль над отступами и версткой",[70,2453,2454,2456],{},[135,2455,2381],{}," (когда освоишь синтаксис).",[53,2458,2459],{},"С другой стороны:",[67,2461,2462,2472,2479,2485,2491],{},[70,2463,2464,2465,2468,2469,104],{},"Оформление мощное, но ",[135,2466,2467],{},"многословное и сложное в настройке"," — большинство LaTeX-резюме выглядят узнаваемо ",[1692,2470,2471],{},"научно",[70,2473,2474,2475,2478],{},"Хотя .tex файлы технически текстовые, громоздкий синтаксис ",[135,2476,2477],{},"усложняет работу с LLM и ATS"," по сравнению с более простыми форматами.",[70,2480,2481,2484],{},[135,2482,2483],{},"Контроль версий возможен",", но шумный синтаксис затрудняет чтение диффов.",[70,2486,2487,2490],{},[135,2488,2489],{},"Портативнее, чем .docx",", но все равно нужен дистрибутив TeX или специализированный редактор.",[70,2492,2493,104],{},[135,2494,2495],{},"Нет реального разделения контента и стиля",[53,2497,2498],{},"LaTeX лучше .docx, но все еще не набирает достаточно баллов. Не мой вариант.",[90,2500,2502],{"id":2501},"markdown-md","Markdown (.md)",[53,2504,2505],{},"Вот тут становится интересно. Сильные стороны:",[67,2507,2508,2513,2518,2524],{},[70,2509,2510,104],{},[135,2511,2512],{},"Очень удобно писать контент",[70,2514,2515,104],{},[135,2516,2517],{},"Полностью дружелюбен к LLM и ATS",[70,2519,2520,2523],{},[135,2521,2522],{},"Контроль версий элементарен"," (особенно с Git).",[70,2525,2526,2529],{},[135,2527,2528],{},"Максимально портативен"," — подойдет любой текстовый редактор.",[53,2531,2532],{},"Гораздо лучше! Но есть заметные пробелы:",[67,2534,2535,2541,2547,2553],{},[70,2536,2537,2540],{},[135,2538,2539],{},"Нет контроля над версткой"," — только структура документа.",[70,2542,2543,2546],{},[135,2544,2545],{},"Нет оформления"," — это просто текст.",[70,2548,2549,2552],{},[135,2550,2551],{},"Нечего разделять",", когда стилей нет.",[70,2554,2555,2558],{},[135,2556,2557],{},"Экспорт в PDF возможен",", но не тривиален.",[53,2560,2561,2562,2565,2566,2569],{},"Главная проблема в том, что Markdown — ",[135,2563,2564],{},"headless"," формат — ему нужен специальный ",[135,2567,2568],{},"тулчейн"," для превращения контента в финальный стилизованный PDF. Впрочем, большинство таких инструментов — с открытым исходным кодом.",[53,2571,2572,2573,2576,2577,2580],{},"Итого: вы ",[1692,2574,2575],{},"можете"," свободно редактировать содержание резюме, но ",[1692,2578,2579],{},"не можете"," управлять его внешним видом из коробки. Явный шаг вперед, но посмотрим другие альтернативы.",[90,2582,2584],{"id":2583},"json-yaml-структурированное-резюме","JSON \u002F YAML — структурированное резюме",[53,2586,2587],{},"Не буду задерживаться на этом — по сути та же история, что и с Markdown, только с еще меньшей поддержкой стилей и более простой структурой.",[2277,2589],{},[48,2591,1919],{"id":1918},[53,2593,2594,2595,2598],{},"Это самые распространенные форматы для резюме, и ни один из них полностью не соответствует моим потребностям. Мне нужен был полный контроль над контентом, стилями ",[1692,2596,2597],{},"и"," версткой. Если бы только существовал язык разметки, разделяющий структуру и стили, с точной версткой и чистым экспортом в PDF...",[53,2600,2601],{},"СТОП... и тут меня осенило!",[90,2603,2605],{"id":2604},"hypertext-markup-language-html","HyperText Markup Language (HTML)",[53,2607,2608],{},"Сразу к делу:",[67,2610,2611,2616,2622,2632,2638,2643],{},[70,2612,2613,2615],{},[135,2614,2370],{}," через печать из браузера или средства автоматизации.",[70,2617,2618,2621],{},[135,2619,2620],{},"Отличный контроль над версткой"," — Flexbox, Grid — все к вашим услугам.",[70,2623,2624,2627,2628,2631],{},[135,2625,2626],{},"Богатое"," — нет, ",[135,2629,2630],{},"БОГАТЕЙШЕЕ"," оформление со всей мощью CSS, включая ховеры и анимации (правда, не в PDF).",[70,2633,2634,2637],{},[135,2635,2636],{},"Полностью дружелюбен к LLM"," — можно легко генерировать и редактировать стилизованные резюме. ATS тоже зачастую справляется с парсингом, хотя слишком сложная разметка может вызвать проблемы.",[70,2639,2640,2642],{},[135,2641,2329],{}," так же прост, как с Markdown — это обычный текст.",[70,2644,2645,2648],{},[135,2646,2647],{},"Полностью портативен"," — и контент, и верстка путешествуют вместе с файлом.",[53,2650,2651],{},"Есть ли минусы? Конечно:",[67,2653,2654],{},[70,2655,2656,2659],{},[135,2657,2658],{},"Контент смешивается с разметкой"," — теги и стили живут рядом с текстом. Читать можно, но разделение не идеальное.",[53,2661,2662],{},"И... да, это все из очевидных минусов!",[90,2664,2666],{"id":2665},"скрытые-подводные-камни","Скрытые подводные камни",[53,2668,2669],{},"Поработав с HTML-резюме какое-то время, я столкнулся с несколькими менее очевидными проблемами:",[67,2671,2672,2678,2703],{},[70,2673,2674,2677],{},[135,2675,2676],{},"Нужно знать HTML и CSS",", чтобы писать резюме. Впрочем, LLM могут сгенерировать его за вас.",[70,2679,2680,2683,2684,2686,2687,2700,2702],{},[135,2681,2682],{},"Рендеринг в браузере отличается на разных ОС."," Один и тот же HTML может выглядеть по-разному на Windows и macOS.",[1913,2685],{},"Взгляните на два рендера одного и того же HTML-резюме на разных системах:",[2116,2688,2141,2690,2141,2695],{"className":2689},[2119,2120,2121,2122],[2125,2691],{"src":2692,"alt":2693,"caption":2694},"\u002Fimages\u002Fresume-macos.png","HTML резюме на macos","HTML на macOS",[2125,2696],{"src":2697,"alt":2698,"caption":2699},"\u002Fimages\u002Fresume-windows.png","HTML резюме на windows","Такой-же HTML на Windows",[1913,2701],{},"Шрифт выглядит иначе, хотя CSS-свойства идентичны. Я долго исследовал эту проблему, и, к сожалению, полностью ее решить нельзя. Хорошая новость — после рендера в PDF результат выглядит одинаково везде.",[70,2704,2705,2708,2709,2711,2712,2798,2800],{},[135,2706,2707],{},"Нет встроенных разрывов страниц."," Пагинацию приходится обрабатывать вручную.",[1913,2710],{},"CSS помогает с помощью свойств вроде:",[202,2713,2715],{"className":1514,"code":2714,"language":1516,"meta":13,"style":13},".page {\n  width: 210mm;\n  height: 297mm;\n}\n\n@media print {\n  .page {\n    break-after: page;\n  }\n}\n",[208,2716,2717,2724,2739,2753,2757,2761,2771,2778,2790,2794],{"__ignoreMap":13},[211,2718,2719,2722],{"class":213,"line":214},[211,2720,2721],{"class":344},".page",[211,2723,949],{"class":217},[211,2725,2726,2729,2731,2734,2737],{"class":213,"line":14},[211,2727,2728],{"class":223},"  width",[211,2730,508],{"class":217},[211,2732,2733],{"class":223},"210",[211,2735,2736],{"class":824},"mm",[211,2738,1556],{"class":217},[211,2740,2741,2744,2746,2749,2751],{"class":213,"line":242},[211,2742,2743],{"class":223},"  height",[211,2745,508],{"class":217},[211,2747,2748],{"class":223},"297",[211,2750,2736],{"class":824},[211,2752,1556],{"class":217},[211,2754,2755],{"class":213,"line":256},[211,2756,259],{"class":217},[211,2758,2759],{"class":213,"line":532},[211,2760,839],{"emptyLinePlaceholder":20},[211,2762,2763,2766,2769],{"class":213,"line":543},[211,2764,2765],{"class":824},"@media",[211,2767,2768],{"class":223}," print",[211,2770,949],{"class":217},[211,2772,2773,2776],{"class":213,"line":551},[211,2774,2775],{"class":344},"  .page",[211,2777,949],{"class":217},[211,2779,2780,2783,2785,2788],{"class":213,"line":559},[211,2781,2782],{"class":223},"    break-after",[211,2784,508],{"class":217},[211,2786,2787],{"class":223},"page",[211,2789,1556],{"class":217},[211,2791,2792],{"class":213,"line":567},[211,2793,1368],{"class":217},[211,2795,2796],{"class":213,"line":578},[211,2797,259],{"class":217},[1913,2799],{},"Они фиксируют размеры под реальную печатную страницу. Но в сочетании с различиями рендеринга между ОС разрывы страниц могут сдвигаться при редактировании на разных устройствах — неприятный источник неконсистентности.",[2277,2802],{},[48,2804,2806],{"id":2805},"создание-инструмента","Создание инструмента",[53,2808,2809],{},"Когда я остановился на HTML, я начал искать подходящий инструмент. Не может быть, чтобы я первый додумался до такого.",[53,2811,2812],{},"Но ничего подходящего не нашлось. Я не смог найти инструмент, дающий прямой полный контроль над HTML-резюме. Ближайшие варианты — конструкторы с формами, куда загружаешь данные и выбираешь шаблон, — но они не бесплатны, и не дают сырой редактируемый файл.",[53,2814,2815],{},"Другой очевидный подход — редактировать HTML прямо в VS Code, но постоянно переключаться на браузер утомительно. Разрывы страниц видны только в режиме предварительного просмотра печати, и экспортировать приходится каждый раз вручную.",[53,2817,2818,2819,104],{},"Поэтому я решил создать инструмент сам. Я назвал его ",[2169,2820],{"className":2821},[2172],[53,2823,2824],{},"Это Monaco редактор с превью в реальном времени, оснащенный различными функциями:",[67,2826,2827,2832,2838,2844,2849,2859],{},[70,2828,2829],{},[135,2830,2831],{},"Экспорт в PDF",[70,2833,2834,2837],{},[135,2835,2836],{},"Импорт \u002F Экспорт HTML"," — свободное сохранение, загрузка и редактирование резюме",[70,2839,2840,2843],{},[135,2841,2842],{},"Поддержка TailwindCSS"," (да, серьезно!)",[70,2845,2846],{},[135,2847,2848],{},"Автоформатирование с Prettier",[70,2850,2851,2854,2855,2858],{},[135,2852,2853],{},"Полезные мелочи"," — контур страницы A4, утилитарные стили, настраиваемый ",[208,2856,2857],{},"\u003Chead>"," для шрифтов и многое другое",[70,2860,2861],{},[135,2862,2863],{},"Полностью бесплатный и с открытым исходным кодом",[53,2865,2866,2867,2871,2872,2877],{},"Если вы дочитали до этого места и вам интересно — можете прямо зайти на ",[98,2868,2870],{"href":1801,"rel":2869},[102],"хост"," или загляните в ",[98,2873,2876],{"href":2874,"rel":2875},"https:\u002F\u002Fgithub.com\u002Frastuhacode\u002Fcv.html",[102],"GitHub",". Буду рад, если окажется полезным, и благодарен за любой фидбек!",[53,2879,2880,2881,2884],{},"Есть еще много возможностей для улучшения. Например, замена текста не так проста, потому что теги ",[208,2882,2883],{},"\u003Cspan>"," и прочая разметка перемешиваются с контентом. Я планирую разделить слои шаблона и данных в будущем.",[48,2886,1639],{"id":1638},[53,2888,2889],{},"Многие разработчики хранят резюме в Markdown, LaTeX или .docx, но HTML предлагает на удивление мощную альтернативу.",[53,2891,2892],{},"Он не идеален, но для моих потребностей оказался лучшим вариантом — обеспечивая непревзойденный контроль над контентом, оформлением и версткой в портативном, версионируемом формате. Подумайте, возможно он подойдет и Вам.",[53,2894,2895],{},"Надеюсь, Вы узнали что-то новое для себя, эта статья оказалась полезной или Вы просто хорошо провели время! До встречи в следующей статье!",[1644,2897,2898],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":13,"searchDepth":14,"depth":14,"links":2900},[2901,2902,2908,2912,2913],{"id":2281,"depth":14,"text":2282},{"id":2348,"depth":14,"text":2349,"children":2903},[2904,2905,2906,2907],{"id":2352,"depth":242,"text":2353},{"id":2435,"depth":242,"text":2436},{"id":2501,"depth":242,"text":2502},{"id":2583,"depth":242,"text":2584},{"id":1918,"depth":14,"text":1919,"children":2909},[2910,2911],{"id":2604,"depth":242,"text":2605},{"id":2665,"depth":242,"text":2666},{"id":2805,"depth":14,"text":2806},{"id":1638,"depth":14,"text":1639},"2026-03-20 00:00:00 UTC","Практическое сравнение форматов для резюме",{},{"title":1738,"description":2915},{"loc":1737},"posts\u002Fstop-using-word-for-your-resume",[1671],"QAh-kcJtbMzhOj77I1ysM17eTlv3jVxM0BaO7uC8fLQ",{"id":2923,"title":2924,"body":2925,"date":3113,"description":3114,"duration":3115,"extension":18,"meta":3116,"navigation":20,"path":3117,"robots":16,"seo":3118,"sitemap":3119,"stem":3120,"tags":3121,"toc":25,"__hash__":3122},"content_ru\u002Fposts\u002Fyou-should-start-contribute-to-open-source.md","Тебе стоит начать контрибьютить в open-source",{"type":7,"value":2926},[2927,2931,2934,2941,2944,2963,2965,2969,2972,2975,2979,2985,2988,3014,3017,3021,3028,3031,3057,3060,3063,3067,3076,3092,3101,3103,3106],[34,2928,2929],{"id":13},[37,2930],{"value":39},[41,2932],{":duration":43,":date":44,"className":2933},[46],[53,2935,2936,2937,2940],{},"Если ты хороший разработчик, но ищешь возможность развиваться дальше, ",[135,2938,2939],{},"стоит начать контрибьютить в open-source",". И причина не только в пользе для сообщества — это в первую очередь выгодно лично тебе.",[53,2942,2943],{},"Я долгое время хотел контрибьютить, но не мог начать, одновременно недооценивая пользу и переоценивая сложность этого процесса. В этой статье я хочу затронуть обе эти темы и, возможно, поменять твой взгляд на OSS.",[53,2945,2946,2947,2952,2953,150,2958,2045],{},"С гордостью скажу, что с начала 2026 года я уже сделал 10+ контрибуций и даже попал в топ-30 контрибьюторов ",[98,2948,2951],{"href":2949,"rel":2950},"https:\u002F\u002Freka-ui.com\u002F",[102],"Reka UI"," — библиотеки, которая лежит в основе ",[98,2954,2957],{"href":2955,"rel":2956},"https:\u002F\u002Fui.nuxt.com\u002F",[102],"NuxtUI",[98,2959,2962],{"href":2960,"rel":2961},"https:\u002F\u002Fwww.shadcn-vue.com\u002F",[102],"shadcn-vue",[2277,2964],{},[48,2966,2968],{"id":2967},"барьер","Барьер",[53,2970,2971],{},"Долгое время идея контрибьютить казалась чем-то недостижимым. Мейнтейнеры — умные, опытные профессионалы, что я вообще могу им предложить? Вдобавок — после работы оставался свободным только вечер, в России контрибьютить не принято, никто вокруг этого не делал, а сам процесс был загадкой. Нужно просто взять открытый issue? Перекопать весь проект в поисках багов?",[53,2973,2974],{},"Я перепробовал разные подходы и нашел тот, который сработал для меня. Хочу им поделиться.",[48,2976,2978],{"id":2977},"подход","Подход",[53,2980,2981,2982,104],{},"Ключевая идея оказалась одновременно простой и неочевидной: ",[135,2983,2984],{},"перестать фиксить баги сторонних библиотек в своем приложении",[53,2986,2987],{},"Вот подход, который помог мне понять, с чего стоит начать:",[289,2989,2990,2996,3002,3008],{},[70,2991,2992,2995],{},[135,2993,2994],{},"Разделяй баги по уровням."," Когда что-то ломается, спроси себя: это баг в твоем коде или в зависимости? Звучит очевидно, но вспомни, сколько раз ты писал костыли в своем приложении, хотя проблема была извне.",[70,2997,2998,3001],{},[135,2999,3000],{},"Читай исходники."," Если баг из сторонней библиотеки — открой исходный код и найди причину. Большинство багов найти проще, чем кажется.",[70,3003,3004,3007],{},[135,3005,3006],{},"Сначала реши, потом спрашивай."," Найди фикс, и только ПОТОМ открой issue с предложением сделать PR. Для меня этот порядок был ключевым — был страх взять на себя ответственность и оказаться недостаточно компетентным для решения проблемы. Когда уже нашел решение, шагнуть вперед гораздо легче.",[70,3009,3010,3013],{},[135,3011,3012],{},"Защищай свои дедлайны."," Если переживаешь за сроки на работе (ведь ты не контролируешь, когда смержат твой PR) — сделай костыль в своем коде и добавь TODO со ссылкой на issue, чтобы отслеживать фикс в апстриме.",[53,3015,3016],{},"Первый вклад не обязан быть масштабным. Недостающий перевод, неэкспортированный тип, опечатка в документации — этого достаточно, чтобы познакомиться с процессом: форки, ветки, создание PR, ответы на ревью и т.д.",[48,3018,3020],{"id":3019},"выгода","Выгода",[53,3022,3023,3024,3027],{},"Так почему я считаю, что ",[135,3025,3026],{},"стоит контрибьютить","?",[53,3029,3030],{},"Как я уже говорил — это один из редких случаев, когда я недооценил пользу, которую получил. Каждый вклад приносит ценность сразу по трем направлениям:",[289,3032,3033,3039,3045],{},[70,3034,3035,3038],{},[135,3036,3037],{},"Твой продукт."," Баг, который ты починил в апстриме? Теперь он исправлен и в твоем проекте — нормально, а не через костыль. Кодовая база становится чище, и ты перестаешь тащить технический долг, который не твой.",[70,3040,3041,3044],{},[135,3042,3043],{},"Сообщество."," Каждый фикс, каждое улучшение, каждый перевод помогает тысячам разработчиков, которые зависят от той же библиотеки. Один твой PR может спасти сотни людей от написания того же костыля, что и у тебя.",[70,3046,3047,3050,3051,3056],{},[135,3048,3049],{},"Ты сам."," Ты ",[98,3052,3055],{"href":3053,"rel":3054},"https:\u002F\u002Fmedium.com\u002F@mohitsingh3406\u002Fwhy-reading-code-is-more-important-than-writing-it-30c01a489375",[102],"читаешь"," код, написанный опытными разработчиками. Получаешь ревью от людей, которые мыслят иначе, чем ты или твоя команда. Учишься писать код, понятный незнакомым людям, общаться в PR-ах, думать о крайних случаях, которых нет в твоем проекте.",[53,3058,3059],{},"Кроме того, контрибьют может стать отличным инструментом для нетворкинга и источником карьерных возможностей. Хотя прямое влияние контрибьютов на офферы переоценено (особенно в РФ) — нетворк, который можно через них построить, точно нет.",[53,3061,3062],{},"Все это — именно те качества, которые отличают хороших разработчиков от профессионалов. Вспомни любого разработчика, которым ты восхищаешься — скорее всего, он контрибьютит в open-source. Я думаю, это не совпадение — вклад в OSS не следствие того, что ты профессионал, а один из ключевых шагов на пути к этому.",[48,3064,3066],{"id":3065},"спорный-момент","Спорный момент",[53,3068,3069,3070,3075],{},"Есть и противоположная позиция, которая хорошо объясняется здесь: ",[98,3071,3074],{"href":3072,"rel":3073},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=5nY_cy8zcO4",[102],"Don't Contribute to Open Source",". Можно заметить, что название моей статьи — прямая противоположность названия видео, и это сделано намеренно. Именно такая позиция долгое время удерживала меня от того, чтобы попробовать контрибьютить и расти профессионально.",[53,3077,3078,3079,3082,3083,1995,3088,3091],{},"Сейчас я скорее не согласен с этой позицией, но все же ",[135,3080,3081],{},"настоятельно"," рекомендую посмотреть это видео, потому что полностью согласен: ситуации вроде ",[98,3084,3087],{"href":3085,"rel":3086},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=YFkeOBqfQBw",[102],"спама в README Node.js",[135,3089,3090],{},"НЕ ДОЛЖНЫ"," происходить. Я также понимаю причины гейткипинга, но считаю, что он должен быть сбалансированным и не слишком агрессивным.",[53,3093,3094,3095,3097,3098,104],{},"Скажу прямо — хотя тебе ",[135,3096,2939],{},", ты также ",[135,3099,3100],{},"обязан быть уважительным и ответственным за свои действия",[48,3102,1639],{"id":1638},[53,3104,3105],{},"Если ты думал о том, чтобы начать контрибьютить, но так этого и не сделал — тебе определенно стоит попробовать — это может стать и точкой роста, и ценным опытом. Что касается меня — я точно продолжу делать вклады в OSS при любой возможности и наличии свободного времени.",[53,3107,3108,3109,104],{},"Собственно, поэтому если хочешь расти как профессионал, тебе ",[3110,3111,2939],"prose-a",{"target":13,"href":3112},"\u002Fru\u002Fposts\u002Fyou-should-start-contribute-to-open-source","2026-04-03 00:00:00 UTC","Как я начал контрибьютить в OSS — и почему тебе тоже стоит","5 мин",{},"\u002Fposts\u002Fyou-should-start-contribute-to-open-source",{"title":2924,"description":3114},{"loc":3117},"posts\u002Fyou-should-start-contribute-to-open-source",[1671],"ZwCZVdLD4mBVxR_3rlRsUnesKfyomVhs34fOT5WO-Ko",1784559755913]