bunja 0.0.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.
@@ -0,0 +1,4 @@
1
+ {
2
+ "editor.formatOnSave": true,
3
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
4
+ }
package/README.md ADDED
@@ -0,0 +1,8 @@
1
+ # Bunja
2
+
3
+ Minimal Dependency Injection Library for React. (Minified & gzipped size < 1kB)\
4
+ Heavily inspired by [Bunshi](https://github.com/saasquatch/bunshi).
5
+
6
+ > Definition: Bunja (分子 / 분자) - Korean for molecule, member or element.
7
+
8
+ ## TODO: How to use
package/bunja.ts ADDED
@@ -0,0 +1,132 @@
1
+ import * as React from "react";
2
+
3
+ type Dep<T> = React.Context<T> | Bunja<T>;
4
+
5
+ class Bunja<T> {
6
+ constructor(
7
+ public id: number,
8
+ public deps: Dep<any>[],
9
+ public contexts: React.Context<any>[],
10
+ public init: (args: any[], dispose: () => void) => BunjaInstance<T>
11
+ ) {}
12
+ }
13
+
14
+ interface BunjaInitResult<T> {
15
+ value: T;
16
+ mount?: () => void;
17
+ unmount?: () => void;
18
+ }
19
+
20
+ export function bunja<T>(deps: [], init: () => BunjaInitResult<T>): Bunja<T>;
21
+ export function bunja<T, U>(
22
+ deps: [Dep<U>],
23
+ init: (u: U) => BunjaInitResult<T>
24
+ ): Bunja<T>;
25
+ export function bunja<T, U, V>(
26
+ deps: [Dep<U>, Dep<V>],
27
+ init: (u: U, v: V) => BunjaInitResult<T>
28
+ ): Bunja<T>;
29
+ export function bunja<T, U, V, W>(
30
+ deps: [Dep<U>, Dep<V>, Dep<W>],
31
+ init: (u: U, v: V, w: W) => BunjaInitResult<T>
32
+ ): Bunja<T>;
33
+ export function bunja<T, const U extends any[]>(
34
+ deps: { [K in keyof U]: Dep<U[K]> },
35
+ init: (...args: U) => BunjaInitResult<T>
36
+ ): Bunja<T> {
37
+ const contexts = deps.filter(
38
+ (dep) => !(dep instanceof Bunja)
39
+ ) as React.Context<any>[];
40
+ const bunjas = deps.filter((dep) => dep instanceof Bunja) as Bunja<any>[];
41
+ const dedupedContexts = Array.from(
42
+ new Set([...contexts, ...bunjas.flatMap((def) => def.contexts)])
43
+ );
44
+ return new Bunja(bunja.counter++, deps, dedupedContexts, (args, dispose) => {
45
+ const noop = () => {};
46
+ const { value, mount = noop, unmount = noop } = init(...(args as U));
47
+ mount();
48
+ return new BunjaInstance(() => (dispose(), unmount()), value);
49
+ });
50
+ }
51
+ bunja.counter = 0;
52
+
53
+ export function useBunja<T>(bunja: Bunja<T>): T {
54
+ const { id, deps, contexts } = bunja;
55
+ const rid = useRid();
56
+ const tuples = contexts.map((c) => [c, React.useContext(c)] as const);
57
+ const scopes = tuples.map(([context, value]) => getScope(context, value));
58
+ const scopeMap = new Map(tuples);
59
+ const args = deps.map((dep) => {
60
+ if (dep instanceof Bunja) return useBunja(dep);
61
+ return scopeMap.get(dep);
62
+ });
63
+ const biid = `${id}:${scopes
64
+ .map(({ id }) => id)
65
+ .sort()
66
+ .join(",")}`;
67
+ const bunjaInstance = getBunjaInstance(bunja, biid, args);
68
+ React.useEffect(() => {
69
+ bunjaInstance.reg(rid);
70
+ return () => bunjaInstance.dereg(rid);
71
+ }, [bunjaInstance]);
72
+ React.useEffect(() => {
73
+ scopes.forEach((scope) => scope.reg(rid));
74
+ return () => scopes.forEach((scope) => scope.dereg(rid));
75
+ }, [rid]);
76
+ return bunjaInstance.value;
77
+ }
78
+
79
+ const useRid = () => React.useState(() => useRid.counter++)[0];
80
+ useRid.counter = 0;
81
+
82
+ const bunjas: Record<string, BunjaInstance<any>> = {};
83
+ function getBunjaInstance<T>(bunja: Bunja<T>, biid: string, args: any[]) {
84
+ return (bunjas[biid] ??= bunja.init(args, () => {
85
+ delete bunjas[biid];
86
+ }));
87
+ }
88
+
89
+ const scopes = new WeakMap<React.Context<any>, Map<any, Scope>>();
90
+ function getScope(context: React.Context<any>, value: any) {
91
+ const m = scopes.get(context) ?? scopes.set(context, new Map()).get(context)!;
92
+ const init = () =>
93
+ new Scope(() => m.delete(value), context, value, getScope.counter++);
94
+ return m.get(value) ?? m.set(value, init()).get(value)!;
95
+ }
96
+ getScope.counter = 0;
97
+
98
+ class RefCounter<T = number> {
99
+ #disposed = false;
100
+ refs = new Set<T>();
101
+ constructor(public dispose: () => void) {}
102
+ reg(reference: T) {
103
+ this.refs.add(reference);
104
+ }
105
+ dereg(reference: T) {
106
+ this.refs.delete(reference);
107
+ setTimeout(() => {
108
+ if (this.#disposed) return;
109
+ if (this.refs.size < 1) {
110
+ this.#disposed = true;
111
+ this.dispose();
112
+ }
113
+ });
114
+ }
115
+ }
116
+
117
+ class BunjaInstance<T> extends RefCounter {
118
+ constructor(dispose: () => void, public value: T) {
119
+ super(dispose);
120
+ }
121
+ }
122
+
123
+ class Scope extends RefCounter {
124
+ constructor(
125
+ dispose: () => void,
126
+ public context: React.Context<any>,
127
+ public value: any,
128
+ public id: number
129
+ ) {
130
+ super(dispose);
131
+ }
132
+ }
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "bunja",
3
+ "version": "0.0.0",
4
+ "description": "Minimal Dependency Injection Library for React",
5
+ "main": "bunja.ts",
6
+ "scripts": {},
7
+ "keywords": [
8
+ "bunja",
9
+ "react",
10
+ "di"
11
+ ],
12
+ "author": "JongChan Choi <jong@chan.moe>",
13
+ "license": "Zlib",
14
+ "devDependencies": {
15
+ "@types/react": "^18",
16
+ "react": "^18"
17
+ },
18
+ "peerDependencies": {
19
+ "react": ">=17"
20
+ }
21
+ }