Главная » Статьи » Супероптимизированные статические сайты

Супероптимизированные статические сайты

Супероптимизированные статические сайты

От автора: если вы используете страницы GitHub, высока вероятность, что вы также используете Jekyll. Самая лучшая вещь, которую предлагает Jekyll — это создание шаблонов, которые отлично подходят как для скорости разработки, так и для обеспечения единообразия вашего сайта.

Jekyll больше 12 лет, и местами его возраст дает о себе знать. Я хотел увидеть, что возможно с сегодняшними, более современными инструментами.

Цепочка инструментов интенсивно используется react-dom/server и использует мощную систему настраиваемых компонентов, которая обрабатывает ресурсы сайта. Хотя я не первый, кто создал статическую систему сайта на основе React, система пользовательских компонентов — это то, что отличает ее и дает возможность:

создавать шаблоны на основе React;

не включает React в клиенте (или любой JS, если вы этого не хотите);

легкое сжатие / минимизация изображений, CSS и JS;

простое долгосрочное кеширование активов;

детальный контроль использования ресурсов, включая встраивание критически важных CSS и JS;

доступна лучшая оптимизация CSS — я не встречал ничего подобного.

Сложив все это вместе, общий вес страницы, на которой вы сейчас находитесь, составляет менее 100 КБ, включая шрифт 50 КБ. Во-первых, шаблоны на основе React. По сути, это то же самое, что и MDX. Вы можете включить HTML и JSX в маркдаун:

Some _regular_ markdown <ReactComponent> Some more _regular_ markdown inside a React component </ReactComponent> <ReactComponentOnItsOwn />

Он запускается на сервере во время сборки и возвращает обычный HTML-код, поэтому для результирующего файла не требуется JS для запуска или сервер для предварительной обработки перед отправкой клиенту.

Компонентная система

Система маркдауна не нова, но она позволяет использовать встроенные компоненты. Они функционируют (в основном) как заменяющие элементы для стандартных компонентов HTML. Например, для рендеринга изображения это так же просто, как:

<Image src="/assets/some-image.png" />

Но с другой стороны это загружает изображение, сжимает его, преобразует в несколько форматов (webp и т.д.) И возвращает элемент picture.

<picture> <source srcset="/res/b462ccd1.webp" type="image/webp" /> <img src="/res/31f3d1bb.png" />
</picture>

Вы заметите, что возвращенные src — это хэши. Все, что находится в папке /res, можно безопасно установить с помощью неопределенной политики кеширования, потому что, если файл изменится, его имя тоже изменится.

Здесь, также есть еще несколько особенностей. Вы можете установить на изображение атрибуты width и height, чтобы избежать изменения макета. Это так же просто, как:

<Image src="/assets/some-image.png" width="compute" height="compute" />

(Здесь мы стараемся поддерживать как можно большую совместимость с исходными элементами HTML)

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

<Image src="/assets/some-image.png" resize="1000w" />

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

// components/ExampleComponent.js import { Image } from "../core/components"; export default ({ src, chlidren }) => ( <div style={{ display: "flex" }}> <Image src="src" /> {children} </div>
);

Когда вы делаете свой собственный макет страницы для вас будут полезны следующие компоненты:

<InlineCss> и <ExternalCss> — рендеры <style> и элементы <link> соответственно

<InlineJs> и <ExternalJs> — оба элемента рендеринга <script>

// layouts/example-layout.js import { InlineCss, ExternalJs } from "../core/components"; export default ({ title, children }) => ( <html> <head> <title>{title ?? "No title"}</title> <meta charSet="utf-8" /> <InlineCss src="/assets/site.css" /> </head> <body> {children} <ExternalJs src="/assets/site.js" /> </body> </html>
);

Как и следовало ожидать, переданные вами CSS и JS оптимизированы с помощью csso и terser соответственно. И, как и изображения, файлы, созданные из <ExternalCss> и <ExternalJs> названные с использованием выходного хеша, поэтому могут кэшироваться на неопределенный срок.

