This commit is contained in:
2026-02-15 19:19:19 +08:00
commit c726cd8804
11 changed files with 392 additions and 0 deletions

19
.cnb.yml Normal file
View File

@@ -0,0 +1,19 @@
# .cnb.yml
include:
- https://cnb.cool/kevisual/cnb/-/blob/main/.cnb/template.yml
.common_env: &common_env
env:
USERNAME: root
imports:
- https://cnb.cool/kevisual/env/-/blob/main/.env.development
$:
vscode:
- docker:
image: docker.cnb.cool/kevisual/dev-env:latest
services:
- vscode
- docker
imports: !reference [.common_env, imports]
stages: !reference [.dev_template, stages]

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
storage
node_modules

2
.npmrc Normal file
View File

@@ -0,0 +1,2 @@
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

25
bun.config.mjs Normal file
View File

@@ -0,0 +1,25 @@
// @ts-check
import { resolvePath } from '@kevisual/use-config';
import { execSync } from 'node:child_process';
const entry = 'src/index.ts';
const naming = 'app';
const external = ['bullmq','ioredis'];
/**
* @type {import('bun').BuildConfig}
*/
await Bun.build({
target: 'node',
format: 'esm',
entrypoints: [resolvePath(entry, { meta: import.meta })],
outdir: resolvePath('./dist', { meta: import.meta }),
naming: {
entry: `${naming}.js`,
},
external: external,
env: 'KEVISUAL_*',
});
// const cmd = `dts -i src/index.ts -o app.d.ts`;
const cmd = `dts -i ${entry} -o ${naming}.d.ts`;
execSync(cmd, { stdio: 'inherit' });

35
package.json Normal file
View File

@@ -0,0 +1,35 @@
{
"name": "@kevisual/context",
"version": "0.0.4",
"description": "",
"main": "index.js",
"scripts": {
"dev": "bun src/dev.ts --watch",
"build": "bun bun.config.mjs",
"publish": "ev npm publish npm -p"
},
"files": [
"dist",
"src"
],
"publishConfig": {
"access": "public"
},
"keywords": [],
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
"license": "MIT",
"packageManager": "pnpm@10.14.0",
"type": "module",
"exports": {
".": {
"import": "./dist/app.js",
"types": "./dist/app.d.ts"
}
},
"devDependencies": {
"@kevisual/load": "^0.0.6",
"@kevisual/types": "^0.0.10",
"@kevisual/use-config": "^1.0.19",
"@types/node": "^24.2.1"
}
}

40
readme.md Normal file
View File

@@ -0,0 +1,40 @@
# 环境变量 context
上下文 的node或则浏览器当中如果global中不存在context则赋值到global如果存在则获取对应的。
```ts
import { useContextKey } from '@kevisual/context';
const v = useContextKey('a', () => '123'); // 如果是第一次初始化返回123否则返回之前初始化的值
```
### types
```ts
type GlobalContext<T = SimpleObject> = {
name?: string;
[key: string]: any;
} & T;
declare const useEnv: <T = SimpleObject>(initEnv?: GlobalContext<T>, initKey?: string, isOverwrite?: boolean) => Required<GlobalContext<T>>;
type InitResult<T> = T | Promise<T> | null;
type InitFn<T> = () => T | Promise<T>;
type Init<T> = T | InitFn<T> | null;
type AsyncResult<T, ASYNC extends boolean = false> = ASYNC extends true ? Promise<T> : T;
type SimpleObject = Record<string, any>;
declare const useEnvKey: <T = any>(key: string, init?: Init<T>, initKey?: string) => InitResult<T>;
declare const usePageEnv: (init?: Init<any>, initKey?: string) => any;
declare const useEnvKeyNew: (key: string, initKey?: string, opts?: {
getNew?: boolean;
init?: Init<any>;
}) => any;
declare const useContext: <T = SimpleObject>(initContext?: GlobalContext<T>, isOverwrite?: boolean) => Required<GlobalContext<T>>;
declare const useContextKey: <T = any, ASYNC extends boolean = false>(key: string, init?: Init<T>, isNew?: boolean) => AsyncResult<T, ASYNC>;
declare const usePageContext: (init?: Init<any>) => any;
declare const useConfig: <T = SimpleObject>(initConfig?: Partial<GlobalContext<T>>, isOverwrite?: boolean) => Required<GlobalContext<Partial<GlobalContext<T>>>>;
declare const useConfigKey: <T = any, ASYNC extends boolean = false>(key: string, init?: Init<T>, isNew?: boolean) => AsyncResult<T, ASYNC>;
declare const usePageConfig: (init?: Init<any>) => any;
export { useConfig, useConfigKey, useContext, useContextKey, useEnv, useEnvKey, useEnvKeyNew, usePageConfig, usePageContext, usePageEnv };
```

