@amriogit/injector 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -61,6 +61,37 @@ const userService = useInject(UserService)
61
61
  useProvide(UserService, MockUserService)
62
62
  ```
63
63
 
64
+ ### Vue 2 集成
65
+
66
+ ```ts
67
+ import Vue from 'vue'
68
+ import { ServicePlugin, mapInject } from '@amriogit/injector/vue2'
69
+
70
+ // 安装插件
71
+ Vue.use(ServicePlugin, {
72
+ setup(injector) {
73
+ injector.provide(API_BASE, '/api')
74
+ },
75
+ })
76
+
77
+ // 使用 mapInject 在 computed 中批量声明依赖
78
+ export default {
79
+ computed: {
80
+ ...mapInject({ UserService, ChannelService }),
81
+ },
82
+ created() {
83
+ this.userService.fetchUsers()
84
+ this.channelService.load()
85
+ },
86
+ }
87
+ ```
88
+
89
+ #### Vue 2 注意事项
90
+
91
+ 1. **推荐 `mapInject`**:批量声明、自动 camelCase 转换、类型安全
92
+ 2. **无额外依赖**:不依赖 Composition API,Vue 2.0+ 通用
93
+ 3. **自动清理**:根组件销毁时自动调用 `injector.reset()`,触发所有 Service 的 `onDestroy` 钩子
94
+
64
95
  ## 特性
65
96
 
66
97
  - **零依赖** — 纯 TypeScript,无运行时依赖
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Vue 2 集成层,用于对接共享 DI 核心。
3
+ * 与 injector.ts 分离,使服务端不打包 Vue。
4
+ *
5
+ * 通过 `Vue.use(ServicePlugin)` 安装后,所有组件实例
6
+ * 可使用 `this.$inject(Token)` 注入 Service。
7
+ *
8
+ * @module injector-vue2
9
+ */
10
+ /**
11
+ * Vue 2 插件预期 `import Vue from 'vue'` 得到 Vue 2 构造函数。
12
+ * 当前项目 devDep 为 Vue 3(与 ./injector-vue.ts 共享),Vue 3 的 ESM
13
+ * 无 default export,因此需要 @ts-expect-error。
14
+ * 在消费方(Vue 2 项目)中,这行能正确解析。
15
+ */
16
+ import VueConstructor from 'vue';
17
+ import { Injector, InjectionToken, type Token } from './injector';
18
+ type ClassToken<T> = {
19
+ new (...args: any[]): T;
20
+ };
21
+ type Resolved<T> = T extends InjectionToken<infer V> ? V : T extends ClassToken<infer V> ? V : never;
22
+ /** 将 PascalCase / SCREAMING_SNAKE 转为 camelCase */
23
+ type CamelCase<S extends string, FromSnake extends boolean = false> = S extends `${infer A}_${infer B}` ? `${Lowercase<A>}${Capitalize<CamelCase<B, true>>}` : FromSnake extends true ? Capitalize<Lowercase<S>> : Uncapitalize<S>;
24
+ /** mapInject 的返回类型 */
25
+ type MapInjectResult<T extends Record<string, Token<any>>> = {
26
+ [K in keyof T as CamelCase<K & string>]: Resolved<T[K]>;
27
+ };
28
+ /** 计算属性定义的类型(每个 key 对应一个 getter) */
29
+ type ComputedGetters<T> = {
30
+ [K in keyof T]: (this: any) => T[K];
31
+ };
32
+ /**
33
+ * Vue 2 插件。
34
+ *
35
+ * 安装后,所有组件实例可通过 `this.$inject(Token)` 访问 Service。
36
+ *
37
+ * 通过 mixin 在根组件销毁时自动清理所有 Service 实例的 onDestroy。
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * import Vue from 'vue'
42
+ * import { ServicePlugin } from '@amriogit/injector/vue2'
43
+ *
44
+ * Vue.use(ServicePlugin, {
45
+ * setup(injector) {
46
+ * injector.provide(API_BASE, '/api')
47
+ * },
48
+ * })
49
+ * ```
50
+ *
51
+ * 在组件中使用(推荐 `mapInject`):
52
+ * ```ts
53
+ * import { mapInject } from '@amriogit/injector/vue2'
54
+ *
55
+ * export default {
56
+ * computed: {
57
+ * ...mapInject({ UserService }),
58
+ * },
59
+ * created() {
60
+ * this.userService.fetchUsers()
61
+ * },
62
+ * }
63
+ * ```
64
+ */
65
+ export declare const ServicePlugin: {
66
+ install(VueInstance: typeof VueConstructor, options?: {
67
+ setup?: (injector: Injector) => void;
68
+ }): void;
69
+ };
70
+ /**
71
+ * 批量声明 Service 注入,返回计算属性定义对象。
72
+ *
73
+ * 键名自动转换:`ChannelService` → `channelService`,`API_TOKEN` → `apiToken`。
74
+ * 配合 `...` 展开到 `computed` 中使用。
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * export default {
79
+ * computed: {
80
+ * ...mapInject({ ChannelService, UserService, API_TOKEN }),
81
+ * // 展开后等价于:
82
+ * // channelService() { return this.$inject(ChannelService) },
83
+ * // userService() { return this.$inject(UserService) },
84
+ * // apiToken() { return this.$inject(API_TOKEN) },
85
+ * },
86
+ * created() {
87
+ * this.channelService.fetchChannels()
88
+ * console.log(this.apiToken)
89
+ * },
90
+ * }
91
+ * ```
92
+ */
93
+ export declare function mapInject<T extends Record<string, Token<any>>>(tokens: T): ComputedGetters<MapInjectResult<T>>;
94
+ export {};
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Vue 2 集成层,用于对接共享 DI 核心。
3
+ * 与 injector.ts 分离,使服务端不打包 Vue。
4
+ *
5
+ * 通过 `Vue.use(ServicePlugin)` 安装后,所有组件实例
6
+ * 可使用 `this.$inject(Token)` 注入 Service。
7
+ *
8
+ * @module injector-vue2
9
+ */
10
+ import { Injector } from './injector';
11
+ // ─── 工具函数 ──────────────────────────────────────────────
12
+ function toCamelCase(key) {
13
+ // SCREAMING_SNAKE_CASE → camelCase
14
+ if (key.includes('_')) {
15
+ return key
16
+ .toLowerCase()
17
+ .split('_')
18
+ .filter(Boolean)
19
+ .map((w, i) => (i === 0 ? w : w[0].toUpperCase() + w.slice(1)))
20
+ .join('');
21
+ }
22
+ // PascalCase → camelCase
23
+ return key[0].toLowerCase() + key.slice(1);
24
+ }
25
+ // ─── Vue 2 Plugin ──────────────────────────────────────────
26
+ /**
27
+ * Vue 2 插件。
28
+ *
29
+ * 安装后,所有组件实例可通过 `this.$inject(Token)` 访问 Service。
30
+ *
31
+ * 通过 mixin 在根组件销毁时自动清理所有 Service 实例的 onDestroy。
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * import Vue from 'vue'
36
+ * import { ServicePlugin } from '@amriogit/injector/vue2'
37
+ *
38
+ * Vue.use(ServicePlugin, {
39
+ * setup(injector) {
40
+ * injector.provide(API_BASE, '/api')
41
+ * },
42
+ * })
43
+ * ```
44
+ *
45
+ * 在组件中使用(推荐 `mapInject`):
46
+ * ```ts
47
+ * import { mapInject } from '@amriogit/injector/vue2'
48
+ *
49
+ * export default {
50
+ * computed: {
51
+ * ...mapInject({ UserService }),
52
+ * },
53
+ * created() {
54
+ * this.userService.fetchUsers()
55
+ * },
56
+ * }
57
+ * ```
58
+ */
59
+ export const ServicePlugin = {
60
+ install(VueInstance, options) {
61
+ const injector = new Injector();
62
+ options?.setup?.(injector);
63
+ // 全局可访问:this.$injector 和 this.$inject 在所有组件实例上可用
64
+ VueInstance.prototype.$injector = injector;
65
+ VueInstance.prototype.$inject = injector.inject.bind(injector);
66
+ // 全局 mixin:根组件销毁时自动清理所有 Service
67
+ VueInstance.mixin({
68
+ beforeDestroy() {
69
+ if (this.$root === this) {
70
+ injector.reset();
71
+ }
72
+ },
73
+ });
74
+ },
75
+ };
76
+ // ─── mapInject ─────────────────────────────────────────────
77
+ /**
78
+ * 批量声明 Service 注入,返回计算属性定义对象。
79
+ *
80
+ * 键名自动转换:`ChannelService` → `channelService`,`API_TOKEN` → `apiToken`。
81
+ * 配合 `...` 展开到 `computed` 中使用。
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * export default {
86
+ * computed: {
87
+ * ...mapInject({ ChannelService, UserService, API_TOKEN }),
88
+ * // 展开后等价于:
89
+ * // channelService() { return this.$inject(ChannelService) },
90
+ * // userService() { return this.$inject(UserService) },
91
+ * // apiToken() { return this.$inject(API_TOKEN) },
92
+ * },
93
+ * created() {
94
+ * this.channelService.fetchChannels()
95
+ * console.log(this.apiToken)
96
+ * },
97
+ * }
98
+ * ```
99
+ */
100
+ export function mapInject(tokens) {
101
+ const result = {};
102
+ for (const key of Object.keys(tokens)) {
103
+ const camelKey = toCamelCase(key);
104
+ const token = tokens[key];
105
+ result[camelKey] = function () {
106
+ return this.$inject(token);
107
+ };
108
+ }
109
+ return result;
110
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amriogit/injector",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "轻量 DI 容器 — Injector, BaseService, InjectionToken。零外部依赖,支持 SSR,Vue 集成。",
6
6
  "keywords": [
@@ -24,7 +24,8 @@
24
24
  "types": "./dist/injector.d.ts",
25
25
  "exports": {
26
26
  ".": "./dist/injector.js",
27
- "./vue": "./dist/injector-vue.js"
27
+ "./vue": "./dist/injector-vue.js",
28
+ "./vue2": "./dist/injector-vue2.js"
28
29
  },
29
30
  "files": [
30
31
  "dist"
@@ -33,7 +34,7 @@
33
34
  "access": "public"
34
35
  },
35
36
  "peerDependencies": {
36
- "vue": "^3.4.0"
37
+ "vue": "^2.6.0 || ^3.4.0"
37
38
  },
38
39
  "peerDependenciesMeta": {
39
40
  "vue": {
@@ -47,6 +48,7 @@
47
48
  "prepublishOnly": "npm test && npm run build"
48
49
  },
49
50
  "devDependencies": {
51
+ "@types/vue": "^2.0.0",
50
52
  "typescript": "^5.8.3",
51
53
  "vitest": "^3.2.4",
52
54
  "vue": "^3.5.34"