70 %
Chris Biscardi

Running a Gatsby Starter as a Theme

Gatsby themes introduce a new model for building Gatsby sites and apps. However, due to the recency of themes being available, the majority of the ecosystem is still in starters that could better serve their users as themes. This series of posts will take you through converting the gatsby-starter-blog to a theme. We'll start by getting the starter to run as a theme.

If you're unfamiliar with themes, you may want to read my introduction on the official gatsby blog or watch my talk at the first ever Gatsby Days -- Tweet this

Setup

In this series of posts, we'll use Yarn Workspaces to work on our theme while also keeping an example usage of said theme close by. It is not required to use Yarn Workspaces and you can develop in any way you feel comfortable, such as using yarn link, local filepaths for package.json dependencies, or anything else. I also wrote an Introduction to Yarn Workspaces if you are unfamiliar and also interested in learning more.

First create a directory to hold our packages.

mkdir converting-a-gatsby-starter-to-themes
cd converting-a-gatsby-starter-to-themes
yarn init -y

Then we can use Gatsby's starter functionality to pull down the blog starter into a gatsby-theme-awesome-blog folder.

sh
npx gatsby new gatsby-theme-awesome-blog https://github.com/gatsbyjs/gatsby-starter-blog

We'll also remove the package-lock.json because we're using yarn and change the name in the ~/gatsby-theme-awesome-blog/package.json from gatsby-starter-blog to gatsby-theme-awesome-blog.

sh
rm gatsby-theme-awesome-blog/package-lock.json
javascript
{
"name": "gatsby-theme-awesome-blog",
...
}

Next, we'll enable workspaces by making our root package.json private (required for workspaces to work), and also add a workspaces config. Here's my package.json before changes.

javascript
{
"name": "converting-a-gatsby-starter-to-themes",
"version": "1.0.0",
"main": "index.js",
"author": "christopherbiscardi <chris@christopherbiscardi.com> (@chrisbiscardi)",
"license": "MIT"
}

and after we configure two workspaces. One for our theme and another called my-blog for our example app. We'll create the example app itself in a bit.

javascript
{
"name": "converting-a-gatsby-starter-to-themes",
"private": true,
"version": "1.0.0",
"main": "index.js",
"author": "christopherbiscardi <chris@christopherbiscardi.com> (@chrisbiscardi)",
"license": "MIT",
"workspaces": [
"gatsby-theme-awesome-blog",
"my-blog"
]
}

We've set up enough to run the starter as it originally exists. This will ensure that you've set up workspaces correctly.

sh
yarn workspace gatsby-theme-awesome-blog develop

Converting to a Theme

Now that we have the starter running, we can start using it as a theme and see what breaks. Let's create the my-blog example app to use the theme.

sh
mkdir my-blog
cd my-blog
yarn init -y

We need to add gatsby-theme-awesome-blog as a dependency of our blog and create the important gatsby-config.js where we'll configure our theme.

in package.json we add the dependency and some useful scripts.

javascript
{
"name": "my-blog",
"version": "1.0.0",
"main": "index.js",
"author": "christopherbiscardi <chris@christopherbiscardi.com> (@chrisbiscardi)",
"license": "MIT",
"dependencies": {
"gatsby": "2.0.111",
"gatsby-theme-awesome-blog": "*"
},
"scripts": {
"build": "gatsby build",
"develop": "gatsby develop",
"serve": "gatsby serve",
"test": "echo \"Write tests! -> https://gatsby.app/unit-testing\""
}
}

in gatsby-config.js

JS
module.exports = {
__experimentalThemes: [`gatsby-theme-awesome-blog`]
};

One point I would like to note here is that using a theme in your new project is a total of two files. The package.json, to declare the dependency on gatsby and your theme, as well as the gatsby-config.js to use the theme.

Using the Theme

Run yarn in the root of the project to install and link our dependencies (including the theme into our blog) and now we can run develop to run our starter as a theme and see what breaks.

