Blog w Gatsby - podstawy
Jak zrobić prosty blog w Gatsby ?
Założenia:
- artykuły napisane w Markdown,
- fragmenty artykułów na stronie głównej,
- każdy artykuł z obrazkiem tytułowym,
- możliwość umieszczania obrazków w treści,
- kolorowanie składni dla bloga o programowaniu.
Nowy projekt w Gatsby
Nowy projekt można rozpocząć na dwa sposoby.
Sposób A (niezalecany)
Można użyć polecenia:
npx gatsby new blog-w-gatsby
To polecenie utworzy cały projekt za nas. Zainstaluje wszystkie potrzebne pakiety i dużo innych niepotrzebnych.
Sposób B (zalecany)
Lepiej zbudować projekt samemu od podstaw. Na początek tworzę katalog i inicjuję projekt. Instaluję też podstawowe pakiety.
mkdir blog-w-gatsby
cd blog-w-gatsby
npm init -y
npm i gatsby react react-dom
W package.json zamieniam wpis scripts na:
"scripts": {
"start": "gatsby develop"
},
Zakładam katalogi:
- src - katalog ze źródłami
- src/pages - miejsce na strony internetowe - w tym przypadku, będzie to tylko strona startowa z listą artykułów (index)
- src/images - katalog z obrazkami
- src/markdown-pages - miejsce na nasze artykuły napisane w markdown
- src/templates - szablon strony z artykułem, na podstawie którego będą generowane strony z plików markdown
mkdir src
mkdir src/pages
mkdir src/images
mkdir src/markdown-pages
mkdir src/templates
Instalacja linterów, formatmerów kodu (opcjonalnie)
Zainstaluję dwa lintery i jeden formatter kodu, aby nasz kod był bardziej zgodny z zasadami dobrych praktyk. Polecam też dodanie pluginów odpowiedzialnych za ich obsługę w edytorze kodu. Ja używam Visual Studio Code i pluginów: ESLint, Prettier - Code formatter i stylelint-plus.
ESLint
Instaluję ESLint i dwie konfiguracje eslint-config-airbnb i eslint-config-prettier.
npm install --save-dev eslint eslint-config-airbnb eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks eslint-config-prettier
Tworzę też plik konfiguracyjny eslinta - ".eslintrc" w katalogu głównym projektu.
{
"extends": ["airbnb", "airbnb/hooks", "prettier", "prettier/react"],
"rules": {
"no-console": 0,
"react/prop-types": 0
}
}
Konfiguracje:
- airbnb - zestaw dobrych praktyk Airbnb,
- airbnb/hooks - dla obsługi React Hooks,
- prettier - dla zgodności z Prettierem, którego zainstaluję za chwilę
- prettier/react - zgodnie z dokumentacją eslint-config-airbnb używa pluginu eslint-plugin-react, który wymaga prettier/react .
Do tego dwa wpisy:
- "no-console": 0 - aby można było używać console.log w kodzie,
- "react/prop-types": 0 - aby nie trzeba było używać prop-types (tylko na potrzeby tego przykładu).
Prettier
Instaluję formater kodu Prettier.
npm install --save-dev --save-exact prettier
Tworzę jego konfigurację w katalogu głównym - ".prettierrc"
{
"singleQuote": true,
"endOfLine": "lf",
"semi": true,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": true
}
Stylelint
I kolejny linter, tym razem do styli.
npm install --save-dev stylelint stylelint-config-recommended stylelint-order
Konfiguracja Stylelint-a w katalogu głównym projektu - ".stylelintrc".
{
"extends": ["stylelint-config-recommended"],
"plugins": ["stylelint-order"],
"rules": {
"order/order": ["custom-properties", "declarations"],
"order/properties-alphabetical-order": true
}
}
Posty w markdown
Posty będą umieszczane w katalogu "src/markdown-pages". Tu przykładowe dwa posty:
post-1.md
---
slug: 'posty/post-1'
date: '2021-02-14'
title: 'Post testowy 1'
featuredImage: ../images/post-1.png
---
Tu jest wstęp to posta testowego nr 1.
<!-- endexcerpt -->
## O tym poście
To jest treść posta 1.
![alt image](../images/image.png)
post-2.md
---
slug: 'posty/post-2'
date: '2021-02-15'
title: 'Post testowy 2'
featuredImage: ../images/post-2.png
---
Tu jest wstęp to posta testowego nr 2.
<!-- endexcerpt -->
## O tym poście
To jest treść posta 2.
```javascript
const x = 1;
const y = 2;
console.log(x + y);
```
Na początku każdego z powyższych plików znajdują się dane posta między liniami "---" (ang. frontmatter). Dane wpisujemy w formie par klucz-wartość. Znajdują się tu: sług - ścieżka do posta, date - data, title - tytuł, featuredImage - obrazek tytułowy. To co znajduje się dalej to Markdown, który będzie zamieniany na HTML. Dodatkowo linia < !-- endexcerpt -- > pozwoli nam podzielić posta na dwie części - wstęp i całą resztę. Wstęp możemy wyświetlić na stronie z listą postów (src/pages/index.jsx).
Instalacja pluginów w Gatsby
Potrzebne będzie kilka pluginów do Gatsby:
- gatsby-source-filesystem - potrzebny do odczytu plików z dysku przez pozostałe pluginy,
- gatsby-transformer-remark - zamienia plik markdown na dane z frontmatter-a i dane HTML,
- gatsby-remark-images - plugin do obsługi obrazków w markdown-ie, obsługuje tylko formaty jpeg i png,
- gatsby-plugin-sharp - wymagany przez gatsby-remark-images,
- gatsby-transformer-sharp - potrzebny do obrazka tytułowego - obsługuje formaty jpeg, png, webp, tiff.
- gatsby-remark-prismjs - dodaje podświetlanie składni
Instalacja pluginów i prismjs do kolorowania składni.
npm install gatsby-source-filesystem gatsby-transformer-remark gatsby-plugin-sharp gatsby-remark-images gatsby-transformer-sharp gatsby-remark-prismjs prismjs
Konfiguracja pluginów w pliku gatsby-config.js:
module.exports = {
plugins: [
`gatsby-plugin-sharp`,
`gatsby-transformer-sharp`,
{
resolve: `gatsby-transformer-remark`,
options: {
excerpt_separator: `<!-- endexcerpt -->`,
plugins: [
`gatsby-remark-prismjs`,
{
resolve: `gatsby-remark-images`,
options: {
maxWidth: 800,
},
},
],
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/src/markdown-pages`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/src/images`,
},
},
],
};
Dodaję wyświetlanie listy postów na stronie głównej src/pages/index.jsx
/* eslint-disable react/no-danger */
import React from 'react';
import { graphql } from 'gatsby';
const IndexPage = ({
data: {
allMarkdownRemark: { edges },
},
}) => (
<ul>
{edges.map((edge) => (
<li key={edge.node.id}>
<a href={edge.node.frontmatter.slug}>{edge.node.frontmatter.title}</a>
<br />
<p dangerouslySetInnerHTML={{ __html: edge.node.excerpt }} />
</li>
))}
</ul>
);
export default IndexPage;
export const pageQuery = graphql`
query {
allMarkdownRemark(sort: { order: DESC, fields: frontmatter___date }) {
edges {
node {
id
excerpt(format: HTML)
frontmatter {
slug
title
}
}
}
}
}
`;
Do wyświetlania obrazków będzie potrzebny gatsby-image:
npm i gatsby-image
Teraz utworzę templatkę dla posta:
src/templates/blogTemplate.jsx
/* eslint-disable react/no-danger */
import Img from 'gatsby-image';
import React from 'react';
import { graphql } from 'gatsby';
export default function BlogPost({ data }) {
const post = data.markdownRemark;
const featuredImgFluid = post.frontmatter.featuredImage.childImageSharp.fluid;
return (
<div>
<h1>{post.frontmatter.title}</h1>
<Img fluid={featuredImgFluid} />
<div dangerouslySetInnerHTML={{ __html: post.html }} />
</div>
);
}
export const query = graphql`
query($slug: String!) {
markdownRemark(frontmatter: { slug: { eq: $slug } }) {
html
frontmatter {
title
featuredImage {
childImageSharp {
fluid(maxWidth: 320) {
...GatsbyImageSharpFluid
}
}
}
}
}
}
`;
Templatkę tę wykorzystam w pliku gatsby-node.js, który będzie odpowiadał za tworzenie strony z postami. Tzn każdy plik .md z katalogu src/markdown-pages zostanie przerobiony na stronę z postem wg powyższego szablonu.
exports.createPages = async ({ actions, graphql, reporter }) => {
const { createPage } = actions;
const blogPostTemplate = require.resolve(`./src/templates/blogTemplate.jsx`);
const result = await graphql(`
{
allMarkdownRemark(sort: { order: DESC, fields: [frontmatter___date] }) {
edges {
node {
frontmatter {
slug
}
}
}
}
}
`);
if (result.errors) {
reporter.panicOnBuild(`Error while running GraphQL query.`);
return;
}
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
createPage({
path: node.frontmatter.slug,
component: blogPostTemplate,
context: {
slug: node.frontmatter.slug,
},
});
});
};
Na koniec w pliku gatsby-browser.js dodaję css odpowiedzialny za kolorowanie składni:
require('prismjs/themes/prism-solarizedlight.css');