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

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",[173,296,297,314,336,347,356,372,387,393,403,413,420,428,434,449,464,475,484],{"__ignoreMap":171},[176,298,299,303,307,311],{"class":178,"line":6},[176,300,302],{"class":301},"sMK4o","\n",[176,315,316,319,321,324,327,330,332,334],{"class":178,"line":120},[176,317,318],{"class":301},"<",[176,320,294],{"class":305},[176,322,323],{"class":309}," lang",[176,325,326],{"class":301},"=",[176,328,329],{"class":301},"\"",[176,331,22],{"class":185},[176,333,329],{"class":301},[176,335,313],{"class":301},[176,337,339,342,345],{"class":178,"line":338},3,[176,340,341],{"class":301}," <",[176,343,344],{"class":305},"head",[176,346,313],{"class":301},[176,348,350,353],{"class":178,"line":349},4,[176,351,352],{"class":301}," <",[176,354,355],{"class":305},"link\n",[176,357,359,362,364,366,369],{"class":178,"line":358},5,[176,360,361],{"class":309}," rel",[176,363,326],{"class":301},[176,365,329],{"class":301},[176,367,368],{"class":185},"stylesheet",[176,370,371],{"class":301},"\"\n",[176,373,375,378,380,382,385],{"class":178,"line":374},6,[176,376,377],{"class":309}," href",[176,379,326],{"class":301},[176,381,329],{"class":301},[176,383,384],{"class":185},"/assets/style/main.css",[176,386,371],{"class":301},[176,388,390],{"class":178,"line":389},7,[176,391,392],{"class":301}," />\n",[176,394,396,399,401],{"class":178,"line":395},8,[176,397,398],{"class":301}," {\n initSlider()\n})\n","dist/assets/script/main.js","js",[173,561,562,588,594,626,634],{"__ignoreMap":171},[176,563,564,568,571,574,577,580,583,586],{"class":178,"line":6},[176,565,567],{"class":566},"s7zQu","import",[176,569,570],{"class":301}," {",[176,572,573],{"class":418}," initSlider",[176,575,576],{"class":301}," }",[176,578,579],{"class":566}," from",[176,581,582],{"class":301}," \"",[176,584,585],{"class":185},"./modules/slider.js",[176,587,371],{"class":301},[176,589,590],{"class":178,"line":120},[176,591,593],{"emptyLinePlaceholder":592},true,"\n",[176,595,596,599,601,605,608,610,613,615,618,621,624],{"class":178,"line":338},[176,597,598],{"class":418},"document",[176,600,512],{"class":301},[176,602,604],{"class":603},"s2Zo4","addEventListener",[176,606,607],{"class":418},"(",[176,609,329],{"class":301},[176,611,612],{"class":185},"DOMContentLoaded",[176,614,329],{"class":301},[176,616,617],{"class":301},",",[176,619,620],{"class":301}," ()",[176,622,623],{"class":309}," =>",[176,625,518],{"class":301},[176,627,628,631],{"class":178,"line":349},[176,629,630],{"class":603}," initSlider",[176,632,633],{"class":305},"()\n",[176,635,636,639],{"class":178,"line":358},[176,637,638],{"class":301},"}",[176,640,641],{"class":418},")\n",[149,643,645],{"id":644},"как-это-работает-под-капотом","Как это работает под капотом",[647,648,650],"h3",{"id":649},"один-css-файл-без-препроцессоров","Один CSS-файл без препроцессоров",[140,652,653,654,656,657,660],{},"Все стили агрегируются в один ",[173,655,498],{},". Внутри используется нативный CSS с ",[173,658,659],{},"@import"," и встроенным в браузеры нестингом.",[165,662,665],{"className":496,"code":663,"filename":664,"language":499,"meta":171,"style":171},"@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",[173,666,667,680,693,697,705,716,733,737,753,765,770],{"__ignoreMap":171},[176,668,669,671,673,676,678],{"class":178,"line":6},[176,670,659],{"class":566},[176,672,582],{"class":301},[176,674,675],{"class":185},"./reset.css",[176,677,329],{"class":301},[176,679,533],{"class":301},[176,681,682,684,686,689,691],{"class":178,"line":120},[176,683,659],{"class":566},[176,685,582],{"class":301},[176,687,688],{"class":185},"./variables.css",[176,690,329],{"class":301},[176,692,533],{"class":301},[176,694,695],{"class":178,"line":338},[176,696,593],{"emptyLinePlaceholder":592},[176,698,699,701,703],{"class":178,"line":349},[176,700,512],{"class":301},[176,702,515],{"class":181},[176,704,518],{"class":301},[176,706,707,710,712,714],{"class":178,"line":358},[176,708,709],{"class":523}," margin-inline",[176,711,159],{"class":301},[176,713,546],{"class":418},[176,715,533],{"class":301},[176,717,718,720,722,725,727,730],{"class":178,"line":374},[176,719,524],{"class":523},[176,721,159],{"class":301},[176,723,724],{"class":603}," var",[176,726,607],{"class":301},[176,728,729],{"class":418},"--max-width",[176,731,732],{"class":301},");\n",[176,734,735],{"class":178,"line":389},[176,736,593],{"emptyLinePlaceholder":592},[176,738,739,742,745,747,750],{"class":178,"line":395},[176,740,741],{"class":418}," @media (",[176,743,744],{"class":523},"max-width",[176,746,159],{"class":301},[176,748,749],{"class":529}," 640px",[176,751,752],{"class":418},") {\n",[176,754,755,758,760,763],{"class":178,"line":405},[176,756,757],{"class":523}," padding-inline",[176,759,159],{"class":301},[176,761,762],{"class":529}," 16px",[176,764,533],{"class":301},[176,766,767],{"class":178,"line":415},[176,768,769],{"class":301}," }\n",[176,771,772],{"class":178,"line":422},[176,773,553],{"class":418},[647,775,777],{"id":776},"стабильные-пути-к-скриптам","Стабильные пути к скриптам",[140,779,780],{},"Vite по умолчанию пытается собрать и забандлить все ассеты. Я обошел это: скрипты не собираются в общий бандл. Лейаут ссылается на файл как обычно:",[165,782,787],{"className":783,"code":784,"filename":785,"language":786,"meta":171,"style":171},"language-astro shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\n","src/layouts/Layout.astro","astro",[173,788,789],{"__ignoreMap":171},[176,790,791],{"class":178,"line":6},[176,792,784],{},[140,794,795,796,799,800,803,804,807],{},"В режиме ",[173,797,798],{},"build"," файлы из ",[173,801,802],{},"src/assets/script/"," просто \"как есть\" копируются в ",[173,805,806],{},"dist/",". Это сохраняет структуру модулей, оставляя код удобным для дальнейшей работы. Никаких хешей и минификации.",[149,809,811],{"id":810},"режимы-сборки","Режимы сборки",[813,814,815,828],"table",{},[816,817,818],"thead",{},[819,820,821,825],"tr",{},[822,823,824],"th",{},"Команда",[822,826,827],{},"Что происходит",[829,830,831,846],"tbody",{},[819,832,833,839],{},[834,835,836],"td",{},[173,837,838],{},"npm run build",[834,840,841,842,845],{},"Сборка ",[144,843,844],{},"для передачи",". CSS/JS не минифицируются, HTML не сжимается. Идеально для отладки и интеграции.",[819,847,848,853],{},[834,849,850],{},[173,851,852],{},"npm run build:prod",[834,854,841,855,858,859,862,863,866],{},[144,856,857],{},"в продакшн",". Всё минифицировано, а ",[173,860,861],{},"main.js"," бандлится через ",[173,864,865],{},"esbuild"," в один компактный файл, импорты разрешаются.",[149,868,870],{"id":869},"итог","Итог",[140,872,873],{},"Этот шаблон во многом заменяет тот самый знакомый многим \"Gulp + PUG/EJS\", объединяя современные технологии для авторинга (Astro, Vite) с максимально предсказуемым и чистым результатом на выходе сборки.",[140,875,876,877,880],{},"Если в проекте HTML/CSS/JS — это ",[144,878,879],{},"конечный продукт",", а не промежуточный артефакт, — шаблон сэкономит вам массу времени.",[882,883,884],"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":171,"searchDepth":120,"depth":120,"links":886},[887,888,889,890,894,895],{"id":151,"depth":120,"text":152},{"id":231,"depth":120,"text":232},{"id":273,"depth":120,"text":274},{"id":644,"depth":120,"text":645,"children":891},[892,893],{"id":649,"depth":338,"text":650},{"id":776,"depth":338,"text":777},{"id":810,"depth":120,"text":811},{"id":869,"depth":120,"text":870},"md",{"icon":62,"githubRepo":63,"githubUrl":64,"publishedAt":65,"tags":898},[67,24,68],{"title":59,"description":60},"guides/templates/astro-clean-template","1F-s9l0rUvp7_iZzkXfzSD2s9trQWkIfpmzwEl4S9UE",[903,906,909,912,915,918,921,924,927],{"title":17,"meta":904,"path":27},{"icon":20,"publishedAt":21,"languageOriginal":22,"tags":905},[24,25,26],{"title":29,"meta":907,"path":37},{"icon":32,"habrUrl":33,"publishedAt":34,"languageOriginal":22,"tags":908},[36,24],{"title":39,"meta":910,"path":46},{"icon":42,"publishedAt":43,"languageOriginal":22,"tags":911},[24,26,45],{"title":48,"meta":913,"path":56},{"icon":32,"habrUrl":51,"publishedAt":52,"languageOriginal":22,"tags":914},[54,55],{"title":59,"meta":916,"path":69},{"icon":62,"githubRepo":63,"githubUrl":64,"publishedAt":65,"tags":917},[67,24,68],{"title":71,"meta":919,"path":80},{"icon":74,"githubRepo":75,"githubUrl":76,"publishedAt":77,"tags":920},[79,24,68],{"title":82,"meta":922,"path":93},{"icon":85,"githubRepo":86,"githubUrl":87,"publishedAt":88,"tags":923},[90,91,92,24,68],{"title":95,"meta":925,"path":102},{"icon":98,"githubRepo":99,"githubUrl":100,"publishedAt":88,"tags":926},[90,24],{"title":104,"meta":928,"path":112},{"icon":107,"githubRepo":108,"githubUrl":109,"publishedAt":88,"tags":929},[90,111,24,68],["Reactive",931],{"$scolor-mode":932,"$si18n:cached-locale-configs":935,"$si18n:resolved-locale":171,"$snuxt-seo-utils:routeRules":940,"$stoasts":941,"$svisitor-had-baseline":934,"$snav-highlight-ack-map":942,"$snav-highlight-pages":943,"$ssite-config":944},{"preference":933,"value":933,"unknown":592,"forced":934},"dark",false,{"ru":936,"en":938},{"fallbacks":937,"cacheable":592},[],{"fallbacks":939,"cacheable":592},[],{"head":-1,"seoMeta":-1},[],{},[],{"_priority":945,"currentLocale":22,"defaultLocale":22,"env":949,"name":950,"url":951},{"env":946,"url":126,"name":947,"defaultLocale":948,"currentLocale":948},-15,-3,-2,"production","Aganov.dev","https://aganov.dev",["Set"],["ShallowReactive",954],{"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",957],{}]