Skip to content

Commit

Permalink
refactor(core): Make load plugins modular, prepare for TS (#34813)
Browse files Browse the repository at this point in the history
* Refactor load plugins modules

* Break loadConfigAndPlugins into two functions

* Extract out local plugin check

* Revert async back to original sync funcs

* Adjust load config interface

* Missed awaits
  • Loading branch information
tyhopp committed Feb 15, 2022
1 parent 3d74584 commit 3c3362b
Show file tree
Hide file tree
Showing 22 changed files with 607 additions and 525 deletions.
5 changes: 2 additions & 3 deletions integration-tests/gatsby-cli/__tests__/build.js
Expand Up @@ -12,9 +12,8 @@ describe(`gatsby build`, () => {
it(`creates a built gatsby site`, () => {
const [code, logs] = GatsbyCLI.from(cwd).invoke(`build`)

logs.should.contain(
`success open and validate gatsby-configs, load plugins`
)
logs.should.contain(`success load gatsby config`)
logs.should.contain(`success load plugins`)
logs.should.contain(`success onPreInit`)
logs.should.contain(`success initialize cache`)
logs.should.contain(`success copy gatsby files`)
Expand Down
5 changes: 2 additions & 3 deletions integration-tests/gatsby-cli/__tests__/develop.js
Expand Up @@ -26,9 +26,8 @@ describe(`gatsby develop`, () => {

// 3. Make sure logs for the user contain expected results
const logs = getLogs()
logs.should.contain(
`success open and validate gatsby-configs, load plugins`
)
logs.should.contain(`success load gatsby config`)
logs.should.contain(`success load plugins`)
logs.should.contain(`success onPreInit`)
logs.should.contain(`success initialize cache`)
logs.should.contain(`success copy gatsby files`)
Expand Down
5 changes: 2 additions & 3 deletions integration-tests/gatsby-cli/__tests__/repl.js
Expand Up @@ -21,9 +21,8 @@ describe(`gatsby repl`, () => {

// 3. Make assertions
const logs = getLogs()
logs.should.contain(
`success open and validate gatsby-configs, load plugins`
)
logs.should.contain(`success load gatsby config`)
logs.should.contain(`success load plugins`)
logs.should.contain(`success onPreInit`)
logs.should.contain(`success initialize cache`)
logs.should.contain(`success copy gatsby files`)
Expand Down
@@ -1,29 +1,23 @@
import reporter from "gatsby-cli/lib/reporter"
import telemetry from "gatsby-telemetry"
import { preferDefault } from "../prefer-default"
import { getConfigFile } from "../get-config-file"
import { internalActions } from "../../redux/actions"
import loadThemes from "../load-themes"
import { store } from "../../redux"
import handleFlags from "../../utils/handle-flags"
import availableFlags from "../../utils/flags"
import { IProgram } from "../../commands/types"
import { IGatsbyConfig } from "../../internal"

import { IFlattenedPlugin } from "./load-plugins/types"

import { preferDefault } from "../bootstrap/prefer-default"
import { getConfigFile } from "../bootstrap/get-config-file"
import { loadPlugins } from "../bootstrap/load-plugins"
import { internalActions } from "../redux/actions"
import loadThemes from "../bootstrap/load-themes"
import { store } from "../redux"
import handleFlags from "../utils/handle-flags"
import availableFlags from "../utils/flags"
import { IProgram } from "../commands/types"

export async function loadConfigAndPlugins({
export async function loadConfig({
siteDirectory,
processFlags = false,
}: {
siteDirectory: string
processFlags?: boolean
program?: IProgram
}): Promise<{
config: any
flattenedPlugins: Array<IFlattenedPlugin>
}> {
}): Promise<IGatsbyConfig> {
// Try opening the site's gatsby-config.js file.
const { configModule, configFilePath } = await getConfigFile(
siteDirectory,
Expand Down Expand Up @@ -94,7 +88,5 @@ export async function loadConfigAndPlugins({

store.dispatch(internalActions.setSiteConfig(config))

const flattenedPlugins = await loadPlugins(config, siteDirectory)

return { config, flattenedPlugins }
return config
}
98 changes: 8 additions & 90 deletions packages/gatsby/src/bootstrap/load-plugins/index.ts
@@ -1,105 +1,23 @@
import _ from "lodash"

import { store } from "../../redux"
import { IGatsbyState } from "../../redux/types"
import * as nodeAPIs from "../../utils/api-node-docs"
import * as browserAPIs from "../../utils/api-browser-docs"
import ssrAPIs from "../../../cache-dir/api-ssr-docs"
import { loadPlugins as loadPluginsInternal } from "./load"
import { loadInternalPlugins } from "./load-internal-plugins"
import {
collatePluginAPIs,
handleBadExports,
handleMultipleReplaceRenderers,
ExportType,
ICurrentAPIs,
validateConfigPluginsOptions,
} from "./validate"
import {
IPluginInfo,
IFlattenedPlugin,
ISiteConfig,
IRawSiteConfig,
} from "./types"
import { IPluginRefObject, PluginRef } from "gatsby-plugin-utils/dist/types"

const getAPI = (api: {
[exportType in ExportType]: { [api: string]: boolean }
}): ICurrentAPIs =>
_.keys(api).reduce<Partial<ICurrentAPIs>>((merged, key) => {
merged[key] = _.keys(api[key])
return merged
}, {}) as ICurrentAPIs

// Create a "flattened" array of plugins with all subplugins
// brought to the top-level. This simplifies running gatsby-* files
// for subplugins.
const flattenPlugins = (plugins: Array<IPluginInfo>): Array<IPluginInfo> => {
const flattened: Array<IPluginInfo> = []
const extractPlugins = (plugin: IPluginInfo): void => {
if (plugin.subPluginPaths) {
for (const subPluginPath of plugin.subPluginPaths) {
// @pieh:
// subPluginPath can look like someOption.randomFieldThatIsMarkedAsSubplugins
// Reason for doing stringified path with . separator was that it was just easier to prevent duplicates
// in subPluginPaths array (as each subplugin in the gatsby-config would add subplugin path).
const segments = subPluginPath.split(`.`)
let roots: Array<any> = [plugin.pluginOptions]
for (const segment of segments) {
if (segment === `[]`) {
roots = roots.flat()
} else {
roots = roots.map(root => root[segment])
}
}
roots = roots.flat()

roots.forEach(subPlugin => {
flattened.push(subPlugin)
extractPlugins(subPlugin)
})
}
}
}

plugins.forEach(plugin => {
flattened.push(plugin)
extractPlugins(plugin)
})

return flattened
}

function normalizePlugin(plugin): IPluginRefObject {
if (typeof plugin === `string`) {
return {
resolve: plugin,
options: {},
}
}

if (plugin.options?.plugins) {
plugin.options = {
...plugin.options,
plugins: normalizePlugins(plugin.options.plugins),
}
}

return plugin
}

function normalizePlugins(plugins?: Array<PluginRef>): Array<IPluginRefObject> {
return (plugins || []).map(normalizePlugin)
}

const normalizeConfig = (config: IRawSiteConfig = {}): ISiteConfig => {
return {
...config,
plugins: (config.plugins || []).map(normalizePlugin),
}
}
import { IFlattenedPlugin } from "./types"
import { normalizeConfig } from "./utils/normalize"
import { getAPI } from "./utils/get-api"
import { flattenPlugins } from "./utils/flatten-plugins"
import { IGatsbyConfig } from "../../internal"

export async function loadPlugins(
rawConfig: IRawSiteConfig = {},
rawConfig: IGatsbyConfig,
rootDir: string
): Promise<Array<IFlattenedPlugin>> {
// Turn all strings in plugins: [`...`] into the { resolve: ``, options: {} } form
Expand All @@ -115,7 +33,7 @@ export async function loadPlugins(
})

// Collate internal plugins, site config plugins, site default plugins
const pluginInfos = loadPluginsInternal(config, rootDir)
const pluginInfos = loadInternalPlugins(config, rootDir)

// Create a flattened array of the plugins
const pluginArray = flattenPlugins(pluginInfos)
Expand Down
154 changes: 154 additions & 0 deletions packages/gatsby/src/bootstrap/load-plugins/load-internal-plugins.ts
@@ -0,0 +1,154 @@
import { slash } from "gatsby-core-utils"
import path from "path"
import reporter from "gatsby-cli/lib/reporter"
import { store } from "../../redux"
import {
IPluginInfo,
IPluginRefObject,
IPluginRefOptions,
ISiteConfig,
} from "./types"
import { processPlugin } from "./process-plugin"
import { createPluginId } from "./utils/create-id"
import { createFileContentHash } from "./utils/create-hash"
import {
addGatsbyPluginCloudPluginWhenInstalled,
incompatibleGatsbyCloudPlugin,
GATSBY_CLOUD_PLUGIN_NAME,
} from "./utils/handle-gatsby-cloud"

const TYPESCRIPT_PLUGIN_NAME = `gatsby-plugin-typescript`

export function loadInternalPlugins(
config: ISiteConfig = {},
rootDir: string
): Array<IPluginInfo> {
// Instantiate plugins.
const plugins: Array<IPluginInfo> = []
const configuredPluginNames = new Set()

// Add internal plugins
const internalPlugins = [
`../../internal-plugins/dev-404-page`,
`../../internal-plugins/load-babel-config`,
`../../internal-plugins/internal-data-bridge`,
`../../internal-plugins/prod-404-500`,
`../../internal-plugins/webpack-theme-component-shadowing`,
`../../internal-plugins/bundle-optimisations`,
`../../internal-plugins/functions`,
].filter(Boolean) as Array<string>

internalPlugins.forEach(relPath => {
const absPath = path.join(__dirname, relPath)
plugins.push(processPlugin(absPath, rootDir))
})

// Add plugins from the site config.
if (config.plugins) {
config.plugins.forEach(plugin => {
const processedPlugin = processPlugin(plugin, rootDir)
plugins.push(processedPlugin)
configuredPluginNames.add(processedPlugin.name)
})
}

// the order of all of these page-creators matters. The "last plugin wins",
// so the user's site comes last, and each page-creator instance has to
// match the plugin definition order before that. This works fine for themes
// because themes have already been added in the proper order to the plugins
// array
plugins.forEach(plugin => {
plugins.push(
processPlugin(
{
resolve: require.resolve(`gatsby-plugin-page-creator`),
options: {
path: slash(path.join(plugin.resolve, `src/pages`)),
pathCheck: false,
},
},
rootDir
)
)
})

if (
_CFLAGS_.GATSBY_MAJOR === `4` &&
configuredPluginNames.has(GATSBY_CLOUD_PLUGIN_NAME) &&
incompatibleGatsbyCloudPlugin(plugins)
) {
reporter.panic(
`Plugin gatsby-plugin-gatsby-cloud is not compatible with your gatsby version. Please upgrade to gatsby-plugin-gatsby-cloud@next`
)
}

if (
!configuredPluginNames.has(GATSBY_CLOUD_PLUGIN_NAME) &&
(process.env.GATSBY_CLOUD === `true` || process.env.GATSBY_CLOUD === `1`)
) {
addGatsbyPluginCloudPluginWhenInstalled(plugins, rootDir)
}

// Support Typescript by default but allow users to override it
if (!configuredPluginNames.has(TYPESCRIPT_PLUGIN_NAME)) {
const processedTypeScriptPlugin = processPlugin(
{
resolve: require.resolve(TYPESCRIPT_PLUGIN_NAME),
options: {
// TODO(@mxstbr): Do not hard-code these defaults but infer them from the
// pluginOptionsSchema of gatsby-plugin-typescript
allExtensions: false,
isTSX: false,
jsxPragma: `React`,
},
},
rootDir
)
plugins.push(processedTypeScriptPlugin)
}

// Add the site's default "plugin" i.e. gatsby-x files in root of site.
plugins.push({
resolve: slash(process.cwd()),
id: createPluginId(`default-site-plugin`),
name: `default-site-plugin`,
version: createFileContentHash(process.cwd(), `gatsby-*`),
pluginOptions: {
plugins: [],
},
})

const program = store.getState().program

// default options for gatsby-plugin-page-creator
let pageCreatorOptions: IPluginRefOptions | undefined = {
path: slash(path.join(program.directory, `src/pages`)),
pathCheck: false,
}

if (config.plugins) {
const pageCreatorPlugin = config.plugins.find(
(plugin): plugin is IPluginRefObject =>
typeof plugin !== `string` &&
plugin.resolve === `gatsby-plugin-page-creator` &&
slash((plugin.options && plugin.options.path) || ``) ===
slash(path.join(program.directory, `src/pages`))
)
if (pageCreatorPlugin) {
// override the options if there are any user specified options
pageCreatorOptions = pageCreatorPlugin.options
}
}

const processedPageCreatorPlugin = processPlugin(
{
resolve: require.resolve(`gatsby-plugin-page-creator`),
options: pageCreatorOptions,
},
rootDir
)

plugins.push(processedPageCreatorPlugin)

return plugins
}

0 comments on commit 3c3362b

Please sign in to comment.