Настройка среды разработки из NPM модулей и создание статического сайта

Создано: 09-08-2022

О чем статья

В статье показано, как настроить собственную сборку из модулей NPM, которая является полным аналогом сборки npm-for-frontend. Эта сборка позволит создавать статические страницы сайта, а именно:

Сама сборка будет выполнять следующие функции:

Быстрый запуск проекта

Чтобы выполнять дальнейшие действия у нас на компьютере должен быть установлен Node.js и NPM.

Откроем терминал и создадим каталог my-project нашего будущего проекта, и сразу перейдем в него:

mkdir my-project
cd my-project

Теперь, если нам нужна система контроля версий Git, то выполним два простых шага, сначала, инициализируем Git:

git init

Затем создадим файл .gitignore:

touch .gitignore

В этом файле будем указывать игнорируемые файлы и каталоги, которые не должны попадать в коммиты. Если файл .gitignore не виден в каталоге, то нужно нажать сочетание клавиш Ctrl+H. Запишем в файле .gitignore следующее:

node_modules

С настройками Git в этом проекте покончено. Теперь не забываем, хотя бы иногда, делать коммиты.

Инициализируем npm. Для этого введем в терминале команду:

npm init -y

Будет создан файл package.json с настройками по умолчанию (опция -y). Наиболее полную информацию о файле package.json можно прочитать в документации по npm.

Подключение и настройка шаблонизатора Pug

В настоящее время мало кто верстает на чистом HTML. Придуманы более удобные для написания языки и инструменты, которые эти языки преобразуют в HTML. Одним из таких инструментов является шаблонизатор Pug, который имеет свой, более удобный синтаксис для написания разметки. После компиляции синтаксис Pug превращается в разметку HTML.

Установим pug. Для этого введем в терминале команду:

npm i -D pug-cli

Вместо опции i можно написать install. Опция -D указывает, что устанавливаемый пакет нужен только для разработки и не будет использоваться в производственной сборке. Поэтому, устанавливаемый пакет будет прописан в секции "devDependencies": {...} файла package.json.

В результате, в корневом каталоге проекта будет создан каталог node_modules и файл package-lock.json. Каталог node_modules содержит все установленные зависимости проекта. Обычно этот каталог не рекомендуется включать в репозитории, что мы и сделали, проигнорировав его в файле .gitignore. Файл package-lock.json хранит записи о точных версиях установленных зависимостей.

Создадим в корне проекта каталог src, в котором создадим каталог pages, в нем создадим каталог home, а уже в нем создадим файл index.pug со следующим содержимым:

src/pages/home/index.pug

h1.name= 'Код написан с помощью разметки Pug!'

Сохраним файл.

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

Настроим Pug для работы. Для этого добавим в секцию "scripts" файла package.json скрипт с именем "pug" (знак плюс + дальше по тексту будет лишь указывать на добавленные строки кода):

{
  "name": "my-project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
+   "pug": "pug --pretty -w src/pages/home/index.pug -o dist",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "pug-cli": "^1.0.0-alpha6"
  }
}

Более подробно об опциях командной строки можно узнать в репозитории pug-cli

После установки и конфигурации шаблонизатора Pug проверим его в работе. Из корневого каталога проекта запустим в терминале команду:

npm run pug

Будет создан каталог dist, а в нем файл index.html. Если мы откроем этот файл в браузере, то увидим следующее:

Код написан с помощью разметки Pug!

Если мы откроем файл dist/index.html в редакторе кода то увидим откомпилированную HTML разметку:

<h1 class="name">Код написан с помощью разметки Pug</h1>

Чтобы данный файл был полноценной HTML страницей и без ошибок проходил валидатор в файл нужно добавить теги <!DOCTYPE html>, <html>, <head>, <body> и некоторые теги <meta>. Данные теги должны присутствовать на каждой HTML странице.

Чтобы не повторять во всех шаблонах Pug одинаковый код, создадим общий шаблон, который будет применятся ко всем страницам нашего сайта. Для этого в каталоге src создадим каталог layouts, внутри которого создадим каталог base, а в нем файл index.pug со следующим содержимым:

src/layouts/base/index.pug

-
  let data = {
    default_description: "Показан пример общего шаблона Pug для всех страниц",
    default_title: "Общий шаблон Pug"
  }

- const {default_title, default_description} = data