index.js and main fields

sh
➜ yarn workspace my-blog develop
yarn workspace v1.13.0
yarn run v1.13.0
$ gatsby develop
error Cannot find module 'gatsby-theme-awesome-blog'
Error: Cannot find module 'gatsby-theme-awesome-blog'
- loader.js:581 Function.Module._resolveFilename
internal/modules/cjs/loader.js:581:15
- v8-compile-cache.js:162 Function.require.resolve
[converting-a-gatsby-starter-to-themes]/[v8-compile-cache]/v8-compile-cache.js:162:23
- index.js:27
[converting-a-gatsby-starter-to-themes]/[gatsby]/dist/bootstrap/load-themes/index.js:27:43
- Generator.next
- debuggability.js:313 Promise._execute
[converting-a-gatsby-starter-to-themes]/[bluebird]/js/release/debuggability.js:313:9
- promise.js:483 Promise._resolveFromExecutor
[converting-a-gatsby-starter-to-themes]/[bluebird]/js/release/promise.js:483:18
- promise.js:79 new Promise
[converting-a-gatsby-starter-to-themes]/[bluebird]/js/release/promise.js:79:10
- index.js:44 resolveTheme
[converting-a-gatsby-starter-to-themes]/[gatsby]/dist/bootstrap/load-themes/index.js:44:17
- index.js:100
[converting-a-gatsby-starter-to-themes]/[gatsby]/dist/bootstrap/load-themes/index.js:100:32
- Generator.next

This first problem can be a little confusing if you're not used to how node packages get resolved. What it means is that it can't find an index.js or a main field in the package.json for our theme. We can solve this by creating an empty index.js file in our theme. You'll also notice this pattern in a lot of gatsby plugins if you check out their source.

sh
touch gatsby-theme-awesome-blog/index.js

Components that don't exist

Now we get a new error

sh
➜ yarn workspace my-blog develop
yarn workspace v1.13.0
yarn run v1.13.0
$ gatsby develop
success open and validate gatsby-configs — 0.017 s
success load plugins — 0.351 s
success onPreInit — 1.036 s
success delete html and css files from previous builds — 0.009 s
success initialize cache — 0.031 s
success copy gatsby files — 0.099 s
success onPreBootstrap — 0.007 s
success source and transform nodes — 0.118 s
success building schema — 0.384 s
The plugin "gatsby-theme-awesome-blog" created a page with a component that doesn't exist
{ path: '/hi-folks/',
component:
'/converting-a-gatsby-starter-to-themes/my-blog/src/templates/blog-post.js',
context:
{ slug: '/hi-folks/',
previous: { fields: [Object], frontmatter: [Object] },
next: null } }
error See the documentation for createPage https://www.gatsbyjs.org/docs/actions/#createPage
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
error Command failed.
Exit code: 1
Command: /Users/chris/.nvm/versions/node/v10.6.0/bin/node
Arguments: /usr/local/Cellar/yarn/1.13.0/libexec/lib/cli.js develop
Directory: /converting-a-gatsby-starter-to-themes/my-blog
Output:
info Visit https://yarnpkg.com/en/docs/cli/workspace for documentation about this command.

This error is telling us that the createPage calls in our theme are creating pages that don't point to React components. If we go to gatsby-theme-awesome-blog/gatsby-node.js we can see why. The blog post template is using path.resolve to create the path to the React component. In a node module, path.resolve will default to the current working directory if you only give it a relative path, like our theme is doing here.

If after processing all given path segments an absolute path has not yet been generated, the current working directory is used. -- node docs

JS
const blogPost = path.resolve(
`./src/templates/blog-post.js`
);

For starters this is fine because we always run scripts from the root of our project and our code isn't tucked away in a node module. When our theme is in a node module, this doesn't work because the current working directory is the directory that our blog is in and our theme is going to be somewhere else (node_modules, a workspace package, etc). Since we're using JavaScript, we can piggy-back on node's require algorithm itself and replace path with require. An additional benefit of require.resolve is that it will error out if it can't find the file. Failing earlier at the source of the problem makes it easier to detect where the actual problem is in our code.