201
src/index.ts Normal file
View File

@@ -0,0 +1,201 @@
import { isBrowser } from './utils/browser.ts';
import { getPathKey } from './utils/path-key.ts';
import { BaseLoad } from '@kevisual/load';
const gt = (globalThis as any) || window || self;
type GlobalContext<T = SimpleObject> = {
name?: string;
[key: string]: any;
} & T;
// 从window对象中获取全局的环境变量如果没有则初始化一个
export const useEnv = <T = SimpleObject>(initEnv?: GlobalContext<T>, initKey = 'config', isOverwrite?: boolean) => {
const env: GlobalContext<T> = gt[initKey];
const _env = env || initEnv;
// 环境不存在则初始化一个
if (!env) {
if (_env) {
gt[initKey] = _env;
} else {
gt[initKey] = {};
}
} else if (isOverwrite) {
// 环境存在且是覆盖模式
gt[initKey] = { ...env, ...initEnv };
}
return gt[initKey] as Required<GlobalContext<T>>;
};
type InitResult<T> = T | Promise<T> | null;
type InitFn<T> = () => T | Promise<T>;
type Init<T> = T | InitFn<T> | null;
type AsyncResult<T, ASYNC extends boolean = false> = ASYNC extends true ? Promise<T> : T;
type SimpleObject = Record<string, any>;
// 从全局环境变量中获取指定的key值如果没有则初始化一个, key不存在返回Env对象
export const useEnvKey = <T = any>(key: string, init?: Init<T>, initKey = 'config'): InitResult<T> => {
const _env = useEnv({}, initKey);
// 已经存在,直接返回
if (key && typeof _env[key] !== 'undefined') {
return _env[key];
}
// 不存在,但是有初始化函数,初始化的返回,同步函数,删除了重新加载?
if (key && init) {
if (typeof init !== 'function') {
_env[key] = init as T;
}
// 如果是函数,执行函数
if (typeof init === 'function') {
const result = (init as InitFn<T>)();
// 如果结果是Promise处理异步结果
if (result instanceof Promise) {
return result.then((res) => {
_env[key] = res;
return res as T;
});
}
_env[key] = result;
}
return _env[key];
}
if (key) {
// 加载
const baseLoad = new BaseLoad();
const voidFn = async () => {
return _env[key];
};
const checkFn = async () => {
const loadRes = await baseLoad.load(voidFn, {
key,
isReRun: true,
checkSuccess: () => _env[key],
timeout: 5 * 60 * 1000,
interval: 1000,
//
});
if (loadRes.code !== 200) {
console.error('load key error');
return null;
}
return _env[key];
};
return checkFn();
}
// 不存在,没有初始化函数
console.error('key is empty ');
return null;
};
export const usePageEnv = (init?: Init<any>, initKey = 'config') => {
const { id } = getPathKey();
return useEnvKey(id, init, initKey);
};
export const useEnvKeyNew = (key: string, initKey = 'config', opts?: { getNew?: boolean; init?: Init<any> }) => {
const _env = useEnv({}, initKey);
if (key) {
delete _env[key];
}
if (opts?.getNew && opts.init) {
return useEnvKey(key, opts.init, initKey);
} else if (opts?.getNew) {
return useEnvKey(key, null, initKey);
}
};
export const useContext = <T = SimpleObject>(initContext?: GlobalContext<T>, isOverwrite?: boolean) => {
return useEnv(initContext, 'context', isOverwrite);
};
export const useContextKey = <T = any, ASYNC extends boolean = false>(key: string, init?: Init<T>, isNew?: boolean): AsyncResult<T, ASYNC> => {
if (isNew) {
return useEnvKeyNew(key, 'context', { getNew: true, init }) as any;
}
return useEnvKey(key, init, 'context') as any;
};
export const use = useContextKey;
export const usePageContext = (init?: Init<any>) => {
const { id } = getPathKey();
return useContextKey(id, init);
};
export const useConfig = <T = SimpleObject>(initConfig?: Partial<GlobalContext<T>>, isOverwrite?: boolean) => {
return useEnv(initConfig, 'config', isOverwrite);
};
export const useConfigKey = <T = any, ASYNC extends boolean = false>(key: string, init?: Init<T>, isNew?: boolean): AsyncResult<T, ASYNC> => {
if (isNew) {
return useEnvKeyNew(key, 'config', { getNew: true, init });
}
return useEnvKey(key, init, 'config') as any;
};
export const usePageConfig = (init?: Init<any>) => {
const { id } = getPathKey();
return useConfigKey(id, init);
};
class InitEnv {
static isInit = false;
static init(opts?: { load?: boolean; page?: boolean }) {
if (InitEnv.isInit) {
return;
}
const { load = true, page = false } = opts || {};
InitEnv.isInit = true;
// bind to window, 必须要的获取全局的环境变量
// @ts-ignore
gt.useConfigKey = useConfigKey;
// @ts-ignore
gt.useContextKey = useContextKey;
// @ts-ignore
gt.use = use;
// @ts-ignore
gt.webEnv = { useConfigKey, useContextKey, use };
// @ts-ignore
load && (gt.Load = BaseLoad);
}
}
InitEnv.init();
/**
* 从环境变量获取
* @param envKey
* @param initKey
* @returns
*/
export const useKey = <T = string>(envKey: T, initKey = 'context') => {
let key: string = envKey as unknown as string;
if (!key) return null;
const _env = useEnv({}, initKey);
// 已经存在,直接返回
if (typeof _env[key] !== 'undefined') {
return _env[key];
}
if (!isBrowser) {
// 如果是node类从process.env获取对应的环境变量
const nodeEev = gt?.process?.env;
if (typeof nodeEev !== 'undefined') {
const value = nodeEev[key];
if (typeof value !== 'undefined') {
return value;
}
}
} else {
/**
* 从浏览器的storage获取变量值
*/
const storage = gt?.localStorage;
if (typeof storage !== 'undefined') {
const value = storage.getItem(key);
if (typeof value !== 'undefined') {
return value;
}
}
}
return null;
}

