Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.gitignore
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2020 Adrian Bece
Copyright (c) 2021 Adrian Bece

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
71 changes: 46 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,19 @@
<img src="https://res.cloudinary.com/dazdt97d3/image/upload/c_scale,q_auto:best,w_200/v1606558223/omni-logo.jpg" alt="Omni font loader logo">
<br/><br/>
<h1>Gatsby Omni Font Loader</h1>
</div>

Performant asynchronous font loading & Flash Of Unstyled Text (FOUT) handling plugin for Gatsby.
* Simple way to add webfonts or custom fonts to Gatsby project
* Performant asynchronous font loading can be enabled
* Font loading listener can be enabled
* Flash Of Unstyled Text (FOUT) handling support

<div align="center">
<br/>
<img src="https://badgen.net/github/tag/codeAdrian/gatsby-omni-font-loader" />
&nbsp;
<img src="https://badgen.net/npm/dt/gatsby-omni-font-loader" />
&nbsp;
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" />
<img src="https://badgen.net/github/tag/codeAdrian/gatsby-omni-font-loader" /> <img src="https://badgen.net/npm/dt/gatsby-omni-font-loader" /> <img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" />
<br/><br/>

<img src="https://badgen.net/github/stars/codeAdrian/gatsby-omni-font-loader" />
&nbsp;
<img src="https://badgen.net/github/open-issues/codeAdrian/gatsby-omni-font-loader" />
&nbsp;
<img src="https://badgen.net/github/closed-issues/codeAdrian/gatsby-omni-font-loader" />
&nbsp;
<img src="https://badgen.net/github/last-commit/codeAdrian/gatsby-omni-font-loader/main" />
&nbsp;
<img src="https://badgen.net/github/license/codeAdrian/gatsby-omni-font-loader" />
&nbsp;
<img src="https://badgen.net/packagephobia/install/gatsby-omni-font-loader" />
<img src="https://badgen.net/github/stars/codeAdrian/gatsby-omni-font-loader" /> <img src="https://badgen.net/github/open-issues/codeAdrian/gatsby-omni-font-loader" /> <img src="https://badgen.net/github/closed-issues/codeAdrian/gatsby-omni-font-loader" /> <img src="https://badgen.net/github/last-commit/codeAdrian/gatsby-omni-font-loader/main" /> <img src="https://badgen.net/github/license/codeAdrian/gatsby-omni-font-loader" /> <img src="https://badgen.net/packagephobia/install/gatsby-omni-font-loader" />
</div>
<br/><br/>

