Що таке Gridsome і як його готувати
August 24, 2021 — ☕☕🍪🍪🍪🍪 8 хвилин чтиваПару років тому я натрапив на подкаст SynTax, в одному з епізодів якого промайнула згадка про Gridsome - генератор статичних вебсайтів, побудований на основі Vue. Ім'я нове, не настільки заїжджене, як, наприклад, Gatsby, тож я відклав його в пам'яті з наміром таки зробити на ньому свій блог (який на той момент перебував на стадії "розчарування в Jekyll").
Станом на вересень 2022 репозиторій Gridsome не відзначається суттєвою активністю, тож я не рекомендую використовувати цей генератор статичних сайтів, особливо для нових проектів
Вступ
Отже, почнімо спочатку. JAMstack - це акронім, який означає J(JavaScript), A(API), та M(Markup), і буквально означає вебсайт, бізнес-логіка якого цілком сконцентрована на клієнтській частині, і не потребує нічого додаткового від веб-сервера. Термін виник з подачі СЕО Netlify Матіаса Біілмана у 2015 році, хоча як технологія цей підхід далеко не новий (той самий Jekyll існує з 2008 року), і до цього була відома під назвою "Static site generators". Можна сперечатися щодо тотожності цих термінів, але в контексті даного допису це не важливо.
Gridsome як продукт з'явився у 2018 році, і фактично являється аналогом Gatsby, тільки побудований на основі Vue. Можливість роботи з будь-якими джерелами даних (локальні файли, CMS, чи публічні інтерфейси, як от: Google-таблиці, Airtable, стрічка Instagram, тощо), універсальний GraphQL-інтерфейс доступу до даних, набір статичних файлів як результат роботи генератора - ці риси притаманні як Gatsby, так і Gridsome.
Початок роботи
Отже, ми таки вирішили скористатися Gridsome. Документація дає достатньо інформації для початку.
- Для ініціалізації проекту (і роботи з проектом загалом) існує спеціальна утиліта командної стрічки, яку нам і треба буде встановити в першу чергу:
- За допомогою Yarn
yarn global add @gridsome/cli
- За допомогою NPM
npm install --global @gridsome/cli
(далі в тексті я буду використовувати Yarn)
- Далі створимо проект, де і буде знаходитися наш блог:
- Команда
gridsome create __blog
створить проект на основі усталеного шаблону - Команда
gridsome develop
, виконана всередині проекту - збере і запустить його. Після цього можна відкритиhttp://localhost:8080
у браузері, і побачити типовий "Hello world":
Наступним кроком документація пропонує створити .vue
-компооненти в директорії src/pages
. Я цей крок пропущу, оскільки у нас блог, і нам значно цікавіше створювати дописи, аніж статичні сторінки.
Робота зі статичним вмістом
Отже, візьмемо класичний варіант - статті в форматі .md
. Для початку нам знадобиться шаблон для відображення цього вмісту. В Gridsome такі шаблони зберігаються у директорії src/templates
. Створімо там файл MarkdownPost.vue
із таким вмістом:
<template>
<Layout>
<h1 class="title">{{ $page.post.title }}</h1>
<em>{{ $page.post.date }}</em>
<div class="content" v-html="$page.post.content" />
</Layout>
</template>
<script>
export default {
metaInfo() { // тут ми встановлюємо вміст мета-тегів сторінки
return {
title: this.$page.post.title,
meta: [
{
name: "description",
content: this.$page.post.description,
},
],
};
},
};
</script>
<page-query>
query MarkdownPost ($id: ID!) { // Назва GraphQL-колекції повинна співпадати з назвою шаблону
post: markdownPost (id: $id) {
title
date
description
content
}
}
</page-query>
Далі, нам потрібно підключити маркдаун-файли як джерело даних. У Gridsome за це відповідає плагін source-filesystem
(так, вірно, як видно з назви, він працює з файлами загалом), яким ми і скористаємося. Внесімо такі зміни в файл gridsome.config.js
:
plugins: [
// ...
{
use: '@gridsome/source-filesystem',
options: {
baseDir: './content',
path: '**/*.md',
typeName: 'MarkdownPage', // Це назва нашої GraphQL-колекції, вона теж повинна співпадати з назвою шаблону
remark: {
externalLinksTarget: '_blank', // Корисні опції для посилань у тексті статті
externalLinksRel: ['noopener', 'noreferrer'],
plugins: ['@gridsome/remark-prismjs'], // Це для кольорової підсвітки синтаксису у вставках коду
},
},
},
// ...
]
Що ми забули?
По-перше, власне дані. Створімо каталог content
у корені проекту - тут будуть жити наші дописи. Також я хочу мати можливість додавати ілюстрації, тому кожна окрема стаття у мене буде жити в окремому каталозі. Gridsome сам підтягне назву каталогу і використає її як посилання на статтю.
Для початку я створю всередині content
каталог how-to-create-a-blog-on-gridsome
, а там - файл index.md
. В результаті у нас повинна вийти отака структура:
$> tree ./content/
./content/
└── how-to-create-a-blog-on-gridsome
└── index.md
1 directory, 1 file
По-друге, кожного разу після внесення змін у файли gridsome.config.js
чи gridsome.server.js
потрібно перезавантажувати проект командою gridsome develop
. Це стосується лише файлів конфігурації, сервер слухняно перезавантажує проект всіх інших випадках, коли ми змінюємо файли застосунку, чи вміст статей.
Після перезапуску gridsome скаже, що щось пішло не так...
$> gridsome develop
Gridsome v0.7.23
Initializing plugins...
Error: No transformer for 'text/markdown' is installed.
#...
...це ми вирішимо встановленням потрібних пакунків yarn add @gridsome/source-filesystem @gridsome/transformer-remark
.
І головне - треба додати сам вміст. Відкриємо нашу статтю ./content/how-to-create-a-blog-on-gridsome/index.md
у текстовому редакторі, і внесемо туди такий вміст:
---
title: Що таке Gridsome і як на ньому зробити блог
date: 2020-01-10
canonical_url: false
description: "Якийсь короткий опис. Markdown працює!"
---
![Example alt image](./gridsome-logo.png)
## Вступ
Beatae doloremque esse nulla porro quidem.
```jsx
class ProfilePage extends React.Component {
showMessage = () => {
alert('Followed ' + this.props.user);
};
}
```
Зверніть увагу, шапка файлу вгорі насправді несхожа на синтаксис маркдауна. Це набір метаданих нашої статті, так званий front-matter
. Справжній вміст починається з зображення.
До речі, про зображення - давайте одразу додамо картинку. Я обрав для цього логотип gridsome, для перевірки більш ніж достатньо.
В результаті структура вмісту повинна мати такий вигляд:
$> tree ./content/
./content/
└── how-to-create-a-blog-on-gridsome
├── gridsome-logo.png
└── index.md
1 directory, 2 files
Готово! Тепер відкриємо сторінку у браузері, використавши назву каталогу в URL: http://localhost:8080/how-to-create-a-blog-on-gridsome
.
Краса! Але не вистачає підсвітки синтаксису. Для неї майже все готово, треба тільки приєднати стилі вибраної теми. Мені сподобалась тема material-oceanic
, її й застосуємо.
- Додаємо до проекту пакунок з темами для Prism:
yarn add prism-themes
; -
А потім відкриймо
src/main.js
, і додаймо ось такий імпорт://... import 'prism-themes/themes/prism-material-oceanic.css'
Тепер стаття готова.
Оформимо головну
Окремі статті - це вже непогано, але для повного щастя нам потрібний повноцінний список дописів для навігації. Досі головна сторінка у нас показувала стоковий "Hello World", пора дати йому ради. Відкриємо src/pages/Index.vue
, і запишемо туди такий вміст:
<template>
<Layout>
<h1>Дописи</h1>
<div v-for="edge in $page.posts.edges" :key="edge.node.id">
<g-link :to="edge.node.path" class="post-preview__title-link">
<h2 class="post-preview__title" v-html="edge.node.title"></h2>
</g-link>
<small class="post-preview__date-mark">
{{ edge.node.date }} — {{ edge.node.timeToRead }} хвилин чтива
</small>
<p class="post-preview__description" v-html="edge.node.description" />
<br /><br />
</div>
</Layout>
</template>
<page-query>
query {
posts: allMarkdownPost() {
edges {
node {
id
title
date
timeToRead
description
path
}
}
}
}
</page-query>
// ...
Тут ми одразу робимо багато всякого:
- Всередині
page-query
ми запитуємо всі статті, скільки їх у нас є всередині каталогаcontent/
. - Всередині
template
, одразу під заголовком, ми їх виводимо одна за одною. Звісно, ми показуємо не весь вміст, нас здебільшого цікавить лише заголовок, дата, і невеличкий опис. Фактично сюди потрапляє все те, що ми раніше закодували в заголовку маркдаун-файлу, уfront-matter
. - Уважний погляд читача міг звернути увагу на поле
timeToRead
, якого насправді немає в оригінальному файлі статті. Це вбудований функціонал парсера gridsome, який додатково до полівfront-matter
додає іще декілька своїх. ЗокремаtimeToRead
обраховується від числа слів у статті
Отже, перейдімо назад на головну http://localhost:8080/
, і ми побачимо щось таке:
Власне, все. Основний функціонал блогу готовий. Пара штрихів - і це можна буде випускати в люди.
Декілька деталей
Правильний URL
Припустімо, я планую розміщувати на одному домені декілька різних проектів, а не лише блог. Щоб потім не думати, чи треба мені налаштовувати переспрямування, чи почати індексацію з нуля, краще одразу організуємо навігацію як треба. Нехай всі статті лежать за адресою /blog/...
. Для цього додамо отакий параметр у gridsome.config.js
:
// ...
templates: {
MarkdownPost: '/blog/:path', // знову ж таки, MarkdownPost має співпадати з назвою нашої graphQL-колекції
},
// нижче - plugins
Перезавантажуємо - і все. Тепер наша стаття доступна за адресою http://localhost:8080/blog/how-to-create-a-blog-on-gridsome
.
Форматування дати
Як ми пам'ятаємо, дата у нас виглядає не дуже. Не всі люди знайомі з форматами ISO 8601, тож давайте подивимося, як ми можемо спростити цей запис. Gridsome одразу пропонує робити форматування всередині GraphQL, для цього треба лише додати отакий вираз до запиту:
<page-query>
query MarkdownPost ($id: ID!) {
post: markdownPost (id: $id) {
title
path
date (format: "MMMM D, YYYY") // оцей
description
content
}
}
</page-query>
Вже краще. Час нам не потрібний, оскільки в метаданих самої статті ми його не вказували.
Що мене в цьому не влаштовує - це локалізація. Точніше, її відсутність. Gridsome не дає можливості контролювати локалізацію в такому форматі запису (і було б дуже дивно, якби він таку можливість давав), тож нам потрібно щось інше. На щастя, далеко ходити не треба - у нас уже є Intl API. Тож GrapQL-запит залишимо як був, а натомість допишемо необхідні перетворення просто в інтерполяцію:
// ...текст шаблону
{{
new Date(edge.node.date).toLocaleDateString('uk-UA', {
year: 'numeric',
month: 'long',
day: 'numeric',
})
}}
—
// ...текст шаблону
Це можна потім винести в якусь загальну функцію для форматування, за потреби.
Але результат виглядає цілком довершено. Окрім оцінки тривалості читання...
папугах печивах
Оцінимо тривалість читання в Мені свого часу дуже сподобалося, як це оформлено у блозі Дена Абрамова.
Дешево й сердито. Що ж... Ні слова більше.
Почнімо з набору емоджі. Я обрав такий. Для спрощення роботи я одразу використав їх еквіваленти в хвилинах як ключі:
const timings = {
0.5: '🍪',
3: '☕',
12: '🫖',
};
В компоненті нам знадобиться два обчислюваних поля. Перше - для власне стрічки з емоджі:
// ...computed properties
emojis() {
let { time } = this;
let str = '';
[12, 3, 0.5].forEach((num) => {
while (time >= num) {
time -= num;
str += timings[num];
}
});
return str;
},
// ...computed properties
Друге - для стрічки з текстом. По-перше - щоб мати можливість в шаблоні вставити цей текст двічі, а по-друге - щоб виправити закінчення в "хвилинах"
// ...computed properties
text() {
const { time } = this;
const lastNumeral = time.toString()[time.toString().length - 1];
let ending = '';
if (['1'].includes(lastNumeral)) {
ending = 'а';
} else if (['2', '3', '4'].includes(lastNumeral)) {
ending = 'и';
}
return `${time} хвилин${ending} чтива`;
},
// ...computed properties
Все, зберемо це в простенький шаблон:
<template>
<span>
<span :title="text">{{ emojis }}</span>
{{ text }}
</span>
</template>
І маємо прекрасне прев'ю для наших дописів.
Висновки
Vue завжди фокусувався на простоті використання і зручності розробника. І Gridsome повністю слідує цим принципам. Цей досвід повинен особливо сподобатися Vue-розробникам. Що ж до новачків - це іще простіше, ніж починати проект на Vue з нуля, оскільки в цьому випадку весь каркас уже зібраний докупи.