refactor(starter): upgrade to new version of gatsby

This commit is contained in:
Alexander Shelepenok
2022-01-09 20:12:31 +00:00
parent 84bdc5899d
commit 67ebabbaac
397 changed files with 26665 additions and 34984 deletions

4
internal/definitions/scss.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
declare module "*.scss" {
const styles: { [className: string]: string };
export = styles;
}

View File

@@ -0,0 +1,2 @@
export { default as routes } from "./routes";
export { default as templates } from "./templates";

View File

@@ -0,0 +1,10 @@
const routes = Object.freeze({
notFoundRoute: "/404",
categoriesListRoute: "/categories",
categoryRoute: "/category",
tagsListRoute: "/tags",
tagRoute: "/tag",
indexRoute: "/",
});
export default routes;

View File

@@ -0,0 +1,12 @@
import path from "path";
const templates = Object.freeze({
categoriesTemplate: path.resolve("./src/templates/categories-template.tsx"),
notFoundTemplate: path.resolve("./src/templates/not-found-template.tsx"),
indexTemplate: path.resolve("./src/templates/index-template.tsx"),
tagsTemplate: path.resolve("./src/templates/tags-template.tsx"),
pageTemplate: path.resolve("./src/templates/page-template.tsx"),
postTemplate: path.resolve("./src/templates/post-template.tsx"),
});
export default templates;

View File

@@ -0,0 +1,142 @@
import { GatsbyNode } from "gatsby";
import * as constants from "./constants";
import * as queries from "./queries";
import * as utils from "./utils";
type CreateWithPagination = (parameters: {
group?: string;
template: string;
total: number;
page: number;
path: string;
}) => void;
const getPaginationPath = (basePath: string, page: number): string =>
[basePath, "page", page].join("/");
const createPages: GatsbyNode["createPages"] = async ({ graphql, actions }) => {
const { createPage } = actions;
createPage({
path: constants.routes.notFoundRoute,
component: constants.templates.notFoundTemplate,
context: {},
});
createPage({
path: constants.routes.tagsListRoute,
component: constants.templates.tagsTemplate,
context: {},
});
createPage({
path: constants.routes.categoriesListRoute,
component: constants.templates.categoriesTemplate,
context: {},
});
const pages = await queries.pagesQuery(graphql);
pages.forEach(edge => {
const { node } = edge;
if (node?.frontmatter?.template === "page" && node?.fields?.slug) {
createPage({
path: node.fields.slug,
component: constants.templates.pageTemplate,
context: { slug: node.fields.slug },
});
} else if (node?.frontmatter?.template === "post" && node?.fields?.slug) {
createPage({
path: node.fields.slug,
component: constants.templates.postTemplate,
context: { slug: node.fields.slug },
});
}
});
const createWithPagination: CreateWithPagination = ({
group,
template,
page,
path,
total,
}) => {
createPage({
component: template,
path: page === 0 ? path : getPaginationPath(path, page),
context: {
group,
pagination: {
currentPage: page,
prevPagePath:
page <= 1 ? path : getPaginationPath(path, utils.decrement(page)),
nextPagePath: getPaginationPath(path, utils.increment(page)),
hasNextPage: page !== utils.decrement(total),
hasPrevPage: page !== 0,
},
},
});
};
const categories = await queries.categoriesQuery(graphql);
const metadata = await queries.metadataQuery(graphql);
const postsLimit = metadata?.postsLimit ?? 1;
categories.forEach(category => {
const total = Math.ceil(category.totalCount / postsLimit);
const path = utils.concat(
constants.routes.categoryRoute,
"/",
utils.toKebabCase(category.fieldValue),
);
for (let page = 0; page < total; page += 1) {
createWithPagination({
group: category.fieldValue,
template: constants.templates.categoriesTemplate,
total,
page,
path,
});
}
});
const tags = await queries.tagsQuery(graphql);
tags.forEach(tag => {
const path = utils.concat(
constants.routes.tagRoute,
"/",
utils.toKebabCase(tag.fieldValue),
);
const total = Math.ceil(tag.totalCount / postsLimit);
for (let page = 0; page < total; page += 1) {
createWithPagination({
group: tag.fieldValue,
template: constants.templates.categoriesTemplate,
total,
page,
path,
});
}
});
const path = constants.routes.indexRoute;
const posts = await queries.postsQuery(graphql);
const total = Math.ceil(posts?.edges?.length ?? 0 / postsLimit);
for (let page = 0; page < total; page += 1) {
createWithPagination({
template: constants.templates.indexTemplate,
total,
page,
path,
});
}
};
export { createPages };