doctype html
html(lang='ru')
  head
    meta(charset='utf-8')
    meta(name='viewport' content='width=device-width, initial-scale=1.0')

    meta(name= 'description' content= description ? description : default_description)
    title= title ? title : default_title

  body
    block main

Содержимое файла src/pages/home/index.pug изменим на следующее:

extends ../../layouts/base/index

block main
  h1.name= 'Код написан с помощью разметки Pug!'

Теперь, если мы откроем файл dist/index.html в редакторе кода, мы увидим следующую разметку:

<!DOCTYPE html>
<html lang="ru">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta
      name="description"
      content="Показан пример общего шаблона Pug для всех страниц"
    />
    <title>Общий шаблон Pug</title>
  </head>
  <body>
    <h1 class="name">Код написан с помощью разметки Pug!</h1>
  </body>
</html>

Если мы этого не увидим, значит мы либо закрыли окно терминала с выполняемым компилятором Pug, либо что-то сделали неправильно.

Проверка данного файла в валидаторе не выявит никаких ошибок.

Далее, мы создадим простую кнопку и добавим ее на страницу сайта. Реализуем кнопку с помощью миксина. Для этого создадим в каталоге src каталог components, в котором создадим каталог button, а в нем файл button.pug со следующим содержимым:

mixin button
  button.button= 'Кнопка добавлена с помощью миксина'

Не забываем сохранить файл.

В файл src/pages/home/index.pug подключим файл button.pug с помощью include и вызовем в нужном месте как миксин +button():

extends ../../layouts/base/index
include ../../components/button/button.pug

block main
  h1.name= 'Код написан с помощью разметки Pug!'
  +button

После сохранения файла перезагрузим страницу браузера и мы увидим заголовок, а под ним кнопку:

Добавлена кнопка с помощью миксина

Настройка локального сервера BrowserSync

После очередного изменения кода, чтобы увидеть откомпилированный результат, нужно каждый раз вручную обновлять страницу браузера. Это очень быстро надоедает. Для того, чтобы после сохранения редактируемого файла браузер автоматически обновлял страницу можно использовать локальный сервер, такой как BrowserSync или Pug Server. Мы будем использовать сервер BrowserSync, установим его. Для этого откроем новое окно терминала, так как в первом окне у нас запущен Pug, который работает в режиме наблюдения за файлами, и, сделав рабочим корневой каталог проекта, введем следующую команду:

npm i -D browser-sync