Эти компоненты позволяют очень просто решить, какие ресурсы вы загружаете на страницу и как они загружаются.

Для создания CSS это всего лишь простой CSS. Нет модулей CSS-in-JS или CSS. Основное внимание здесь уделялось созданию наилучшего доступного CSS-вывода.
Например, откройте веб-инспектор в этом кодовом блоке и посмотрите выделенное ниже.

<just-some random="code"> to <!-- demonstrate highlighting -->
</just-some>

Вы также можете посмотреть где угодно на сайте. Имена классов короткие … Слишком короткие …

CSS не был написан таким образом: имена классов (и пользовательские свойства CSS) минимизируются как часть процесса сборки. Для ваших файлов CSS это происходит автоматически в процессе минификации.

Для HTML (не компонентов React) в маркдауне это также происходит автоматически. Для компонентов React вам понадобится classNames. Он работает аналогично пакету classnames, но также выполняет минимизацию.

// components/ExampleComponent.js import { Image } from "../core/components";
import { classNames } from "../core/css"; export default ({ src, inverted }) => ( <div className={classNames([ "hero-section", inverted && "hero-section--inverted", ])} > <p className={classNames("paragraph paragraph--large")}> A large paragraph </p> {/* * Note that built-in components apply the `classNames` helper themselves * Make sure you don't double apply it! */} <Image src={src} className="hero-section__image" /> </div>
);

Если вы ссылаетесь на имена классов или пользовательские свойства из файлов JS, вам необходимо использовать глобальные константы CSS_CLASSES и CSS_VARS:

// /assets/example.js const heroSection = document.querySelector("." + CSS_CLASSES["hero-section"]);
const heroSection.style.setProperty( CSS_VARS["--some-css-var"], "some-value"
)

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

Например, на этом сайте я получаю несколько предупреждений от выделения синтаксиса:

The following classes were defined in CSS, but never used in any non-CSS files: hljs-quote, hljs-doctag, hljs-formula, hljs-section, hljs-selector-tag, hljs-subst, hljs-regexp, hljs-attribute, hljs-variable, hljs-template-variable, hljs-type, hljs-selector-class, hljs-selector-attr, hljs-selector-pseudo, hljs-symbol, hljs-bullet, hljs-link, hljs-selector-id, hljs-emphasis, hljs-strong The following classes used one more non-CSS files, but never defined in CSS: hljs, language-diff, language-objc, language-objectivec, hljs-meta-keyword, twitter-tweet, language-jsx, hljs-function, xml, hljs-tag, hljs-params, language-xml, language-c#, language-reasonml, hljs-operator, hljs-module-access, hljs-module, hljs-identifier, hljs-constructor, hljs-pattern-match

Для первого набора предупреждений я мог бы пройти и удалить их из выделенного CSS — хотя есть дополнительные расходы на обслуживание, чтобы гарантировать, что я добавлю их обратно, если они действительно используются.

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

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

Статус проекта

Весь код генерации сайта содержится в репо. Он не публикуется как отдельный пакет.

Джейкоб пишет код на GitHub

Если вы хотите использовать код, вы должны иметь возможность его разбить, удалить все во всех папках, кроме /core, а затем добавить свой собственный контент по мере необходимости.

Запустите, yarn build чтобы создать рабочую версию сайта. Он выводится в /site.

Запустите yarn start, чтобы запустить сервер разработки. Он будет следить за изменениями файлов и при необходимости перестраивать, и вы можете нажать r, чтобы выполнить полное перестроение. Вы можете увидеть свой сайт в порту 8080. Он пропускает большую часть минификации по соображениям производительности или эргономики разработчика.

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

Когда вы пишете такой блог, каждая перекомпиляция выполняется за 0,1–0,2 секунды на ноутбуке 6-летней давности.

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

У меня нет особого интереса (или времени) для публикации и поддержки этого как отдельного проекта. Однако, если вы хотите это сделать, то вполне можете. Считайте весь код /core под лицензией MIT — то есть все, что не дословно на моем сайте.

Источник: jacobdoescode.com

Редакция: Команда webformyself.