c12
智能配置加载器。
c12(发音为 /siːtwelv/,类似于 c-twelve)是一个智能配置加载器。
✅ 特性
.js
,.ts
,.mjs
,.cjs
,.mts
,.cts
.json
配置文件加载器,基于 unjs/jiti.jsonc
,.json5
,.yaml
,.yml
,.toml
配置文件加载器,基于 unjs/confbox.config/
目录支持,遵循 config dir 提案.rc
配置支持,基于 unjs/rc9.env
文件支持,基于 dotenv- 多个源通过 unjs/defu 合并
- 从最近的
package.json
文件读取配置 - 从多个本地或 Git 源扩展配置
- 使用环境特定配置覆盖
- 配置监视器,支持自动重载和 HMR
🦴 被以下项目使用
用法
安装包
# ✨ Auto-detect
npx nypm install c12
# npm
npm install c12
# yarn
yarn add c12
# pnpm
pnpm install c12
# bun
bun install c12
导入
ESM (Node.js, Bun)
import { loadConfig, watchConfig } from "c12";
CommonJS (传统 Node.js)
const { loadConfig, watchConfig } = require("c12");
加载配置
// Get loaded config
const { config } = await loadConfig({});
// Get resolved config and extended layers
const { config, configFile, layers } = await loadConfig({});
加载优先级
c12 使用 unjs/defu 按以下顺序合并配置源
- 通过选项传入的配置覆盖
- 当前工作目录(CWD)中的配置文件
- 当前工作目录(CWD)中的 RC 文件
- 用户主目录中的全局 RC 文件
- 来自
package.json
的配置 - 通过选项传入的默认配置
- 扩展的配置层
选项
cwd
从此工作目录解析配置。默认值为 process.cwd()
name
配置基础名称。默认值为 config
。
configFile
不带扩展名的配置文件名。默认值由 name
生成(例如,如果 name
是 foo
,则配置文件将是 => foo.config
)。
设置为 false
以避免加载配置文件。
rcFile
RC 配置文件名。默认值由 name
生成 (name=foo => .foorc
)。
设置为 false
以禁用加载 RC 配置。
globalRC
从工作区目录和用户主目录加载 RC 配置。仅在提供 rcFile
时启用。设置为 false
以禁用此功能。
dotenv
如果启用,则加载 .env
文件。默认禁用。
packageJson
从最近的 package.json
文件加载配置。默认禁用。
如果传入 true
,c12 将使用 package.json
中的 name
字段。
您也可以传入字符串或字符串数组作为值来使用这些字段。
defaults
指定默认配置。它具有最低优先级,并且在配置扩展后应用。
defaultConfig
指定默认配置。它在配置扩展前应用。
overrides
指定覆盖配置。它具有最高优先级,并且在配置扩展前应用。
omit$Keys
排除已解析配置中以 $
开头的环境特定和内置键。默认值为 false
。
jiti
用于导入配置文件的自定义 unjs/jiti 实例。
jitiOptions
用于导入配置文件的自定义 unjs/jiti 选项。
giget
从 Git 源扩展层时,传递给 unjs/giget 的选项。
envName
用于环境特定配置的环境名称。
默认值为 process.env.NODE_ENV
。您可以将 envName
设置为 false
或空字符串以禁用此功能。
扩展配置
如果解析的配置包含 extends
键,它将用于扩展配置。
扩展可以是嵌套的,每个层可以从一个或多个基础扩展。
最终配置是使用 unjs/defu 合并扩展选项和用户选项的结果。
extends
中的每个项都是一个字符串,可以是当前配置文件的绝对或相对路径,指向用于扩展的配置文件或包含配置文件的目录。如果它以 github:
、gitlab:
、bitbucket:
或 https:
开头,c12 会自动克隆它。
对于自定义合并策略,您可以通过 layers
属性直接访问每个层。
示例
// config.ts
export default {
colors: {
primary: "user_primary",
},
extends: ["./theme"],
};
// config.dev.ts
export default {
dev: true,
};
// theme/config.ts
export default {
extends: "../base",
colors: {
primary: "theme_primary",
secondary: "theme_secondary",
},
};
// base/config.ts
export default {
colors: {
primary: 'base_primary'
text: 'base_text'
}
}
加载的配置将如下所示
{
dev: true,
colors: {
primary: 'user_primary',
secondary: 'theme_secondary',
text: 'base_text'
}
}
层
[
{ 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/ */ },
]
从远程源扩展配置层
您还可以从 npm 或 GitHub 等远程源扩展配置。
在仓库中,应该有一个 config.ts
(或 config.{name}.ts
)文件才能被视为有效的配置层。
示例: 从 GitHub 仓库扩展
// config.ts
export default {
extends: "gh:user/repo",
};
示例: 从带有分支和子路径的 GitHub 仓库扩展
// config.ts
export default {
extends: "gh:user/repo/theme#dev",
};
示例: 扩展私有仓库并安装依赖项
// config.ts
export default {
extends: ["gh:user/repo", { auth: process.env.GITHUB_TOKEN, install: true }],
};
您可以在层配置中向 giget: {}
传递更多选项。
更多信息请参考 unjs/giget。
环境特定配置
用户可以使用以下配置键定义环境特定配置
$test: {...}
$development: {...}
$production: {...}
$env: { [env]: {...} }
c12 尝试匹配 envName
,如果指定,则覆盖环境配置。
注意: 环境将在扩展每个配置层时应用。这样,层可以提供环境特定配置。
示例
{
// Default configuration
logLevel: 'info',
// Environment overrides
$test: { logLevel: 'silent' },
$development: { logLevel: 'warning' },
$production: { logLevel: 'error' },
$env: {
staging: { logLevel: 'debug' }
}
}
监视配置
您可以使用 watchConfig
而不是 loadConfig
来加载配置并监视所有预期配置路径中的更改、添加和删除,并自动使用新配置重新加载。
生命周期钩子
onWatch
: 此函数总是在配置更新、添加或删除时(尝试重新加载配置之前)调用。acceptHMR
: 通过实现此函数,您可以比较旧函数和新函数,并在不需要完全重新加载时返回true
。onUpdate
: 此函数总是在新配置更新后调用。如果acceptHMR
返回 true,则会跳过此函数。
import { watchConfig } from "c12";
const config = watchConfig({
cwd: ".",
// chokidarOptions: {}, // Default is { ignoreInitial: true }
// debounce: 200 // Default is 100. You can set it to false to disable debounced watcher
onWatch: (event) => {
console.log("[watcher]", event.type, event.path);
},
acceptHMR({ oldConfig, newConfig, getDiff }) {
const diff = getDiff();
if (diff.length === 0) {
console.log("No config changed detected!");
return true; // No changes!
}
},
onUpdate({ oldConfig, newConfig, getDiff }) {
const diff = getDiff();
console.log("Config updated:\n" + diff.map((i) => i.toJSON()).join("\n"));
},
});
console.log("watching config files:", config.watchingFiles);
console.log("initial config", config.config);
// Stop watcher when not needed anymore
// await config.unwatch();