JS
const blogPost = require.resolve(
`./src/templates/blog-post.js`
);

Now we can run again

JS
➜ yarn workspace my-blog develop
yarn workspace v1.13.0
yarn run v1.13.0
$ gatsby develop
success open and validate gatsby-configs — 0.019 s
success load plugins — 0.310 s
success onPreInit — 0.650 s
success delete html and css files from previous builds — 0.012 s
success initialize cache — 0.036 s
success copy gatsby files — 0.094 s
success onPreBootstrap — 0.007 s
success source and transform nodes — 0.117 s
success building schema — 0.340 s
success createPages — 0.053 s
success createPagesStatefully — 0.011 s
success onPreExtractQueries — 0.005 s
success update schema — 0.203 s
warning The GraphQL query in the non-page component "/converting-a-gatsby-starter-to-themes/gatsby-theme-awesome-blog/src/pages/404.js" will not be run.
warning The GraphQL query in the non-page component "/converting-a-gatsby-starter-to-themes/gatsby-theme-awesome-blog/src/pages/index.js" will not be run.
Exported queries are only executed for Page components. It's possible you're
trying to create pages in your gatsby-node.js and that's failing for some
reason.
If the failing component(s) is a regular component and not intended to be a page
component, you generally want to use a <StaticQuery> (https://gatsbyjs.org/docs/static-query)
instead of exporting a page query.
If you're more experienced with GraphQL, you can also export GraphQL
fragments from components and compose the fragments in the Page component
query and pass data down into the child component — http://graphql.org/learn/queries/#fragments
success extract queries from components — 0.168 s
Generating image thumbnails [==============================] 4/4 0.0 secs 100%
success run graphql queries — 0.353 s — 6/6 17.07 queries/second
success write out page data — 0.004 s
success write out redirect data — 0.002 s
Generating image thumbnails [==============================] 11/11 0.4 secs 100%
info bootstrap finished - 4.907 s
error Plugin gatsby-plugin-manifest returned an error
Error: icon (content/assets/gatsby-icon.png) does not exist as defined in gatsby-config.js. Make sure the file exists relative to the root of the site.
success onPostBootstrap — 0.016 s
(node:34557) DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead
error Input file is missing
Error: Input file is missing
error UNHANDLED REJECTION
Error: Input file is missing

This time we're getting an issue from gatsby-plugin-manifest about an icon that can't be found. If we dive into the doesNotExist function that is used right before generating icons we can see it's using a statSync, which needs a full file path because it performs a similar resolution to path.resolve.

JS
exports.doesIconExist = function doesIconExist(srcIcon) {
try {
return fs.statSync(srcIcon).isFile();
} catch (e) {
if (e.code === `ENOENT`) {
return false;
} else {
throw e;
}
}
};

In this case we'll use path.resolve since we're trying to generate a path for a .png file. In gatsby-theme-awesome-blog/gatsby-config.js we'll replace the icon key with path.resolve and a __dirname as the first path to resolve with.

JS
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `Gatsby Starter Blog`,
short_name: `GatsbyJS`,
start_url: `/`,
background_color: `#ffffff`,
theme_color: `#663399`,
display: `minimal-ui`,
icon: path.resolve(__dirname, `content/assets/gatsby-icon.png`),
},
},

If we run again, we'll see that we're past the other two path issues, and onto a third :) Path changes are the major change you'll need to make when converting a starter to a theme because it's likely that (due to documentation and other factors) the paths most starters are using include assumptions about running in the same working directory as the site and not an NPM package.

The third is for gatsby-plugin-typography

