Перейти к основному содержимому

Astro Clean Template

Главная цель этого шаблона — получить на выходе чистый, читаемый и готовый к передаче код. Полезно для работы с CMS. Без зашифрованных имён классов, склеенных в нечитаемое месиво скриптов и бесконечных хешей в названиях файлов.

Установка

Развернуть можно через мой инструмент Stack:

npx @davidaganov/stack

Проблема классической сборки

Часто бывает так, что фронтендеру нужно сделать не полноценное SPA со сложной логикой, а просто качественно сверстать сайт (или набор страниц), который потом передадут для интеграции с CMS (Bitrix, WordPress или любой самопис).

Если взять для этой задачи привычный стек (Vue/Nuxt/React/Next), на выходе вы получите папку dist, с которой будет невозможно работать:

  • Непредсказуемые хеши в именах файлов: main.abc12def.js
  • Склеенные стили, которые сложно разделить или точечно переопределить
  • JSX/SFC-компоненты, которые нельзя просто открыть в браузере и посмотреть

Разработчик открывает результат и не понимает, где именно лежит нужный ему кусок логики, потому что всё собрано в один минифицированный файл. С другой стороны — писать всё на голом HTML в 2026 году или тащить в проект Gulp — это отдельный вид извращения. Хочется иметь компонентный подход, переиспользуемые лейауты и Scoped стили.

Решение

Astro Clean Template решает эту проблему. Я взял Astro как идеальный инструмент для авторинга (чтобы писать компоненты и лейауты с комфортом), но переписал процесс сборки так, чтобы на выходе получалась классическая, "олдскульная" структура файлов.

dist/index.html
<!DOCTYPE html>
<html lang="ru">
  <head>
    <link
      rel="stylesheet"
      href="/assets/style/main.css"
    />
  </head>
  <body>
    ...
    <script
      is:inline
      type="module"
      src="/assets/script/main.js"
    ></script>
  </body>
</html>

Как это работает под капотом

Один CSS-файл без препроцессоров

Все стили агрегируются в один dist/assets/style/main.css. Внутри используется нативный CSS с @import и встроенным в браузеры нестингом.

src/assets/styles/main.css
@import "./reset.css";
@import "./variables.css";

.container {
  margin-inline: auto;
  max-width: var(--max-width);

  @media (max-width: 640px) {
    padding-inline: 16px;
  }
}

Стабильные пути к скриптам

Vite по умолчанию пытается собрать и забандлить все ассеты. Я обошел это: скрипты не собираются в общий бандл. Лейаут ссылается на файл как обычно:

src/layouts/Layout.astro
<script is:inline type="module" src="/assets/script/main.js"></script>

В режиме build файлы из src/assets/script/ просто "как есть" копируются в dist/. Это сохраняет структуру модулей, оставляя код удобным для дальнейшей работы. Никаких хешей и минификации.

Режимы сборки

КомандаЧто происходит
npm run buildСборка для передачи. CSS/JS не минифицируются, HTML не сжимается. Идеально для отладки и интеграции.
npm run build:prodСборка в продакшн. Всё минифицировано, а main.js бандлится через esbuild в один компактный файл, импорты разрешаются.

Итог

Этот шаблон во многом заменяет тот самый знакомый многим "Gulp + PUG/EJS", объединяя современные технологии для авторинга (Astro, Vite) с максимально предсказуемым и чистым результатом на выходе сборки.

Если в проекте HTML/CSS/JS — это конечный продукт, а не промежуточный артефакт, — шаблон сэкономит вам массу времени.