Expand Down Expand Up @@ -56,6 +47,9 @@ Add the following snippet to `gatsby-config.js` plugins array.
/* Plugin options */
options: {

/* Font loading mode */
mode: "async",

/* Enable font loading listener to handle FOUT */
enableListener: true,

Expand Down Expand Up @@ -101,24 +95,24 @@ Add the following snippet to `gatsby-config.js` plugins array.
</tr>
</thead>
<tbody>
<tr>
</tr>
<td>mode</td>
<td>Can be set to `"async"` (default) or `"render-blocking"`. In `async` mode, fonts are loaded in optimal way, but FOUT is visible. In `render-blocking` mode FOUT will happen in rare cases, but the font files will become render-blocking.</td>
<td>async</td>
<tr>
<td>enableListener</td>
<td>Enable font loading listener to handle Flash Of Unstyled Text. If enabled, CSS classes will be applied to HTML once each font has finished loading.</td>
<td>Works in `async` mode. Enable font loading listener to handle Flash Of Unstyled Text. If enabled, CSS classes will be applied to HTML once each font has finished loading.</td>
<td>false</td>
</tr>
<tr>
<td>preconnect</td>
<td>URLs used for preconnect meta. Base URL where <strong>font files</strong> are hosted.</td>
<td>[]</td>
</tr>
<tr>
<td>interval</td>
<td>Font listener interval (in ms). Default is 300ms. Recommended: >=300ms. </td>
<td>Works if `enableListener` is `true`. Font listener interval (in ms). Default is 300ms. Recommended: >=300ms. </td>
<td>300</td>
</tr>
<tr>
<td>timeout</td>
<td>Font listener timeout value (in ms). Default is 30s (30000ms). Listener will no longer check for loaded fonts after timeout, fonts will still be loaded and displayed, but without handling FOUT.</td>
<td>Works if `enableListener` is `true`. Font listener timeout value (in ms). Default is 30s (30000ms). Listener will no longer check for loaded fonts after timeout, fonts will still be loaded and displayed, but without handling FOUT.</td>
<td>30000</td>
</tr>
<tr>
Expand All @@ -131,9 +125,36 @@ Add the following snippet to `gatsby-config.js` plugins array.
<td>Web fonts config. File link should point to font CSS file. Array of <code>{name: "Font name", file: "https://url-to-font-css.path"}</code> objects.</td>
<td>[]</td>
</tr>
<tr>
<td>preconnect</td>
<td>URLs used for preconnect meta. Base URL where <strong>font files</strong> are hosted.</td>
<td>[]</td>
</tr>
<tr>
<td>preload</td>
<td>Additional URLs used for preload meta. Preload for URLs provided under `file` attribute of `custom` and `web` fonts are automatically generated.</td>
<td>[]</td>
</tr>
<tbody>
</table>

## Async mode vs Render-blocking mode
### Async mode
Load font stylesheets and files in low-priority mode. If you want to add fonts in a performant way, handle FOUT on your own and make sure that the page render times are low, you should use `async` mode.

__Pros:__ Performance, content is displayed before font files are downloaded and parsed
<br/>
__Cons:__ FOUT needs to be handled

### Render-blocking mode
Load font stylesheets and files in high-priority mode. If you want to use this plugin as a simple way to add fonts to your project as you would do in any other project, without any performance optimizations and FOUT handling, you should use `render-blocking` mode.

__Pros:__ Simple markup, FOUT won't occur in most cases
<br/>
__Cons:__ Font stylesheets and font files can delay first content paint time



## Handling FOUT with Font loading listener

When loading fonts asynchronously, Flash Of Unstyled Text (FOUT) might happen because fonts load few moments later after page is displayed to the user.
Expand Down
8 changes: 4 additions & 4 deletions components/FontListener.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ declare var document: { fonts: any }

interface Props {
fontNames: string[]
interval?: number
timeout?: number
interval: number
timeout: number
}

export const FontListener: React.FC<Props> = ({
fontNames,
interval = 300,
timeout = 30000,
interval,
timeout,
}) => {
const [hasLoaded, setHasLoaded] = useState<Boolean>(false)
const [loadedFonts, setLoadedFonts] = useState<string[]>([])
Expand Down
5 changes: 5 additions & 0 deletions consts/defaults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const INTERVAL_DEFAULT = 300

export const TIMEOUT_DEFAULT = 30000

export const MODE_DEFAULT = "async"
1 change: 1 addition & 0 deletions consts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./defaults"
14 changes: 13 additions & 1 deletion gatsby-browser.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import React from "react"
import { AsyncFonts, FontListener } from "./components"
import { INTERVAL_DEFAULT, MODE_DEFAULT, TIMEOUT_DEFAULT } from "./consts"
import { getFontFiles, getFontNames } from "./utils"

export const wrapRootElement = (
{ element },
{ custom = [], web = [], enableListener, interval, timeout }
{
custom = [],
web = [],
enableListener,
interval = INTERVAL_DEFAULT,
timeout = TIMEOUT_DEFAULT,
mode = MODE_DEFAULT,
}
) => {
if (mode !== "async") {
return element
}

const allFonts = [...custom, ...web]
const fontFiles = getFontFiles(allFonts)
const fontNames = getFontNames(allFonts)
Expand Down
30 changes: 19 additions & 11 deletions gatsby-ssr.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
import {
getFontConfig,
getFontFiles,
getFontNames,
getTestFonts,
} from "./utils"
import { MODE_DEFAULT } from "./consts"
import { getFontConfig, getTestFonts } from "./generators"
import { getFontFiles, getFontNames } from "./utils"

export const onRenderBody = (
{ setHeadComponents, setPostBodyComponents },
{ enableListener, preconnect = [], web = [], custom = [] }
{
enableListener,
preconnect = [],
preload = [],
web = [],
custom = [],
mode = MODE_DEFAULT,
}
) => {
const allFonts = [...web, ...custom]
const preload = getFontFiles(allFonts)
const allPreloads = preload.concat(getFontFiles(allFonts))
const fontNames = getFontNames(allFonts)

const preloadConfig = getFontConfig(preconnect, preload)
const preloadConfig = getFontConfig(
preconnect,
allPreloads,
mode === "async" ? [] : allFonts
)

if (enableListener && Boolean(allFonts.length)) {
if (enableListener && Boolean(allFonts.length) && mode === "async") {
const testFontConfig = getTestFonts(fontNames)
setPostBodyComponents(testFontConfig)
}

if(preloadConfig && Boolean(preloadConfig.length)) {
if (preloadConfig && Boolean(preloadConfig.length)) {
setHeadComponents(preloadConfig)
}
}
44 changes: 44 additions & 0 deletions generators/getFontConfig.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from "react"
import { arrayCheck, kebabCase } from "../utils"

export const getFontConfig = (
preconnectConfig: string[],
preloadConfig: string[],
renderBlockingFonts?: { name: string | string[]; file: string }[]
) => {
const headComponents = []

if (arrayCheck(preconnectConfig)) {
preconnectConfig.forEach(href => {
headComponents.push(
<link
key={`preconnect-${href}`}
rel="preconnect"
href={href}
crossOrigin="true"
/>
)
})
}

if (arrayCheck(preloadConfig)) {
preloadConfig.forEach(href => {
headComponents.push(
<link key={`preload-${href}`} rel="preload" as="style" href={href} />
)
})
}

if (arrayCheck(renderBlockingFonts)) {
renderBlockingFonts.forEach(({ name, file }) => {
const key = Array.isArray(name)
? name.map(n => kebabCase(n)).join("-")
: kebabCase(name)
headComponents.push(
<link key={`render-blocking-${key}`} href={file} rel="stylesheet" />
)
})
}

return headComponents
}
File renamed without changes.
2 changes: 2 additions & 0 deletions generators/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./getFontConfig"
export * from "./getTestFonts"
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@
},
"peerDependencies": {
"gatsby": ">=1",
"react-helmet": "^6.1.0"
"react-helmet": ">=6.0.0"
}
}
2 changes: 2 additions & 0 deletions utils/arrayCheck.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const arrayCheck = (arr?: unknown) =>
arr && Array.isArray(arr) && Boolean(arr.length)
39 changes: 0 additions & 39 deletions utils/getFontConfig.tsx

This file was deleted.

2 changes: 2 additions & 0 deletions utils/getFontFiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const getFontFiles = (allFonts: { file: string }[]) =>
allFonts.map(({ file }) => file)
7 changes: 7 additions & 0 deletions utils/getFontNames.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const getFontNames = (allFonts: { name: string }[]) => {
const fontNames = []
allFonts.forEach(({ name }) =>
Array.isArray(name) ? fontNames.push(...name) : fontNames.push(name)
)
return fontNames
}
7 changes: 4 additions & 3 deletions utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./getFontConfig"
export * from "./getTestFonts"
export * from "./utils"
export * from "./arrayCheck"
export * from "./kebabCase"
export * from "./getFontFiles"
export * from "./getFontNames"
6 changes: 6 additions & 0 deletions utils/kebabCase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const kebabCase = (str: string) =>
str
.match(/[A-Z]{2,}(?=[A-Z][a-z0-9]*|\b)|[A-Z]?[a-z0-9]*|[A-Z]|[0-9]+/g)
.filter(Boolean)
.map(x => x.toLowerCase())
.join("-")
17 changes: 0 additions & 17 deletions utils/utils.ts

This file was deleted.