Let's talk a bit about the core Gatsby themes algorithm. For
background it may be good to read
Introducing Gatsby Themes.
Shortly, what we're going to cover today are the internals
responsible for how we combine multiple themes with the
user's site when we run
gatsby develop or
There are two main pieces to how the core of theming works:
gatsby-config.jsfor every theme specified in the user's
In addition there are a couple characteristics that make this implementation of theming small (in terms of code size) and co-located (not spread out through the entire codebase).
gatsby-config.js is the data structure at the core of a
Gatsby site. Everything a site is capable of is pointed to
from this file. A valid
gatsby-config.js is defined as
The config keys we're most interested in today are
allow us to define functionality that hooks into Gatsby
lifecycles from a set of gatsby-* files (
gatsby-node, etc). This logic can create pages, tell us
how to render pages, define data transformations, and much
more. If you took every other key away and just had
plugins left, you could still build a fully functional
Mapping is an advanced feature that allows us to define mappings between Gatsby data nodes so that we can query them later.
__experimentalThemes is our field. It allows us to define
which themes we want to use and the options we pass to them
just like plugins. At this point it may be useful to define
what a theme is in the context of Gatsby. A theme is a
Gatsby site. When building a theme you have access to all of
the plugin APIs, the ability to define portions of
gatsby-config.js, and the ability to ship any other code
that may help as part of your theme (react components, page
Because the Gatsby Config is so important already (it is the
base of a Gatsby site) we chose to use it as the base of
theming as well. As a result of that what we really want is
a way to combine multiple
gatsby-config.js files into a
gatsby-config.js. This yields a way to encode
anything you can do with a Gatsby site, as part of a
section of code
we're talking about today is responsible for loading the
various configs and can easily be reproduced right here as
it is not that long. Part of the reason it isn't that much
code is that we have placed it before the Redux action
that sets the final Gatsby config in the
By hooking into the bootstrap before any plugins are loaded,
before any pages are created, and before any queries are
run, we have the opportunity to modify the user's Gatsby
config and let the effects flow through the system as they
typically would. That is: using multiple themes has the same
behavior as using a single
We can walk through the code line by line to understand what's happening. First we check to see if there is a config at all, and if there is, we also check to see if any themes have been defined.
This allows us to restrict any behavioral changes we might make to only people who have opted in to using themes. The result is that we can ship this (and have been shipping this code) for many releases now without bothering any existing sites.
themesConfig is the result of iterating over every theme,
loading the config, and merging it with the existing config
so far. It is all of the themes' Gatsby configs combined.
For each plugin, we transform the theme name and any options into a Gatsby config we can merge into the rest.
Themes can be declared in the same way plugins can, either as strings or as objects.
So we do some mechanical checking to see which version it is and then load the config file using the same utility functions the user's Gatsby config is loaded with
Then, to enable themes to use a
gatsby-config.js that can
accept options, we let themes define their config as a
function which takes those options.
Finally, we take the resulting Gatsby config and return it.
Because we want themes to also have access to plugin
gatsby-ssr, etc, we use
the theme directly as it's own plugin after all of the
other plugins it has defined. This gives themes the
opportunity to override any functionality they may need to.
After mapping over and generating all of the Gatsby configs, we merge them into a single config
and finally, we add the user's config last and set the new "mega config" to be the Gatsby config that the rest of the Gatsby lifecycles use.
Now we still haven't talked about how one config is merged
with another. There's less to talk about here. We take
a and config
b and we merge each key of the
gatsby config for each of the configs with a process
specific to each key.
By default, we just take the user's value over any other.
"last value wins". This is used for keys like
mapping both do recursive merges using
and finally the most interesting merge is
concatenate all of the plugins together, and remove the
plugins that are duplicates of each other.