Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(gatsby): extend core theming composition API to be recursive (#1…
…0787) * introduce child theming * add themes reducer for component shadowing usage * Update packages/gatsby/src/internal-plugins/webpack-theme-component-shadowing/gatsby-node.js Co-Authored-By: ChristopherBiscardi <chris@christopherbiscardi.com> * more * enable component shadowing for child themes * move shadowing to work on src/ * lintup, add comments
- Loading branch information
1 parent
84abff0
commit 63c9dd9
Showing
6 changed files
with
151 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
const path = require(`path`) | ||
const mergeGatsbyConfig = require(`../../utils/merge-gatsby-config`) | ||
const Promise = require(`bluebird`) | ||
const _ = require(`lodash`) | ||
const debug = require(`debug`)(`gatsby:load-themes`) | ||
const preferDefault = require(`../prefer-default`) | ||
const getConfigFile = require(`../get-config-file`) | ||
|
||
// get the gatsby-config file for a theme | ||
const resolveTheme = async themeSpec => { | ||
const themeName = themeSpec.resolve || themeSpec | ||
const themeDir = path.dirname(require.resolve(themeName)) | ||
const theme = await preferDefault(getConfigFile(themeDir, `gatsby-config`)) | ||
// if theme is a function, call it with the themeConfig | ||
let themeConfig = theme | ||
if (_.isFunction(theme)) { | ||
themeConfig = theme(themeSpec.options || {}) | ||
} | ||
return { themeName, themeConfig, themeSpec } | ||
} | ||
|
||
// single iteration of a recursive function that resolve parent themes | ||
// It's recursive because we support child themes declaring parents and | ||
// have to resolve all the way `up the tree` of parent/children relationships | ||
// | ||
// Theoretically, there could be an infinite loop here but in practice there is | ||
// no use case for a loop so I expect that to only happen if someone is very | ||
// off track and creating their own set of themes | ||
const processTheme = ({ themeName, themeConfig, themeSpec }) => { | ||
// gatsby themes don't have to specify a gatsby-config.js (they might only use gatsby-node, etc) | ||
// in this case they're technically plugins, but we should support it anyway | ||
// because we can't guarentee which files theme creators create first | ||
if (themeConfig && themeConfig.__experimentalThemes) { | ||
// for every parent theme a theme defines, resolve the parent's | ||
// gatsby config and return it in order [parentA, parentB, child] | ||
return Promise.mapSeries(themeConfig.__experimentalThemes, async spec => { | ||
const themeObj = await resolveTheme(spec) | ||
return processTheme(themeObj) | ||
}).then(arr => arr.concat([{ themeName, themeConfig, themeSpec }])) | ||
} else { | ||
// if a theme doesn't define additional themes, return the original theme | ||
return [{ themeName, themeConfig, themeSpec }] | ||
} | ||
} | ||
|
||
module.exports = async config => { | ||
const themesA = await Promise.mapSeries( | ||
config.__experimentalThemes, | ||
async themeSpec => { | ||
const themeObj = await resolveTheme(themeSpec) | ||
return processTheme(themeObj) | ||
} | ||
).then(arr => _.flattenDeep(arr)) | ||
|
||
// log out flattened themes list to aid in debugging | ||
debug(themesA) | ||
|
||
// map over each theme, adding the theme itself to the plugins | ||
// list in the config for the theme. This enables the usage of | ||
// gatsby-node, etc in themes. | ||
return ( | ||
Promise.mapSeries(themesA, ({ themeName, themeConfig = {}, themeSpec }) => { | ||
return { | ||
...themeConfig, | ||
plugins: [ | ||
...(themeConfig.plugins || []), | ||
// theme plugin is last so it's gatsby-node, etc can override it's declared plugins, like a normal site. | ||
{ resolve: themeName, options: themeSpec.options || {} }, | ||
], | ||
} | ||
}) | ||
/** | ||
* themes resolve to a gatsby-config, so here we merge all of the configs | ||
* into a single config, making sure to maintain the order in which | ||
* they were defined so that later configs, like the user's site and | ||
* children, can override functionality in earlier themes. | ||
*/ | ||
.reduce(mergeGatsbyConfig, {}) | ||
.then(newConfig => { | ||
return { | ||
config: mergeGatsbyConfig(newConfig, config), | ||
themes: themesA, | ||
} | ||
}) | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
module.exports = (state = {}, action) => { | ||
switch (action.type) { | ||
case `SET_RESOLVED_THEMES`: | ||
return { | ||
...state, | ||
themes: action.payload, | ||
} | ||
|
||
default: | ||
return state | ||
} | ||
} |