Skip to content

Commit

Permalink
feat: custom merger to replace built-in defu (#160)
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 authored Jun 11, 2024
1 parent 764f4ac commit 18c9e30
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 27 deletions.
64 changes: 44 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ Custom [unjs/jiti](https://github.com/unjs/jiti) options to import configuration

Options passed to [unjs/giget](https://github.com/unjs/giget) when extending layer from git source.

### `merger`

Custom options merger function. Default is [defu](https://github.com/unjs/defu).

**Note:** Custom merge function should deeply merge options with arguments high -> low priority.

### `envName`

Environment name used for [environment specific configuration](#environment-specific-configuration).
Expand Down Expand Up @@ -216,10 +222,10 @@ export default {
// base/config.ts
export default {
colors: {
primary: 'base_primary',
text: 'base_text'
}
}
primary: "base_primary",
text: "base_text",
},
};
```

The loaded configuration would look like this:
Expand All @@ -228,21 +234,39 @@ The loaded configuration would look like this:
const config = {
dev: true,
colors: {
primary: 'user_primary',
secondary: 'theme_secondary',
text: 'base_text'
}
}
primary: "user_primary",
secondary: "theme_secondary",
text: "base_text",
},
};
```

Layers:

```js
[
{ config: { /* theme config */ }, configFile: "/path/to/theme/config.ts", cwd: "/path/to/theme " },
{ config: { /* base config */ }, configFile: "/path/to/base/config.ts", cwd: "/path/to/base" },
{ config: { /* dev config */ }, configFile: "/path/to/config.dev.ts", cwd: "/path/" },
]
{
config: {
/* theme config */
},
configFile: "/path/to/theme/config.ts",
cwd: "/path/to/theme ",
},
{
config: {
/* base config */
},
configFile: "/path/to/base/config.ts",
cwd: "/path/to/base",
},
{
config: {
/* dev config */
},
configFile: "/path/to/config.dev.ts",
cwd: "/path/",
},
];
```

## Extending config layer from remote sources
Expand Down Expand Up @@ -300,16 +324,16 @@ c12 tries to match [`envName`](#envname) and override environment config if spec
```js
export default {
// Default configuration
logLevel: 'info',
logLevel: "info",

// Environment overrides
$test: { logLevel: 'silent' },
$development: { logLevel: 'warning' },
$production: { logLevel: 'error' },
$test: { logLevel: "silent" },
$development: { logLevel: "warning" },
$production: { logLevel: "error" },
$env: {
staging: { logLevel: 'debug' }
}
}
staging: { logLevel: "debug" },
},
};
```

## Watching configuration
Expand Down
20 changes: 13 additions & 7 deletions src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ export async function loadConfig<
};
}

// Custom merger
const _merger = options.merger || defu;

// Create jiti instance
options.jiti =
options.jiti ||
Expand Down Expand Up @@ -126,7 +129,7 @@ export async function loadConfig<
// 3. user home
rcSources.push(rc9.readUser({ name: options.rcFile, dir: options.cwd }));
}
_configs.rc = defu({} as T, ...rcSources);
_configs.rc = _merger({} as T, ...rcSources);
}

// Load config from package.json
Expand All @@ -142,7 +145,7 @@ export async function loadConfig<
).filter((t) => t && typeof t === "string");
const pkgJsonFile = await readPackageJSON(options.cwd).catch(() => {});
const values = keys.map((key) => pkgJsonFile?.[key]);
_configs.packageJson = defu({} as T, ...values);
_configs.packageJson = _merger({} as T, ...values);
}

// Resolve config sources
Expand All @@ -155,7 +158,7 @@ export async function loadConfig<
}

// Combine sources
r.config = defu(
r.config = _merger(
configs.overrides,
configs.main,
configs.rc,
Expand All @@ -168,7 +171,7 @@ export async function loadConfig<
await extendConfig(r.config, options);
r.layers = r.config._layers;
delete r.config._layers;
r.config = defu(r.config, ...r.layers!.map((e) => e.config)) as T;
r.config = _merger(r.config, ...r.layers!.map((e) => e.config)) as T;
}

// Preserve unmerged sources as layers
Expand All @@ -190,7 +193,7 @@ export async function loadConfig<

// Apply defaults
if (options.defaults) {
r.config = defu(r.config, options.defaults) as T;
r.config = _merger(r.config, options.defaults) as T;
}

// Remove environment-specific and built-in keys start with $
Expand Down Expand Up @@ -296,6 +299,9 @@ async function resolveConfig<
}
}

// Custom merger
const _merger = options.merger || defu;

// Download giget URIs and resolve to local path
if (GIGET_PREFIXES.some((prefix) => source.startsWith(prefix))) {
const { downloadTemplate } = await import("giget");
Expand Down Expand Up @@ -393,7 +399,7 @@ async function resolveConfig<
...res.config!.$env?.[options.envName],
};
if (Object.keys(envConfig).length > 0) {
res.config = defu(envConfig, res.config);
res.config = _merger(envConfig, res.config);
}
}

Expand All @@ -403,7 +409,7 @@ async function resolveConfig<

// Overrides
if (res.sourceOptions!.overrides) {
res.config = defu(res.sourceOptions!.overrides, res.config) as T;
res.config = _merger(res.sourceOptions!.overrides, res.config) as T;
}

// Always windows paths
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ export interface LoadConfigOptions<

giget?: DownloadTemplateOptions;

merger?: (...sources: Array<T | null | undefined>) => T;

extend?:
| false
| {
Expand Down

0 comments on commit 18c9e30

Please sign in to comment.