sh
➜ yarn workspace my-blog develop
yarn workspace v1.13.0
yarn run v1.13.0
$ gatsby develop
success open and validate gatsby-configs — 0.017 s
success load plugins — 0.309 s
success onPreInit — 0.633 s
success delete html and css files from previous builds — 0.023 s
success initialize cache — 0.015 s
success copy gatsby files — 0.111 s
success onPreBootstrap — 0.007 s
success source and transform nodes — 0.081 s
success building schema — 0.360 s
success createPages — 0.056 s
success createPagesStatefully — 0.011 s
success onPreExtractQueries — 0.005 s
success update schema — 0.220 s
warning The GraphQL query in the non-page component "/converting-a-gatsby-starter-to-themes/gatsby-theme-awesome-blog/src/pages/404.js" will not be run.
warning The GraphQL query in the non-page component "/converting-a-gatsby-starter-to-themes/gatsby-theme-awesome-blog/src/pages/index.js" will not be run.
Exported queries are only executed for Page components. It's possible you're
trying to create pages in your gatsby-node.js and that's failing for some
reason.
If the failing component(s) is a regular component and not intended to be a page
component, you generally want to use a <StaticQuery> (https://gatsbyjs.org/docs/static-query)
instead of exporting a page query.
If you're more experienced with GraphQL, you can also export GraphQL
fragments from components and compose the fragments in the Page component
query and pass data down into the child component — http://graphql.org/learn/queries/#fragments
success extract queries from components — 0.160 s
success run graphql queries — 0.081 s — 6/6 75.96 queries/second
success write out page data — 0.004 s
success write out redirect data — 0.001 s
⡀ onPostBootstrapdone generating icons for manifest
success onPostBootstrap — 0.221 s
info bootstrap finished - 4.51 s
(node:35229) DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead
here ../node_modules/gatsby-plugin-typography/.cache/typography.js
Module not found: Error: Can't resolve '/converting-a-gatsby-starter-to-themes/my-blog/src/utils/typography' in '/converting-a-gatsby-starter-to-themes/node_modules/gatsby-plugin-typography/.cache'
resolve '/converting-a-gatsby-starter-to-themes/my-blog/src/utils/typography' in '/converting-a-gatsby-starter-to-themes/node_modules/gatsby-plugin-typography/.cache'
using description file: /converting-a-gatsby-starter-to-themes/node_modules/gatsby-plugin-typography/package.json (relative path: ./.cache)
using description file: /converting-a-gatsby-starter-to-themes/my-blog/package.json (relative path: ./src/utils/typography)
no extension
/converting-a-gatsby-starter-to-themes/my-blog/src/utils/typography doesn't exist
.mjs
/converting-a-gatsby-starter-to-themes/my-blog/src/utils/typography.mjs doesn't exist
.js
/converting-a-gatsby-starter-to-themes/my-blog/src/utils/typography.js doesn't exist
.jsx
/converting-a-gatsby-starter-to-themes/my-blog/src/utils/typography.jsx doesn't exist
.wasm
/converting-a-gatsby-starter-to-themes/my-blog/src/utils/typography.wasm doesn't exist
.json
/converting-a-gatsby-starter-to-themes/my-blog/src/utils/typography.json doesn't exist
as directory
/converting-a-gatsby-starter-to-themes/my-blog/src/utils/typography doesn't exist
[/converting-a-gatsby-starter-to-themes/my-blog/src/utils/typography]
[/converting-a-gatsby-starter-to-themes/my-blog/src/utils/typography.mjs]
[/converting-a-gatsby-starter-to-themes/my-blog/src/utils/typography.js]
[/converting-a-gatsby-starter-to-themes/my-blog/src/utils/typography.jsx]
[/converting-a-gatsby-starter-to-themes/my-blog/src/utils/typography.wasm]
[/converting-a-gatsby-starter-to-themes/my-blog/src/utils/typography.json]
@ ../node_modules/gatsby-plugin-typography/.cache/typography.js 1:17-134
@ ../node_modules/gatsby-plugin-typography/gatsby-ssr.js
@ ./.cache/api-runner-ssr.js
@ ./.cache/develop-static-entry.js
error There was an error compiling the html.js component for the development server.
See our docs page on debugging HTML builds for help https://gatsby.app/debug-html

gatsby-plugin-typography has a few different ways to resolve the utils file it looks for based on if the path is an absolute path or not. This means we can change our path to an absolute path like before and it will work again. In this case, we're looking for a JavaScript file again, so we'll use require.resolve.

JS
{
resolve: `gatsby-plugin-typography`,
options: {
pathToConfigModule: require.resolve(`./src/utils/typography`),
},
},

It compiles! and pages...

Ok so now our site is compiling and running and we still have one warning to deal with.

➜ yarn workspace my-blog develop
yarn workspace v1.13.0
yarn run v1.13.0
$ gatsby develop
success open and validate gatsby-configs — 0.021 s
success load plugins — 0.304 s
success onPreInit — 0.627 s
success delete html and css files from previous builds — 0.025 s
success initialize cache — 0.029 s
success copy gatsby files — 0.093 s
success onPreBootstrap — 0.007 s
success source and transform nodes — 0.118 s
success building schema — 0.362 s
success createPages — 0.056 s
success createPagesStatefully — 0.012 s
success onPreExtractQueries — 0.005 s
success update schema — 0.207 s
warning The GraphQL query in the non-page component "/converting-a-gatsby-starter-to-themes/gatsby-theme-awesome-blog/src/pages/404.js" will not be run.
warning The GraphQL query in the non-page component "/converting-a-gatsby-starter-to-themes/gatsby-theme-awesome-blog/src/pages/index.js" will not be run.
Exported queries are only executed for Page components. It's possible you're
trying to create pages in your gatsby-node.js and that's failing for some
reason.
If the failing component(s) is a regular component and not intended to be a page
component, you generally want to use a <StaticQuery> (https://gatsbyjs.org/docs/static-query)
instead of exporting a page query.
If you're more experienced with GraphQL, you can also export GraphQL
fragments from components and compose the fragments in the Page component
query and pass data down into the child component — http://graphql.org/learn/queries/#fragments
success extract queries from components — 0.162 s
success run graphql queries — 0.216 s — 6/6 27.97 queries/second
success write out page data — 0.004 s
success write out redirect data — 0.001 s
⠄ onPostBootstrapdone generating icons for manifest
success onPostBootstrap — 0.183 s
info bootstrap finished - 4.627 s
(node:36031) DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead
(node:36031) DeprecationWarning: Resolver#doResolve: The type arguments (string) is now a hook argument (Hook). Pass a reference to the hook instead.
Warning: React version not specified in eslint-plugin-react settings. See https://github.com/yannickcr/eslint-plugin-react#configuration .
DONE Compiled successfully in 3985ms
You can now view my-blog in the browser.
http://localhost:8000/
View GraphiQL, an in-browser IDE, to explore your site's data and schema
http://localhost:8000/___graphql
Note that the development build is not optimized.
To create a production build, use gatsby build
ℹ 「wdm」:
ℹ 「wdm」: Compiled successfully.

If we visit the working site we'll see the standard Gatsby development page showing each of the URLs for our blog posts. What we won't see is the pages that are in gatsby-theme-awesome-blog/src/pages, including the index.js or / page. This is because src/pages is handled by default with every Gatsby site by gatsby-plugin-page-creator which is automatically instantiated. While Gatsby themes will eventually add this to our themes by default as well, it isn't implemented yet so we'll do it ourselves for now. In gatsby-config.js, add the following plugin.

JS
{
resolve: `gatsby-plugin-page-creator`,
options: {
path: path.resolve(__dirname, `src/pages`),
},
},

Fin

In total, we created an empty index.js, changed a few of our paths, and added a plugin to our starter to change it into a theme. In the near future we won't have to add the plugin either. Creating Gatsby themes is very much the same as creating a normal Gatsby site. In the next post we'll start making our theme a bit more "theme-like" by moving the markdown files into our blog instead of letting them live in the theme itself.