web-background 1.0.3 → 1.0.5
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 +142 -61
- package/dist/background.d.ts +3 -1
- package/dist/index.js +1 -1
- package/dist/modules/WorkerBuilder.d.ts +2 -1
- package/dist/modules/cacheManager.d.ts +16 -0
- package/dist/modules/captureContext.d.ts +1 -0
- package/dist/modules/functionUtils.d.ts +9 -0
- package/dist/modules/serializeFunction.d.ts +3 -0
- package/dist/modules/workerCleanupRegistry.d.ts +4 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,68 +1,149 @@
|
|
|
1
|
-
# Web Background
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
1
|
+
# 🚀 Web Background
|
|
2
|
+
|
|
3
|
+
**Simple Web Worker Library - Run complex tasks in the background!**
|
|
4
|
+
|
|
5
|
+
Web Background is a simple yet powerful library that enables you to execute heavy computations in the background without blocking the main thread in web browsers.
|
|
6
|
+
|
|
7
|
+
## ✨ Features
|
|
8
|
+
|
|
9
|
+
- 🎯 **Simple API**: Execute workers instantly with a single function
|
|
10
|
+
- 🧠 **Automatic Context Capture**: Automatically detects and transfers external variables
|
|
11
|
+
- 🚀 **High-Performance Caching**: Cache identical functions for faster re-execution
|
|
12
|
+
- 🧹 **Automatic Memory Management**: Auto-cleanup workers and cache on GC
|
|
13
|
+
- 💪 **Full TypeScript Support**: Complete type safety
|
|
14
|
+
- 📦 **Zero Dependencies**: No external dependencies
|
|
15
|
+
|
|
16
|
+
## 📦 Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install web-background
|
|
20
|
+
# or
|
|
21
|
+
yarn add web-background
|
|
22
|
+
# or
|
|
23
|
+
pnpm add web-background
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 🚀 Quick Start
|
|
27
|
+
|
|
28
|
+
### Basic Usage
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { background } from "web-background";
|
|
32
|
+
|
|
33
|
+
// Simple background execution
|
|
34
|
+
const heavyCalculation = background((numbers: number[]) => {
|
|
35
|
+
// CPU intensive task
|
|
36
|
+
return numbers.reduce((sum, num) => sum + Math.sqrt(num), 0);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Execute without blocking main thread
|
|
40
|
+
const result = await heavyCalculation([1, 4, 9, 16, 25]);
|
|
41
|
+
console.log(result); // 15
|
|
42
|
+
|
|
43
|
+
// When external variables are needed (optional context passing)
|
|
44
|
+
const multiplier = 10;
|
|
45
|
+
const processWithContext = background(
|
|
46
|
+
(numbers: number[]) => numbers.map((x) => x * multiplier),
|
|
47
|
+
{ multiplier } // Explicit passing when needed
|
|
48
|
+
);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Complex Real-world Example
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// Image data processing
|
|
55
|
+
const processImage = background((imageData: ImageData) => {
|
|
56
|
+
const { data, width, height } = imageData;
|
|
57
|
+
const processed = new Uint8ClampedArray(data.length);
|
|
58
|
+
|
|
59
|
+
// Apply Gaussian blur
|
|
60
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
61
|
+
const y = Math.floor(i / 4 / width);
|
|
62
|
+
const x = Math.floor(i / 4 - width * y);
|
|
63
|
+
|
|
64
|
+
processed[i] = data[i] * 0.8; // R
|
|
65
|
+
processed[i + 1] = data[i + 1] * 0.8; // G
|
|
66
|
+
processed[i + 2] = data[i + 2] * 0.8; // B
|
|
67
|
+
processed[i + 3] = data[i + 3]; // A
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return new ImageData(processed, width, height);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Usage
|
|
74
|
+
const canvas = document.querySelector("canvas");
|
|
75
|
+
const ctx = canvas.getContext("2d");
|
|
76
|
+
const originalData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
77
|
+
|
|
78
|
+
const blurredData = await processImage(originalData);
|
|
79
|
+
ctx.putImageData(blurredData, 0, 0);
|
|
49
80
|
```
|
|
50
81
|
|
|
82
|
+
## 🛠️ API Reference
|
|
83
|
+
|
|
84
|
+
### `background<T>(fn: T, context?: Record<string, any>): FunctionInBackground<T>`
|
|
85
|
+
|
|
86
|
+
Converts a function to run in a web worker.
|
|
87
|
+
|
|
88
|
+
**Parameters:**
|
|
89
|
+
|
|
90
|
+
- `fn`: Function to execute in the background
|
|
91
|
+
- `context` (optional): Explicitly pass external variables when needed
|
|
92
|
+
|
|
93
|
+
**Returns:**
|
|
94
|
+
|
|
95
|
+
- Function with the same signature as the original but returns a `Promise`
|
|
96
|
+
|
|
97
|
+
**Type:**
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
type FunctionInBackground<Fn> = (
|
|
101
|
+
...params: Parameters<Fn>
|
|
102
|
+
) => Promise<ReturnType<Fn>>;
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## 📋 Constraints & Guidelines
|
|
106
|
+
|
|
107
|
+
### ✅ What's Available
|
|
108
|
+
|
|
109
|
+
- Pure computation logic
|
|
110
|
+
- JSON serializable data (strings, numbers, arrays, objects, etc.)
|
|
111
|
+
- Built-in global objects like `Math`, `Date`, `JSON`
|
|
112
|
+
- Explicitly passed functions and variables
|
|
113
|
+
|
|
114
|
+
### ❌ What's Not Available
|
|
115
|
+
|
|
116
|
+
- DOM APIs (`document`, `window`, etc.)
|
|
117
|
+
- Web APIs (`fetch`, `localStorage`, etc. have separate worker support)
|
|
118
|
+
- Non-serializable data (functions, Symbols, undefined, etc.)
|
|
119
|
+
- ES6 module imports (context passing required in bundler environments)
|
|
120
|
+
|
|
121
|
+
### 💡 Best Practices
|
|
122
|
+
|
|
123
|
+
1. **Context is optional**:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
// ✅ Pure function without external variables
|
|
127
|
+
const pureWorker = background((data) => complexCalculation(data));
|
|
51
128
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
129
|
+
// ✅ Pass context when needed (more stable)
|
|
130
|
+
const contextWorker = background((data) => processWithConfig(data, config), {
|
|
131
|
+
config,
|
|
132
|
+
});
|
|
133
|
+
```
|
|
55
134
|
|
|
56
|
-
|
|
57
|
-
대부분의 경우 이 모듈을 사용할 필요는 없으나, 비용이 큰 작업을 처리해야 할 경우 사용할 것을 추천합니다.
|
|
135
|
+
2. **Use only for heavy computations**:
|
|
58
136
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
137
|
+
```typescript
|
|
138
|
+
// ❌ Simple operations
|
|
139
|
+
const add = background((a, b) => a + b);
|
|
62
140
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
141
|
+
// ✅ CPU intensive operations
|
|
142
|
+
const processLargeArray = background((data) => {
|
|
143
|
+
// Complex processing logic...
|
|
144
|
+
});
|
|
145
|
+
```
|
|
66
146
|
|
|
67
|
-
|
|
68
|
-
|
|
147
|
+
3. **Keep data size appropriate**:
|
|
148
|
+
- Large objects or complex contexts cause serialization overhead
|
|
149
|
+
- Pass only necessary data for performance optimization
|
package/dist/background.d.ts
CHANGED
|
@@ -1 +1,3 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type FunctionInBackground<Callback extends (params: any) => any> = (...params: Parameters<Callback>) => Promise<ReturnType<Callback>>;
|
|
2
|
+
export declare function background<Payload, ReturnValue>(fn: (payload: Payload) => ReturnValue): FunctionInBackground<typeof fn>;
|
|
3
|
+
export declare function background<Payload, ReturnValue>(fn: (payload: Payload) => ReturnValue, context?: Record<string, any>): FunctionInBackground<typeof fn>;
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
class e extends Worker{postMessage(e){super.postMessage(e)}addEventListener(e,t){super.addEventListener(e,t)}}class t extends e{constructor(e,t){super(e,t)}request(e){return new Promise((t=>{const n=({data:e})=>{t(e),this.removeEventListener("message",n)};this.postMessage(e),this.addEventListener("message",n)}))}}"function"==typeof SuppressedError&&SuppressedError;class n{static fromModule(e,t){var{module:n}=t,r=function(e,t){var n={};for(var
|
|
1
|
+
class e extends Worker{postMessage(e){super.postMessage(e)}addEventListener(e,t){super.addEventListener(e,t)}}class t extends e{constructor(e,t){super(e,t)}request(e){return new Promise((t=>{const n=({data:e})=>{t(e),this.removeEventListener("message",n)};this.postMessage(e),this.addEventListener("message",n)}))}}"function"==typeof SuppressedError&&SuppressedError;class n{constructor(){this.store=new Map}get(e){return this.store.get(e)}set(e,t){this.store.set(e,t)}delete(e){this.store.delete(e)}clear(){this.store.clear()}size(){return this.store.size}}const o=new n,r=new class extends n{deleteByPrefix(e){for(const[t,n]of this.store)t.startsWith(e)&&this.delete(t)}},s=new n,i=new n;class c{static increment(e){const t=i.get(e)||0;i.set(e,t+1)}static decrement(e){const t=i.get(e)||0;return t<=1?(i.delete(e),!0):(i.set(e,t-1),!1)}static getCount(e){return i.get(e)||0}}class a{static getContext(e){return o.get(e)}static setContext(e,t){o.set(e,t),c.increment(e)}static getSerialization(e){return r.get(e)}static setSerialization(e,t){r.set(e,t);const n=e.split(":")[0];c.increment(n)}static getIdentifiers(e){return s.get(e)}static setIdentifiers(e,t){s.set(e,t),c.increment(e)}static decrementUsage(e){c.decrement(e)&&(o.delete(e),s.delete(e),r.deleteByPrefix(e+":"),console.log(`'${e.substring(0,50)}...' 함수 캐시 정리 완료`))}static getCacheStats(){return{contextCacheSize:o.size(),serializationCacheSize:r.size(),identifierCacheSize:s.size(),activeFunctions:i.size()}}static clearAllCaches(){o.clear(),r.clear(),s.clear(),i.clear(),console.log("모든 캐시가 정리되었습니다")}}const l=new Set(["function","return","var","let","const","if","else","for","while","do","break","continue","switch","case","default","try","catch","finally","throw","new","this","typeof","instanceof","in","of","class","extends","super","async","await","yield","true","false","null","undefined","delete","void","with","export","import","from"]),f=new Set(["console","Math","Date","JSON","Array","Object","String","Number","Boolean","RegExp","Error","Promise","setTimeout","setInterval","clearTimeout","clearInterval","parseInt","parseFloat","isNaN","isFinite","decodeURIComponent","encodeURIComponent","window","document","global","process","self","URL","URLSearchParams","fetch","XMLHttpRequest","localStorage","sessionStorage","location","history","navigator","Blob","File","FileReader","FormData","Headers","Request","Response","AbortController","AbortSignal","crypto","performance","requestAnimationFrame","cancelAnimationFrame","requestIdleCallback","cancelIdleCallback"]);function u(e){const t=a.getIdentifiers(e);if(t)return t;const n=new Set,o=/\b[a-zA-Z_$][a-zA-Z0-9_$]*\b/g;let r;for(;null!==(r=o.exec(e));){const t=r[0];d(t)||p(t,e)||y(t,e)||n.add(t)}const s=Array.from(n);return a.setIdentifiers(e,s),s}function d(e){return l.has(e)}function g(e){return f.has(e)}function p(e,t){return[new RegExp(`function\\s*\\([^)]*\\b${e}\\b[^)]*\\)`),new RegExp(`\\([^)]*\\b${e}\\b[^)]*\\)\\s*=>`),new RegExp(`^\\s*${e}\\s*=>`),new RegExp(`async\\s+\\([^)]*\\b${e}\\b[^)]*\\)\\s*=>`),new RegExp(`async\\s+${e}\\s*=>`)].some((e=>e.test(t)))}function y(e,t){return[new RegExp(`(?:var|let|const)\\s+${e}\\b`),new RegExp(`(?:var|let|const)\\s+\\{[^}]*\\b${e}\\b[^}]*\\}`),new RegExp(`function\\s+${e}\\b`)].some((e=>e.test(t)))}function w(e){if(null==e)return!0;if("boolean"==typeof e||"number"==typeof e||"string"==typeof e)return!0;if("function"==typeof e||"symbol"==typeof e||"bigint"==typeof e)return!1;if(Array.isArray(e))return e.every((e=>w(e)));if("object"==typeof e)try{return JSON.stringify(e),!0}catch(e){return!1}return!1}function h(e,t){const n=`${e.toString()}:${JSON.stringify(t||{})}`,o=a.getSerialization(n);if(o)return o;const r=function(e,t){const n=e.toString();let o="(function() {\n";const r=Object.entries(t),s=r.filter((([,e])=>"function"!=typeof e)),i=r.filter((([,e])=>"function"==typeof e));s.forEach((([e,t])=>{try{o+=` const ${e} = ${JSON.stringify(t)};\n`}catch(t){o+=` const ${e} = undefined;\n`}})),i.forEach((([e,t])=>{"function"==typeof t&&(o+=` const ${e} = ${t.toString()};\n`)})),r.length>0&&(o+="\n");return o+=` return ${n};\n`,o+="})()",o}(e,t||{});return a.setSerialization(n,r),r}class b{static fromModule(e,t){var{module:n,context:o}=t,r=function(e,t){var n={};for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&t.indexOf(o)<0&&(n[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var r=0;for(o=Object.getOwnPropertySymbols(e);r<o.length;r++)t.indexOf(o[r])<0&&Object.prototype.propertyIsEnumerable.call(e,o[r])&&(n[o[r]]=e[o[r]])}return n}(t,["module","context"]);const s=o||function(e){const t=u(e.toString()),n={};return t.forEach((e=>{if(!g(e))try{const t=globalThis[e]||("undefined"!=typeof window?window[e]:void 0);void 0!==t&&("function"==typeof t||w(t))&&(n[e]=t)}catch(e){}})),n}(n),i=h(n,s);console.log(i);const c=`\n self.addEventListener('message', async event => {\n const func = ${i};\n const result = await func(${n.length>1?"...event.data":"event.data"});\n self.postMessage(result);\n });\n `.trim(),a=new Blob([c]),l=URL.createObjectURL(a),f=new e(l,r),d=f.terminate;return f.terminate=()=>{d.call(f),URL.revokeObjectURL(l)},f}}const m=new FinalizationRegistry((e=>{e.worker.terminate(),a.decrementUsage(e.funcStr),console.log("워커 정리 및 캐시 정리 완료")}));function S(e){const t=e.toString(),n=a.getContext(t);if(n)return console.log("캐시된 컨텍스트 사용:",n),n;const o={};console.log("함수 문자열:",t);const r=u(t),s=function(e){const t=e.match(/function[^(]*\(([^)]*)\)|^\s*\(([^)]*)\)\s*=>|^\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=>/),n=new Set;t&&(t[1]||t[2]||t[3]||"").split(",").forEach((e=>{const t=e.trim().split("=")[0].trim();t&&/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(t)&&n.add(t)}));return n}(t),i=function(e){const t=new Set,n=/(?:var|let|const)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g;let o;for(;null!==(o=n.exec(e));)t.add(o[1]);return t}(t),c=function(){const e={};return"undefined"!=typeof window&&Object.getOwnPropertyNames(window).forEach((t=>{try{if(!g(t)){const n=Object.getOwnPropertyDescriptor(window,t);if(n&&n.configurable){const n=window[t];void 0!==n&&(e[t]=n)}}}catch(e){}})),"undefined"!=typeof globalThis&&Object.getOwnPropertyNames(globalThis).forEach((t=>{try{g(t)||t in e||(e[t]=globalThis[t])}catch(e){}})),e}();return console.log("추출된 식별자들:",r),console.log("매개변수들:",Array.from(s)),console.log("로컬 변수들:",Array.from(i)),console.log("호출자 스코프:",Object.keys(c)),r.forEach((e=>{if(!(d(e)||g(e)||s.has(e)||i.has(e))){if(console.log(`'${e}' 검사 중...`),e in c){const t=c[e];return console.log(`호출자 스코프에서 '${e}' 발견:`,typeof t),void(("function"==typeof t||w(t))&&(o[e]=t))}try{let t;"undefined"!=typeof window&&e in window?(t=window[e],console.log(`window에서 '${e}' 발견:`,typeof t)):"undefined"!=typeof globalThis&&e in globalThis&&(t=globalThis[e],console.log(`globalThis에서 '${e}' 발견:`,typeof t)),void 0!==t&&("function"==typeof t||w(t))&&(o[e]=t)}catch(t){console.log(`'${e}' 접근 실패:`,t)}}})),console.log("최종 컨텍스트:",o),a.setContext(t,o),o}function $(e,n){const o=void 0!==n?n:S(e);console.log("최종 사용될 컨텍스트:",o);let r=null;return function n(...s){if("undefined"==typeof window)throw new Error("[web-background] You must use background in browser");return null==r&&(r=b.fromModule(t,{module:e,context:o}),m.register(n,{worker:r,funcStr:e.toString()})),r.request.call(r,s.length>1?s:s[0])}}export{$ as background};
|
|
@@ -4,8 +4,9 @@ interface WorkerConstructor<Worker> {
|
|
|
4
4
|
}
|
|
5
5
|
interface Options extends WorkerOptions {
|
|
6
6
|
module: (...args: any[]) => any;
|
|
7
|
+
context?: Record<string, any>;
|
|
7
8
|
}
|
|
8
9
|
export declare class WorkerBuilder {
|
|
9
|
-
static fromModule<W extends Worker>(Worker: WorkerConstructor<W>, { module, ...options }: Options): W;
|
|
10
|
+
static fromModule<W extends Worker>(Worker: WorkerConstructor<W>, { module, context, ...options }: Options): W;
|
|
10
11
|
}
|
|
11
12
|
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare class CacheManager {
|
|
2
|
+
static getContext(funcStr: string): Record<string, any> | undefined;
|
|
3
|
+
static setContext(funcStr: string, context: Record<string, any>): void;
|
|
4
|
+
static getSerialization(cacheKey: string): string | undefined;
|
|
5
|
+
static setSerialization(cacheKey: string, result: string): void;
|
|
6
|
+
static getIdentifiers(funcStr: string): string[] | undefined;
|
|
7
|
+
static setIdentifiers(funcStr: string, identifiers: string[]): void;
|
|
8
|
+
static decrementUsage(funcStr: string): void;
|
|
9
|
+
static getCacheStats(): {
|
|
10
|
+
contextCacheSize: number;
|
|
11
|
+
serializationCacheSize: number;
|
|
12
|
+
identifierCacheSize: number;
|
|
13
|
+
activeFunctions: number;
|
|
14
|
+
};
|
|
15
|
+
static clearAllCaches(): void;
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function captureCallerContext(fn: Function): Record<string, any>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function extractIdentifiers(funcStr: string): string[];
|
|
2
|
+
export declare function isKeyword(identifier: string): boolean;
|
|
3
|
+
export declare function isGlobalBuiltIn(identifier: string): boolean;
|
|
4
|
+
export declare function isParameter(identifier: string, funcStr: string): boolean;
|
|
5
|
+
export declare function isLocalVariable(identifier: string, funcStr: string): boolean;
|
|
6
|
+
export declare function isSerializable(value: any): boolean;
|
|
7
|
+
export declare function getGlobalScope(): Record<string, any>;
|
|
8
|
+
export declare function extractParametersFromFunction(funcStr: string): Set<string>;
|
|
9
|
+
export declare function extractLocalVariables(funcStr: string): Set<string>;
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"Background",
|
|
8
8
|
"Web Background"
|
|
9
9
|
],
|
|
10
|
-
"version": "1.0.
|
|
10
|
+
"version": "1.0.5",
|
|
11
11
|
"main": "dist/index.js",
|
|
12
12
|
"module": "dist/index.js",
|
|
13
13
|
"type": "module",
|
|
@@ -24,10 +24,11 @@
|
|
|
24
24
|
"@babel/preset-env": "^7.23.3",
|
|
25
25
|
"@rollup/plugin-babel": "^6.0.4",
|
|
26
26
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
27
|
+
"@rollup/plugin-strip": "^3.0.4",
|
|
27
28
|
"@rollup/plugin-terser": "^0.4.4",
|
|
28
29
|
"rollup": "^4.4.0",
|
|
29
30
|
"rollup-plugin-typescript2": "^0.36.0",
|
|
30
31
|
"typescript": "5.2"
|
|
31
32
|
},
|
|
32
|
-
"
|
|
33
|
-
}
|
|
33
|
+
"files": ["dist"]
|
|
34
|
+
}
|