View File

@@ -0,0 +1,58 @@
import { GatsbyNode } from "gatsby";
import { createFilePath } from "gatsby-source-filesystem";
import * as constants from "./constants";
import * as types from "./types";
import * as utils from "./utils";
const onCreateNode: GatsbyNode["onCreateNode"] = ({
node,
actions,
getNode,
}) => {
const { createNodeField } = actions;
if (node.internal.type === "MarkdownRemark") {
const { frontmatter, parent }: types.Edge["node"] = node;
const { tags, category, slug } = frontmatter || {};
if (slug) {
const dirname = parent && getNode(parent)?.relativeDirectory;
const value =
typeof dirname === "string"
? utils.concat("/", dirname, "/", slug)
: utils.concat("/", slug);
createNodeField({ node, name: "slug", value });
} else {
const value = createFilePath({ node, getNode });
createNodeField({ node, name: "slug", value });
}
if (tags) {
const value = tags.map(tag =>
utils.concat(
constants.routes.tagRoute,
"/",
utils.toKebabCase(tag),
"/",
),
);
createNodeField({ node, name: "tagSlugs", value });
}
if (category) {
const value = utils.concat(
constants.routes.categoryRoute,
"/",
utils.toKebabCase(category),
"/",
);
createNodeField({ node, name: "categorySlug", value });
}
}
};
export { onCreateNode };

View File

@@ -0,0 +1,32 @@
import { CreatePagesArgs } from "gatsby";
interface CategoriesQueryResult {
allMarkdownRemark: {
group: Array<{
fieldValue: string;
totalCount: number;
}>;
};
}
const categoriesQuery = async (graphql: CreatePagesArgs["graphql"]) => {
const result = await graphql<CategoriesQueryResult>(`
{
allMarkdownRemark(
filter: {
frontmatter: { template: { eq: "post" }, draft: { ne: true } }
}
sort: { order: DESC, fields: [frontmatter___date] }
) {
group(field: frontmatter___category) {
fieldValue
totalCount
}
}
}
`);
return result?.data?.allMarkdownRemark?.group ?? [];
};
export default categoriesQuery;

View File

@@ -0,0 +1,5 @@
export { default as categoriesQuery } from "./categories-query";
export { default as metadataQuery } from "./metadata-query";
export { default as postsQuery } from "./posts-query";
export { default as pagesQuery } from "./pages-query";
export { default as tagsQuery } from "./tags-query";

View File

@@ -0,0 +1,23 @@
import { CreatePagesArgs } from "gatsby";
interface MetadataQueryResult {
site: {
siteMetadata: {
postsLimit?: number;
};
};
}
const metadataQuery = async (graphql: CreatePagesArgs["graphql"]) => {
const result = await graphql<MetadataQueryResult>(`
query SiteMetaData {
site {
postsLimit
}
}
`);
return result?.data?.site.siteMetadata ?? {};
};
export default metadataQuery;

View File

@@ -0,0 +1,32 @@
import { CreatePagesArgs } from "gatsby";
import * as types from "../types";
export interface PagesQueryResult {
allMarkdownRemark: {
edges?: Array<types.Edge>;
};
}
const pagesQuery = async (graphql: CreatePagesArgs["graphql"]) => {
const result = await graphql<PagesQueryResult>(`
{
allMarkdownRemark(filter: { frontmatter: { draft: { ne: true } } }) {
edges {
node {
frontmatter {
template
}
fields {
slug
}
}
}
}
}
`);
return result?.data?.allMarkdownRemark?.edges ?? [];
};
export default pagesQuery;

View File

@@ -0,0 +1,29 @@
import { CreatePagesArgs } from "gatsby";
import * as types from "../types";
export interface PostsQueryResult {
allMarkdownRemark: {
edges?: Array<types.Edge>;
};
}
const postsQuery = async (graphql: CreatePagesArgs["graphql"]) => {
const result = await graphql<PostsQueryResult>(`
{
allMarkdownRemark(filter: { frontmatter: { draft: { ne: true } } }) {
edges {
node {
fields {
slug
}
}
}
}
}
`);
return result?.data?.allMarkdownRemark;
};
export default postsQuery;

