├── .yarnrc
├── gatsby-plugin-webfonts
├── index.js
├── .babelrc
├── src
│ ├── components
│ │ ├── css.js
│ │ ├── preconnect.js
│ │ └── preload.js
│ ├── __tests__
│ │ └── create-options.test.js
│ ├── create-options.js
│ ├── gatsby-node.js
│ ├── web-fonts.js
│ ├── gatsby-ssr.js
│ └── modules
│ │ ├── selfHosted.js
│ │ ├── google.js
│ │ └── utils.js
├── .npmignore
├── LICENSE
├── package.json
├── .gitignore
└── README.md
├── e2e-tests
├── development-runtime
│ ├── README.md
│ ├── cypress
│ │ ├── support
│ │ │ ├── index.js
│ │ │ └── commands.js
│ │ ├── fixtures
│ │ │ └── example.json
│ │ ├── integration
│ │ │ ├── navigation.js
│ │ │ └── fonts.js
│ │ └── plugins
│ │ │ └── index.js
│ ├── plugins
│ │ └── gatsby-plugin-top-layout
│ │ │ ├── package.json
│ │ │ ├── gatsby-ssr.js
│ │ │ ├── gatsby-browser.js
│ │ │ └── top-layout.js
│ ├── cypress.json
│ ├── fonts
│ │ ├── OpenSans300.woff
│ │ ├── OpenSans300.woff2
│ │ └── OpenSans400.woff2
│ ├── src
│ │ ├── components
│ │ │ ├── link.js
│ │ │ └── pro-tip.js
│ │ ├── theme.js
│ │ └── pages
│ │ │ ├── about.js
│ │ │ └── index.js
│ ├── package.json
│ ├── .gitignore
│ └── gatsby-config.js
├── production-runtime
│ ├── README.md
│ ├── cypress
│ │ ├── support
│ │ │ ├── index.js
│ │ │ └── commands.js
│ │ ├── fixtures
│ │ │ └── example.json
│ │ ├── integration
│ │ │ ├── navigation.js
│ │ │ └── fonts.js
│ │ └── plugins
│ │ │ └── index.js
│ ├── plugins
│ │ └── gatsby-plugin-top-layout
│ │ │ ├── package.json
│ │ │ ├── gatsby-ssr.js
│ │ │ ├── gatsby-browser.js
│ │ │ └── top-layout.js
│ ├── cypress.json
│ ├── fonts
│ │ ├── OpenSans300.woff
│ │ ├── OpenSans300.woff2
│ │ └── OpenSans400.woff2
│ ├── src
│ │ ├── components
│ │ │ ├── link.js
│ │ │ └── pro-tip.js
│ │ ├── theme.js
│ │ └── pages
│ │ │ ├── about.js
│ │ │ └── index.js
│ ├── package.json
│ ├── .gitignore
│ └── gatsby-config.js
└── path-prefix-prod-runtime
│ ├── README.md
│ ├── cypress
│ ├── support
│ │ ├── index.js
│ │ └── commands.js
│ ├── fixtures
│ │ └── example.json
│ ├── integration
│ │ ├── navigation.js
│ │ ├── path-prefix.js
│ │ └── fonts.js
│ └── plugins
│ │ └── index.js
│ ├── plugins
│ ├── gatsby-plugin-top-layout
│ │ ├── package.json
│ │ ├── gatsby-ssr.js
│ │ ├── gatsby-browser.js
│ │ └── top-layout.js
│ └── src
│ │ ├── components
│ │ ├── link.js
│ │ └── pro-tip.js
│ │ ├── theme.js
│ │ └── pages
│ │ ├── index.js
│ │ └── about.js
│ ├── cypress.json
│ ├── fonts
│ ├── OpenSans300.woff
│ ├── OpenSans300.woff2
│ └── OpenSans400.woff2
│ ├── src
│ ├── components
│ │ ├── link.js
│ │ └── pro-tip.js
│ ├── theme.js
│ └── pages
│ │ ├── about.js
│ │ └── index.js
│ ├── package.json
│ ├── gatsby-config.js
│ └── .gitignore
├── lerna.json
├── .prettierrc.js
├── .eslintignore
├── .eslintrc.js
├── README.md
├── LICENSE
├── .gitignore
├── package.json
└── .circleci
└── config.yml
/.yarnrc:
--------------------------------------------------------------------------------
1 | --install.ignore-engines true
--------------------------------------------------------------------------------
/gatsby-plugin-webfonts/index.js:
--------------------------------------------------------------------------------
1 | //noop
2 |
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/README.md:
--------------------------------------------------------------------------------
1 | # gatsby-plugin-webfonts
2 |
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/README.md:
--------------------------------------------------------------------------------
1 | # gatsby-plugin-webfonts
2 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/README.md:
--------------------------------------------------------------------------------
1 | # gatsby-plugin-webfonts
2 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "npmClient": "yarn",
3 | "useWorkspaces": true,
4 | "version": "2.3.2"
5 | }
6 |
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | import "gatsby-cypress";
2 | import "./commands";
3 |
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | import "gatsby-cypress";
2 | import "./commands";
3 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | import "gatsby-cypress";
2 | import "./commands";
3 |
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/plugins/gatsby-plugin-top-layout/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gatsby-plugin-top-layout"
3 | }
4 |
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/plugins/gatsby-plugin-top-layout/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gatsby-plugin-top-layout"
3 | }
4 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/plugins/gatsby-plugin-top-layout/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gatsby-plugin-top-layout"
3 | }
4 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: true,
3 | singleQuote: false,
4 | tabWidth: 2,
5 | trailingComma: "all",
6 | };
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrl": "http://localhost:8000",
3 | "failOnStatusCode": false,
4 | "video": false
5 | }
6 |
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrl": "http://localhost:9000",
3 | "failOnStatusCode": false,
4 | "video": false
5 | }
6 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /.git
2 | /coverage
3 | /e2e-tests/**/public/
4 |
5 | /gatsby-plugin-webfonts/**/*.js
6 | !/gatsby-plugin-webfonts/src/**/*.js
7 |
8 | node_modules
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrl": "http://localhost:9000/prefix/",
3 | "failOnStatusCode": false,
4 | "video": false
5 | }
6 |
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/fonts/OpenSans300.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hupe1980/gatsby-plugin-webfonts/HEAD/e2e-tests/development-runtime/fonts/OpenSans300.woff
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/fonts/OpenSans300.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hupe1980/gatsby-plugin-webfonts/HEAD/e2e-tests/development-runtime/fonts/OpenSans300.woff2
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/fonts/OpenSans400.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hupe1980/gatsby-plugin-webfonts/HEAD/e2e-tests/development-runtime/fonts/OpenSans400.woff2
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/fonts/OpenSans300.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hupe1980/gatsby-plugin-webfonts/HEAD/e2e-tests/production-runtime/fonts/OpenSans300.woff
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/fonts/OpenSans300.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hupe1980/gatsby-plugin-webfonts/HEAD/e2e-tests/production-runtime/fonts/OpenSans300.woff2
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/fonts/OpenSans400.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hupe1980/gatsby-plugin-webfonts/HEAD/e2e-tests/production-runtime/fonts/OpenSans400.woff2
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/fonts/OpenSans300.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hupe1980/gatsby-plugin-webfonts/HEAD/e2e-tests/path-prefix-prod-runtime/fonts/OpenSans300.woff
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/fonts/OpenSans300.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hupe1980/gatsby-plugin-webfonts/HEAD/e2e-tests/path-prefix-prod-runtime/fonts/OpenSans300.woff2
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/fonts/OpenSans400.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hupe1980/gatsby-plugin-webfonts/HEAD/e2e-tests/path-prefix-prod-runtime/fonts/OpenSans400.woff2
--------------------------------------------------------------------------------
/gatsby-plugin-webfonts/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "babel-preset-gatsby-package",
5 | {
6 | "browser": true
7 | }
8 | ]
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/gatsby-plugin-webfonts/src/components/css.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function Css({ css }) {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/plugins/gatsby-plugin-top-layout/gatsby-ssr.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import TopLayout from "./top-layout";
3 |
4 | export const wrapRootElement = ({ element }) => {
5 | return {element};
6 | };
7 |
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/plugins/gatsby-plugin-top-layout/gatsby-ssr.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import TopLayout from "./top-layout";
3 |
4 | export const wrapRootElement = ({ element }) => {
5 | return {element};
6 | };
7 |
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/plugins/gatsby-plugin-top-layout/gatsby-browser.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import TopLayout from "./top-layout";
3 |
4 | export const wrapRootElement = ({ element }) => {
5 | return {element};
6 | };
7 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/plugins/gatsby-plugin-top-layout/gatsby-ssr.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import TopLayout from "./top-layout";
3 |
4 | export const wrapRootElement = ({ element }) => {
5 | return {element};
6 | };
7 |
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/plugins/gatsby-plugin-top-layout/gatsby-browser.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import TopLayout from "./top-layout";
3 |
4 | export const wrapRootElement = ({ element }) => {
5 | return {element};
6 | };
7 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/plugins/gatsby-plugin-top-layout/gatsby-browser.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import TopLayout from "./top-layout";
3 |
4 | export const wrapRootElement = ({ element }) => {
5 | return {element};
6 | };
7 |
--------------------------------------------------------------------------------
/gatsby-plugin-webfonts/src/components/preconnect.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function Preconnect({
4 | disabled,
5 | href,
6 | crossOrigin = `anonymous`,
7 | }) {
8 | if (disabled) return null;
9 |
10 | return ;
11 | }
12 |
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/src/components/link.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import MuiLink from "@mui/material/Link";
3 | import { Link as GatsbyLink } from "gatsby";
4 |
5 | const Link = React.forwardRef(function Link(props, ref) {
6 | return ;
7 | });
8 |
9 | export default Link;
10 |
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/src/components/link.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import MuiLink from "@mui/material/Link";
3 | import { Link as GatsbyLink } from "gatsby";
4 |
5 | const Link = React.forwardRef(function Link(props, ref) {
6 | return ;
7 | });
8 |
9 | export default Link;
10 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/src/components/link.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import MuiLink from "@mui/material/Link";
3 | import { Link as GatsbyLink } from "gatsby";
4 |
5 | const Link = React.forwardRef(function Link(props, ref) {
6 | return ;
7 | });
8 |
9 | export default Link;
10 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/plugins/src/components/link.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import MuiLink from "@mui/material/Link";
3 | import { Link as GatsbyLink } from "gatsby";
4 |
5 | const Link = React.forwardRef(function Link(props, ref) {
6 | return ;
7 | });
8 |
9 | export default Link;
10 |
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | import "@testing-library/cypress/add-commands";
2 |
3 | Cypress.Commands.add(`lifecycleCallCount`, (action) =>
4 | cy
5 | .window()
6 | .then(
7 | (win) =>
8 | win.___PageComponentLifecycleCallsLog.filter(
9 | (entry) => entry.action === action,
10 | ).length,
11 | ),
12 | );
13 |
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | import "@testing-library/cypress/add-commands";
2 |
3 | Cypress.Commands.add(`lifecycleCallCount`, (action) =>
4 | cy
5 | .window()
6 | .then(
7 | (win) =>
8 | win.___PageComponentLifecycleCallsLog.filter(
9 | (entry) => entry.action === action,
10 | ).length,
11 | ),
12 | );
13 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | import "@testing-library/cypress/add-commands";
2 |
3 | Cypress.Commands.add(`lifecycleCallCount`, (action) =>
4 | cy
5 | .window()
6 | .then(
7 | (win) =>
8 | win.___PageComponentLifecycleCallsLog.filter(
9 | (entry) => entry.action === action,
10 | ).length,
11 | ),
12 | );
13 |
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/cypress/integration/navigation.js:
--------------------------------------------------------------------------------
1 | describe(`navigation`, () => {
2 | beforeEach(() => {
3 | cy.visit(`/`).waitForRouteChange();
4 | });
5 |
6 | it(`displays content from other pages`, () => {
7 | cy.visit(`/about`).waitForRouteChange();
8 |
9 | cy.getTestElement(`about-message`)
10 | .invoke(`text`)
11 | .should(`equal`, `Gatsby example`);
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/cypress/integration/navigation.js:
--------------------------------------------------------------------------------
1 | describe(`navigation`, () => {
2 | beforeEach(() => {
3 | cy.visit(`/`).waitForRouteChange();
4 | });
5 |
6 | it(`displays content from other pages`, () => {
7 | cy.visit(`/about`).waitForRouteChange();
8 |
9 | cy.getTestElement(`about-message`)
10 | .invoke(`text`)
11 | .should(`equal`, `Gatsby example`);
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/cypress/integration/navigation.js:
--------------------------------------------------------------------------------
1 | describe(`navigation`, () => {
2 | beforeEach(() => {
3 | cy.visit(`/`).waitForRouteChange();
4 | });
5 |
6 | it(`displays content from other pages`, () => {
7 | cy.visit(`/about`).waitForRouteChange();
8 |
9 | cy.getTestElement(`about-message`)
10 | .invoke(`text`)
11 | .should(`equal`, `Gatsby example`);
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/cypress/integration/path-prefix.js:
--------------------------------------------------------------------------------
1 | const { pathPrefix } = require(`../../gatsby-config`);
2 |
3 | const withTrailingSlash = (url) => `${url}/`;
4 |
5 | describe(`Production pathPrefix`, () => {
6 | beforeEach(() => {
7 | cy.visit(`/`).waitForRouteChange();
8 | });
9 |
10 | it(`returns 200 on base route`, () => {
11 | cy.location(`pathname`).should(`eq`, withTrailingSlash(pathPrefix));
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: `@babel/eslint-parser`,
3 | extends: [`react-app`, `prettier`],
4 | plugins: [`prettier`],
5 | rules: {
6 | "prettier/prettier": `error`,
7 | quotes: [`error`, `backtick`],
8 | },
9 | overrides: [
10 | {
11 | files: [`**/cypress/integration/**/*`, `**/cypress/support/**/*`],
12 | globals: {
13 | cy: false,
14 | Cypress: false,
15 | },
16 | },
17 | ],
18 | };
19 |
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/src/theme.js:
--------------------------------------------------------------------------------
1 | import red from "@mui/material/colors/red";
2 | import { createTheme } from "@mui/material/styles";
3 |
4 | // A custom theme for this app
5 | const theme = createTheme({
6 | palette: {
7 | primary: {
8 | main: `#556cd6`,
9 | },
10 | secondary: {
11 | main: `#19857b`,
12 | },
13 | error: {
14 | main: red.A400,
15 | },
16 | background: {
17 | default: `#fff`,
18 | },
19 | },
20 | });
21 |
22 | export default theme;
23 |
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/src/theme.js:
--------------------------------------------------------------------------------
1 | import red from "@mui/material/colors/red";
2 | import { createTheme } from "@mui/material/styles";
3 |
4 | // A custom theme for this app
5 | const theme = createTheme({
6 | palette: {
7 | primary: {
8 | main: `#556cd6`,
9 | },
10 | secondary: {
11 | main: `#19857b`,
12 | },
13 | error: {
14 | main: red.A400,
15 | },
16 | background: {
17 | default: `#fff`,
18 | },
19 | },
20 | });
21 |
22 | export default theme;
23 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/src/theme.js:
--------------------------------------------------------------------------------
1 | import red from "@mui/material/colors/red";
2 | import { createTheme } from "@mui/material/styles";
3 |
4 | // A custom theme for this app
5 | const theme = createTheme({
6 | palette: {
7 | primary: {
8 | main: `#556cd6`,
9 | },
10 | secondary: {
11 | main: `#19857b`,
12 | },
13 | error: {
14 | main: red.A400,
15 | },
16 | background: {
17 | default: `#fff`,
18 | },
19 | },
20 | });
21 |
22 | export default theme;
23 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/plugins/src/theme.js:
--------------------------------------------------------------------------------
1 | import red from "@mui/material/colors/red";
2 | import { createTheme } from "@mui/material/styles";
3 |
4 | // A custom theme for this app
5 | const theme = createTheme({
6 | palette: {
7 | primary: {
8 | main: `#556cd6`,
9 | },
10 | secondary: {
11 | main: `#19857b`,
12 | },
13 | error: {
14 | main: red.A400,
15 | },
16 | background: {
17 | default: `#fff`,
18 | },
19 | },
20 | });
21 |
22 | export default theme;
23 |
--------------------------------------------------------------------------------
/gatsby-plugin-webfonts/src/__tests__/create-options.test.js:
--------------------------------------------------------------------------------
1 | import createOptions, { defaultOptions } from "../create-options";
2 |
3 | describe(`createOptions`, () => {
4 | it(`should return default options`, () => {
5 | const options = createOptions();
6 | expect(options).toEqual(defaultOptions);
7 | });
8 |
9 | it(`should overwrite default options`, () => {
10 | const expected = [`test`];
11 | const { formats } = createOptions({ formats: expected });
12 | expect(formats).toEqual(expected);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # gatsby-plugin-webfonts
2 |
3 | > A [Gatsby](https://github.com/gatsbyjs/gatsby) plugin to handle cdn, base64 and self hosted webfonts
4 |
5 | - Creates minified @font-face CSS rules
6 | - Supports font-display property (Default: 'swap')
7 | - Handles preconnect and preload optimizations
8 | - Automatically downloads fonts for self hosting
9 | - Supports cdn, base64 and self hosted Fonts (Default: 'selfHosted')
10 |
11 | ## Install
12 |
13 | ```sh
14 | // with npm
15 | npm install gatsby-plugin-webfonts
16 |
17 | // with yarn
18 | yarn add gatsby-plugin-webfonts
19 | ```
20 |
21 | ## Documentation
22 |
23 | [The documentation](/gatsby-plugin-webfonts/README.md)
24 |
25 | ## License
26 |
27 | [MIT](LICENSE)
28 |
--------------------------------------------------------------------------------
/gatsby-plugin-webfonts/src/components/preload.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function Preload({
4 | css,
5 | disabled,
6 | format = `woff2`,
7 | crossOrigin = `anonymous`,
8 | }) {
9 | if (disabled) return null;
10 |
11 | const regex = /url\((.+?)\)/gi;
12 | const fontUrls = css
13 | .match(regex)
14 | .map((urlString) => urlString.replace(regex, `$1`))
15 | .filter((urlString) => urlString.endsWith(`.${format}`));
16 |
17 | return fontUrls.map((url, key) => (
18 |
26 | ));
27 | }
28 |
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example plugins/index.js can be used to load plugins
3 | //
4 | // You can change the location of this file or turn off loading
5 | // the plugins file with the 'pluginsFile' configuration option.
6 | //
7 | // You can read more here:
8 | // https://on.cypress.io/plugins-guide
9 | // ***********************************************************
10 |
11 | // This function is called when a project is opened or re-opened (e.g. due to
12 | // the project's config changing)
13 |
14 | module.exports = (on, config) => {
15 | // `on` is used to hook into various events Cypress emits
16 | // `config` is the resolved Cypress config
17 | };
18 |
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example plugins/index.js can be used to load plugins
3 | //
4 | // You can change the location of this file or turn off loading
5 | // the plugins file with the 'pluginsFile' configuration option.
6 | //
7 | // You can read more here:
8 | // https://on.cypress.io/plugins-guide
9 | // ***********************************************************
10 |
11 | // This function is called when a project is opened or re-opened (e.g. due to
12 | // the project's config changing)
13 |
14 | module.exports = (on, config) => {
15 | // `on` is used to hook into various events Cypress emits
16 | // `config` is the resolved Cypress config
17 | };
18 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example plugins/index.js can be used to load plugins
3 | //
4 | // You can change the location of this file or turn off loading
5 | // the plugins file with the 'pluginsFile' configuration option.
6 | //
7 | // You can read more here:
8 | // https://on.cypress.io/plugins-guide
9 | // ***********************************************************
10 |
11 | // This function is called when a project is opened or re-opened (e.g. due to
12 | // the project's config changing)
13 |
14 | module.exports = (on, config) => {
15 | // `on` is used to hook into various events Cypress emits
16 | // `config` is the resolved Cypress config
17 | };
18 |
--------------------------------------------------------------------------------
/gatsby-plugin-webfonts/.npmignore:
--------------------------------------------------------------------------------
1 | .babelrc
2 | # Logs
3 | logs
4 | *.log
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
18 | .grunt
19 |
20 | # node-waf configuration
21 | .lock-wscript
22 |
23 | # Compiled binary addons (http://nodejs.org/api/addons.html)
24 | build/Release
25 |
26 | # Dependency directory
27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
28 | node_modules
29 | *.un~
30 | yarn.lock
31 | src
32 | flow-typed
33 | coverage
34 | decls
35 | examples
36 |
--------------------------------------------------------------------------------
/gatsby-plugin-webfonts/src/create-options.js:
--------------------------------------------------------------------------------
1 | export const defaultOptions = {
2 | fonts: {},
3 | formats: [`woff2`, `woff`],
4 | useMinify: true,
5 | usePreload: true,
6 | usePreconnect: false,
7 | formatAgents: {
8 | eot: `Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C; .NET4.0E)`,
9 | ttf: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/534.59.8 (KHTML, like Gecko) Version/5.1.9 Safari/534.59.8`,
10 | woff: `Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; rv:11.0) like Gecko`,
11 | woff2: `Mozilla/5.0 (Windows NT 10.0; Win64; x64; ServiceUI 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393`,
12 | },
13 | };
14 |
15 | export default function createOptions(pluginOptions) {
16 | return {
17 | ...defaultOptions,
18 | ...pluginOptions,
19 | };
20 | }
21 |
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/plugins/gatsby-plugin-top-layout/top-layout.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { Helmet } from "react-helmet";
4 | import CssBaseline from "@mui/material/CssBaseline";
5 | import { ThemeProvider } from "@mui/material/styles";
6 | import theme from "../../src/theme";
7 |
8 | export default function TopLayout(props) {
9 | return (
10 | <>
11 |
12 |
16 |
20 |
21 |
22 | {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
23 |
24 | {props.children}
25 |
26 | >
27 | );
28 | }
29 |
30 | TopLayout.propTypes = {
31 | children: PropTypes.node,
32 | };
33 |
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/plugins/gatsby-plugin-top-layout/top-layout.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { Helmet } from "react-helmet";
4 | import CssBaseline from "@mui/material/CssBaseline";
5 | import { ThemeProvider } from "@mui/material/styles";
6 | import theme from "../../src/theme";
7 |
8 | export default function TopLayout(props) {
9 | return (
10 | <>
11 |
12 |
16 |
20 |
21 |
22 | {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
23 |
24 | {props.children}
25 |
26 | >
27 | );
28 | }
29 |
30 | TopLayout.propTypes = {
31 | children: PropTypes.node,
32 | };
33 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/plugins/gatsby-plugin-top-layout/top-layout.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { Helmet } from "react-helmet";
4 | import CssBaseline from "@mui/material/CssBaseline";
5 | import { ThemeProvider } from "@mui/material/styles";
6 | import theme from "../../src/theme";
7 |
8 | export default function TopLayout(props) {
9 | return (
10 | <>
11 |
12 |
16 |
20 |
21 |
22 | {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
23 |
24 | {props.children}
25 |
26 | >
27 | );
28 | }
29 |
30 | TopLayout.propTypes = {
31 | children: PropTypes.node,
32 | };
33 |
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/src/components/pro-tip.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import Link from "@mui/material/Link";
3 | import SvgIcon from "@mui/material/SvgIcon";
4 | import Typography from "@mui/material/Typography";
5 |
6 | function LightBulbIcon(props) {
7 | return (
8 |
9 |
10 |
11 | );
12 | }
13 |
14 | export default function ProTip() {
15 | return (
16 |
17 |
18 | Pro tip: See more{` `}
19 |
20 | templates
21 |
22 | {` `}
23 | on the Material-UI documentation.
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/src/components/pro-tip.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import Link from "@mui/material/Link";
3 | import SvgIcon from "@mui/material/SvgIcon";
4 | import Typography from "@mui/material/Typography";
5 |
6 | function LightBulbIcon(props) {
7 | return (
8 |
9 |
10 |
11 | );
12 | }
13 |
14 | export default function ProTip() {
15 | return (
16 |
17 |
18 | Pro tip: See more{` `}
19 |
20 | templates
21 |
22 | {` `}
23 | on the Material-UI documentation.
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/src/components/pro-tip.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import Link from "@mui/material/Link";
3 | import SvgIcon from "@mui/material/SvgIcon";
4 | import Typography from "@mui/material/Typography";
5 |
6 | function LightBulbIcon(props) {
7 | return (
8 |
9 |
10 |
11 | );
12 | }
13 |
14 | export default function ProTip() {
15 | return (
16 |
17 |
18 | Pro tip: See more{` `}
19 |
20 | templates
21 |
22 | {` `}
23 | on the Material-UI documentation.
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/plugins/src/components/pro-tip.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import Link from "@mui/material/Link";
3 | import SvgIcon from "@mui/material/SvgIcon";
4 | import Typography from "@mui/material/Typography";
5 |
6 | function LightBulbIcon(props) {
7 | return (
8 |
9 |
10 |
11 | );
12 | }
13 |
14 | export default function ProTip() {
15 | return (
16 |
17 |
18 | Pro tip: See more{` `}
19 |
20 | templates
21 |
22 | {` `}
23 | on the Material-UI documentation.
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/gatsby-plugin-webfonts/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/plugins/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Container from "@mui/material/Container";
3 | import Typography from "@mui/material/Typography";
4 | import Box from "@mui/material/Box";
5 | import MuiLink from "@mui/material/Link";
6 |
7 | import ProTip from "../components/pro-tip";
8 | import Link from "../components/link";
9 |
10 | function MadeWithLove() {
11 | return (
12 |
13 | {`Built with love by the `}
14 |
15 | Material-UI
16 |
17 | {` team.`}
18 |
19 | );
20 | }
21 |
22 | export default function App() {
23 | return (
24 |
25 |
26 |
27 | Gatsby example
28 |
29 |
30 | Go to the about page
31 |
32 |
33 |
34 |
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/src/pages/about.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Container from "@mui/material/Container";
3 | import Typography from "@mui/material/Typography";
4 | import Box from "@mui/material/Box";
5 | import MuiLink from "@mui/material/Link";
6 |
7 | import ProTip from "../components/pro-tip";
8 | import Link from "../components/link";
9 |
10 | function MadeWithLove() {
11 | return (
12 |
13 | {`Built with love by the `}
14 |
15 | Material-UI
16 |
17 | {` team.`}
18 |
19 | );
20 | }
21 |
22 | export default function App() {
23 | return (
24 |
25 |
26 |
32 | Gatsby example
33 |
34 | Go to the main page
35 |
36 |
37 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/src/pages/about.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Container from "@mui/material/Container";
3 | import Typography from "@mui/material/Typography";
4 | import Box from "@mui/material/Box";
5 | import MuiLink from "@mui/material/Link";
6 |
7 | import ProTip from "../components/pro-tip";
8 | import Link from "../components/link";
9 |
10 | function MadeWithLove() {
11 | return (
12 |
13 | {`Built with love by the `}
14 |
15 | Material-UI
16 |
17 | {` team.`}
18 |
19 | );
20 | }
21 |
22 | export default function App() {
23 | return (
24 |
25 |
26 |
32 | Gatsby example
33 |
34 | Go to the main page
35 |
36 |
37 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/src/pages/about.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Container from "@mui/material/Container";
3 | import Typography from "@mui/material/Typography";
4 | import Box from "@mui/material/Box";
5 | import MuiLink from "@mui/material/Link";
6 |
7 | import ProTip from "../components/pro-tip";
8 | import Link from "../components/link";
9 |
10 | function MadeWithLove() {
11 | return (
12 |
13 | {`Built with love by the `}
14 |
15 | Material-UI
16 |
17 | {` team.`}
18 |
19 | );
20 | }
21 |
22 | export default function App() {
23 | return (
24 |
25 |
26 |
32 | Gatsby example
33 |
34 | Go to the main page
35 |
36 |
37 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/gatsby-plugin-webfonts/src/gatsby-node.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import fs from "fs-extra";
3 | import isEmpty from "lodash.isempty";
4 |
5 | import createOptions from "./create-options";
6 | import webFonts from "./web-fonts";
7 |
8 | export const onPreBootstrap = async (
9 | { cache, createContentDigest, store, pathPrefix, reporter },
10 | pluginOptions,
11 | ) => {
12 | if (isEmpty(pluginOptions.fonts)) return;
13 |
14 | const { directory } = store.getState().program;
15 |
16 | const cacheFolder = path.join(directory, `.cache`, `webfonts`);
17 | const publicFolder = path.join(directory, `public`, `static`, `webfonts`);
18 |
19 | const options = createOptions({
20 | ...pluginOptions,
21 | cacheFolder,
22 | pathPrefix,
23 | directory,
24 | });
25 |
26 | const optionsCacheKey = `options-${createContentDigest(options)}`;
27 |
28 | const cachedOptions = await cache.get(optionsCacheKey);
29 |
30 | if (!cachedOptions) {
31 | await webFonts(options, reporter);
32 | await cache.set(optionsCacheKey, options);
33 | }
34 |
35 | const filter = (src) => path.extname(src) !== `.css`;
36 | await fs.copy(cacheFolder, publicFolder, { filter });
37 | };
38 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/plugins/src/pages/about.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Container from "@mui/material/Container";
3 | import Typography from "@mui/material/Typography";
4 | import Box from "@mui/material/Box";
5 | import MuiLink from "@mui/material/Link";
6 |
7 | import ProTip from "../components/pro-tip";
8 | import Link from "../components/link";
9 |
10 | function MadeWithLove() {
11 | return (
12 |
13 | {`Built with love by the `}
14 |
15 | Material-UI
16 |
17 | {` team.`}
18 |
19 | );
20 | }
21 |
22 | export default function App() {
23 | return (
24 |
25 |
26 |
32 | Gatsby example
33 |
34 | Go to the main page
35 |
36 |
37 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/gatsby-plugin-webfonts/src/web-fonts.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import fs from "fs-extra";
3 | import postcss from "postcss";
4 | import cssnano from "cssnano";
5 |
6 | import google from "./modules/google";
7 | import selfHosted from "./modules/selfHosted";
8 |
9 | export default async function webFonts(options, reporter) {
10 | const modules = {
11 | google: google(options, 1),
12 | google2: google(options, 2),
13 | selfHosted: selfHosted(options, reporter),
14 | };
15 |
16 | const merge = async (css) => {
17 | const plugins = options.useMinify
18 | ? [
19 | cssnano({
20 | preset: `default`,
21 | }),
22 | ]
23 | : [];
24 |
25 | const { css: minifiedCss } = await postcss(plugins).process(css, {
26 | from: undefined,
27 | });
28 |
29 | return minifiedCss;
30 | };
31 |
32 | const cssStrings = await Promise.all(
33 | Object.keys(options.fonts).map((key) => {
34 | return modules[key](options.fonts[key]);
35 | }),
36 | );
37 |
38 | const css = await merge(cssStrings.flat().join(``));
39 | const filePath = path.join(options.cacheFolder, `webfonts.css`);
40 | await fs.outputFile(filePath, css);
41 | }
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
63 | # Lib folder
64 | lib
65 |
66 | .DS_Store
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "production-runtime",
3 | "private": true,
4 | "version": "2.3.2",
5 | "dependencies": {
6 | "@emotion/react": "11.10.5",
7 | "@emotion/styled": "11.10.5",
8 | "@mui/material": "5.10.17",
9 | "gatsby": "latest",
10 | "gatsby-plugin-material-ui": "latest",
11 | "gatsby-plugin-react-helmet": "latest",
12 | "gatsby-plugin-webfonts": "^2.3.2",
13 | "react": "18.2.0",
14 | "react-dom": "18.2.0",
15 | "react-helmet": "latest"
16 | },
17 | "license": "MIT",
18 | "scripts": {
19 | "build": "gatsby build",
20 | "develop": "gatsby develop",
21 | "format": "prettier --write src/**/*.{js,jsx}",
22 | "start": "yarn develop",
23 | "serve": "gatsby serve",
24 | "cy:open": "cypress open",
25 | "cy:run": "cypress run --browser chrome --headless",
26 | "start-server-and-test": "start-server-and-test serve http://localhost:9000 cy:run",
27 | "test": "CYPRESS_SUPPORT=y yarn build && yarn start-server-and-test "
28 | },
29 | "devDependencies": {
30 | "@testing-library/cypress": "^8.0.7",
31 | "cypress": "^9.5.3",
32 | "gatsby-cypress": "^2.11.0",
33 | "start-server-and-test": "^1.15.2"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "development-runtime",
3 | "private": true,
4 | "version": "2.3.2",
5 | "dependencies": {
6 | "@emotion/react": "11.10.5",
7 | "@emotion/styled": "11.10.5",
8 | "@mui/material": "5.10.17",
9 | "gatsby": "latest",
10 | "gatsby-plugin-material-ui": "latest",
11 | "gatsby-plugin-react-helmet": "latest",
12 | "gatsby-plugin-webfonts": "^2.3.2",
13 | "react": "18.2.0",
14 | "react-dom": "18.2.0",
15 | "react-helmet": "latest"
16 | },
17 | "license": "MIT",
18 | "scripts": {
19 | "build": "gatsby build",
20 | "develop": "gatsby develop",
21 | "format": "prettier --write src/**/*.{js,jsx}",
22 | "start": "yarn develop",
23 | "serve": "gatsby serve",
24 | "cy": "cypress",
25 | "cy:open": "cypress open",
26 | "cy:run": "cypress run --browser chrome --headless",
27 | "start-server-and-test": "start-server-and-test develop http://localhost:8000 cy:run",
28 | "test": "CYPRESS_SUPPORT=y yarn start-server-and-test "
29 | },
30 | "devDependencies": {
31 | "@testing-library/cypress": "^8.0.7",
32 | "cypress": "^9.5.3",
33 | "gatsby-cypress": "^2.11.0",
34 | "start-server-and-test": "^1.15.2"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/gatsby-plugin-webfonts/src/gatsby-ssr.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import isEmpty from "lodash.isempty";
3 |
4 | import createOptions from "./create-options";
5 | import { isGooglePreconnectEnabled } from "./modules/google";
6 | import Preconnect from "./components/preconnect";
7 | import Preload from "./components/preload";
8 | import Css from "./components/css";
9 | // https://www.gatsbyjs.com/docs/reference/release-notes/migrating-from-v2-to-v3/#using-fs-in-ssr
10 | // eslint-disable-next-line import/no-webpack-loader-syntax
11 | import webfontsCss from "!!raw-loader!/.cache/webfonts/webfonts.css";
12 |
13 | export const onRenderBody = ({ setHeadComponents }, pluginOptions) => {
14 | if (isEmpty(pluginOptions.fonts)) return;
15 |
16 | const { usePreload, formats, ...options } = createOptions(pluginOptions);
17 |
18 | setHeadComponents([
19 | ,
24 | ,
30 | ,
31 | ]);
32 | };
33 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "path-prefix-prod-runtime",
3 | "private": true,
4 | "version": "2.3.2",
5 | "dependencies": {
6 | "@emotion/react": "11.10.5",
7 | "@emotion/styled": "11.10.5",
8 | "@mui/material": "5.10.17",
9 | "gatsby": "latest",
10 | "gatsby-plugin-material-ui": "latest",
11 | "gatsby-plugin-react-helmet": "latest",
12 | "gatsby-plugin-webfonts": "^2.3.2",
13 | "react": "18.2.0",
14 | "react-dom": "18.2.0",
15 | "react-helmet": "latest"
16 | },
17 | "license": "MIT",
18 | "scripts": {
19 | "build": "gatsby build --prefix-paths",
20 | "develop": "gatsby develop",
21 | "format": "prettier --write src/**/*.{js,jsx}",
22 | "start": "yarn develop",
23 | "serve": "gatsby serve --prefix-paths",
24 | "cy:open": "cypress open",
25 | "cy:run": "cypress run --browser chrome --headless",
26 | "start-server-and-test": "start-server-and-test serve http://localhost:9000/prefix/ cy:run",
27 | "test": "CYPRESS_SUPPORT=y yarn build && yarn start-server-and-test "
28 | },
29 | "devDependencies": {
30 | "@testing-library/cypress": "^8.0.7",
31 | "cypress": "^9.5.3",
32 | "gatsby-cypress": "^2.11.0",
33 | "start-server-and-test": "^1.15.2"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/gatsby-config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | pathPrefix: `/prefix`,
3 | plugins: [
4 | `gatsby-plugin-top-layout`,
5 | `gatsby-plugin-material-ui`,
6 | {
7 | resolve: `gatsby-plugin-webfonts`,
8 | options: {
9 | fonts: {
10 | google: [
11 | {
12 | family: `Roboto`,
13 | variants: [`300`, `400`, `500`],
14 | },
15 | ],
16 | google2: [
17 | {
18 | family: `Rubik`,
19 | axes: `wght@300..600`,
20 | },
21 | ],
22 | selfHosted: [
23 | {
24 | family: `Open Sans`,
25 | urls: {
26 | woff2: `/fonts/OpenSans300.woff2`,
27 | },
28 | fontStyle: `normal`,
29 | fontWeight: 300,
30 | },
31 | {
32 | family: `Open Sans`,
33 | urls: {
34 | woff2: `/fonts/OpenSans400.woff2`,
35 | },
36 | fontStyle: `normal`,
37 | fontWeight: 400,
38 | },
39 | ],
40 | },
41 | },
42 | },
43 | `gatsby-plugin-react-helmet`,
44 | ],
45 | siteMetadata: {
46 | title: `My page`,
47 | },
48 | };
49 |
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # dotenv environment variables file
55 | .env
56 |
57 | # gatsby files
58 | .cache/
59 | public
60 |
61 | # Mac files
62 | .DS_Store
63 |
64 | # Yarn
65 | yarn-error.log
66 | .pnp/
67 | .pnp.js
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # dotenv environment variables file
55 | .env
56 |
57 | # gatsby files
58 | .cache/
59 | public
60 |
61 | # Mac files
62 | .DS_Store
63 |
64 | # Yarn
65 | yarn-error.log
66 | .pnp/
67 | .pnp.js
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/gatsby-config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | `gatsby-plugin-top-layout`,
4 | `gatsby-plugin-material-ui`,
5 | {
6 | resolve: `gatsby-plugin-webfonts`,
7 | options: {
8 | fonts: {
9 | google: [
10 | {
11 | family: `Roboto`,
12 | variants: [`300`, `400`, `500`],
13 | },
14 | ],
15 | google2: [
16 | {
17 | family: `Rubik`,
18 | axes: `wght@300..600`,
19 | },
20 | ],
21 | selfHosted: [
22 | {
23 | family: `Open Sans`,
24 | urls: {
25 | woff2: `/fonts/OpenSans300.woff2`,
26 | },
27 | fontStyle: `normal`,
28 | fontWeight: 300,
29 | display: `block`,
30 | },
31 | {
32 | family: `Open Sans`,
33 | urls: {
34 | woff2: `/fonts/OpenSans400.woff2`,
35 | },
36 | fontStyle: `normal`,
37 | fontWeight: 400,
38 | },
39 | ],
40 | },
41 | },
42 | },
43 | `gatsby-plugin-react-helmet`,
44 | ],
45 | siteMetadata: {
46 | title: `My page`,
47 | },
48 | };
49 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # dotenv environment variables file
55 | .env
56 |
57 | # gatsby files
58 | .cache/
59 | public
60 |
61 | # Mac files
62 | .DS_Store
63 |
64 | # Yarn
65 | yarn-error.log
66 | .pnp/
67 | .pnp.js
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/gatsby-config.js:
--------------------------------------------------------------------------------
1 | const path = require(`path`);
2 |
3 | module.exports = {
4 | plugins: [
5 | `gatsby-plugin-top-layout`,
6 | `gatsby-plugin-material-ui`,
7 | {
8 | resolve: `gatsby-plugin-webfonts`,
9 | options: {
10 | fonts: {
11 | google: [
12 | {
13 | family: `Roboto`,
14 | variants: [`300`, `400`, `500`],
15 | },
16 | ],
17 | google2: [
18 | {
19 | family: `Rubik`,
20 | axes: `wght@300..600`,
21 | },
22 | ],
23 | selfHosted: [
24 | {
25 | family: `Open Sans`,
26 | urls: {
27 | woff2: path.join(`fonts`, `OpenSans300.woff2`),
28 | },
29 | fontStyle: `normal`,
30 | fontWeight: 300,
31 | },
32 | {
33 | family: `Open Sans`,
34 | urls: {
35 | woff2: path.join(`fonts`, `OpenSans400.woff2`),
36 | },
37 | fontStyle: `normal`,
38 | fontWeight: 400,
39 | },
40 | ],
41 | },
42 | },
43 | },
44 | `gatsby-plugin-react-helmet`,
45 | ],
46 | siteMetadata: {
47 | title: `My page`,
48 | },
49 | };
50 |
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Container from "@mui/material/Container";
3 | import Typography from "@mui/material/Typography";
4 | import Box from "@mui/material/Box";
5 | import MuiLink from "@mui/material/Link";
6 |
7 | import ProTip from "../components/pro-tip";
8 | import Link from "../components/link";
9 |
10 | function MadeWithLove() {
11 | return (
12 |
13 | {`Built with love by the `}
14 |
15 | Material-UI
16 |
17 | {` team.`}
18 |
19 | );
20 | }
21 |
22 | export default function App() {
23 | return (
24 |
25 |
26 |
27 | Gatsby example
28 |
29 |
36 | Static Font Example
37 |
38 |
39 | Go to the about page
40 |
41 |
42 |
43 |
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Container from "@mui/material/Container";
3 | import Typography from "@mui/material/Typography";
4 | import Box from "@mui/material/Box";
5 | import MuiLink from "@mui/material/Link";
6 |
7 | import ProTip from "../components/pro-tip";
8 | import Link from "../components/link";
9 |
10 | function MadeWithLove() {
11 | return (
12 |
13 | {`Built with love by the `}
14 |
15 | Material-UI
16 |
17 | {` team.`}
18 |
19 | );
20 | }
21 |
22 | export default function App() {
23 | return (
24 |
25 |
26 |
27 | Gatsby example
28 |
29 |
36 | Static Font Example
37 |
38 |
39 | Go to the about page
40 |
41 |
42 |
43 |
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Container from "@mui/material/Container";
3 | import Typography from "@mui/material/Typography";
4 | import Box from "@mui/material/Box";
5 | import MuiLink from "@mui/material/Link";
6 |
7 | import ProTip from "../components/pro-tip";
8 | import Link from "../components/link";
9 |
10 | function MadeWithLove() {
11 | return (
12 |
13 | {`Built with love by the `}
14 |
15 | Material-UI
16 |
17 | {` team.`}
18 |
19 | );
20 | }
21 |
22 | export default function App() {
23 | return (
24 |
25 |
26 |
27 | Gatsby example
28 |
29 |
36 | Static Font Example
37 |
38 |
39 | Go to the about page
40 |
41 |
42 |
43 |
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/gatsby-plugin-webfonts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gatsby-plugin-webfonts",
3 | "version": "2.3.2",
4 | "description": "A Gatsby plugin to handle cdn, base64 and self hosted webfonts",
5 | "license": "MIT",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/hupe1980/gatsby-plugin-webfonts"
9 | },
10 | "keywords": [
11 | "gatsby-plugin",
12 | "gatsby",
13 | "plugin",
14 | "web",
15 | "fonts",
16 | "loader",
17 | "google",
18 | "webfonts",
19 | "webfontloader"
20 | ],
21 | "main": "index.js",
22 | "scripts": {
23 | "build": "babel src --out-dir . --ignore **/__tests__",
24 | "build: watch": "babel -w src --out-dir . --ignore **/__tests__",
25 | "prepare": "cross-env NODE_ENV=production npm run build",
26 | "jest": "jest",
27 | "jest:watch": "jest --watch"
28 | },
29 | "peerDependencies": {
30 | "gatsby": "^3.0.0 || ^4.0.0 || ^5.0.0"
31 | },
32 | "dependencies": {
33 | "axios": "^1.2.1",
34 | "cssnano": "^5.1.14",
35 | "fs-extra": "^11.1.0",
36 | "lodash.isempty": "^4.4.0",
37 | "postcss": "^8.4.19",
38 | "postcss-js": "^4.0.0"
39 | },
40 | "devDependencies": {
41 | "@babel/cli": "^7.19.3",
42 | "@babel/core": "^7.20.5",
43 | "babel-jest": "^27.5.1",
44 | "babel-preset-gatsby-package": "^2.11.0",
45 | "cross-env": "^7.0.3",
46 | "jest": "^29.3.1"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/gatsby-plugin-webfonts/.gitignore:
--------------------------------------------------------------------------------
1 | /gatsby-node.js
2 | /gatsby-ssr.js
3 | /gatsby-browser.js
4 | /web-fonts.js
5 | /create-options.js
6 | /components
7 | /modules
8 |
9 | # Logs
10 | logs
11 | *.log
12 | npm-debug.log*
13 | yarn-debug.log*
14 | yarn-error.log*
15 |
16 | # Runtime data
17 | pids
18 | *.pid
19 | *.seed
20 | *.pid.lock
21 |
22 | # Directory for instrumented libs generated by jscoverage/JSCover
23 | lib-cov
24 |
25 | # Coverage directory used by tools like istanbul
26 | coverage
27 |
28 | # nyc test coverage
29 | .nyc_output
30 |
31 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
32 | .grunt
33 |
34 | # Bower dependency directory (https://bower.io/)
35 | bower_components
36 |
37 | # node-waf configuration
38 | .lock-wscript
39 |
40 | # Compiled binary addons (http://nodejs.org/api/addons.html)
41 | build/Release
42 |
43 | # Dependency directories
44 | node_modules/
45 | jspm_packages/
46 |
47 | # Typescript v1 declaration files
48 | typings/
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Optional REPL history
57 | .node_repl_history
58 |
59 | # Output of 'npm pack'
60 | *.tgz
61 |
62 | # dotenv environment variables file
63 | .env
64 |
65 | # gatsby files
66 | .cache/
67 | public
68 |
69 | # Mac files
70 | .DS_Store
71 |
72 | # Yarn
73 | yarn-error.log
74 | .pnp/
75 | .pnp.js
76 | # Yarn Integrity file
77 | .yarn-integrity
78 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "root",
3 | "private": true,
4 | "description": "A Gatsby plugin to handle cdn, base64 and self hosted webfonts",
5 | "license": "MIT",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/hupe1980/gatsby-plugin-webfonts"
9 | },
10 | "keywords": [
11 | "gatsby-plugin",
12 | "gatsby",
13 | "plugin",
14 | "web",
15 | "font",
16 | "loader",
17 | "google",
18 | "webfont",
19 | "webfontloader"
20 | ],
21 | "workspaces": [
22 | "e2e-tests/*",
23 | "gatsby-plugin-webfonts"
24 | ],
25 | "scripts": {
26 | "test:development-runtime": "yarn prepare && yarn workspace development-runtime test",
27 | "test:production-runtime": "yarn prepare && yarn workspace production-runtime test",
28 | "test:path-prefix-prod-runtime": "yarn prepare && yarn workspace path-prefix-prod-runtime test",
29 | "prepare": "lerna run prepare",
30 | "lint": "eslint . --report-unused-disable-directives",
31 | "test:unit": "lerna run jest --stream",
32 | "test:all": "yarn test:unit && yarn test:development-runtime && yarn test:production-runtime && yarn test:path-prefix-prod-runtime",
33 | "outdated:all": "yarn outdated",
34 | "upgrade:all": "yarn upgrade-interactive --latest"
35 | },
36 | "devDependencies": {
37 | "@babel/eslint-parser": "^7.19.1",
38 | "eslint": "^8.29.0",
39 | "eslint-config-prettier": "^8.5.0",
40 | "eslint-config-react-app": "^7.0.1",
41 | "eslint-plugin-flowtype": "^8.0.3",
42 | "eslint-plugin-import": "^2.26.0",
43 | "eslint-plugin-jsx-a11y": "^6.6.1",
44 | "eslint-plugin-prettier": "^4.2.1",
45 | "eslint-plugin-react": "^7.31.11",
46 | "eslint-plugin-react-hooks": "^4.6.0",
47 | "lerna": "^6.1.0",
48 | "prettier": "^2.8.1"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/gatsby-plugin-webfonts/src/modules/selfHosted.js:
--------------------------------------------------------------------------------
1 | import postcss from "postcss";
2 | import postcssJs from "postcss-js";
3 | import path from "path";
4 | import { constants, copyFileSync, existsSync, mkdirSync } from "fs";
5 |
6 | const defaultFontOptions = {
7 | fontDisplay: `swap`,
8 | };
9 |
10 | export default function selfHosted(
11 | { cacheFolder, directory, pathPrefix = `` },
12 | reporter,
13 | ) {
14 | const getFontFace = async (font) => {
15 | const { family, urls, ...cssProperties } = createFontOptions(font);
16 |
17 | const src = Object.entries(urls).map(([format, url]) => {
18 | const sourcePath = path.join(directory, url);
19 | if (!existsSync(sourcePath)) {
20 | reporter.panicOnBuild(
21 | `Specified selfHosted font file missing: "${sourcePath}"`,
22 | );
23 | return ``;
24 | }
25 |
26 | const fileName = path.basename(url);
27 | const cssDir = path.join(
28 | pathPrefix ? pathPrefix : `/`,
29 | `static`,
30 | `webfonts`,
31 | );
32 |
33 | if (!existsSync(cacheFolder)) {
34 | mkdirSync(cacheFolder, { recursive: true }, (err) => {
35 | reporter.error(err);
36 | });
37 | }
38 |
39 | const outputPath = path.join(cacheFolder, fileName);
40 |
41 | copyFileSync(
42 | sourcePath,
43 | outputPath,
44 | constants.COPYFILE_FICLONE,
45 | (err) => {
46 | reporter.error(err);
47 | },
48 | );
49 |
50 | return `url("${cssDir}/${fileName}") format("${format}")`;
51 | });
52 |
53 | const { css } = await postcss().process(cssProperties, {
54 | parser: postcssJs,
55 | from: undefined,
56 | });
57 |
58 | return `
59 | @font-face {
60 | font-family: "${family}";
61 | src: ${src};
62 | ${css}
63 | }
64 | `;
65 | };
66 |
67 | return (fonts) => Promise.all(fonts.map(getFontFace));
68 | }
69 |
70 | function createFontOptions(options) {
71 | return {
72 | ...defaultFontOptions,
73 | ...options,
74 | };
75 | }
76 |
--------------------------------------------------------------------------------
/gatsby-plugin-webfonts/src/modules/google.js:
--------------------------------------------------------------------------------
1 | import { downloadCss, downloadFonts, parseCss, encodeFonts } from "./utils";
2 |
3 | const API_URL = `https://fonts.googleapis.com/css`;
4 |
5 | const defaultFontOptions = {
6 | fontDisplay: `swap`,
7 | strategy: `selfHosted`, //'base64' || 'cdn'
8 | };
9 |
10 | export default function google(
11 | { cacheFolder, pathPrefix, formats, formatAgents },
12 | version,
13 | ) {
14 | return (fonts) =>
15 | Promise.all(
16 | fonts.map(async (font) => {
17 | const { fontDisplay, strategy } = createFontOptions(font);
18 |
19 | const requestUrl = createRequestUrl(font, version);
20 |
21 | const cssStrings = await Promise.all(
22 | formats.map(async (format) => {
23 | const css = await downloadCss(requestUrl, formatAgents[format]);
24 |
25 | if (strategy === `cdn`) return css;
26 |
27 | return strategy === `selfHosted`
28 | ? downloadFonts(css, cacheFolder, pathPrefix)
29 | : encodeFonts(css);
30 | }),
31 | );
32 |
33 | return parseCss(cssStrings.join(``), {
34 | fontDisplay,
35 | useMerge: strategy !== `base64`,
36 | });
37 | }),
38 | );
39 | }
40 |
41 | export function isGooglePreconnectEnabled(options) {
42 | const { usePreconnect } = options;
43 | const fonts = options.fonts.google;
44 |
45 | if (!usePreconnect) return false;
46 |
47 | if (!fonts || fonts.length === 0) return false;
48 |
49 | return (
50 | fonts.findIndex((font) => {
51 | const { strategy } = createFontOptions(font);
52 | return strategy === `cdn`;
53 | }) > -1
54 | );
55 | }
56 |
57 | export function createRequestUrl(font, version) {
58 | if (!font.family) return null;
59 |
60 | let requestUrl = API_URL;
61 |
62 | if (version === 2) {
63 | requestUrl += `2`;
64 | }
65 |
66 | requestUrl += `?family=${font.family.replace(/ /g, `+`)}`;
67 |
68 | switch (version) {
69 | case 1:
70 | if (font.variants) {
71 | requestUrl += `:${font.variants.join(`,`)}`;
72 | }
73 | break;
74 | case 2:
75 | if (font.axes) {
76 | requestUrl += `:${font.axes}`;
77 | }
78 | break;
79 | default:
80 | return null;
81 | }
82 |
83 | if (font.subsets) {
84 | requestUrl += `&subset=${font.subsets.join(`,`)}`;
85 | }
86 |
87 | if (font.text && font.text.length > 0) {
88 | requestUrl += `&text=${encodeURIComponent(font.text)}`;
89 | }
90 |
91 | if (font.fontDisplay) {
92 | requestUrl += `&display=${encodeURIComponent(font.fontDisplay)}`;
93 | }
94 |
95 | return requestUrl;
96 | }
97 |
98 | function createFontOptions(options) {
99 | return {
100 | ...defaultFontOptions,
101 | ...options,
102 | };
103 | }
104 |
--------------------------------------------------------------------------------
/e2e-tests/development-runtime/cypress/integration/fonts.js:
--------------------------------------------------------------------------------
1 | describe(`fonts`, () => {
2 | beforeEach(() => {
3 | cy.visit(`/`).waitForRouteChange();
4 | });
5 |
6 | it(`displays content with the self hosted font-family`, () => {
7 | cy.getTestElement(`self-hosted-font`)
8 | .should(`have.css`, `font-family`)
9 | .and(`match`, /Open Sans/);
10 | });
11 |
12 | it(`displays content with the self hosted font-weight`, () => {
13 | cy.getTestElement(`self-hosted-font`)
14 | .should(`have.css`, `font-weight`)
15 | .and(`equal`, `400`);
16 | });
17 |
18 | it(`successfully loads fonts`, () => {
19 | cy.document().its(`fonts.status`).should(`equal`, `loaded`);
20 | });
21 |
22 | it(`successfully loads all google fonts`, () => {
23 | const validArr = [];
24 | const getFonts = async (font) => {
25 | if (font.family === `Roboto`) {
26 | validArr.push(font.weight);
27 | }
28 | };
29 |
30 | cy.document()
31 | .its(`fonts`)
32 | .invoke(`forEach`, getFonts)
33 | .then(() => {
34 | expect(validArr).to.include(`300`);
35 | expect(validArr).to.include(`400`);
36 | expect(validArr).to.include(`500`);
37 | });
38 | });
39 |
40 | it(`successfully loads all google2 fonts`, () => {
41 | const validArr = [];
42 | const getFonts = async (font) => {
43 | if (font.family === `Rubik`) {
44 | validArr.push(font.weight);
45 | }
46 | };
47 |
48 | cy.document()
49 | .its(`fonts`)
50 | .invoke(`forEach`, getFonts)
51 | .then(() => {
52 | expect(validArr).to.include(`300`);
53 | expect(validArr).to.include(`400`);
54 | expect(validArr).to.include(`500`);
55 | expect(validArr).to.include(`600`);
56 | expect(validArr).to.not.include(`700`);
57 | });
58 | });
59 |
60 | it(`successfully loads all self hosted fonts`, () => {
61 | const validArr = [];
62 | const getFonts = async (font) => {
63 | if (font.family === `Open Sans`) validArr.push(font.weight);
64 | };
65 |
66 | cy.document()
67 | .its(`fonts`)
68 | .invoke(`forEach`, getFonts)
69 | .then(() => {
70 | expect(validArr).to.include(`300`);
71 | expect(validArr).to.include(`400`);
72 | expect(validArr).to.not.include(`500`);
73 | expect(validArr).to.not.include(`700`);
74 | });
75 | });
76 |
77 | it(`successfully adds link to preload self hosted fonts`, () => {
78 | cy.get(`head`).within(() => {
79 | cy.get(`link[href="/static/webfonts/OpenSans300.woff2"]`)
80 | .should(`have.attr`, `rel`, `preload`)
81 | .and(`have.attr`, `as`, `font`)
82 | .and(`have.attr`, `type`, `font/woff2`);
83 | });
84 | cy.get(`head`).within(() => {
85 | cy.get(`link[href="/static/webfonts/OpenSans400.woff2"]`)
86 | .should(`have.attr`, `rel`, `preload`)
87 | .and(`have.attr`, `as`, `font`)
88 | .and(`have.attr`, `type`, `font/woff2`);
89 | });
90 | });
91 | });
92 |
--------------------------------------------------------------------------------
/e2e-tests/production-runtime/cypress/integration/fonts.js:
--------------------------------------------------------------------------------
1 | describe(`fonts`, () => {
2 | beforeEach(() => {
3 | cy.visit(`/`).waitForRouteChange();
4 | });
5 |
6 | it(`displays content with the self hosted font-family`, () => {
7 | cy.getTestElement(`self-hosted-font`)
8 | .should(`have.css`, `font-family`)
9 | .and(`match`, /Open Sans/);
10 | });
11 |
12 | it(`displays content with the self hosted font-weight`, () => {
13 | cy.getTestElement(`self-hosted-font`)
14 | .should(`have.css`, `font-weight`)
15 | .and(`equal`, `400`);
16 | });
17 |
18 | it(`successfully loads fonts`, () => {
19 | cy.document().its(`fonts.status`).should(`equal`, `loaded`);
20 | });
21 |
22 | it(`successfully loads all google fonts`, () => {
23 | const validArr = [];
24 | const getFonts = async (font) => {
25 | if (font.family === `Roboto`) {
26 | validArr.push(font.weight);
27 | }
28 | };
29 |
30 | cy.document()
31 | .its(`fonts`)
32 | .invoke(`forEach`, getFonts)
33 | .then(() => {
34 | expect(validArr).to.include(`300`);
35 | expect(validArr).to.include(`400`);
36 | expect(validArr).to.include(`500`);
37 | });
38 | });
39 |
40 | it(`successfully loads all google2 fonts`, () => {
41 | const validArr = [];
42 | const getFonts = async (font) => {
43 | if (font.family === `Rubik`) {
44 | validArr.push(font.weight);
45 | }
46 | };
47 |
48 | cy.document()
49 | .its(`fonts`)
50 | .invoke(`forEach`, getFonts)
51 | .then(() => {
52 | expect(validArr).to.include(`300`);
53 | expect(validArr).to.include(`400`);
54 | expect(validArr).to.include(`500`);
55 | expect(validArr).to.include(`600`);
56 | expect(validArr).to.not.include(`700`);
57 | });
58 | });
59 |
60 | it(`successfully loads all self hosted fonts`, () => {
61 | const validArr = [];
62 | const getFonts = async (font) => {
63 | if (font.family === `Open Sans`) validArr.push(font.weight);
64 | };
65 |
66 | cy.document()
67 | .its(`fonts`)
68 | .invoke(`forEach`, getFonts)
69 | .then(() => {
70 | expect(validArr).to.include(`300`);
71 | expect(validArr).to.include(`400`);
72 | expect(validArr).to.not.include(`500`);
73 | expect(validArr).to.not.include(`700`);
74 | });
75 | });
76 |
77 | it(`successfully adds link to preload self hosted fonts`, () => {
78 | cy.get(`head`).within(() => {
79 | cy.get(`link[href="/static/webfonts/OpenSans300.woff2"]`)
80 | .should(`have.attr`, `rel`, `preload`)
81 | .and(`have.attr`, `as`, `font`)
82 | .and(`have.attr`, `type`, `font/woff2`);
83 | });
84 | cy.get(`head`).within(() => {
85 | cy.get(`link[href="/static/webfonts/OpenSans400.woff2"]`)
86 | .should(`have.attr`, `rel`, `preload`)
87 | .and(`have.attr`, `as`, `font`)
88 | .and(`have.attr`, `type`, `font/woff2`);
89 | });
90 | });
91 | });
92 |
--------------------------------------------------------------------------------
/e2e-tests/path-prefix-prod-runtime/cypress/integration/fonts.js:
--------------------------------------------------------------------------------
1 | import { pathPrefix } from "../../gatsby-config";
2 |
3 | describe(`fonts`, () => {
4 | beforeEach(() => {
5 | cy.visit(`/`).waitForRouteChange();
6 | });
7 |
8 | it(`displays content with the self hosted font-family`, () => {
9 | cy.getTestElement(`self-hosted-font`)
10 | .should(`have.css`, `font-family`)
11 | .and(`match`, /Open Sans/);
12 | });
13 |
14 | it(`displays content with the self hosted font-weight`, () => {
15 | cy.getTestElement(`self-hosted-font`)
16 | .should(`have.css`, `font-weight`)
17 | .and(`equal`, `400`);
18 | });
19 |
20 | it(`successfully loads fonts`, () => {
21 | cy.document().its(`fonts.status`).should(`equal`, `loaded`);
22 | });
23 |
24 | it(`successfully loads all google fonts`, () => {
25 | const validArr = [];
26 | const getFonts = async (font) => {
27 | if (font.family === `Roboto`) {
28 | validArr.push(font.weight);
29 | }
30 | };
31 |
32 | cy.document()
33 | .its(`fonts`)
34 | .invoke(`forEach`, getFonts)
35 | .then(() => {
36 | expect(validArr).to.include(`300`);
37 | expect(validArr).to.include(`400`);
38 | expect(validArr).to.include(`500`);
39 | });
40 | });
41 |
42 | it(`successfully loads all google2 fonts`, () => {
43 | const validArr = [];
44 | const getFonts = async (font) => {
45 | if (font.family === `Rubik`) {
46 | validArr.push(font.weight);
47 | }
48 | };
49 |
50 | cy.document()
51 | .its(`fonts`)
52 | .invoke(`forEach`, getFonts)
53 | .then(() => {
54 | expect(validArr).to.include(`300`);
55 | expect(validArr).to.include(`400`);
56 | expect(validArr).to.include(`500`);
57 | expect(validArr).to.include(`600`);
58 | expect(validArr).to.not.include(`700`);
59 | });
60 | });
61 |
62 | it(`successfully loads all self hosted fonts`, () => {
63 | const validArr = [];
64 | const getFonts = async (font) => {
65 | if (font.family === `Open Sans`) validArr.push(font.weight);
66 | };
67 |
68 | cy.document()
69 | .its(`fonts`)
70 | .invoke(`forEach`, getFonts)
71 | .then(() => {
72 | expect(validArr).to.include(`300`);
73 | expect(validArr).to.include(`400`);
74 | expect(validArr).to.not.include(`500`);
75 | expect(validArr).to.not.include(`700`);
76 | });
77 | });
78 |
79 | it(`successfully adds link to preload self hosted fonts`, () => {
80 | cy.get(`head`).within(() => {
81 | cy.get(`link[href="${pathPrefix}/static/webfonts/OpenSans300.woff2"]`)
82 | .should(`have.attr`, `rel`, `preload`)
83 | .and(`have.attr`, `as`, `font`)
84 | .and(`have.attr`, `type`, `font/woff2`);
85 | });
86 | cy.get(`head`).within(() => {
87 | cy.get(`link[href="${pathPrefix}/static/webfonts/OpenSans400.woff2"]`)
88 | .should(`have.attr`, `rel`, `preload`)
89 | .and(`have.attr`, `as`, `font`)
90 | .and(`have.attr`, `type`, `font/woff2`);
91 | });
92 | });
93 | });
94 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | aliases:
4 | node-executor: &node-executor
5 | docker:
6 | - image: circleci/node:14
7 |
8 | e2e-executor: &e2e-executor
9 | docker:
10 | - image: cypress/browsers:node14.15.0-chrome86-ff82
11 |
12 | restore_cache: &restore_cache
13 | restore_cache:
14 | name: Restore node_modules cache
15 | keys:
16 | - yarn-cypress-cache-{{ checksum "yarn.lock" }}
17 |
18 | install_node_modules: &install_node_modules
19 | run:
20 | name: Install node modules
21 | command: yarn --frozen-lockfile
22 |
23 | persist_cache: &persist_cache
24 | save_cache:
25 | name: Save node modules cache
26 | key: yarn-cypress-cache-{{ checksum "yarn.lock" }}
27 | paths:
28 | - ~/.cache
29 |
30 | attach_to_bootstrap: &attach_to_bootstrap
31 | attach_workspace:
32 | at: gatsby-plugin-webfonts
33 |
34 | e2e-test-workflow: &e2e-test-workflow
35 | filters:
36 | branches:
37 | ignore:
38 | - master
39 | requires:
40 | - bootstrap
41 |
42 | test_template: &test_template
43 | steps:
44 | - checkout
45 | - <<: *restore_cache
46 | - <<: *install_node_modules
47 | - <<: *persist_cache
48 | - <<: *attach_to_bootstrap
49 | - run: yarn test:unit
50 |
51 | jobs:
52 | bootstrap:
53 | <<: *node-executor
54 | steps:
55 | - checkout
56 | - <<: *restore_cache
57 | - <<: *install_node_modules
58 | - <<: *persist_cache
59 | - persist_to_workspace:
60 | root: gatsby-plugin-webfonts
61 | paths:
62 | - "*"
63 |
64 | lint:
65 | <<: *node-executor
66 | steps:
67 | - checkout
68 | - <<: *restore_cache
69 | - <<: *install_node_modules
70 | - <<: *persist_cache
71 | - run: yarn lint
72 |
73 | unit_tests:
74 | <<: *node-executor
75 | <<: *test_template
76 |
77 | e2e_tests_development_runtime:
78 | <<: *e2e-executor
79 | steps:
80 | - checkout
81 | - <<: *restore_cache
82 | - <<: *install_node_modules
83 | - <<: *persist_cache
84 | - <<: *attach_to_bootstrap
85 | - run: yarn test:development-runtime
86 |
87 | e2e_tests_production_runtime:
88 | <<: *e2e-executor
89 | steps:
90 | - checkout
91 | - <<: *restore_cache
92 | - <<: *install_node_modules
93 | - <<: *persist_cache
94 | - <<: *attach_to_bootstrap
95 | - run: yarn test:production-runtime
96 |
97 | e2e_tests_path_prefix_prod_runtime:
98 | <<: *e2e-executor
99 | steps:
100 | - checkout
101 | - <<: *restore_cache
102 | - <<: *install_node_modules
103 | - <<: *persist_cache
104 | - <<: *attach_to_bootstrap
105 | - run: yarn test:path-prefix-prod-runtime
106 |
107 | workflows:
108 | version: 2
109 | build-test:
110 | jobs:
111 | - bootstrap
112 | - lint
113 | - unit_tests:
114 | requires:
115 | - bootstrap
116 | - e2e_tests_development_runtime:
117 | <<: *e2e-test-workflow
118 | - e2e_tests_production_runtime:
119 | <<: *e2e-test-workflow
120 | - e2e_tests_path_prefix_prod_runtime:
121 | <<: *e2e-test-workflow
122 |
--------------------------------------------------------------------------------
/gatsby-plugin-webfonts/src/modules/utils.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import fs from "fs-extra";
3 | import axios from "axios";
4 | import postcss from "postcss";
5 | import postcssJs from "postcss-js";
6 |
7 | function fontFaceReducer(fontDisplay = `swap`, useMerge) {
8 | return (acc, obj) => {
9 | if (useMerge) {
10 | const srcs = obj.src.split(`,`);
11 |
12 | const index = acc.findIndex((element) => {
13 | return element.src.split(`,`)[0] === srcs[0];
14 | });
15 |
16 | if (index > -1) {
17 | // we don't know how many 'local'-names the font might have, so we
18 | // just use the last entry, which should be the 'url' entry of the
19 | // requested type.
20 | acc[index].src = `${acc[index].src}, ${srcs[srcs.length - 1]}`;
21 | return acc;
22 | }
23 | }
24 |
25 | obj.fontDisplay = fontDisplay;
26 | acc.push(obj);
27 | return acc;
28 | };
29 | }
30 |
31 | export async function parseCss(cssString, { fontDisplay = `swap`, useMerge }) {
32 | const root = postcss.parse(cssString);
33 |
34 | const cssObject = postcssJs.objectify(root);
35 |
36 | if (cssObject[`@font-face`]) {
37 | const reducer = fontFaceReducer(fontDisplay, useMerge);
38 | cssObject[`@font-face`] = Array.isArray(cssObject[`@font-face`])
39 | ? cssObject[`@font-face`].reduce(reducer, [])
40 | : reducer([], cssObject[`@font-face`]);
41 | }
42 |
43 | const { css } = await postcss().process(cssObject, {
44 | parser: postcssJs,
45 | from: undefined,
46 | });
47 |
48 | return css;
49 | }
50 |
51 | export async function downloadCss(url, userAgent, headers = {}) {
52 | const response = await axios.get(url, {
53 | headers: {
54 | accept: `text/css,*/*;q=0.1`,
55 | "User-Agent": userAgent,
56 | ...headers,
57 | },
58 | });
59 |
60 | return response.data;
61 | }
62 |
63 | export async function downloadFont(url, headers = {}) {
64 | const response = await axios.get(url, {
65 | responseType: `arraybuffer`,
66 | headers,
67 | });
68 |
69 | return response.data;
70 | }
71 |
72 | export async function downloadFonts(css, downloadFolder, pathPrefix) {
73 | const regex = /url\((.+?)\)/gi;
74 | const fontUrls = css
75 | .match(regex)
76 | .map((urlString) => urlString.replace(regex, `$1`));
77 |
78 | const fontSrcs = await Promise.all(
79 | fontUrls.map(async (fontUrl) => {
80 | const { pathname } = new URL(fontUrl);
81 | const filePath = path.join(downloadFolder, pathname);
82 |
83 | const font = await downloadFont(fontUrl);
84 |
85 | await fs.outputFile(filePath, font);
86 |
87 | return `${pathPrefix || ``}/static/webfonts${pathname}`;
88 | }),
89 | );
90 |
91 | fontSrcs.forEach((src, index) => (css = css.replace(fontUrls[index], src)));
92 |
93 | return css;
94 | }
95 |
96 | export async function encodeFonts(css) {
97 | const regex = /url\((.+?)\)/gi;
98 | const fontUrls = css
99 | .match(regex)
100 | .map((urlString) => urlString.replace(regex, `$1`));
101 |
102 | const fontsEncoded = await Promise.all(
103 | fontUrls.map(async (fontUrl) => {
104 | const font = await downloadFont(fontUrl);
105 | const format = path.extname(fontUrl).substr(1);
106 | return `"data:application/x-font-${format};base64,${Buffer.from(
107 | font,
108 | `binary`,
109 | ).toString(`base64`)}"`;
110 | }),
111 | );
112 |
113 | fontsEncoded.forEach(
114 | (font, index) => (css = css.replace(fontUrls[index], font)),
115 | );
116 |
117 | return css;
118 | }
119 |
--------------------------------------------------------------------------------
/gatsby-plugin-webfonts/README.md:
--------------------------------------------------------------------------------
1 | # gatsby-plugin-webfonts
2 |
3 | > A [Gatsby](https://github.com/gatsbyjs/gatsby) plugin to handle cdn, base64 and self hosted webfonts
4 |
5 | - Creates minified @font-face CSS rules
6 | - Supports font-display property (Default: 'swap')
7 | - Handles preconnect and preload optimizations
8 | - Automatically downloads fonts for self hosting
9 | - Supports cdn, base64 and self hosted Fonts (Default: 'selfHosted')
10 | - Supports header user-agent for specific font type
11 |
12 | ## Install
13 |
14 | ```sh
15 | // with npm
16 | npm install gatsby-plugin-webfonts
17 |
18 | // with yarn
19 | yarn add gatsby-plugin-webfonts
20 | ```
21 |
22 | ## How to use
23 |
24 | Edit `gatsby-config.js`
25 |
26 | ```javascript
27 | module.exports = {
28 | plugins: [
29 | {
30 | resolve: `gatsby-plugin-webfonts`,
31 | options: {
32 | fonts: {
33 | google: [
34 | {
35 | family: "Roboto", // 'font-family' property
36 | variants: ["300", "400", "500"],
37 | //subsets: ['latin'],
38 | //text: 'Hello',
39 | //fontDisplay: 'swap' || 'auto' || 'block' || 'fallback' || 'optional',
40 | //strategy: 'selfHosted' || 'base64' || 'cdn',
41 | // Other properties as per https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face (except 'src' & 'font-family') can go here i.e.
42 | //[cssProperty]: 'value',
43 | },
44 | ],
45 | selfHosted: [
46 | {
47 | family: "Open Sans",
48 | urls: {
49 | woff2: `/font/OpenSans400.woff2`,
50 | woff: `/font/OpenSans400.woff`,
51 | //[format]: '/[filepath]/[filename],
52 | },
53 | //[cssProperty]: 'value',
54 | },
55 | ],
56 | },
57 | // formatAgents: {
58 | // eot: `Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C; .NET4.0E)`,
59 | // ttf: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/534.59.8 (KHTML, like Gecko) Version/5.1.9 Safari/534.59.8`,
60 | // woff: `Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; rv:11.0) like Gecko`,
61 | // woff2: `Mozilla/5.0 (Windows NT 10.0; Win64; x64; ServiceUI 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393`,
62 | // },
63 | //formats: ['woff2', 'woff', 'otf', 'ttf'],
64 | //useMinify: true,
65 | //usePreload: true,
66 | //usePreconnect: false,
67 | },
68 | },
69 | ],
70 | };
71 | ```
72 |
73 | ## Google Fonts
74 |
75 | Using [Google's Font API](https://code.google.com/apis/webfonts/docs/getting_started.html), name the font families you'd like to load.
76 |
77 | ```javascript
78 | module.exports = {
79 | plugins: [
80 | {
81 | resolve: `gatsby-plugin-webfonts`,
82 | options: {
83 | fonts: {
84 | google: [
85 | {
86 | family: "Roboto",
87 | variants: ["300", "400", "500"],
88 | },
89 | {
90 | family: "Open Sans Condensed",
91 | variants: ["300", "700"],
92 | },
93 | ],
94 | },
95 | },
96 | },
97 | ],
98 | };
99 | ```
100 |
101 | You can also supply the text parameter or array of subsets to perform character subsetting:
102 |
103 | ```javascript
104 | module.exports = {
105 | plugins: [
106 | {
107 | resolve: `gatsby-plugin-webfonts`,
108 | options: {
109 | fonts: {
110 | google: [
111 | {
112 | family: "Roboto",
113 | variants: ["300", "400", "500"],
114 | text: "Hello",
115 | },
116 | {
117 | family: "Roboto",
118 | variants: ["300", "400", "500"],
119 | subsets: ["latin", ""],
120 | },
121 | ],
122 | },
123 | },
124 | },
125 | ],
126 | };
127 | ```
128 |
129 | Pass you user-agent for specific font type:
130 |
131 | ```javascript
132 | module.exports = {
133 | plugins: [
134 | {
135 | resolve: `gatsby-plugin-webfonts`,
136 | options: {
137 | fonts: {
138 | google: [
139 | {
140 | family: "Roboto",
141 | variants: ["300", "400", "500"],
142 | },
143 | ],
144 | },
145 | formatAgents: {
146 | woff: `Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; rv:11.0) like Gecko`,
147 | woff2: `Mozilla/5.0 (Windows NT 10.0; Win64; x64; ServiceUI 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393`,
148 | },
149 | },
150 | },
151 | ],
152 | };
153 | ```
154 |
155 | The text subsetting functionality is only available for Google fonts.
156 |
157 | ### Google Fonts v2
158 |
159 | > This is an extension of the "Google Fonts" setting which uses the latest API.
160 |
161 | You can also use the latest [Google Fonts API v2](https://developers.google.com/fonts/docs/css2).
162 |
163 | Use the `axes` option like so:
164 |
165 | ```javascript
166 | module.exports = {
167 | plugins: [
168 | {
169 | resolve: `gatsby-plugin-webfonts`,
170 | options: {
171 | fonts: {
172 | google2: [
173 | {
174 | family: "Roboto",
175 | axes: "wght@300;400;500",
176 | },
177 | ],
178 | },
179 | },
180 | },
181 | ],
182 | };
183 | ```
184 |
185 | A [variable font](https://web.dev/variable-fonts/) packs all the styles and weights of a font family into a single file.
186 |
187 | Only a few Google Fonts are available as [variable fonts](https://fonts.google.com/variablefonts).
188 | Some have their own custom axes that can be set accordingly.
189 |
190 | ```javascript
191 | module.exports = {
192 | plugins: [
193 | {
194 | resolve: `gatsby-plugin-webfonts`,
195 | options: {
196 | fonts: {
197 | google2: [
198 | {
199 | family: "Rubik",
200 | axes: "wght@300..600", // multiple ranges are supported, ex: "wght@300..500;700..900"
201 | },
202 | ],
203 | },
204 | },
205 | },
206 | ],
207 | };
208 | ```
209 |
210 | ## Self Hosted Fonts
211 |
212 | Add your own self hosted font files. The plugin will handle the imports & preloading. Strategy is always `selfHosted`, subsets are already defined within your font file.
213 |
214 | ```javascript
215 | module.exports = {
216 | plugins: [
217 | {
218 | resolve: `gatsby-plugin-webfonts`,
219 | options: {
220 | fonts: {
221 | selfHosted: [
222 | {
223 | family: "Open Sans",
224 | urls: {
225 | // src attributes
226 | // path relative to gatsby project root
227 | woff2: `/examplePath/filename.woff2`,
228 | woff: `/examplePath/filename.woff`,
229 | otf: `/examplePath/filename.otf`,
230 | ttf: `/examplePath/filename.ttf`,
231 | },
232 | fontStyle: "light",
233 | fontWeight: 300,
234 | },
235 | ],
236 | },
237 | },
238 | },
239 | ],
240 | };
241 | ```
242 |
243 | As per gatsby docs it is recommended not to put fonts in the `/static` directory. This plugin will automatically be copy them across to `/public/webfonts/selfHosted`.
244 |
245 | ## License
246 |
247 | [MIT](LICENSE)
248 |
--------------------------------------------------------------------------------