\n \n\n","html",[176,299,300,317,339,350,359,375,390,396,406,416,423,431,437,452,467,478,487],{"__ignoreMap":174},[179,301,302,306,310,314],{"class":181,"line":124},[179,303,305],{"class":304},"sMK4o","\n",[179,318,319,322,324,327,330,333,335,337],{"class":181,"line":6},[179,320,321],{"class":304},"<",[179,323,297],{"class":308},[179,325,326],{"class":312}," lang",[179,328,329],{"class":304},"=",[179,331,332],{"class":304},"\"",[179,334,22],{"class":188},[179,336,332],{"class":304},[179,338,316],{"class":304},[179,340,342,345,348],{"class":181,"line":341},3,[179,343,344],{"class":304}," <",[179,346,347],{"class":308},"head",[179,349,316],{"class":304},[179,351,353,356],{"class":181,"line":352},4,[179,354,355],{"class":304}," <",[179,357,358],{"class":308},"link\n",[179,360,362,365,367,369,372],{"class":181,"line":361},5,[179,363,364],{"class":312}," rel",[179,366,329],{"class":304},[179,368,332],{"class":304},[179,370,371],{"class":188},"stylesheet",[179,373,374],{"class":304},"\"\n",[179,376,378,381,383,385,388],{"class":181,"line":377},6,[179,379,380],{"class":312}," href",[179,382,329],{"class":304},[179,384,332],{"class":304},[179,386,387],{"class":188},"/assets/style/main.css",[179,389,374],{"class":304},[179,391,393],{"class":181,"line":392},7,[179,394,395],{"class":304}," />\n",[179,397,399,402,404],{"class":181,"line":398},8,[179,400,401],{"class":304}," {\n initSlider()\n})\n","dist/assets/script/main.js","js",[176,564,565,591,597,629,637],{"__ignoreMap":174},[179,566,567,571,574,577,580,583,586,589],{"class":181,"line":124},[179,568,570],{"class":569},"s7zQu","import",[179,572,573],{"class":304}," {",[179,575,576],{"class":421}," initSlider",[179,578,579],{"class":304}," }",[179,581,582],{"class":569}," from",[179,584,585],{"class":304}," \"",[179,587,588],{"class":188},"./modules/slider.js",[179,590,374],{"class":304},[179,592,593],{"class":181,"line":6},[179,594,596],{"emptyLinePlaceholder":595},true,"\n",[179,598,599,602,604,608,611,613,616,618,621,624,627],{"class":181,"line":341},[179,600,601],{"class":421},"document",[179,603,515],{"class":304},[179,605,607],{"class":606},"s2Zo4","addEventListener",[179,609,610],{"class":421},"(",[179,612,332],{"class":304},[179,614,615],{"class":188},"DOMContentLoaded",[179,617,332],{"class":304},[179,619,620],{"class":304},",",[179,622,623],{"class":304}," ()",[179,625,626],{"class":312}," =>",[179,628,521],{"class":304},[179,630,631,634],{"class":181,"line":352},[179,632,633],{"class":606}," initSlider",[179,635,636],{"class":308},"()\n",[179,638,639,642],{"class":181,"line":361},[179,640,641],{"class":304},"}",[179,643,644],{"class":421},")\n",[152,646,648],{"id":647},"как-это-работает-под-капотом","Как это работает под капотом",[650,651,653],"h3",{"id":652},"один-css-файл-без-препроцессоров","Один CSS-файл без препроцессоров",[143,655,656,657,659,660,663],{},"Все стили агрегируются в один ",[176,658,501],{},". Внутри используется нативный CSS с ",[176,661,662],{},"@import"," и встроенным в браузеры нестингом.",[168,665,668],{"className":499,"code":666,"filename":667,"language":502,"meta":174,"style":174},"@import \"./reset.css\";\n@import \"./variables.css\";\n\n.container {\n margin-inline: auto;\n max-width: var(--max-width);\n\n @media (max-width: 640px) {\n padding-inline: 16px;\n }\n}\n","src/assets/styles/main.css",[176,669,670,683,696,700,708,719,736,740,756,768,773],{"__ignoreMap":174},[179,671,672,674,676,679,681],{"class":181,"line":124},[179,673,662],{"class":569},[179,675,585],{"class":304},[179,677,678],{"class":188},"./reset.css",[179,680,332],{"class":304},[179,682,536],{"class":304},[179,684,685,687,689,692,694],{"class":181,"line":6},[179,686,662],{"class":569},[179,688,585],{"class":304},[179,690,691],{"class":188},"./variables.css",[179,693,332],{"class":304},[179,695,536],{"class":304},[179,697,698],{"class":181,"line":341},[179,699,596],{"emptyLinePlaceholder":595},[179,701,702,704,706],{"class":181,"line":352},[179,703,515],{"class":304},[179,705,518],{"class":184},[179,707,521],{"class":304},[179,709,710,713,715,717],{"class":181,"line":361},[179,711,712],{"class":526}," margin-inline",[179,714,162],{"class":304},[179,716,549],{"class":421},[179,718,536],{"class":304},[179,720,721,723,725,728,730,733],{"class":181,"line":377},[179,722,527],{"class":526},[179,724,162],{"class":304},[179,726,727],{"class":606}," var",[179,729,610],{"class":304},[179,731,732],{"class":421},"--max-width",[179,734,735],{"class":304},");\n",[179,737,738],{"class":181,"line":392},[179,739,596],{"emptyLinePlaceholder":595},[179,741,742,745,748,750,753],{"class":181,"line":398},[179,743,744],{"class":421}," @media (",[179,746,747],{"class":526},"max-width",[179,749,162],{"class":304},[179,751,752],{"class":532}," 640px",[179,754,755],{"class":421},") {\n",[179,757,758,761,763,766],{"class":181,"line":408},[179,759,760],{"class":526}," padding-inline",[179,762,162],{"class":304},[179,764,765],{"class":532}," 16px",[179,767,536],{"class":304},[179,769,770],{"class":181,"line":418},[179,771,772],{"class":304}," }\n",[179,774,775],{"class":181,"line":425},[179,776,556],{"class":421},[650,778,780],{"id":779},"стабильные-пути-к-скриптам","Стабильные пути к скриптам",[143,782,783],{},"Vite по умолчанию пытается собрать и забандлить все ассеты. Я обошел это: скрипты не собираются в общий бандл. Лейаут ссылается на файл как обычно:",[168,785,789],{"className":786,"code":787,"filename":788,"language":67,"meta":174,"style":174},"language-astro shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\n","src/layouts/Layout.astro",[176,790,791],{"__ignoreMap":174},[179,792,793],{"class":181,"line":124},[179,794,787],{},[143,796,797,798,801,802,805,806,809],{},"В режиме ",[176,799,800],{},"build"," файлы из ",[176,803,804],{},"src/assets/script/"," просто \"как есть\" копируются в ",[176,807,808],{},"dist/",". Это сохраняет структуру модулей, оставляя код удобным для дальнейшей работы. Никаких хешей и минификации.",[152,811,813],{"id":812},"режимы-сборки","Режимы сборки",[815,816,817,830],"table",{},[818,819,820],"thead",{},[821,822,823,827],"tr",{},[824,825,826],"th",{},"Команда",[824,828,829],{},"Что происходит",[831,832,833,848],"tbody",{},[821,834,835,841],{},[836,837,838],"td",{},[176,839,840],{},"npm run build",[836,842,843,844,847],{},"Сборка ",[147,845,846],{},"для передачи",". CSS/JS не минифицируются, HTML не сжимается. Идеально для отладки и интеграции.",[821,849,850,855],{},[836,851,852],{},[176,853,854],{},"npm run build:prod",[836,856,843,857,860,861,864,865,868],{},[147,858,859],{},"в продакшн",". Всё минифицировано, а ",[176,862,863],{},"main.js"," бандлится через ",[176,866,867],{},"esbuild"," в один компактный файл, импорты разрешаются.",[152,870,872],{"id":871},"итог","Итог",[143,874,875],{},"Этот шаблон во многом заменяет тот самый знакомый многим \"Gulp + PUG/EJS\", объединяя современные технологии для авторинга (Astro, Vite) с максимально предсказуемым и чистым результатом на выходе сборки.",[143,877,878,879,882],{},"Если в проекте HTML/CSS/JS — это ",[147,880,881],{},"конечный продукт",", а не промежуточный артефакт, — шаблон сэкономит вам массу времени.",[884,885,886],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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 .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sqsOY, html code.shiki .sqsOY{--shiki-light:#8796B0;--shiki-default:#B2CCD6;--shiki-dark:#B2CCD6}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}",{"title":174,"searchDepth":6,"depth":6,"links":888},[889,890,891,892,896,897],{"id":154,"depth":6,"text":155},{"id":234,"depth":6,"text":235},{"id":276,"depth":6,"text":277},{"id":647,"depth":6,"text":648,"children":893},[894,895],{"id":652,"depth":341,"text":653},{"id":779,"depth":341,"text":780},{"id":812,"depth":6,"text":813},{"id":871,"depth":6,"text":872},"md",{"icon":62,"githubRepo":63,"githubUrl":64,"publishedAt":65,"tags":900},[67,24,68],{"title":59,"description":60},"guides/templates/astro-clean-template","SNBcQ-1E9CB-GIrnkU2Yr605zM22rMOWHroqZ0lsDFQ",[905,908,911,914,917,920,923,926,929],{"title":17,"meta":906,"path":27},{"icon":20,"publishedAt":21,"languageOriginal":22,"tags":907},[24,25,26],{"title":29,"meta":909,"path":37},{"icon":32,"habrUrl":33,"publishedAt":34,"languageOriginal":22,"tags":910},[36,24],{"title":39,"meta":912,"path":46},{"icon":42,"publishedAt":43,"languageOriginal":22,"tags":913},[24,26,45],{"title":48,"meta":915,"path":56},{"icon":32,"habrUrl":51,"publishedAt":52,"languageOriginal":22,"tags":916},[54,55],{"title":59,"meta":918,"path":69},{"icon":62,"githubRepo":63,"githubUrl":64,"publishedAt":65,"tags":919},[67,24,68],{"title":71,"meta":921,"path":79},{"icon":74,"githubRepo":75,"githubUrl":76,"publishedAt":77,"tags":922},[36,24,68],{"title":81,"meta":924,"path":92},{"icon":84,"githubRepo":85,"githubUrl":86,"publishedAt":87,"tags":925},[89,90,91,24,68],{"title":94,"meta":927,"path":101},{"icon":97,"githubRepo":98,"githubUrl":99,"publishedAt":87,"tags":928},[89,24],{"title":103,"meta":930,"path":111},{"icon":106,"githubRepo":107,"githubUrl":108,"publishedAt":87,"tags":931},[89,110,24,68],["Reactive",933],{"$scolor-mode":934,"$si18n:cached-locale-configs":937,"$si18n:resolved-locale":174,"$snuxt-seo-utils:routeRules":942,"$stoasts":943,"$svisitor-had-baseline":936,"$snav-highlight-ack-map":944,"$snav-highlight-pages":945,"$ssite-config":946},{"preference":935,"value":935,"unknown":595,"forced":936},"dark",false,{"ru":938,"en":940},{"fallbacks":939,"cacheable":595},[],{"fallbacks":941,"cacheable":595},[],{"head":-1,"seoMeta":-1},[],{},[],{"_priority":947,"currentLocale":22,"defaultLocale":22,"env":952,"name":953,"url":954},{"env":948,"url":949,"name":950,"defaultLocale":951,"currentLocale":951},-15,0,-3,-2,"production","Aganov.dev","https://aganov.dev",["Set"],["ShallowReactive",957],{"nav-highlights:content_ru":-1,"sidebar:section-root:content_ru:guides":-1,"github-stars:davidaganov/davidaganov.github.io":-1,"site-nav:all-sections:content_ru":-1,"sidebar:collection:articles:/docs/guides/articles:content_ru":-1,"sidebar:collection:templates:/docs/guides/templates:content_ru":-1,"content-page:content_ru:/docs/guides/templates/astro-clean-template":-1,"docs-search:ru:":-1,"top-projects:content_ru":-1,"docs-flat-nav:content_ru:guides":-1},"/docs/guides/templates/astro-clean-template",["Reactive",960],{}]