View File

@@ -0,0 +1,31 @@
import { CreatePagesArgs } from "gatsby";
interface TagsQueryResult {
allMarkdownRemark: {
group: Array<{
fieldValue: string;
totalCount: number;
}>;
};
}
const tagsQuery = async (graphql: CreatePagesArgs["graphql"]) => {
const result = await graphql<TagsQueryResult>(`
{
allMarkdownRemark(
filter: {
frontmatter: { template: { eq: "post" }, draft: { ne: true } }
}
) {
group(field: frontmatter___tags) {
fieldValue
totalCount
}
}
}
`);
return result?.data?.allMarkdownRemark?.group || [];
};
export default tagsQuery;

View File

@@ -0,0 +1,25 @@
import { Node as GatsbyNode } from "gatsby";
interface Frontmatter {
slug?: string;
template?: string;
category?: string;
tags?: Array<string>;
}
interface Fields {
slug?: string;
categorySlug?: string;
tagSlugs?: Array<string>;
}
interface Node extends GatsbyNode {
fields?: Fields;
frontmatter?: Frontmatter;
}
interface Edge {
node: Node;
}
export default Edge;

View File

@@ -0,0 +1 @@
export type { default as Edge } from "./edge";

View File

@@ -0,0 +1,3 @@
const concat = (...args: string[]): string => args.join("");
export default concat;

View File

@@ -0,0 +1,3 @@
const decrement = (n: number): number => n - 1;
export default decrement;

View File

@@ -0,0 +1,3 @@
const increment = (n: number): number => n + 1;
export default increment;

View File

@@ -0,0 +1,4 @@
export { default as toKebabCase } from "./to-kebab-case";
export { default as decrement } from "./decrement";
export { default as increment } from "./increment";
export { default as concat } from "./concat";

View File

@@ -0,0 +1,6 @@
const toKebabCase = (str: string = ""): string => str.toLowerCase()
.replace(/[^\w\s]/gi, "")
.split(" ").join("-")
.split("_").join("-");
export default toKebabCase;

View File

@@ -0,0 +1,52 @@
export default {
allMarkdownRemark: {
group: [
{
fieldValue: "typography",
totalCount: 1,
},
{
fieldValue: "design inspiration",
totalCount: 1,
},
],
edges: [
{
node: {
id: "08870ea6-bdc8-4ec6-bf72-1e7d4488eb72",
fields: {
slug: "/posts/perfecting-the-art-of-perfection",
categorySlug: "/typography",
},
frontmatter: {
date: "2016-09-01",
description:
"An Essay on Typography by Eric Gill takes the reader back to the year 1930. The year when a conflict between two worlds came to its term. The machines of the industrial world finally took over the handicrafts.",
category: "typography",
title: "Perfecting the Art of Perfection",
template: "post",
},
html: "",
},
},
{
node: {
id: "066adc91-f87a-4e57-9fef-7a677baf5c1d",
fields: {
slug: "/posts/the-birth-of-movable-type",
categorySlug: "/design-inspiration",
},
frontmatter: {
date: "2016-09-01",
description:
"German inventor Johannes Gutenberg developed a method of movable type and used it to create one of the western worlds first major printed books, the “FortyTwoLine” Bible.",
category: "design inspiration",
title: "Johannes Gutenberg: The Birth of Movable Type",
template: "post",
},
html: "",
},
},
],
},
};

View File

@@ -0,0 +1,8 @@
import contacts from "./contacts";
export default {
photo: "/static/photo.jpg",
bio: "Pellentesque odio nisi, euismod in, pharetra a, ultricies in, diam. Sed arcu.",
name: "John Doe",
contacts,
};

View File

@@ -0,0 +1,8 @@
export default {
rss: "#",
email: "#",
github: "#",
twitter: "#",
telegram: "#",
vkontakte: "#",
};

View File

