Use resolveConfig to normalize plugins

This commit is contained in:
Jordan Pittman 2024-08-19 15:08:21 -04:00 • committed by Philipp Spiess
parent d9e3fd613b
commit 3553b5912e
3 changed files with 55 additions and 26 deletions

View file

@ -1,4 +1,5 @@
import type { DesignSystem } from '../../design-system'
import type { PluginWithConfig } from '../../plugin-api'
import { createThemeFn } from '../../theme-fn'
import { deepMerge, isPlainObject } from './deep-merge'
import {
@ -11,6 +12,7 @@ import {
interface ResolutionContext {
design: DesignSystem
configs: UserConfig[]
plugins: PluginWithConfig[]
theme: Record<string, ThemeValue>
extend: Record<string, ThemeValue[]>
result: ResolvedConfig
@ -18,12 +20,14 @@ interface ResolutionContext {
let minimal: ResolvedConfig = {
theme: {},
plugins: [],
}
export function resolveConfig(design: DesignSystem, configs: UserConfig[]): ResolvedConfig {
let ctx: ResolutionContext = {
design,
configs,
configs: [],
plugins: [],
theme: {},
extend: {},
@ -31,11 +35,16 @@ export function resolveConfig(design: DesignSystem, configs: UserConfig[]): Reso
result: structuredClone(minimal),
}
for (let config of configs) {
resolveInternal(ctx, config)
}
// Merge themes
mergeTheme(ctx)
return {
theme: ctx.theme as ResolvedConfig['theme'],
plugins: ctx.plugins,
}
}
@ -71,6 +80,41 @@ export interface PluginUtils {
theme(keypath: string, defaultValue?: any): any
}
function resolveInternal(ctx: ResolutionContext, user: UserConfig): void {
let plugins: PluginWithConfig[] = []
// Normalize plugins so they share the same shape
for (let plugin of user.plugins ?? []) {
if ('__isOptionsFunction' in plugin) {
// Happens with `plugin.withOptions()` when no options were passed:
// e.g. `require("my-plugin")` instead of `require("my-plugin")(options)`
plugins.push(plugin())
} else if ('handler' in plugin) {
// Happens with `plugin(…)`:
// e.g. `require("my-plugin")`
//
// or with `plugin.withOptions()` when the user passed options:
// e.g. `require("my-plugin")(options)`
plugins.push(plugin)
} else {
// Just a plain function without using the plugin(…) API
plugins.push({ handler: plugin })
}
}
// Apply configs from plugins
for (let plugin of plugins) {
ctx.plugins.push(plugin)
if (plugin.config) {
resolveInternal(ctx, plugin.config)
}
}
// Then apply the "user" config
ctx.configs.push(user)
}
function mergeTheme(ctx: ResolutionContext) {
let api: PluginUtils = {
theme: createThemeFn(ctx.design, () => ctx.theme, resolveValue),

View file

@ -1,9 +1,11 @@
import type { Plugin, PluginWithConfig } from '../../plugin-api'
import type { PluginUtils } from './resolve-config'
export type ResolvableTo<T> = T | ((utils: PluginUtils) => T)
export interface UserConfig {
theme?: ThemeConfig
plugins?: Plugin[]
}
export type ThemeValue = ResolvableTo<Record<string, unknown>> | null | undefined
@ -15,4 +17,5 @@ export type ThemeConfig = Record<string, ThemeValue> & {
export interface ResolvedConfig {
theme: Record<string, Record<string, unknown>>
plugins: PluginWithConfig[]
}

View file

@ -326,37 +326,19 @@ function objectToAst(rules: CssInJs | CssInJs[]): AstNode[] {
return ast
}
export function registerPlugins(plugins: Plugin[], designSystem: DesignSystem, ast: AstNode[]) {
let pluginObjects = []
for (let plugin of plugins) {
if ('__isOptionsFunction' in plugin) {
// Happens with `plugin.withOptions()` when no options were passed:
// e.g. `require("my-plugin")` instead of `require("my-plugin")(options)`
pluginObjects.push(plugin())
} else if ('handler' in plugin) {
// Happens with `plugin(…)`:
// e.g. `require("my-plugin")`
//
// or with `plugin.withOptions()` when the user passed options:
// e.g. `require("my-plugin")(options)`
pluginObjects.push(plugin)
} else {
// Just a plain function without using the plugin(…) API
pluginObjects.push({ handler: plugin, config: {} as UserConfig })
}
}
// Now merge all the configs and make all that crap work
export function registerPlugins(
plugins: Plugin[],
designSystem: DesignSystem,
ast: AstNode[],
) {
let resolvedConfig = resolveConfig(designSystem, [
createCompatConfig(designSystem.theme),
...pluginObjects.map(({ config }) => config ?? {}),
{ plugins },
])
let pluginApi = buildPluginApi(designSystem, ast, resolvedConfig)
// Loop over the handlers and run them all with the resolved config + CSS theme probably somehow
for (let { handler } of pluginObjects) {
for (let { handler } of resolvedConfig.plugins) {
handler(pluginApi)
}