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 CHANGED
@@ -1,68 +1,149 @@
1
- # Web Background
2
- This module provides background runtime available on the web.
3
- 웹에서 사용할 있는 백그라운드 실행환경을 제공합니다.
4
-
5
- ## API
6
- ### background(fn)
7
-
8
- ```tsx
9
- type FunctionInBackground<Fn> = (...params: Parameters<Fn>) => Promise<ReturnType<Fn>>;
10
-
11
- background<Fn>(fn: Fn): FunctionInBackground<Fn>
12
- ```
13
-
14
- ## Example
15
- [CodeSandbox](https://8pj3sf.csb.app/)
16
-
17
- ```ts
18
- async function runLongTask () {
19
- const getVertexCoordinates = background((imageData: ImageData) => {
20
- const { data, width } = imageData;
21
- let [minX, minY, maxX, maxY] = [Infinity, Infinity, 0, 0];
22
-
23
- for(let i = 0, leng = data.length; i < leng; i += 4) {
24
- const [r, g, b, a] = data.slice(i, i+4);
25
- const isFilled = Math.max(r, g, b, a) > 0;
26
- if(isFilled) {
27
- const y = Math.floor(i / 4 / width);
28
- const x = Math.floor(i / 4 - width * y);
29
-
30
- minX = Math.min(minX, x);
31
- minY = Math.min(minY, y);
32
- maxX = Math.max(maxX, x);
33
- maxY = Math.max(maxY, y);
34
- }
35
- }
36
-
37
- return {
38
- x: minX === Infinity ? 0 : minX,
39
- y: minY === Infinity ? 0 : minY,
40
- width: maxX,
41
- height: maxY
42
- };
43
- })
44
-
45
- const coordinate = await getVertexCoordinates(
46
- canvas.getContext('2d').getImageData(0, 0, 1000, 1000)
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
- ## Tip
53
- - Web Background is implemented as a Web Worker and is available in web browsers.
54
- Web Background는 웹 워커로 구현되며 브라우저에서 사용할 수 있습니다.
129
+ // ✅ Pass context when needed (more stable)
130
+ const contextWorker = background((data) => processWithConfig(data, config), {
131
+ config,
132
+ });
133
+ ```
55
134
 
56
- - In most cases, you don't need to use it. It's recommended to use it for long working
57
- 대부분의 경우 이 모듈을 사용할 필요는 없으나, 비용이 큰 작업을 처리해야 할 경우 사용할 것을 추천합니다.
135
+ 2. **Use only for heavy computations**:
58
136
 
59
- - Most WebAPIs including DOM APIs are cannot be used.
60
- DOM API를 포함한 대부분의 WebAPI를 사용하지 못합니다.
61
- [Read More](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API)
137
+ ```typescript
138
+ // Simple operations
139
+ const add = background((a, b) => a + b);
62
140
 
63
- - To send arguments, you must use data from structured clone algorithm types.
64
- 인자로 전송 가능한 데이터 유형은 복사 가능한 타입의 데이터만 가능합니다.
65
- [Read More](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm)
141
+ // CPU intensive operations
142
+ const processLargeArray = background((data) => {
143
+ // Complex processing logic...
144
+ });
145
+ ```
66
146
 
67
- - It runs in isolation, cannot be access external variables and modules
68
- 격리된 환경에서 실행되므로 외부 모듈 외부 변수에 접근할 수 없습니다.
147
+ 3. **Keep data size appropriate**:
148
+ - Large objects or complex contexts cause serialization overhead
149
+ - Pass only necessary data for performance optimization
@@ -1 +1,3 @@
1
- export declare function background<Payload extends any[], ReturnValue = void>(fn: (...payloads: Payload) => ReturnValue): (...payload: Payload) => Promise<ReturnValue>;
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 r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var s=0;for(r=Object.getOwnPropertySymbols(e);s<r.length;s++)t.indexOf(r[s])<0&&Object.prototype.propertyIsEnumerable.call(e,r[s])&&(n[r[s]]=e[r[s]])}return n}(t,["module"]);const s=n.toString();console.log(null);const o=`\n self.addEventListener('message', async event => {\n const result = await (${s})(${n.length>1?"...event.data":"event.data"});\n \n self.postMessage(result);\n });\n `.trim(),a=new Blob([o]),l=URL.createObjectURL(a),u=new e(l,r),i=u.terminate;return u.terminate=()=>{i.call(u),URL.revokeObjectURL(l)},u}}const r=new FinalizationRegistry((e=>e.terminate()));function s(e){let s=null;return function o(...a){if("undefined"==typeof window)throw new Error("[web-background] You must use background in browser");return null==s&&(s=n.fromModule(t,{module:e}),r.register(o,s)),s.request.call(s,a.length>1?a:a[0])}}export{s as background};
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>;
@@ -0,0 +1,3 @@
1
+ export declare function serializeFunction(func: Function, providedContext?: Record<string, any>): string;
2
+ export declare function deserializeFunction(serializedFunc: string): Function;
3
+ export declare function extractClosureContext(func: Function): Record<string, any>;
@@ -1 +1,4 @@
1
- export declare const workerCleanupRegistry: FinalizationRegistry<Worker>;
1
+ export declare const workerCleanupRegistry: FinalizationRegistry<{
2
+ worker: Worker;
3
+ funcStr: string;
4
+ }>;
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "Background",
8
8
  "Web Background"
9
9
  ],
10
- "version": "1.0.3",
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
- "dependencies": {}
33
- }
33
+ "files": ["dist"]
34
+ }