@ticatec/dyna-js 0.0.4 → 0.1.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 +207 -8
- package/README_CN.md +207 -8
- package/dist/ModuleLoader.d.ts +93 -0
- package/dist/ModuleLoader.d.ts.map +1 -0
- package/dist/ModuleLoader.esm.js +106 -0
- package/dist/ModuleLoader.js +110 -0
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -11,13 +11,13 @@ A TypeScript library for safe dynamic code execution using `new Function()` that
|
|
|
11
11
|
|
|
12
12
|
## Features
|
|
13
13
|
|
|
14
|
-
✅ **Universal Compatibility** - Works in both Node.js and browser environments
|
|
15
|
-
✅ **TypeScript Support** - Full type safety with comprehensive type definitions
|
|
16
|
-
✅ **Singleton Pattern** - Initialize once, use anywhere
|
|
17
|
-
✅ **Module Imports** - Pre-define classes and functions for dynamic code
|
|
18
|
-
✅ **Configurable Security** - Fine-grained control over allowed APIs and operations
|
|
19
|
-
✅ **Multiple Build Formats** - CommonJS and ESM support
|
|
20
|
-
✅ **Performance Monitoring** - Built-in execution time tracking
|
|
14
|
+
✅ **Universal Compatibility** - Works in both Node.js and browser environments
|
|
15
|
+
✅ **TypeScript Support** - Full type safety with comprehensive type definitions
|
|
16
|
+
✅ **Singleton Pattern** - Initialize once, use anywhere
|
|
17
|
+
✅ **Module Imports** - Pre-define classes and functions for dynamic code
|
|
18
|
+
✅ **Configurable Security** - Fine-grained control over allowed APIs and operations
|
|
19
|
+
✅ **Multiple Build Formats** - CommonJS and ESM support
|
|
20
|
+
✅ **Performance Monitoring** - Built-in execution time tracking
|
|
21
21
|
|
|
22
22
|
## Installation
|
|
23
23
|
|
|
@@ -298,6 +298,206 @@ const validation = dynamicValidator({
|
|
|
298
298
|
});
|
|
299
299
|
```
|
|
300
300
|
|
|
301
|
+
## ModuleLoader - Dynamic Module Management
|
|
302
|
+
|
|
303
|
+
`ModuleLoader` is a singleton class for managing dynamic JavaScript modules with automatic caching and version control. It's perfect for loading remote code modules and managing their lifecycle.
|
|
304
|
+
|
|
305
|
+
### Key Features
|
|
306
|
+
|
|
307
|
+
- **Singleton Pattern** - Single instance manages all modules
|
|
308
|
+
- **Version Control** - Automatic digest-based freshness checking
|
|
309
|
+
- **Local Caching** - Stores modules in localStorage for offline access
|
|
310
|
+
- **Lazy Loading** - Modules are only instantiated when needed
|
|
311
|
+
- **Custom Hooks** - Override default behaviors with custom implementations
|
|
312
|
+
|
|
313
|
+
### Basic Usage
|
|
314
|
+
|
|
315
|
+
#### 1. Initialize the ModuleLoader
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
import { ModuleLoader } from '@ticatec/dyna-js';
|
|
319
|
+
|
|
320
|
+
// Define how to load modules from your server
|
|
321
|
+
const loadModuleFromServer = async (moduleInfo) => {
|
|
322
|
+
const response = await fetch(`/api/modules/${moduleInfo.code}`);
|
|
323
|
+
return response.json(); // Should return { code, digest, scriptText }
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// Initialize the singleton
|
|
327
|
+
const moduleLoader = ModuleLoader.initialize(loadModuleFromServer, {
|
|
328
|
+
// All options are optional - defaults use localStorage
|
|
329
|
+
});
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
#### 2. Check and Update Modules
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
// Check a list of modules and update if needed
|
|
336
|
+
await moduleLoader.checkFreshScripts([
|
|
337
|
+
{ code: 'user-form', digest: 'abc123...' },
|
|
338
|
+
{ code: 'data-grid', digest: 'def456...' },
|
|
339
|
+
{ code: 'chart-widget', digest: 'ghi789...' }
|
|
340
|
+
]);
|
|
341
|
+
|
|
342
|
+
// This will:
|
|
343
|
+
// 1. Check each module's digest against localStorage
|
|
344
|
+
// 2. Download and save modules that have changed
|
|
345
|
+
// 3. Clear the module instance cache
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
#### 3. Create and Use Modules
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
// Import dependencies that the module needs
|
|
352
|
+
import React from 'react';
|
|
353
|
+
import ReactDOM from 'react-dom';
|
|
354
|
+
|
|
355
|
+
// Create the module instance
|
|
356
|
+
const UserForm = moduleLoader.createModule('user-form', {
|
|
357
|
+
React,
|
|
358
|
+
ReactDOM,
|
|
359
|
+
// Add any other dependencies your module needs
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Use the module (it's now cached for subsequent calls)
|
|
363
|
+
const formInstance = new UserForm({
|
|
364
|
+
title: 'User Registration',
|
|
365
|
+
onSubmit: handleSubmit
|
|
366
|
+
});
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Advanced Configuration
|
|
370
|
+
|
|
371
|
+
#### Custom Module Check Function
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
const moduleLoader = ModuleLoader.initialize(loadModuleFromServer, {
|
|
375
|
+
// Custom logic to check if module needs updating
|
|
376
|
+
moduleCheck: (moduleInfo) => {
|
|
377
|
+
const localVersion = localStorage.getItem(`version:${moduleInfo.code}`);
|
|
378
|
+
return localVersion === moduleInfo.version;
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
#### Custom Storage Implementation
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
// Use your own storage solution (IndexedDB, custom cache, etc.)
|
|
387
|
+
const moduleLoader = ModuleLoader.initialize(loadModuleFromServer, {
|
|
388
|
+
// Custom save function
|
|
389
|
+
saveModule: (moduleData) => {
|
|
390
|
+
myCustomCache.set(moduleData.code, {
|
|
391
|
+
digest: moduleData.digest,
|
|
392
|
+
script: moduleData.scriptText,
|
|
393
|
+
timestamp: Date.now()
|
|
394
|
+
});
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
// Custom load function
|
|
398
|
+
loadLocalModule: (moduleCode) => {
|
|
399
|
+
const cached = myCustomCache.get(moduleCode);
|
|
400
|
+
return cached ? cached.script : null;
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Complete Example
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
import { ModuleLoader, initializeDynaJs } from '@ticatec/dyna-js';
|
|
409
|
+
import React from 'react';
|
|
410
|
+
import ReactDOM from 'react-dom';
|
|
411
|
+
|
|
412
|
+
// 1. Initialize DynaJs with your base imports
|
|
413
|
+
initializeDynaJs({
|
|
414
|
+
defaultImports: {
|
|
415
|
+
Dialog: DialogClass,
|
|
416
|
+
MessageBox: MessageBoxClass
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// 2. Setup module loader
|
|
421
|
+
const loadModule = async (moduleInfo) => {
|
|
422
|
+
const response = await fetch(`/api/modules/${moduleInfo.code}`, {
|
|
423
|
+
method: 'POST',
|
|
424
|
+
headers: { 'Content-Type': 'application/json' },
|
|
425
|
+
body: JSON.stringify({ digest: moduleInfo.digest })
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
if (!response.ok) throw new Error('Failed to load module');
|
|
429
|
+
return response.json();
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
const moduleLoader = ModuleLoader.initialize(loadModule, {});
|
|
433
|
+
|
|
434
|
+
// 3. On application startup, check for module updates
|
|
435
|
+
async function initializeApp() {
|
|
436
|
+
try {
|
|
437
|
+
// Get module manifest from server
|
|
438
|
+
const manifest = await fetch('/api/modules/manifest').then(r => r.json());
|
|
439
|
+
|
|
440
|
+
// Update all modules that have changed
|
|
441
|
+
await moduleLoader.checkFreshScripts(manifest.modules);
|
|
442
|
+
|
|
443
|
+
console.log('All modules are up to date!');
|
|
444
|
+
} catch (error) {
|
|
445
|
+
console.error('Failed to update modules:', error);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// 4. Load and use modules on demand
|
|
450
|
+
function loadUserForm() {
|
|
451
|
+
// This will use the cached version from localStorage
|
|
452
|
+
const UserForm = moduleLoader.createModule('user-form', {
|
|
453
|
+
React,
|
|
454
|
+
ReactDOM,
|
|
455
|
+
Dialog: DialogClass,
|
|
456
|
+
MessageBox: MessageBoxClass
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
// Subsequent calls return the same cached instance
|
|
460
|
+
const form = new UserForm();
|
|
461
|
+
form.render();
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Initialize
|
|
465
|
+
initializeApp().then(() => {
|
|
466
|
+
loadUserForm();
|
|
467
|
+
});
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Module Data Format
|
|
471
|
+
|
|
472
|
+
Your server should return module data in this format:
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
interface ModuleData {
|
|
476
|
+
code: string; // Unique module identifier
|
|
477
|
+
digest: string; // Hash/version identifier (e.g., MD5, SHA-256)
|
|
478
|
+
scriptText: string; // The actual JavaScript code to execute
|
|
479
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Best Practices
|
|
483
|
+
|
|
484
|
+
1. **Version Control**: Use content-based hashing (MD5, SHA-256) for the digest field
|
|
485
|
+
2. **Graceful Degradation**: Handle network failures when loading modules
|
|
486
|
+
3. **Dependency Injection**: Pass all required dependencies in the imports object
|
|
487
|
+
4. **Cache Invalidation**: Call `checkFreshScripts()` on app startup and periodically
|
|
488
|
+
5. **Error Handling**: Wrap module creation in try-catch blocks
|
|
489
|
+
|
|
490
|
+
```typescript
|
|
491
|
+
try {
|
|
492
|
+
const module = moduleLoader.createModule('my-module', imports);
|
|
493
|
+
const instance = new module();
|
|
494
|
+
instance.run();
|
|
495
|
+
} catch (error) {
|
|
496
|
+
console.error('Module failed to load or execute:', error);
|
|
497
|
+
// Fallback to default behavior
|
|
498
|
+
}
|
|
499
|
+
```
|
|
500
|
+
|
|
301
501
|
## Error Handling
|
|
302
502
|
|
|
303
503
|
```typescript
|
|
@@ -327,7 +527,6 @@ Full TypeScript support with comprehensive type definitions:
|
|
|
327
527
|
```typescript
|
|
328
528
|
import {
|
|
329
529
|
DynaJs,
|
|
330
|
-
ExecutionResult,
|
|
331
530
|
ExecutionOptions,
|
|
332
531
|
ModuleImports
|
|
333
532
|
} from '@ticatec/dyna-js';
|
package/README_CN.md
CHANGED
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
|
|
12
12
|
## 功能特性
|
|
13
13
|
|
|
14
|
-
✅ **通用兼容性** - 同时支持 Node.js 和浏览器环境
|
|
15
|
-
✅ **TypeScript 支持** - 完整的类型安全和类型定义
|
|
16
|
-
✅ **单例模式** - 一次初始化,全局使用
|
|
17
|
-
✅ **模块导入** - 为动态代码预定义类和函数
|
|
18
|
-
✅ **可配置安全性** - 对允许的 API 和操作进行细粒度控制
|
|
19
|
-
✅ **多种构建格式** - 支持 CommonJS 和 ESM
|
|
20
|
-
✅ **性能监控** - 内置执行时间跟踪
|
|
14
|
+
✅ **通用兼容性** - 同时支持 Node.js 和浏览器环境
|
|
15
|
+
✅ **TypeScript 支持** - 完整的类型安全和类型定义
|
|
16
|
+
✅ **单例模式** - 一次初始化,全局使用
|
|
17
|
+
✅ **模块导入** - 为动态代码预定义类和函数
|
|
18
|
+
✅ **可配置安全性** - 对允许的 API 和操作进行细粒度控制
|
|
19
|
+
✅ **多种构建格式** - 支持 CommonJS 和 ESM
|
|
20
|
+
✅ **性能监控** - 内置执行时间跟踪
|
|
21
21
|
|
|
22
22
|
## 安装
|
|
23
23
|
|
|
@@ -298,6 +298,206 @@ const validation = dynamicValidator({
|
|
|
298
298
|
});
|
|
299
299
|
```
|
|
300
300
|
|
|
301
|
+
## ModuleLoader - 动态模块管理
|
|
302
|
+
|
|
303
|
+
`ModuleLoader` 是一个单例类,用于管理具有自动缓存和版本控制的动态 JavaScript 模块。它非常适合加载远程代码模块并管理它们的生命周期。
|
|
304
|
+
|
|
305
|
+
### 核心特性
|
|
306
|
+
|
|
307
|
+
- **单例模式** - 单个实例管理所有模块
|
|
308
|
+
- **版本控制** - 基于摘要的自动新鲜度检查
|
|
309
|
+
- **本地缓存** - 将模块存储在 localStorage 中以供离线访问
|
|
310
|
+
- **懒加载** - 模块仅在需要时才实例化
|
|
311
|
+
- **自定义钩子** - 使用自定义实现覆盖默认行为
|
|
312
|
+
|
|
313
|
+
### 基础用法
|
|
314
|
+
|
|
315
|
+
#### 1. 初始化 ModuleLoader
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
import { ModuleLoader } from '@ticatec/dyna-js';
|
|
319
|
+
|
|
320
|
+
// 定义如何从服务器加载模块
|
|
321
|
+
const loadModuleFromServer = async (moduleInfo) => {
|
|
322
|
+
const response = await fetch(`/api/modules/${moduleInfo.code}`);
|
|
323
|
+
return response.json(); // 应该返回 { code, digest, scriptText }
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// 初始化单例
|
|
327
|
+
const moduleLoader = ModuleLoader.initialize(loadModuleFromServer, {
|
|
328
|
+
// 所有选项都是可选的 - 默认使用 localStorage
|
|
329
|
+
});
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
#### 2. 检查和更新模块
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
// 检查模块列表并在需要时更新
|
|
336
|
+
await moduleLoader.checkFreshScripts([
|
|
337
|
+
{ code: 'user-form', digest: 'abc123...' },
|
|
338
|
+
{ code: 'data-grid', digest: 'def456...' },
|
|
339
|
+
{ code: 'chart-widget', digest: 'ghi789...' }
|
|
340
|
+
]);
|
|
341
|
+
|
|
342
|
+
// 这将:
|
|
343
|
+
// 1. 检查每个模块的摘要与 localStorage 中的对比
|
|
344
|
+
// 2. 下载并保存已更改的模块
|
|
345
|
+
// 3. 清除模块实例缓存
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
#### 3. 创建和使用模块
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
// 导入模块需要的依赖项
|
|
352
|
+
import React from 'react';
|
|
353
|
+
import ReactDOM from 'react-dom';
|
|
354
|
+
|
|
355
|
+
// 创建模块实例
|
|
356
|
+
const UserForm = moduleLoader.createModule('user-form', {
|
|
357
|
+
React,
|
|
358
|
+
ReactDOM,
|
|
359
|
+
// 添加模块需要的任何其他依赖项
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// 使用模块(它现在已被缓存以供后续调用)
|
|
363
|
+
const formInstance = new UserForm({
|
|
364
|
+
title: '用户注册',
|
|
365
|
+
onSubmit: handleSubmit
|
|
366
|
+
});
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### 高级配置
|
|
370
|
+
|
|
371
|
+
#### 自定义模块检查函数
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
const moduleLoader = ModuleLoader.initialize(loadModuleFromServer, {
|
|
375
|
+
// 自定义逻辑来检查模块是否需要更新
|
|
376
|
+
moduleCheck: (moduleInfo) => {
|
|
377
|
+
const localVersion = localStorage.getItem(`version:${moduleInfo.code}`);
|
|
378
|
+
return localVersion === moduleInfo.version;
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
#### 自定义存储实现
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
// 使用您自己的存储解决方案(IndexedDB、自定义缓存等)
|
|
387
|
+
const moduleLoader = ModuleLoader.initialize(loadModuleFromServer, {
|
|
388
|
+
// 自定义保存函数
|
|
389
|
+
saveModule: (moduleData) => {
|
|
390
|
+
myCustomCache.set(moduleData.code, {
|
|
391
|
+
digest: moduleData.digest,
|
|
392
|
+
script: moduleData.scriptText,
|
|
393
|
+
timestamp: Date.now()
|
|
394
|
+
});
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
// 自定义加载函数
|
|
398
|
+
loadLocalModule: (moduleCode) => {
|
|
399
|
+
const cached = myCustomCache.get(moduleCode);
|
|
400
|
+
return cached ? cached.script : null;
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### 完整示例
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
import { ModuleLoader, initializeDynaJs } from '@ticatec/dyna-js';
|
|
409
|
+
import React from 'react';
|
|
410
|
+
import ReactDOM from 'react-dom';
|
|
411
|
+
|
|
412
|
+
// 1. 使用基础导入初始化 DynaJs
|
|
413
|
+
initializeDynaJs({
|
|
414
|
+
defaultImports: {
|
|
415
|
+
Dialog: DialogClass,
|
|
416
|
+
MessageBox: MessageBoxClass
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// 2. 设置模块加载器
|
|
421
|
+
const loadModule = async (moduleInfo) => {
|
|
422
|
+
const response = await fetch(`/api/modules/${moduleInfo.code}`, {
|
|
423
|
+
method: 'POST',
|
|
424
|
+
headers: { 'Content-Type': 'application/json' },
|
|
425
|
+
body: JSON.stringify({ digest: moduleInfo.digest })
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
if (!response.ok) throw new Error('加载模块失败');
|
|
429
|
+
return response.json();
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
const moduleLoader = ModuleLoader.initialize(loadModule, {});
|
|
433
|
+
|
|
434
|
+
// 3. 在应用启动时,检查模块更新
|
|
435
|
+
async function initializeApp() {
|
|
436
|
+
try {
|
|
437
|
+
// 从服务器获取模块清单
|
|
438
|
+
const manifest = await fetch('/api/modules/manifest').then(r => r.json());
|
|
439
|
+
|
|
440
|
+
// 更新所有已更改的模块
|
|
441
|
+
await moduleLoader.checkFreshScripts(manifest.modules);
|
|
442
|
+
|
|
443
|
+
console.log('所有模块都是最新的!');
|
|
444
|
+
} catch (error) {
|
|
445
|
+
console.error('更新模块失败:', error);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// 4. 按需加载和使用模块
|
|
450
|
+
function loadUserForm() {
|
|
451
|
+
// 这将使用 localStorage 中的缓存版本
|
|
452
|
+
const UserForm = moduleLoader.createModule('user-form', {
|
|
453
|
+
React,
|
|
454
|
+
ReactDOM,
|
|
455
|
+
Dialog: DialogClass,
|
|
456
|
+
MessageBox: MessageBoxClass
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
// 后续调用返回相同的缓存实例
|
|
460
|
+
const form = new UserForm();
|
|
461
|
+
form.render();
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// 初始化
|
|
465
|
+
initializeApp().then(() => {
|
|
466
|
+
loadUserForm();
|
|
467
|
+
});
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### 模块数据格式
|
|
471
|
+
|
|
472
|
+
您的服务器应以此格式返回模块数据:
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
interface ModuleData {
|
|
476
|
+
code: string; // 唯一的模块标识符
|
|
477
|
+
digest: string; // 哈希/版本标识符(例如 MD5、SHA-256)
|
|
478
|
+
scriptText: string; // 要执行的实际 JavaScript 代码
|
|
479
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### 最佳实践
|
|
483
|
+
|
|
484
|
+
1. **版本控制**:对 digest 字段使用基于内容的哈希(MD5、SHA-256)
|
|
485
|
+
2. **优雅降级**:处理加载模块时的网络故障
|
|
486
|
+
3. **依赖注入**:在 imports 对象中传递所有必需的依赖项
|
|
487
|
+
4. **缓存失效**:在应用启动时和定期调用 `checkFreshScripts()`
|
|
488
|
+
5. **错误处理**:将模块创建包装在 try-catch 块中
|
|
489
|
+
|
|
490
|
+
```typescript
|
|
491
|
+
try {
|
|
492
|
+
const module = moduleLoader.createModule('my-module', imports);
|
|
493
|
+
const instance = new module();
|
|
494
|
+
instance.run();
|
|
495
|
+
} catch (error) {
|
|
496
|
+
console.error('模块加载或执行失败:', error);
|
|
497
|
+
// 回退到默认行为
|
|
498
|
+
}
|
|
499
|
+
```
|
|
500
|
+
|
|
301
501
|
## 错误处理
|
|
302
502
|
|
|
303
503
|
```typescript
|
|
@@ -327,7 +527,6 @@ try {
|
|
|
327
527
|
```typescript
|
|
328
528
|
import {
|
|
329
529
|
DynaJs,
|
|
330
|
-
ExecutionResult,
|
|
331
530
|
ExecutionOptions,
|
|
332
531
|
ModuleImports
|
|
333
532
|
} from '@ticatec/dyna-js';
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Function type for checking if a module needs to be updated
|
|
3
|
+
* @param data - Module data containing code and digest information
|
|
4
|
+
* @returns true if the module is up-to-date, false if it needs to be reloaded
|
|
5
|
+
*/
|
|
6
|
+
export type ModuleCheck = (data: any) => boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Function type for loading a module from remote source
|
|
9
|
+
* @param data - Module data containing information needed to load the module
|
|
10
|
+
* @returns Promise that resolves to the loaded module data
|
|
11
|
+
*/
|
|
12
|
+
export type LoadModule = (data: any) => Promise<any>;
|
|
13
|
+
/**
|
|
14
|
+
* Function type for saving a module to local storage
|
|
15
|
+
* @param data - Module data to be saved
|
|
16
|
+
*/
|
|
17
|
+
export type SaveModuleToLocalStorage = (data: any) => void;
|
|
18
|
+
/**
|
|
19
|
+
* Function type for loading a module from local storage
|
|
20
|
+
* @param data - Module identifier or data
|
|
21
|
+
* @returns The module script text
|
|
22
|
+
*/
|
|
23
|
+
export type LoadLocalModule = (data: any) => string;
|
|
24
|
+
/**
|
|
25
|
+
* Configuration options for ModuleLoader
|
|
26
|
+
*/
|
|
27
|
+
export interface ModuleLoaderOptions {
|
|
28
|
+
/** Optional custom module check function */
|
|
29
|
+
moduleCheck?: ModuleCheck;
|
|
30
|
+
/** Optional custom save module function */
|
|
31
|
+
saveModule?: SaveModuleToLocalStorage;
|
|
32
|
+
/** Optional custom load local module function */
|
|
33
|
+
loadLocalModule?: LoadLocalModule;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* ModuleLoader is a singleton class for managing dynamic module loading with caching
|
|
37
|
+
* It supports checking for module updates, loading from remote sources, and caching in localStorage
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* const loader = ModuleLoader.initialize(
|
|
42
|
+
* async (data) => fetch(`/api/modules/${data.code}`).then(r => r.json()),
|
|
43
|
+
* {}
|
|
44
|
+
* );
|
|
45
|
+
*
|
|
46
|
+
* await loader.checkFreshScripts([{ code: 'module1', digest: 'abc123' }]);
|
|
47
|
+
* const module = loader.createModule('module1', { React, ReactDOM });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export default class ModuleLoader {
|
|
51
|
+
private static instance;
|
|
52
|
+
private readonly loadModule;
|
|
53
|
+
private readonly moduleCheck;
|
|
54
|
+
private readonly saveModule;
|
|
55
|
+
private readonly loadLocalModule;
|
|
56
|
+
private modulesMap;
|
|
57
|
+
/**
|
|
58
|
+
* Private constructor for singleton pattern
|
|
59
|
+
* @param loadModule - Function to load module from remote
|
|
60
|
+
* @param moduleCheck - Function to check module freshness
|
|
61
|
+
* @param saveModule - Function to save module locally
|
|
62
|
+
* @param loadLocalModule - Function to load module from local storage
|
|
63
|
+
*/
|
|
64
|
+
private constructor();
|
|
65
|
+
/**
|
|
66
|
+
* Initialize the ModuleLoader singleton instance
|
|
67
|
+
* @param loadModule - Function to load module from remote source
|
|
68
|
+
* @param options - Configuration options with optional custom implementations
|
|
69
|
+
* @returns The singleton ModuleLoader instance
|
|
70
|
+
*/
|
|
71
|
+
static initialize(loadModule: LoadModule, options: ModuleLoaderOptions): ModuleLoader;
|
|
72
|
+
/**
|
|
73
|
+
* Check and update modules that are not fresh
|
|
74
|
+
* Iterates through the list, checks each module, and reloads if needed
|
|
75
|
+
* Clears the module cache after checking all modules
|
|
76
|
+
*
|
|
77
|
+
* @param list - Array of module data objects to check
|
|
78
|
+
* @returns Promise that resolves when all modules are checked and updated
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
checkFreshScripts(list: Array<any>): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Create or retrieve a cached module instance
|
|
84
|
+
* Loads the module script from local storage and executes it with provided imports
|
|
85
|
+
* Caches the result for subsequent calls
|
|
86
|
+
*
|
|
87
|
+
* @param key - The module key/code to load
|
|
88
|
+
* @param imports - Object containing dependencies to inject into the module
|
|
89
|
+
* @returns The instantiated module
|
|
90
|
+
*/
|
|
91
|
+
createModule(key: string, imports: any): any;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=ModuleLoader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ModuleLoader.d.ts","sourceRoot":"","sources":["../src/ModuleLoader.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC;AAEjD;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;AAErD;;;GAGG;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;AAE3D;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,GAAG,KAAK,MAAM,CAAC;AA+BpD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAChC,4CAA4C;IAC5C,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,2CAA2C;IAC3C,UAAU,CAAC,EAAE,wBAAwB,CAAC;IACtC,iDAAiD;IACjD,eAAe,CAAC,EAAE,eAAe,CAAA;CACpC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,OAAO,OAAO,YAAY;IAE7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA2B;IACtD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,UAAU,CAAmB;IAErC;;;;;;OAMG;IACH,OAAO;IAQP;;;;;OAKG;IACH,MAAM,CAAC,UAAU,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,mBAAmB,GAAG,YAAY;IAQrF;;;;;;;;OAQG;IACG,iBAAiB,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC;IAUxC;;;;;;;;OAQG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,GAAG;CAU/C"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { getDynaJs } from "./index";
|
|
2
|
+
/**
|
|
3
|
+
* Default module check function that compares message digest
|
|
4
|
+
* @param data - Module data containing code and digest
|
|
5
|
+
* @returns true if the local digest matches the remote digest
|
|
6
|
+
*/
|
|
7
|
+
const checkMessageDigest = (data) => {
|
|
8
|
+
let localDigest = window.localStorage.getItem(`md:${data.code}`);
|
|
9
|
+
return localDigest == data.digest;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Default function to save module to localStorage
|
|
13
|
+
* Saves both the module digest and script text
|
|
14
|
+
* @param data - Module data containing code, digest, and scriptText
|
|
15
|
+
*/
|
|
16
|
+
const saveModule = (data) => {
|
|
17
|
+
window.localStorage.setItem(`md:${data.code}`, data.digest);
|
|
18
|
+
window.localStorage.setItem(`script:${data.code}`, data.scriptText);
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Default function to load module script from localStorage
|
|
22
|
+
* @param key - Module key/code
|
|
23
|
+
* @returns The module script text
|
|
24
|
+
*/
|
|
25
|
+
const loadLocalModule = (key) => {
|
|
26
|
+
return window.localStorage.getItem(`script:${key}`);
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* ModuleLoader is a singleton class for managing dynamic module loading with caching
|
|
30
|
+
* It supports checking for module updates, loading from remote sources, and caching in localStorage
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const loader = ModuleLoader.initialize(
|
|
35
|
+
* async (data) => fetch(`/api/modules/${data.code}`).then(r => r.json()),
|
|
36
|
+
* {}
|
|
37
|
+
* );
|
|
38
|
+
*
|
|
39
|
+
* await loader.checkFreshScripts([{ code: 'module1', digest: 'abc123' }]);
|
|
40
|
+
* const module = loader.createModule('module1', { React, ReactDOM });
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export default class ModuleLoader {
|
|
44
|
+
/**
|
|
45
|
+
* Private constructor for singleton pattern
|
|
46
|
+
* @param loadModule - Function to load module from remote
|
|
47
|
+
* @param moduleCheck - Function to check module freshness
|
|
48
|
+
* @param saveModule - Function to save module locally
|
|
49
|
+
* @param loadLocalModule - Function to load module from local storage
|
|
50
|
+
*/
|
|
51
|
+
constructor(loadModule, moduleCheck, saveModule, loadLocalModule) {
|
|
52
|
+
this.loadModule = loadModule;
|
|
53
|
+
this.moduleCheck = moduleCheck;
|
|
54
|
+
this.saveModule = saveModule;
|
|
55
|
+
this.loadLocalModule = loadLocalModule;
|
|
56
|
+
this.modulesMap = new Map();
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Initialize the ModuleLoader singleton instance
|
|
60
|
+
* @param loadModule - Function to load module from remote source
|
|
61
|
+
* @param options - Configuration options with optional custom implementations
|
|
62
|
+
* @returns The singleton ModuleLoader instance
|
|
63
|
+
*/
|
|
64
|
+
static initialize(loadModule, options) {
|
|
65
|
+
if (ModuleLoader.instance == null) {
|
|
66
|
+
ModuleLoader.instance = new ModuleLoader(loadModule, options.moduleCheck ?? checkMessageDigest, options.saveModule ?? saveModule, options.loadLocalModule ?? loadLocalModule);
|
|
67
|
+
}
|
|
68
|
+
return ModuleLoader.instance;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check and update modules that are not fresh
|
|
72
|
+
* Iterates through the list, checks each module, and reloads if needed
|
|
73
|
+
* Clears the module cache after checking all modules
|
|
74
|
+
*
|
|
75
|
+
* @param list - Array of module data objects to check
|
|
76
|
+
* @returns Promise that resolves when all modules are checked and updated
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
async checkFreshScripts(list) {
|
|
80
|
+
for (let item of list) {
|
|
81
|
+
if (!this.moduleCheck(item)) {
|
|
82
|
+
let data = await this.loadModule(item);
|
|
83
|
+
this.saveModule(data);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
this.modulesMap.clear();
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Create or retrieve a cached module instance
|
|
90
|
+
* Loads the module script from local storage and executes it with provided imports
|
|
91
|
+
* Caches the result for subsequent calls
|
|
92
|
+
*
|
|
93
|
+
* @param key - The module key/code to load
|
|
94
|
+
* @param imports - Object containing dependencies to inject into the module
|
|
95
|
+
* @returns The instantiated module
|
|
96
|
+
*/
|
|
97
|
+
createModule(key, imports) {
|
|
98
|
+
let module = this.modulesMap.get(key);
|
|
99
|
+
if (module == null) {
|
|
100
|
+
let scriptText = this.loadLocalModule(key);
|
|
101
|
+
module = getDynaJs().executeSync(scriptText, { imports });
|
|
102
|
+
this.modulesMap.set(key, module);
|
|
103
|
+
}
|
|
104
|
+
return module;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("./index");
|
|
4
|
+
/**
|
|
5
|
+
* Default module check function that compares message digest
|
|
6
|
+
* @param data - Module data containing code and digest
|
|
7
|
+
* @returns true if the local digest matches the remote digest
|
|
8
|
+
*/
|
|
9
|
+
const checkMessageDigest = (data) => {
|
|
10
|
+
let localDigest = window.localStorage.getItem(`md:${data.code}`);
|
|
11
|
+
return localDigest == data.digest;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Default function to save module to localStorage
|
|
15
|
+
* Saves both the module digest and script text
|
|
16
|
+
* @param data - Module data containing code, digest, and scriptText
|
|
17
|
+
*/
|
|
18
|
+
const saveModule = (data) => {
|
|
19
|
+
window.localStorage.setItem(`md:${data.code}`, data.digest);
|
|
20
|
+
window.localStorage.setItem(`script:${data.code}`, data.scriptText);
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Default function to load module script from localStorage
|
|
24
|
+
* @param key - Module key/code
|
|
25
|
+
* @returns The module script text
|
|
26
|
+
*/
|
|
27
|
+
const loadLocalModule = (key) => {
|
|
28
|
+
return window.localStorage.getItem(`script:${key}`);
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* ModuleLoader is a singleton class for managing dynamic module loading with caching
|
|
32
|
+
* It supports checking for module updates, loading from remote sources, and caching in localStorage
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const loader = ModuleLoader.initialize(
|
|
37
|
+
* async (data) => fetch(`/api/modules/${data.code}`).then(r => r.json()),
|
|
38
|
+
* {}
|
|
39
|
+
* );
|
|
40
|
+
*
|
|
41
|
+
* await loader.checkFreshScripts([{ code: 'module1', digest: 'abc123' }]);
|
|
42
|
+
* const module = loader.createModule('module1', { React, ReactDOM });
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
class ModuleLoader {
|
|
46
|
+
/**
|
|
47
|
+
* Private constructor for singleton pattern
|
|
48
|
+
* @param loadModule - Function to load module from remote
|
|
49
|
+
* @param moduleCheck - Function to check module freshness
|
|
50
|
+
* @param saveModule - Function to save module locally
|
|
51
|
+
* @param loadLocalModule - Function to load module from local storage
|
|
52
|
+
*/
|
|
53
|
+
constructor(loadModule, moduleCheck, saveModule, loadLocalModule) {
|
|
54
|
+
this.loadModule = loadModule;
|
|
55
|
+
this.moduleCheck = moduleCheck;
|
|
56
|
+
this.saveModule = saveModule;
|
|
57
|
+
this.loadLocalModule = loadLocalModule;
|
|
58
|
+
this.modulesMap = new Map();
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Initialize the ModuleLoader singleton instance
|
|
62
|
+
* @param loadModule - Function to load module from remote source
|
|
63
|
+
* @param options - Configuration options with optional custom implementations
|
|
64
|
+
* @returns The singleton ModuleLoader instance
|
|
65
|
+
*/
|
|
66
|
+
static initialize(loadModule, options) {
|
|
67
|
+
var _a, _b, _c;
|
|
68
|
+
if (ModuleLoader.instance == null) {
|
|
69
|
+
ModuleLoader.instance = new ModuleLoader(loadModule, (_a = options.moduleCheck) !== null && _a !== void 0 ? _a : checkMessageDigest, (_b = options.saveModule) !== null && _b !== void 0 ? _b : saveModule, (_c = options.loadLocalModule) !== null && _c !== void 0 ? _c : loadLocalModule);
|
|
70
|
+
}
|
|
71
|
+
return ModuleLoader.instance;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check and update modules that are not fresh
|
|
75
|
+
* Iterates through the list, checks each module, and reloads if needed
|
|
76
|
+
* Clears the module cache after checking all modules
|
|
77
|
+
*
|
|
78
|
+
* @param list - Array of module data objects to check
|
|
79
|
+
* @returns Promise that resolves when all modules are checked and updated
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
async checkFreshScripts(list) {
|
|
83
|
+
for (let item of list) {
|
|
84
|
+
if (!this.moduleCheck(item)) {
|
|
85
|
+
let data = await this.loadModule(item);
|
|
86
|
+
this.saveModule(data);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
this.modulesMap.clear();
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Create or retrieve a cached module instance
|
|
93
|
+
* Loads the module script from local storage and executes it with provided imports
|
|
94
|
+
* Caches the result for subsequent calls
|
|
95
|
+
*
|
|
96
|
+
* @param key - The module key/code to load
|
|
97
|
+
* @param imports - Object containing dependencies to inject into the module
|
|
98
|
+
* @returns The instantiated module
|
|
99
|
+
*/
|
|
100
|
+
createModule(key, imports) {
|
|
101
|
+
let module = this.modulesMap.get(key);
|
|
102
|
+
if (module == null) {
|
|
103
|
+
let scriptText = this.loadLocalModule(key);
|
|
104
|
+
module = (0, index_1.getDynaJs)().executeSync(scriptText, { imports });
|
|
105
|
+
this.modulesMap.set(key, module);
|
|
106
|
+
}
|
|
107
|
+
return module;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
exports.default = ModuleLoader;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ticatec/dyna-js",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "A TypeScript library for dynamic code execution using new Function() that works in both Node.js and browser environments",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.esm.js",
|
|
@@ -41,8 +41,7 @@
|
|
|
41
41
|
"license": "MIT",
|
|
42
42
|
"repository": {
|
|
43
43
|
"type": "git",
|
|
44
|
-
"url": "git+https://github.com/ticatec/DynaJS.git"
|
|
45
|
-
"directory": "dynamic-script-loader"
|
|
44
|
+
"url": "git+https://github.com/ticatec/DynaJS.git"
|
|
46
45
|
},
|
|
47
46
|
"bugs": {
|
|
48
47
|
"url": "https://github.com/ticatec/DynaJS/issues"
|