I was trying to create an IRC bot written in Javascript + NodeJS. This bot should be able to load plugins while running and should be able to reload the same plugin after changes etc.
What works?
Loading files at runtime + executing its code.
What's wrong?
After loading the same plugin again if still executes my code, but now it happens twice or nth times I load the plugins.
Current code:
bot.match(/\.load/i, function(msg) {
require('./plugins/plug.js')(this);
});
module.exports = function(bot) {
bot.match(/\.ping/i, function(msg) {
msg.reply('pong');
});
So, is there any way to fix my issues and make this work?
P.s. I'm using IRC-JS as a base for this bot.
updated, fixed:
Even changes to that file are ignored, so it must be something like a
cache.
Fixed by clearing the require.cache
require won't reload a file. Once it has been loaded once, it is in memory and it will not reload again. It sounds like you want to leave the bot on and change the contents of the scripts it requires 'on the fly'. You can do that by deleting require.cache. Check out node.js require() cache - possible to invalidate? and http://nodejs.org/api/modules.html#modules_caching
To answer my own question. While loading plugins, I add every object to a global array. This can be easily accessed and deleted afterwards.
function clearplugins() {
require("fs").readdirSync("./plugins/active").forEach(function(file) {
delete require.cache[require.resolve("./plugins/active/" + file)];
});
plugins.forEach(function(obj) {
obj();
});
}
function loadplugins() {
require("fs").readdirSync("./plugins/active").forEach(function(file) {
if(file.slice(-2) == 'js') {
plugins.push(require("./plugins/active/" + file)(bot));
console.log(file+' loaded');
}
});
}
Related
I have code basically like this in one file, index.js:
module.exports = {
const processHandler = require("myOtherFile.js");
cp = require("child_process");
cp.fork(pathOfCodeForFork);
cp.send("message");
}
In another file (myOtherFile.js) I have:
process.on("message", function(msg) {
console.log("here is the message " + msg);
}
The file loads but the code inside the process.on block never executes. Would really appreciate some guidance. New to child processes and probably not setting this up correctly.
So I asked this question, but I figured out the issue and wanted to post for anyone else struggling with this (and ask a follow-up). It turns out that pulling out the require for the myOtherFile.js from the module causes this to work (so, place const processHandler = require("myOtherFile.js"); above the module code altogether). The follow up, is I am not sure why? Is the reference to process different inside and outside the module? Or is this a circular requires issue somehow? Is this about the order in which things get run? Something else?
I am using Browserify to require a JS file, like this require('./typescript/build/crossing-puzzle-setup' . However, considering there will be many different types of puzzles, I was wondering, is it possible to "unrequire" a module if I no longer need it?
It makes little sense to unload a module, as the code only takes a very small amount of RAM, and no it is not possible to unload it as the engine cannot predict that require("some module") doesnt get called again, so it cannot unload the stored data of that module.
If you really are loading so much code that you need to force the browser to unload it, you could force the page to refresh and then load the previous state of the page. However, the user will see the browser’s “page loading” indicators during the refresh.
function startReload() {
// Save all the state you need into a string. An example object:
const importantAppState = {currentPuzzle: 'level1', username: 'roy'};
localStorage.setItem('stateBeforeReload', JSON.stringify(importantAppState));
window.location.reload();
}
// Run this function whenever your page loads:
function onLoad() {
const previousState = localStorage.setItem('stateBeforeReload');
if (previousState !== null) {
// you should re-initialize your app from the state here,
// dynamically calling `require` on only the files you need
localStorage.removeItem('stateBeforeReload');
}
}
for those looking for a nodeJS solution:
require('../myLib');
delete require.cache[require.resolve('../myLib')];
Not sure if the above clear 100% memory linked to the module. For that, a secure way seem to be using this node module: node-disrequire
A friend has asked me to capture a client-side rendered website built with React.js, preferably using PhantomJS. I'm using a simple rendering script as follows:
var system = require('system'),
fs = require('fs'),
page = new WebPage(),
url = system.args[1],
output = system.args[2],
result;
page.open(url, function (status) {
if (status !== 'success') {
console.log('FAILED to load the url');
phantom.exit();
} else {
result = page.evaluate(function(){
var html, doc;
html = document.querySelector('html');
return html.outerHTML;
});
if(output){
var rendered = fs.open(output,'w');
rendered.write(result);
rendered.flush();
rendered.close();
}else{
console.log(result);
}
}
phantom.exit();
});
The url is http://azertyjobs.tk
I consistently get an error
ReferenceError: Can't find variable: Promise
http://azertyjobs.tk/build/bundle.js:34
http://azertyjobs.tk/build/bundle.js:1 in t
...
Ok so I figured out that ES6 Promises aren't natively supported by PhantomJS yet, so I tried various extra packages like the following https://www.npmjs.com/package/es6-promise and initiated the variable as such:
var Promise = require('es6-promise').Promise
However this still produces the same error, although Promise is now a function. The output of the webpage is also still as good as empty (obviously..)
Now I'm pretty oldschool, so this whole client-side rendering stuff is kind of beyond me (in every aspect), but maybe someone has a solution. I've tried using a waiting script too, but that brought absolutely nothing. Am I going about this completely wrong? Is this even possible to do?
Much appreciated!
Ludwig
I've tried the polyfill you linked and it didn't work, changed for core.js and was able to make a screenshot. You need to inject the polyfill before the page is opened:
page.onInitialized = function() {
if(page.injectJs('core.js')){
console.log("Polyfill loaded");
}
}
page.open(url, function (status) {
setTimeout(function(){
page.render('output.jpg');
phantom.exit();
}, 3000);
});
What you need to understand is that there are several parts of a page loading. First there is the HTML - the same thing you see when you "view source" on a web page. Next there are images and scripts and other resources loaded. Then the scripts are executed, which may or may not result in more content being loaded and possible modifications to the HTML.
What you must do then is figure out a way to determine when the page is actually "loaded" as the user sees it. PhantomJS provides a paradigm for you to waitFor content to load. Read through their example and see if you can figure out a method which works for you. Take special note of where they put phantom.exit(); as you want to make sure that happens at the very end. Good luck.
Where (how) are you trying to initialise Promise? You'll need to create it as a property of window, or use es6-promise as a global polyfill, like this require('es6-promise').polyfill(); or this require('es6-promise/auto'); (from the readme).
Also, what do you mean by "capture"? How If you're trying to scrape data, you may have better luck using X-ray. It supports Phantom, Nightmare and other drivers.
Keep in mind also that React can also be server rendered. React is like templating, but with live data bindings. It's not as complicated as you're making it out to be.
Good afternoon everyone! I have searched the web to resolve this problem and tried to implement what I could see as the solution, but I still occasionally run into problems...
The project is modular in design with each module having a .html, .js, and .css file. When loading a module, those associated files are checked for and if they already exist in the DOM (e.g. the module has been opened at least once already), then there are no problems. If the files aren't already loaded (e.g. the module hasn't been loaded yet), then occasionally I run into a problem where a javascript function is called, but the external .js file hasn't been loaded yet. Here's the below function responsible for handling this:
function loadFile(sType,sURI,sCallback) {
// sType the type of file to load: link, script
// sURI the URI of the file to load
// sCallback the code to execute after successfully loading the file
var ref = document.createElement(sType);
if (sType == 'script') {
ref.setAttribute("type","text/javascript");
ref.setAttribute("src",sURI);
} else if (sType == 'link') {
ref.setAttribute("rel","stylesheet");
ref.setAttribute("type","text/css");
ref.setAttribute("href",sURI);
}
ref.async = true;
ref.onreadystatechange = ref.onload = function() {
var state = ref.readyState;
if (! sCallback.done && (! state || /loaded|complete/.test(state))) {
sCallback.done = true;
if (typeof(sCallback) === 'function') {
callback();
} else {
eval(sCallback);
}
}
};
document.getElementsByTagName('head')[0].appendChild(ref);
}
There are several other SO articles that were used for the above function:
Dynamically load external javascript file, and wait for it to load - without using JQuery
Javascript check if function exists
So the above function will be called like:
loadFile('link',"module.css?cache=0", $("#divModule").hide().load("module.php?action=init").fadeIn('slow'));
loadFile('script',"module.js?cache=0", "initModule('req')");
The first call will load the .html file contents (via the module.php call) for the module after the .css file has downloaded. The second call will call the modules js init function after the .js file has downloaded.
It doesn't appear to have any issues with the .css file, but sometimes the module will not load correctly meaning that the layout is rendered correctly, but no values populate (which is what happens with the modules js init function). If I check the 'Web console' in FF, there aren't any errors that are thrown. Any thoughts?
Thanks,
Dave
Hard to say for me w/o seeing what's happening in that module.php code, but it looks to me like your first one at least is firing immediately, rather than as a callback. Unless I'm missing something, the callback should be a function, while right now you're actually executing the jquery hide/load stuff right when you call loadfile. You might want to wrap it in a function.
In the uninstall part of my bootstrapped addons I do some important stuff. I delete any files it created and all preferences. However this uses some services.
This is an example of one of my uninstall procedures:
function uninstall(aData, aReason) {
if (aReason == ADDON_UNINSTALL) { //have to put this here because uninstall fires on upgrade/downgrade too
//this is real uninstall
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/devtools/Console.jsm');
Cu.import('resource://gre/modules/osfile.jsm');
//if custom images were used lets delete them now
var customImgPrefs = ['customImgIdle', 'customImgLoading'];
[].forEach.call(customImgPrefs, function(n) {
//cant check the pref i guess because its probably unintialized or deleted before i used have a `if(prefs[n].value != '') {`
//var normalized = OS.Path.normalize(prefs[n].value);
//var profRootDirLoc = OS.Path.join(OS.Constants.Path.profileDir, OS.Path.basename(normalized));
var profRootDirLoc = OS.Path.join(OS.Constants.Path.profileDir, 'throbber-restored-' + n);
var promiseDelete = OS.File.remove(profRootDirLoc);
console.log('profRootDirLoc', profRootDirLoc)
promiseDelete.then(
function() {
Services.prompt.alert(null, 'deleted', 'success on ' + n);
},
function(aRejReas) {
console.warn('Failed to delete copy of custom throbber ' + n + ' image for reason: ', aRejReas);
Services.prompt.alert(null, 'deleted', 'FAILED on ' + n);
}
);
});
Services.prefs.deleteBranch(prefPrefix);
}
The reason I post rather than test is because I tested and it worked but is there any special cases? Like what if addon was disabled, browser restarted, and then user opened addon manager then uninstalled. Special cases like these and any others? Do they require me to import all my stuff again?
uninstall wlll be called regardless of whether the add-on was previously enabled or not, and regardless of whether the add-on was compatible or not, as long at the add-on is still present.
It won't be called, of course, if the user manually deleted the add-on XPI (or unpacked directory) from their profile while the browser wasn't running, because then on the next start there is nothing left to call.
This also means that uninstall might be the first (and only) add-on function called. If the add-on was always disabled right from the browser start and it is then disabled, then there won't be any other calls. This is important to be aware of. Consider the following contrived example.
var myId;
Cu.reportError("global exec"); // Thiw will be always run, as well.
function startup(data) {
myId = data.id,
}
function uninstall() {
Cu.reportError(myId); // might be undefined if startup never ran.
}
So, there are three and a half special "things" to consider:
uninstall does not run when the XPI is manually removed while the browser is not running. 2. When uinstalled properly, then uninstall will always be run.
.. even if no other add-on functions were called prior to that.
This also means that any global code in your bootstrap.js will also run on uninstall, as a consequent of loading bootstrap.js.
Upon cursory inspection, your code does not seem to rely on anything initialized elsewhere, so it should be fine.
I'd like to point out however, that it is generally considered a bad idea to remove user configuration upon uninstall if not specifically instructed by the user to do so. Same goes for configuration files and user data files. If you do so, you should ask before.
Users will regularly uninstall and then reinstall stuff, only to find their carefully crafted preferences, etc. are gone otherwise.