New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Composing Gatsby Sites #8917

Merged
merged 2 commits into from Oct 16, 2018
File filter...
Filter file types
Jump to file or symbol
Failed to load files and symbols.
+185 −2
Diff settings

Always

Just for now

@@ -8,8 +8,10 @@ const crypto = require(`crypto`)
const del = require(`del`)
const path = require(`path`)
const convertHrtime = require(`convert-hrtime`)
const Promise = require(`bluebird`)

const apiRunnerNode = require(`../utils/api-runner-node`)
const mergeGatsbyConfig = require(`../utils/merge-gatsby-config`)
const { graphql } = require(`graphql`)
const { store, emitter } = require(`../redux`)
const loadPlugins = require(`./load-plugins`)
@@ -62,14 +64,42 @@ module.exports = async (args: BootstrapArgs) => {
})

// Try opening the site's gatsby-config.js file.
let activity = report.activityTimer(`open and validate gatsby-config`, {
let activity = report.activityTimer(`open and validate gatsby-configs`, {
parentSpan: bootstrapSpan,
})
activity.start()
const config = await preferDefault(
let config = await preferDefault(
getConfigFile(program.directory, `gatsby-config`)
)

// theme gatsby configs can be functions or objects
if (config.__experimentalThemes) {
const themesConfig = await Promise.mapSeries(
config.__experimentalThemes,
async ([themeName, themeConfig]) => {
const theme = await preferDefault(
getConfigFile(themeName, `gatsby-config`)
)
// if theme is a function, call it with the themeConfig
let themeConfigObj = theme
if (_.isFunction(theme)) {
themeConfigObj = theme(themeConfig)
}
// themes function as plugins too (gatsby-node, etc)
return {
...themeConfigObj,
plugins: [
...(themeConfigObj.plugins || []),
// theme plugin is last so it's gatsby-node, etc can override it's declared plugins, like a normal site.
{ resolve: themeName, options: themeConfig },
],
}
}
).reduce(mergeGatsbyConfig, {})

config = mergeGatsbyConfig(themesConfig, config)
}

if (config && config.polyfill) {
report.warn(
`Support for custom Promise polyfills has been removed in Gatsby v2. We only support Babel 7's new automatic polyfilling behavior.`
@@ -1,6 +1,7 @@
const Joi = require(`joi`)

export const gatsbyConfigSchema = Joi.object().keys({
__experimentalThemes: Joi.array(),
polyfill: Joi.boolean(),
siteMetadata: Joi.object(),
pathPrefix: Joi.string(),
@@ -0,0 +1,113 @@
const mergeGatsbyConfig = require(`../merge-gatsby-config`)

describe(`Merge gatsby config`, () => {
it(`Merging empty config is an identity operation`, () => {
const emptyConfig = {}
const basicConfig = {
plugins: [`gatsby-mdx`],
}

expect(mergeGatsbyConfig(basicConfig, emptyConfig)).toEqual(basicConfig)
expect(mergeGatsbyConfig(emptyConfig, basicConfig)).toEqual(basicConfig)
})

it(`Merging plugins concatenates them`, () => {
const basicConfig = {
plugins: [`gatsby-mdx`],
}
const morePlugins = {
plugins: [`a-plugin`, `b-plugin`, { resolve: `c-plugin`, options: {} }],
}
expect(mergeGatsbyConfig(basicConfig, morePlugins)).toEqual({
plugins: [
`gatsby-mdx`,
`a-plugin`,
`b-plugin`,
{ resolve: `c-plugin`, options: {} },
This conversation was marked as resolved by ChristopherBiscardi

This comment has been minimized.

@jlengstorf

jlengstorf Oct 8, 2018

Member

Would it make sense to add a test here for duplicates? That seems like something that will need to be handled very deliberately, so a test seems prudent.

This comment has been minimized.

@ChristopherBiscardi

ChristopherBiscardi Oct 8, 2018

Contributor

yes, I had left it off to deal with when thinking more about sub-theming but we can definitely add "filter out exact match duplicates" now without affecting anything I think. I'll add _.uniqWith(plugins, _.isEqual) for now (with a test), which should cover exact matches across strings and objects.

],
})
expect(mergeGatsbyConfig(morePlugins, basicConfig)).toEqual({
plugins: [
`a-plugin`,
`b-plugin`,
{ resolve: `c-plugin`, options: {} },
`gatsby-mdx`,
],
})
})

it(`Merging plugins uniqs them, keeping the first occurrence`, () => {
const basicConfig = {
plugins: [`gatsby-mdx`],
}
const morePlugins = {
plugins: [
`a-plugin`,
`gatsby-mdx`,
`b-plugin`,
{ resolve: `c-plugin`, options: {} },
],
}
expect(mergeGatsbyConfig(basicConfig, morePlugins)).toEqual({
plugins: [
`gatsby-mdx`,
`a-plugin`,
`b-plugin`,
{ resolve: `c-plugin`, options: {} },
],
})
expect(mergeGatsbyConfig(morePlugins, basicConfig)).toEqual({
plugins: [
`a-plugin`,
`gatsby-mdx`,
`b-plugin`,
{ resolve: `c-plugin`, options: {} },
],
})
})

it(`Merging siteMetadata is recursive`, () => {
const a = {
siteMetadata: {
title: `my site`,
something: { else: 1 },
},
}

const b = {
siteMetadata: {
something: { nested: 2 },
},
}

expect(mergeGatsbyConfig(a, b)).toEqual({
siteMetadata: {
title: `my site`,
something: { else: 1, nested: 2 },
},
})
})

it(`Merging proxy is overriden`, () => {
const a = {
proxy: {
prefix: `/something-not/api`,
url: `http://examplesite.com/api/`,
},
}

const b = {
proxy: {
prefix: `/api`,
url: `http://examplesite.com/api/`,
},
}

expect(mergeGatsbyConfig(a, b)).toEqual({
proxy: {
prefix: `/api`,
url: `http://examplesite.com/api/`,
},
})
})
})
@@ -0,0 +1,39 @@
const _ = require(`lodash`)
/**
* Defines how a theme object is merged with the user's config
*/
module.exports = (a, b) => {
// a and b are gatsby configs, If they have keys, that means there are values to merge
const allGatsbyConfigKeysWithAValue = _.uniq(
Object.keys(a).concat(Object.keys(b))
)

// reduce the array of mergable keys into a single gatsby config object
const mergedConfig = allGatsbyConfigKeysWithAValue.reduce(
(config, gatsbyConfigKey) => {
// choose a merge function for the config key if there's one defined,
// otherwise use the default value merge function
const mergeFn = howToMerge[gatsbyConfigKey] || howToMerge.byDefault
return {
...config,
[gatsbyConfigKey]: mergeFn(a[gatsbyConfigKey], b[gatsbyConfigKey]),
}
},
{}
)

// return the fully merged config
return mergedConfig
}
const howToMerge = {
/**
* pick a truthy value by default.
* This makes sure that if a single value is defined, that one it used.
* We prefer the "right" value, because the user's config will be "on the right"
*/
byDefault: (a, b) => b || a,
siteMetadata: (objA, objB) => _.merge({}, objA, objB),
// plugins are concatenated and uniq'd, so we don't get two of the same plugin value
plugins: (a = [], b = []) => _.uniqWith(a.concat(b), _.isEqual),
mapping: (objA, objB) => _.merge({}, objA, objB),
}
ProTip! Use n and p to navigate between commits in a pull request.