Skip to content

Commit 80e25d6

Browse files
committed
feat: lazyload client and server modules
1 parent afe8ed9 commit 80e25d6

File tree

3 files changed

+284
-60
lines changed

3 files changed

+284
-60
lines changed

src/client.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import observer from '@cocreate/observer';
2+
3+
function listen(callback, selector) {
4+
5+
function observerCallback({ target }) {
6+
// let isInit = target.querySelector(selector)/testtt
7+
// if (isInit) {
8+
callback()
9+
// console.log('lazyloaded', selector)
10+
observer.uninit(observerCallback)
11+
// }
12+
}
13+
14+
observer.init({
15+
name: 'lazyloadObserver',
16+
observe: ['childList'],
17+
target: selector,
18+
callback: observerCallback
19+
})
20+
21+
let selectorAttributes = [];
22+
let attributes = selector.split(",")
23+
for (let attribute of attributes) {
24+
let attr = attribute.trim()
25+
if (attr.startsWith("[")) {
26+
let pos = attr.indexOf("*")
27+
if (pos == -1)
28+
pos = attr.indexOf("=")
29+
if (pos !== -1) {
30+
attr = attr.slice(1, pos)
31+
} else {
32+
attr = attr.slice(1, -1)
33+
}
34+
selectorAttributes.push(attr)
35+
}
36+
37+
}
38+
if (selectorAttributes.length > 0)
39+
observer.init({
40+
name: 'lazyloadAttributeObserver',
41+
observe: ['attributes'],
42+
attributeName: selectorAttributes,
43+
target: selector,
44+
callback: observerCallback
45+
});
46+
47+
}
48+
49+
export async function lazyLoad(name, selector, callback) {
50+
if (document.querySelector(selector))
51+
await dependency(name, await callback())
52+
else
53+
listen(callback, selector)
54+
}
55+
56+
export async function dependency(name, promise) {
57+
let component = await promise;
58+
Object.assign(window.CoCreate, {
59+
[name]: component.default
60+
});
61+
}

src/index.js