33
src/test/a.ts Normal file
View File

@@ -0,0 +1,33 @@
import { useConfigKey, useConfig } from '@/index.ts';
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const v = useConfigKey<{ name: string }>('a', async () => {
await sleep(1000);
return {
name: 'a',
};
});
console.log('v', v.name);
const v2 = await useConfigKey<{ name: string }, true>('a');
console.log('v2', v2.name);
const v3 = useConfigKey<{ name: string }>('a', {
name: 'bbba',
});
console.log('v3', v3.name);
const b = useConfig<{ f: string }>(
{
name: 'b',
age: 18,
getName: () => {
return 'b';
},
},
true,
);
console.log('b', b.name, b.age, b.getName?.());

1
src/utils/browser.ts Normal file
View File

@@ -0,0 +1 @@
export const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';

14
src/utils/path-key.ts Normal file
View File

@@ -0,0 +1,14 @@
export const getPathKey = () => {
if (!isBrowser) {
return { path: '', key: '', id: '', prefix: '' };
}
// 从localtion.href的路径中/a/b 中 a为pathb为key
const pathname = location.pathname;
const paths = pathname.split('/');
let [path, key] = paths.slice(1);
path = path || '';
key = key || '';
return { path, key, id: path + '---' + key, prefix: `/${path}/${key}` };
};
const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';

20
tsconfig.json Normal file
View File

@@ -0,0 +1,20 @@
{
"extends": "@kevisual/types/json/backend.json",
"compilerOptions": {
"baseUrl": "./",
"typeRoots": [
"node_modules/@types",
],
"paths": {
"@/*": [
"src/*"
],
"*": [
"types/*"
]
}
},
"include": [
"src/**/*.ts"
],
}