Добавим в файл package.json настройку, отмеченную знаком +:

  "scripts": {
    "pug": "pug --pretty -w src/pages/home/index.pug -o dist",
+   "serve": "browser-sync dist -w",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

Данный скрипт будет запускать сервер, а сервер будет наблюдать за всеми файлами в каталоге dist. Скрипт работает в browser-sync версии 2.23.0 и выше. Аналог данного скрипта, который будет работать в browser-sync любой версии приведен ниже:

  "scripts": {
    ...
    "serve": "browser-sync start -s dist -f dist"
  },

Теперь в окне терминала выполним команду:

npm run serve

В результате, откроется браузер, в котором мы увидим нашу страницу.

Теперь, каждый раз, когда в каталоге dist происходят какие-либо изменения сервер будет автоматически обновлять браузер.

Последовательный и параллельный запуск скриптов с помощью npm-run-all

Мы видим один неприятный момент. Для того, чтобы запустить шаблонизатор Pug в режиме наблюдения мы должны открыть окно терминала и выполнить команду npm run pug, затем, чтобы запустить сервер, нужно открыть другое окно терминала и выполнить npm run serve. И так нужно поступать каждый раз, когда начинаете процесс разработки. В дальнейшем, нам понадобится запускать в режиме наблюдения препроцессор Sass, постпроцессор Postcss, компилятор Babel и др. Если на каждую такую задачу открывать окно терминала и запускать команду типа npm run *, то это будет рутина.

Чтобы упростить процесс одновременного запуска нескольких npm-скриптов можно воспользоваться пакетами concurrently или npm-run-all. Здесь мы воспользуемся инструментом npm-run-all. Сначала установим его. Для этого откроем третье окно терминала и сделав рабочим корневой каталог проекта выполним следующую команду:

npm i -D npm-run-all

Затем отредактируем файл package.json:

  "scripts": {
+   "watch:pug": "pug --pretty -w src/pages/home/index.pug -o dist",
+   "watch:serve": "browser-sync dist -w",
+   "dev": "npm-run-all -p watch:*",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

Сохраним файл package.json.

Мы добавили скрипт "dev": "npm-run-all -p watch:*", в котором опция -p (или --parallel) указывает на параллельное выполнение скриптов, следующих за этой опцией. У нас за этой опцией следует шаблон watch:*. Это значит, что параллельно будут выполнятся все скрипты, у которых в названии есть watch:. Поэтому, мы изменили имена скриптов: имя "pug" заменили на "watch:pug"; имя "serve" заменили на "watch:serve".

Более подробно об опциях командной строки можно узнать из документации инструмента npm-run-all

Теперь, закроем все окна терминала кроме одного последнего и запустим следующую команду:

npm run dev

Мы увидим, что откроется браузер со страницей нашего сайта. И для этого нам понадобилось одно окно терминала и всего одна команда.

Возможно, кто-то скажет, что в NPM есть операторы & и &&, с помощью которых можно выполнить все тоже самое. Но оператор & не работает в Windows cmd.exe, а npm-run-all -p там отлично работает.

Установка и настройка rimraf для очистки каталогов

Перед началом компиляции полезно будет очищать каталог dist от предыдущих файлов. Для этого будем использовать пакет rimraf, который является аналогом UNIX команды rm -rf, но работает в любой системе, которая поддерживает NPM. Установим этот пакет:

npm i -D rimraf

В файл package.json внесем правки:

  "scripts": {
+   "clean": "rimraf dist",
+   "pug": "pug --pretty src/pages/home/index.pug -o dist",
    "watch:pug": "pug --pretty -w src/pages/home/index.pug -o dist",
    "watch:serve": "browser-sync dist -w",
+   "dev": "npm-run-all clean pug -p watch:*",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

Мы создали скрипт "clean" для запуска rimraf и указали в скрипте "dev", чтобы скрипт "clean" запускался самым первым. Так как скрипт "clean" удаляет каталог dist, то при параллельном запуске pug и browser-sync последний выдает ошибку. Поэтому, мы добавили дополнительный скрипт "pug" без режима наблюдения.

Закроем все окна терминала и откроем одно новое и сделав корневой каталог проекта рабочим выполним в терминале привычную нам команду:

npm run dev

npm-run-all выполнил сначала скрипт "clean", потом скрипт "pug', а после параллельно запустил два скрипта: "watch:pug" и "watch:serve".

Установка и настройка препроцессора node-sass и постпроцессора PostCSS с плагинами PostCSS Preset Env и cssnano

Для написания стилей мы будем использовать препроцессор Sass. Он позволяет нам использовать переменные, вложенности, миксины, наследование, математические операторы и др.

При написании стилей мы не хотим задумываться о префиксах, которые необходимы для корректной работы некоторых браузеров, а также, мы уже сегодня хотим использовать современный CSS не думая о его поддержке всеми браузерами. В этом нам поможет PostCSS с плагином PostCSS Preset Env. Этот плагин преобразует современный CSS в тот CSS, который понятен большинству браузеров, а также расставляет префиксы при необходимости.

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

Нам еще понадобится postcss-cli, чтобы запускать PostCSS из командной строки. Для начала установим все необходимые пакеты:

npm i -D node-sass postcss postcss-cli postcss-preset-env cssnano

Теперь приступим к написанию стилей. Создадим файл ./src/index.scss и скопируем в него следующее:

$font-size: 1rem;
$font-color: lch(28 99 35);

html {
  font-size: $font-size;
  color: $font-color;
}

.example {
  user-select: none;
}

Добавим в файл package.json следующий код:

  "scripts": {
+   "clean": "rimraf dist tmp",
    "pug": "pug --pretty src/pages/home/index.pug -o dist",
    "watch:pug": "pug --pretty -w src/pages/home/index.pug -o dist",
+   "sass": "node-sass src/index.scss -o tmp",
+   "watch:sass": "node-sass -w src/index.scss -o tmp",
+   "watch:post": "postcss -w tmp -d dist -u postcss-preset-env",
    "watch:serve": "browser-sync dist -w",
+   "dev": "npm-run-all clean pug sass -p watch:*",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

Разберемся, что делают эти скрипты.

rimraf удаляет каталоги dist и tmp.

node-sass создает каталог tmp и однократно выполняет следующее - файл src/index.scss и подключенные к нему другие файлы .scss компилирует и помещает файл tmp/index.css. Опция -o указывает, что скомпилированные файлы выводятся в каталог, который указывается после этой опции. Здесь этим каталогом является временный каталог tmp. Временный он потому, что после компиляции препроцессором файлы должны будут обработаны постпроцессором.

node-sass следит за файлом src/index.scss и каждый раз компилирует его при изменении содержимого, а полученный файл сохраняет в каталоге tmp с именем index.css. Скрипт "watch:sass" не сильно отличается от скрипта "sass". Разница в опции -w, которая запускает node-sass в режиме наблюдения. Если вызывать скрипт с опцией -w, то node-sass не сможет создать отсутствующий каталог. Поэтому сначала вызываем скрипт без опции -w, а потом с этой опцией.

postcss следит за каталогом tmp и если содержимое файлов в нем меняется, то файлы прогоняются через указанные плагины и помещаются в каталог dist с расширением .css Опция -u указывает, какие плагины должен использовать postcss - здесь postcss-preset-env

npm-run-all запускает последовательно скрипт clean потом sass, затем запускает параллельно (-p) скрипты, у которых имя начинается на watch:

Теперь, в терминале запустим команду:

npm run dev

Файл src/index.scss будет откомпилирован в dist/index.css, у которого внутри будет следующее:

html {
  font-size: 1rem;
  color: rgb(146, 0, 17);
  color: color(display-p3 0.54534 0 0.06183);
}

.example {
  -webkit-user-select: none;
  -moz-user-select: none;
  user-select: none;
}

Для того, чтобы наши стили отобразились на странице, добавим в основной шаблон src/layouts/base/index.pug ссылку на откомпилированный dist/index.css

-
  let data = {
    default_description: "Показан пример общего шаблона Pug для всех страниц",
    default_title: "Общий шаблон Pug"
  }

- const {default_title, default_description} = data

doctype html
html(lang='ru')
  head
    meta(charset='utf-8')
    meta(name='viewport' content='width=device-width, initial-scale=1.0')
+   link(rel='stylesheet' href='index.css')

    meta(name= 'description' content= description ? description : default_description)
    title= title ? title : default_title

  body
    block main

Сохраним изменения и мы увидим, что страница браузера обновилась со стилевым оформлением заголовка:

Стилизация заголовка

Установка и настройка пакетного конвертера md-pug-to-html

Все более распространенным способом написания статей является язык разметки Markdown. Статья пишется на Markdown и сохраняется в файле с расширением .md. Если же в статье нужно поместить рисунки, то удобнее всего создать каталог и поместить в него статью и рисунки. Для каждой статьи создается свой каталог, а каталоги статей объединяются в каталоги по темам.

Теперь, нам нужен пакетный конвертер, который сможет сканировать эти каталоги, искать файлы Markdown, преобразовать их в Html страницы и помещать в каталог сайта с сохранением исходной структуры каталогов. Также нужно, чтобы изображения к статьям были перемещены в нужные места. При этом, Html страницы должны конвертироваться согласно заданному шаблону Pug.

Для вышеописанной задачи мне не удалось найти ничего подходящего в репозитории NPM. Наверное, плохо искал. Поэтому, я создал свой пакетный конвертер MdPugToHtml.

Установим этот пакет в нашем проекте

npm i -D md-pug-to-html

Для конвертера MdPugToHtml написана подробная справка как на русском, так и на английском языках. Здесь мы рассмотрим, как пользоваться этим пакетом в нашем конкретном случае.

Для начала создадим каталог с файлами Markdown. В корневом каталоге проекта создадим каталог content, а в нем еще пару каталогов article1 и article2. Каждому такому каталогу нужно давать имена, отражающие название статьи. Они потом будут отображаться в ЧПУ. В каждом таком каталоге будет хранится одна статья и сопутствующие ей файлы изображений если они присутствуют. Сам файл статьи лучше называть везде одинаково - index.md.

Создадим файл content/article1/index.md для первой статьи и запишем в него следующее:

---
title: Статья первая
description: Краткое описание первой статьи
create: 10-08-2022
---

## Заголовок h2 в первой статье

Создадим файл content/article2/index.md для второй статьи и запишем в него следующее:

---
title: Статья вторая
description: Краткое описание второй статьи
create: 11-08-2022
---

## Заголовок h2 во второй статье

Блок данных, указанный в начале каждого файла называется Frontmatter, имеет формат YAML/TOML/JSON и отделяется с двух сторон тремя дефисами ---.

Frontmatter не является стандартом для Markdown и не распознается многими конвертерами. Однако конвертер MdPugToHtml умеет распознавать и обрабатывать Frontmatter благодаря пакету gray-matter, который встроен в пакет MdPugToHtml в качестве зависимости.

В файле package.json настроим MdPugToHtml

  "scripts": {
    "clean": "rimraf dist tmp",
+   "md-pug-to-html": "md-pug-to-html content -o dist -t src/pages/article",
    "pug": "pug --pretty src/pages/home/index.pug -o dist",
    "watch:pug": "pug --pretty -w src/pages/home/index.pug -o dist",
    "sass": "node-sass src/index.scss -o tmp",
    "watch:sass": "node-sass -w src/index.scss -o tmp",
    "watch:post": "postcss -w tmp -d dist -u postcss-preset-env",
    "watch:serve": "browser-sync dist -w",
+   "dev": "npm-run-all clean md-pug-to-html pug sass -p watch:*",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

Здесь мы указали в качестве аргумента каталог content, где расположены файлы Markdown, с помощью опции -o указываем каталог сборки проекта dist, а с помощью опции -t - каталог src/pages/article, где расположен шаблон Pug для этих страниц. Так как у нас еще нет такого шаблона, то мы его можем создать самостоятельно. При этом имя файла шаблона должно быть mpth-template.pug.

Для этого, в каталоге src/pages создадим каталог article, в котором создадим файл mpth-template.pug со следующим содержимым:

extends ../../layouts/base/index

block main
  .content
    .article
      h1= data.title
      .creationDate= `Создано: ${data.create}`
      != contentHtml

Теперь выполним команду:

npm run dev

Закроем открывшееся окно браузера и перейдем в каталог dist. Здесь мы увидим три новых каталога article1, article2 и images. В первых двух расположены страницы HTML, а каталог images пуст, в него будут копироваться файлы изображений.

Если мы перейдем в каталог src, то увидим новый каталог data, в котором находится файл mpth-data.pug. Этот файл содержит массив объектов с URL адресами, заголовками и кратким описанием для каждой созданной статьи:

- const dataListItems = [{"pathFile":"article1/","title":"Статья первая","description":"Краткое описание первой статьи"},{"pathFile":"article2/","title":"Статья вторая","description":"Краткое описание второй статьи"}]

Этот файл мы будем использовать в компоненте, который будет отображать список ссылок на статьи. Для этого в каталоге src/components создадим каталог list-articles, а в нем создадим файл index.pug, в который скопируем код миксина:

mixin list-articles

  include ../../data/mpth-data.pug
  ul.list__box
    each item in dataListItems
      li
        a.list__item(href=item.pathFile)= item.title
        p= item.description

Теперь откроем файл src/pages/home/index.pug и подключим в него созданный выше миксин с помощью include ../../components/list-articles/index, а затем пропишем его вызов +list-articles:

extends ../../layouts/base/index
include ../../components/button/button
include ../../components/list-articles/index

block main
  h1.name= 'Код написан с помощью разметки Pug!'
  +button
  +list-articles

Перейдем в браузер с нашей страницей, где мы увидим список ссылок на статьи с кратким описанием:

Список статей

При нажатии на любую из ссылок мы перейдем на соответствующую страницу со статьей.

Установка и настройка сборщика Rollup

Для сборки JavaScript файлов будем применять сборщик Rollup. Он будет объединять JavaScript модули в один файл index.js. Установим Rollup:

npm i -D rollup

Для демонстрации работы сборщика Rollup создадим файл src/components/button/button.js и напишем в нем функцию, которая будет изменять текст на кнопке:

export function button() {
  let button = document.querySelector('.button');
  let text = button.textContent;
  const message = "Этот текст изменен JavaScript'ом";

  button.addEventListener('click', () => {
    text === message ? (text = 'Жми еще!') : (text = message);
    button.textContent = text;
  });
}

Затем, создадим главный файл скрипта src/index.js, в который сделаем импорт вышеуказанного модуля и вызов функции:

import { button } from './components/button/button.js';

button();

Теперь в главном шаблоне нашего проекта src/layouts/base/index.pug укажем, где будет расположен собранный сборщиком Rollup скрипт index.js. Он будет находится в каталоге dist. Так как это каталог сборки проекта, и, следовательно он будет корневым каталогом будущего сайта, то мы указываем, просто, имя файла script(defer src='index.js'). defer мы указали, чтобы скрипт запустился после построения дерева DOM.

-
  let data = {
    default_description: "Показан пример общего шаблона Pug для всех страниц",
    default_title: "Общий шаблон Pug"
  }

- const {default_title, default_description} = data

doctype html
html(lang='ru')
  head
    meta(charset='utf-8')
    meta(name='viewport' content='width=device-width, initial-scale=1.0')
    link(rel='stylesheet' href='index.css')

    script(defer src='index.js')

    meta(name= 'description' content= description ? description : default_description)
    title= title ? title : default_title

  body
    block main

Добавим в файл package.json скрипт запуска для Rollup:

  "scripts": {
    "clean": "rimraf dist tmp",
    "md-pug-to-html": "md-pug-to-html -i=content -o=dist -t=src/pages/article",
    "pug": "pug --pretty src/pages/home/index.pug -o dist",
    "watch:pug": "pug --pretty -w src/pages/home/index.pug -o dist",
    "sass": "node-sass src/index.scss -o tmp",
    "watch:sass": "node-sass -w src/index.scss -o tmp",
    "watch:post": "postcss -w tmp -d dist -u postcss-preset-env",
    "watch:serve": "browser-sync dist -w",
+   "watch:rollup": "rollup -w -c rollup.config.js",
    "dev": "npm-run-all clean md-pug-to-html pug sass -p watch:*",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

В скрипте "watch:rollup" параметр -w (или--watch) сообщает сборщику, что он должен работать в режиме наблюдения, а параметр -c (или --config) говорит, что есть файл настроек и имя ему rollup.config.js.

Раз указан файл настроек rollup.config.js, то создадим его в корне проекта и напишем в нем следующее:

export default {
  input: 'src/index.js',

  watch: {
    include: './src/**',
    clearScreen: false,
  },

  output: {
    file: 'dist/index.js',
    format: 'iife',
  },
};

Свойство input указывает путь к главному скрипту проекта, с которого Rollup начнет собирать все скрипты в один конечный файл-бандл. Адрес этого бандла указан в свойстве file объекта output. В этом же объекте свойство format указывает на формат вывода файла. Здесь указан формат 'iife', что превращает код выходного файла в немедленно вызываемую функцию (IIFE). Более подробно о параметре format см. документацию.

Для режима наблюдения существует множество настроек. Ознакомиться с ними можно в документации. В нашем же файле свойство include ограничивает область наблюдения каталогом src, а свойство clearScreen запрещает очистку экрана при перезапуске сборщика Rollup.

Сохраним все изменения и перезапустим проект

npm run dev

Откроется окно браузера, в котором при нажатии на кнопку будет изменяться текст на ней. При первом нажатии текст Код написан с помощью разметки Pug будет заменен на Этот текст изменен JavaScript'ом, при втором нажатии текст изменится на Жми еще!.

Оптимизация изображений

Одной из главных причин медленной загрузки сайта является большой объем контента с изображениями. Для того, чтобы сайт загружался быстрее нужно максимально оптимизировать эти изображения. Оптимизация изображений подразумевает, как минимум, уменьшение размера файлов при сохранении визуального качества. Чтобы не заниматься этим вручную мы автоматизируем этот процесс. Для автоматического сжатия можно использовать такие инструменты как ImageMagick, libvips или imagemin. Мы воспользуемся последним.

Установка и настройка модуля imagemin

Imagemin — популярный модуль сжатия изображений.

Установим imagemin-cli:

npm i -D imagemin-cli

Добавим в файл package.json следующий код:

"scripts": {
    "clean": "rimraf dist tmp",
    "md-pug-to-html": "md-pug-to-html -i=content -o=dist -t=src/pages/article",
    "pug": "pug --pretty src/pages/home/index.pug -o dist",
    "watch:pug": "pug --pretty -w src/pages/home/index.pug -o dist",
    "sass": "node-sass src/index.scss -o tmp",
    "watch:sass": "node-sass -w src/index.scss -o tmp",
    "watch:post": "postcss -w tmp -d dist -u postcss-preset-env",
+   "img": "imagemin src/pages/**/images src/images -o dist/images",
    "watch:serve": "browser-sync dist -w",
    "watch:rollup": "rollup -w -c rollup.config.js",
+   "dev": "npm-run-all clean pug sass img -p watch:*",
    "build": "echo \"There's nothing yet, but you can contribute\" && exit 1",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

Скрипт "img" указывает imagemin найти и сжать все изображения в каталогах src/pages/**/images и src/images и поместить их в dist/images. Более подробные настройки можно посмотреть в документации.

Установка и настройка модуля SVGO

Обычно, SVG изображения содержат избыточный код, который любят оставлять редакторы графики. SVGO минимизирует файлы SVG удаляя тот самый ненужный код. Существует онлайн инструмент по оптимизации SVG файлов и кода - SVGOMG. Также, существуют плагины: для PostCSS - postcss-svgo; для Rollup - rollup-plugin-svgo.

Мы будем использовать SVGO, который можно установить как CLI:

npm i -D svgo

Настроим package.json:

"scripts": {
  ...
  "watch:svg": "svgo -f src/assets/svg -o dist/svg",
  ...
}

Флаг -f (или --folder) указывает, что будут обрабатываться не отдельные файлы, а каталог, что все файлы с расширением .svg будут взяты из каталога src/assets/svg, оптимизированы и помещены в каталог dist/svg, который будет создан при его отсутствии.

Создадим каталог src/assets/svg и поместим туда несколько файлов .svg, взять их можно здесь. Если мы скачаем значок email_black_24dp в виде файла SVG и откроем этот файл в любом текстовом редакторе, то мы увидим следующее:

<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-.4 4.25l-7.07 4.42c-.32.2-.74.2-1.06 0L4.4 8.25c-.25-.16-.4-.43-.4-.72 0-.67.73-1.07 1.3-.72L12 11l6.7-4.19c.57-.35 1.3.05 1.3.72 0 .29-.15.56-.4.72z"/></svg>

Чтобы значок гибко подстраивался под размер шрифта заменим значения атрибутов height и width с 24px на 1em:

<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="#000000"><path ....

и сохраним этот файл. То же самое проделаем с другими файлами.

Затем в файл src/pages/home/index.pug добавим следующее:

extends ../../layouts/base/index
include ../../components/button/button
include ../../components/list-articles/index

block main
  h1.name= 'Код написан с помощью разметки Pug!'
  +button
  p.text= 'Нажми кнопку'
  +list-articles
  img(src='images/built-with.png')
+ p.text= 'Если что-то не так, звони по телефону:'
+ a.phone-link(href='tel:+79189999999')
+   img.icon-phone(src='svg/phone_black_24dp.svg')
+   .phone-number='+79189999999'

В конец уже существующего файла src/index.scss добавим следующие стили:

.phone-link {
  display: flex;
  text-decoration: none;
  color: $font-color;
}

.icon-phone {
  width: 2.5 * $font-size;
  height: 2.5 * $font-size;
}

.phone-number {
  font-size: 2.4 * $font-size;
}

Запустим сборку:

npm run dev

В окне браузера в самом низу мы увидим следующее:

SVG значок телефона

Установка и настройка генератора спрайтов svg-sprite-generator

svg-sprite-generator - генератор SVG-спрайтов, который из отдельных файлов SVG создает один файл-спрайт.

Установим пакет:

npm i -D svg-sprite-generator

Напишем настройки в package.json

  "scripts": {
    "clean": "rimraf dist tmp",
    "pug": "pug --pretty src/pages/home/index.pug -o dist",
    "watch:pug": "pug -w --pretty src/pages/home/index.pug -o dist",
    "sass": "node-sass src/pages/home/index.scss -o tmp",
    "watch:sass": "node-sass -w src/pages/home/index.scss -o tmp",
+   "watch:svg": "svgo -f src/assets/svg -o dist/svg && svg-sprite-generate -d dist/svg -o dist/svg/sprite.svg",
    "watch:post": "postcss -w tmp/index.css -d dist -u postcss-preset-env",
    "img": "imagemin src/pages/**/images/* src/images/* -o dist/images",
    "watch:serve": "browser-sync dist -w",
    "watch:rollup": "rollup --watch -c rollup.config.js",
    "dev": "npm-run-all clean pug sass img -p watch:*",
    "build": "echo \"There's nothing yet, but you can contribute\" && exit 1",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

С помощью цепочки из операторов && можно последовательно запускать различные скрипты. Так в скрипте "watch:svg" мы добавим через && другой скрипт. Это значит, что после успешного выполнения кода -f src/assets/svg -o dist/svg будет выполнен код svg-sprite-generate -d dist/svg -o dist/svg/sprite.svg, который означает, что все файлы с расширением .svg будут взяты из каталога dist/svg, объединены в спрайт sprite.svg и этот спрайт будет помещен в этот же каталог.

Выполним сборку всего проекта:

npm run dev

Мы обнаружим в каталоге dist/svg/ новый файл sprite.svg. Спрайт внутри выглядит, примерно, так:

<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg">
  <symbol id="email_black_24dp">
    <path d="M0 0h24v24H0V0z" fill="none" /><path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2..." />
  </symbol>
  <symbol id="phone_black_24dp">
    <path d="M0 0h24v24H0V0z" fill="none" /><path d="m19.23 15.26-2.54-.29a1.99 1.99 0 0 0-1.64.57l-1.84 1.84a15.045 15.045 0 0 1-6.59-..." />
  </symbol>
  <symbol id="whatsapp_black_24dp">
    <path d="M0 0h24v24H0z" /><g fill="none"><path d="M0 0h24v24H0z" /></g><path d="M19.05 4.91A9.816 9.816 0 0 0 12.04 2c-5.46 0-9.91 4.45-9.91 9..." />
  </symbol>
</svg>

Здесь в один спрайт упаковано три файла SVG. Код каждого отдельного файла SVG заключен между тегами <symbol>, а это значит, что значки не будут отображаться если мы попробуем вызвать отдельный элемент спрайта вот так: <img src="svg/sprite.svg#email_black_24dp"/>. Отобразить нужный значок можно, если его вызвать с помощью команды <use> по уникальному идентификатору. Соответственно, тэг <use> должен быть обернут тегом <svg>. Отобразим какой-нибудь значок из спрайта, для этого добавим в конец файла src/pages/home/index.pug следующий код:

  p.text= 'или пиши на почту:'
  a.email-link(href='mailto:[email protected]' title='Написать письмо')
    svg.icon(role='img')
      use(href='svg/sprite.svg#email_black_24dp')
    .email-address='[email protected]'

Стилизуем данный код в файле src/index.scss:

.email-link {
  display: flex;
  text-decoration: none;
  color: $font-color;

  &:hover {
    text-decoration: underline;
    background-color: bisque;
  }
}

.icon {
  width: 2.5 * $font-size;
  height: 2.5 * $font-size;
}

.email-address {
  font-size: 2 * $font-size;
}

Запустим сборку:

npm run dev

В окне браузера в самом низу мы увидим следующее:

SVG значок email

Копирование файлов и каталогов

Часто возникает необходимость скопировать некоторые файлы или даже каталоги в сборку проекта без их преобразования. Для примера возьмем файл robots.txt, который используется для ограничения доступа поисковым роботам к определенным ресурсам сайта.

Создадим в корне проекта каталог static, в котором создадим файл robots.txt со следующим содержимым:

User-agent: *
Disallow:
Disallow: /404.html
Allow: /
Sitemap: https://jinv.ru/sitemap/sitemap-index.xml
Host: https://jinv.ru

Данный файл должен без изменений попасть в корень сайта, а так как корневым каталогом будущего сайта является каталог dist, то файл robots.txt должен копироваться в этот каталог при каждой сборке проекта.

Установим fse-cli:

npm i -D @atao60/fse-cli

Пакет fse-cli является интерфейсом командной строки для fs-extra, который, в свою очередь, расширяет стандартный модуль fs дополнительными методами для работы с файловой системой.

Обычно файл robots.txt и другие подобные ему ресурсы не используются на этапе разработки, а нужны только в готовой сборке. Поэтому, мы настроим package.json для fse-cli следующим образом:

  "scripts": {
    ...
    "watch:serve": "browser-sync dist -w",
    "watch:rollup": "rollup -w -c rollup.config.js",
    "build:rollup": "rollup -c rollup.config.js",
+   "copy": "fse copy static dist",
    "dev": "npm-run-all clean md-pug-to-html pug sass img -p watch:*",
+   "build": "npm-run-all clean md-pug-to-html build:pug sass build:svg build:post img build:rollup copy",
    "test": "echo \"Error: no test specified\" && exit 1"
  }

После того, как мы выполним команду сборки проекта:

npm run build

Содержимое каталога static будет скопировано в каталог dist. Обратите внимание, сам каталог static не копируется.

Данная статья не является законченной. Она будет пополняться и изменяться по мере развития проекта npm-for-frontend