Lines changed: 13 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,14 @@
1-
import observer from '@cocreate/observer';
2-
3-
function listen(callback, selector) {
4-
5-
function observerCallback({ target }) {
6-
// let isInit = target.querySelector(selector)
7-
// if (isInit) {
8-
callback()
9-
// console.log('lazyloaded', selector)
10-
observer.uninit(observerCallback)
11-
// }
12-
}
13-
14-
observer.init({
15-
name: 'lazyloadObserver',
16-
observe: ['childList'],
17-
target: selector,
18-
callback: observerCallback
19-
})
20-
21-
let selectorAttributes = [];
22-
let attributes = selector.split(",")
23-
for (let attribute of attributes) {
24-
let attr = attribute.trim()
25-
if (attr.startsWith("[")) {
26-
let pos = attr.indexOf("*")
27-
if (pos == -1)
28-
pos = attr.indexOf("=")
29-
if (pos !== -1) {
30-
attr = attr.slice(1, pos)
31-
} else {
32-
attr = attr.slice(1, -1)
33-
}
34-
selectorAttributes.push(attr)
35-
}
36-
37-
}
38-
if (selectorAttributes.length > 0)
39-
observer.init({
40-
name: 'lazyloadAttributeObserver',
41-
observe: ['attributes'],
42-
attributeName: selectorAttributes,
43-
target: selector,
44-
callback: observerCallback
1+
(function (root, factory) {
2+
if (typeof define === 'function' && define.amd) {
3+
define(["./client"], function (CoCreateLazyLoader) {
4+
return factory(CoCreateLazyLoader)
455
});
46-
47-
}
48-
49-
export async function lazyLoad(name, selector, callback) {
50-
if (document.querySelector(selector))
51-
await dependency(name, await callback())
52-
else
53-
listen(callback, selector)
54-
}
55-
56-
export async function dependency(name, promise) {
57-
let component = await promise;
58-
Object.assign(window.CoCreate, {
59-
[name]: component.default
60-
});
61-
}
6+
} else if (typeof module === 'object' && module.exports) {
7+
const CoCreateLazyLoader = require("./server.js")
8+
module.exports = factory(CoCreateLazyLoader);
9+
} else {
10+
root.returnExports = factory(root["./client.js"]);
11+
}
12+
}(typeof self !== 'undefined' ? self : this, function (CoCreateLazyLoader) {
13+
return CoCreateLazyLoader;
14+
}));

src/server.js

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
const EventEmitter = require('events');
2+
const eventEmitter = new EventEmitter();
3+
const fs = require('fs').promises;
4+
const path = require('path');
5+
const vm = require('vm');
6+
7+
const scriptsDirectory = './scripts';
8+
9+
class CoCreateLazyLoader {
10+
constructor(crud) {
11+
this.wsManager = crud.wsManager
12+
this.crud = crud
13+
this.init()
14+
}
15+
16+
init() {
17+
// TODO: check CoCreate.config.js using config
18+
const lazyLoadConfig = Config('lazyload')
19+
if (!lazyLoadConfig)
20+
return
21+
22+
for (let key of Object.keys(lazyLoadConfig.lazyload)) {
23+
let moduleConfig = lazyLoadConfig.lazyload[key];
24+
eventEmitter.on(moduleConfig.event, async () => {
25+
try {
26+
const module = await require(moduleConfig.path);
27+
28+
if (typeof moduleConfig.unload === 'number') {
29+
setTimeout(() => {
30+
// Implement module unload logic
31+
}, moduleConfig.unload);
32+
}
33+
34+
// Use openaiModule here
35+
36+
} catch (error) {
37+
console.log()
38+
}
39+
});
40+
41+
console.log("Module Key:", key);
42+
console.log("Module Config:", moduleConfig);
43+
44+
}
45+
46+
}
47+
}
48+
49+
// Emitting the event somewhere in your application
50+
// eventEmitter.emit('openai');
51+
52+
let exclusion = {};
53+
54+
function generateExclusionList() {
55+
exclusion = { ...require.cache };
56+
}
57+
58+
function getModuleDependencies(modulePath) {
59+
let moduleObj = require.cache[modulePath];
60+
if (!moduleObj) {
61+
return [];
62+
}
63+
64+
// Get all child module paths
65+
return moduleObj.children.map(child => child.id);
66+
}
67+
68+
function isModuleUsedElsewhere(modulePath, moduleName) {
69+
return Object.keys(require.cache).some(path => {
70+
const moduleObj = require.cache[path];
71+
return moduleObj.children.some(child => child.id === modulePath && path !== modulePath);
72+
});
73+
}
74+
75+
function clearModuleCache(moduleName) {
76+
try {
77+
const modulePath = require.resolve(moduleName);
78+
const dependencies = getModuleDependencies(modulePath);
79+
80+
// Recursively clear dependencies from cache
81+
dependencies.forEach(depPath => {
82+
clearModuleCache(depPath);
83+
});
84+
85+
// Check if the module is a dependency of other modules
86+
const moduleObj = require.cache[modulePath];
87+
if (moduleObj && moduleObj.parent) {
88+
console.log(`Module ${moduleName} is a dependency of other modules.`);
89+
return;
90+
}
91+
92+
// Check if the module is used by other modules
93+
if (isModuleUsedElsewhere(modulePath, moduleName)) {
94+
console.log(`Module ${moduleName} is a dependency of other modules.`);
95+
return;
96+
}
97+
98+
// Remove the module from the cache
99+
delete require.cache[modulePath];
100+
console.log(`Module ${moduleName} has been removed from cache.`);
101+
} catch (error) {
102+
console.error(`Error clearing module cache for ${moduleName}: ${error.message}`);
103+
}
104+
}
105+
106+
// Function to create the scripts directory if it doesn't exist
107+
async function createScriptsDirectory() {
108+
try {
109+
await fs.mkdir(scriptsDirectory, { recursive: true });
110+
console.log(`Scripts directory created at ${scriptsDirectory}`);
111+
} catch (error) {
112+
console.error('Error creating scripts directory:', error);
113+
throw error; // Halt execution if directory creation fails
114+
}
115+
}
116+
117+
// Call this function at the start of your application
118+
createScriptsDirectory();
119+
120+
// Function to fetch script from database and save to disk
121+
async function fetchScriptFromDatabaseAndSave(scriptId, pathname) {
122+
let data = {
123+
method: 'object.read',
124+
array: 'files',
125+
$filter: {
126+
query: [
127+
{ key: "host", value: [hostname, '*'], operator: "$in" },
128+
{ key: "pathname", value: pathname, operator: "$eq" }
129+
],
130+
limit: 1
131+
},
132+
organization_id
133+
};
134+
135+
let file = await crud.send(data);
136+
let src;
137+
138+
if (file && file.object && file.object[0]) {
139+
src = file.object[0].src;
140+
} else {
141+
throw new Error('Script not found in database');
142+
}
143+
144+
// Save to disk for future use
145+
const scriptPath = path.join(scriptsDirectory, `${scriptId}.js`);
146+
await fs.writeFile(scriptPath, src);
147+
148+
return src;
149+
}
150+
151+
// Map to track timeouts and contexts for each script
152+
const scriptTimeouts = new Map();
153+
154+
// Function to execute a script with a debounce timeout
155+
async function executeScriptWithTimeout(scriptId, pathname, timeoutDuration = 10000) {
156+
let context;
157+
let scriptContent;
158+
159+
// Check if the script is already loaded
160+
if (scriptTimeouts.has(scriptId)) {
161+
clearTimeout(scriptTimeouts.get(scriptId).timeout);
162+
context = scriptTimeouts.get(scriptId).context;
163+
} else {
164+
// Check if script exists on disk, else fetch from database
165+
const scriptPath = path.join(scriptsDirectory, `${scriptId}.js`);
166+
try {
167+
await fs.access(scriptPath);
168+
scriptContent = await fs.readFile(scriptPath, 'utf8');
169+
} catch {
170+
scriptContent = await fetchScriptFromDatabaseAndSave(scriptId, pathname);
171+
}
172+
173+
// Execute the script
174+
context = new vm.createContext({});
175+
const script = new vm.Script(scriptContent);
176+
script.runInContext(context);
177+
}
178+
179+
// Reset or set the timeout
180+
const timeout = setTimeout(() => {
181+
for (const key in context) {
182+
if (context.hasOwnProperty(key)) {
183+
delete context[key];
184+
}
185+
}
186+
scriptTimeouts.delete(scriptId);
187+
console.log(`Script ${scriptId} removed due to inactivity.`);
188+
}, timeoutDuration);
189+
190+
// Update the map
191+
scriptTimeouts.set(scriptId, { context, timeout });
192+
}
193+
194+
// Example usage
195+
const scriptId = 'unique-script-id';
196+
const pathname = '/path/to/script'; // Set the appropriate pathname
197+
198+
executeScriptWithTimeout(scriptId, pathname, 10000).then(() => {
199+
console.log(`Script ${scriptId} executed.`);
200+
});
201+
202+
203+
// Call this function at the start of your server
204+
// generateExclusionList();
205+
206+
// Example usage
207+
// const moduleName = 'your-module-name';
208+
// clearModuleCache(moduleName);
209+
210+
module.exports = CoCreateLazyLoader;

0 commit comments

Comments
 (0)