@@ -0,0 +1,28 @@
import React from "react";
const gatsby = jest.requireActual("gatsby");
export default {
...gatsby,
graphql: jest.fn(),
Link: jest
.fn()
.mockImplementation(
({
activeClassName,
activeStyle,
getProps,
innerRef,
ref,
replace,
to,
...rest
}) =>
React.createElement("a", {
...rest,
href: to,
}),
),
StaticQuery: jest.fn(),
useStaticQuery: jest.fn(),
};

View File

@@ -0,0 +1,7 @@
export { default as allMarkdownRemark } from "./all-markdown-remark";
export { default as markdownRemark } from "./markdown-remark";
export { default as siteMetadata } from "./site-metadata";
export { default as pageContext } from "./page-context";
export { default as contacts } from "./contacts";
export { default as author } from "./author";
export { default as menu } from "./menu";

View File

@@ -0,0 +1,16 @@
export default {
id: "08870ea6-bdc8-4ec6-bf72-1e7d4488eb72",
fields: {
slug: "/posts/perfecting-the-art-of-perfection",
categorySlug: "/typography",
},
frontmatter: {
date: "2016-09-01",
description:
"An Essay on Typography by Eric Gill takes the reader back to the year 1930. The year when a conflict between two worlds came to its term. The machines of the industrial world finally took over the handicrafts.",
category: "typography",
title: "Perfecting the Art of Perfection",
template: "post",
},
html: "",
};

View File

@@ -0,0 +1,5 @@
export default [
{ label: "Articles", path: "/" },
{ label: "About Me", path: "/pages/about" },
{ label: "Contact Me", path: "/pages/contacts" },
];

View File

@@ -0,0 +1,12 @@
export default {
pageContext: {
group: "typography",
pagination: {
currentPage: 2,
prevPagePath: "/typography/page/1",
nextPagePath: "/typography/page/3",
hasNextPage: true,
hasPrevPage: true,
},
},
};

View File

@@ -0,0 +1,17 @@
import author from "./author";
import menu from "./menu";
export default {
site: {
siteMetadata: {
url: "https://www.lumen.local",
title: "Blog by John Doe",
subtitle:
"Pellentesque odio nisi, euismod in, pharetra a, ultricies in, diam. Sed arcu.",
copyright: "All rights reserved.",
postsPerPage: 4,
author,
menu,
},
},
};

View File

@@ -0,0 +1,30 @@
import type { Config } from "@jest/types";
import swc from "./swc-config";
const jestConfig: Config.InitialOptions = {
testEnvironment: "jsdom",
rootDir: "../../",
moduleNameMapper: {
"@/hooks": ["<rootDir>/src/hooks"],
"@/utils": ["<rootDir>/src/utils"],
"@/constants": ["<rootDir>/src/constants"],
"@/utils/([^\\.]*)$": ["<rootDir>/src/utils"],
"@/pages/([^\\.]*)$": ["<rootDir>/src/pages/$1"],
"@/hooks/([^\\.]*)$": ["<rootDir>/src/hooks/$1"],
"@/mocks": ["<rootDir>/internal/testing/__mocks__"],
"@/scss/([^\\.]*)$": ["<rootDir>/src/assets/scss/$1"],
"@/constants/([^\\.]*)$": ["<rootDir>/src/constants/$1"],
"@/images/([^\\.]*)$": ["<rootDir>/src/assets/images/$1"],
"@/components/([^\\.]*)$": ["<rootDir>/src/components/$1"],
".+\\.(css|sass|scss)$": "identity-obj-proxy",
".+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
"identity-obj-proxy",
},
transform: { "^.+\\.(t)sx?$": ["@swc/jest", swc] },
setupFiles: ["<rootDir>/internal/testing/jest-setup.ts"],
testPathIgnorePatterns: ["node_modules", ".cache", "public"],
transformIgnorePatterns: ["node_modules/(?!(gatsby)/)"],
};
export default jestConfig;

View File

@@ -0,0 +1 @@
jest.mock("gatsby", () => jest.requireActual("./__mocks__/gatsby").default);

View File

@@ -0,0 +1,19 @@
const config = {
sourceMaps: true,
module: {
type: "commonjs",
},
jsc: {
parser: {
syntax: "typescript",
tsx: true,
},
transform: {
react: {
runtime: "automatic",
},
},
},
};
export default config;