I am attempting to pipe the stdout & stdin of a child_process to a browser & display it in an html page. I am using browserify to get node.js to run on the browser. My code for spawning the child_process is like this.
var child = require('child_process');
var myREPL = child.spawn('myshell.exe', ['args']);
// myREPL.stdout.pipe(process.stdout, { end: false });
process.stdin.resume();
process.stdin.pipe(myREPL.stdin, { end: false });
myREPL.stdin.on('end', function() {
process.stdout.write('REPL stream ended.');
});
myREPL.on('exit', function (code) {
process.exit(code);
});
myREPL.stdout.on('data', function(data) {
console.log('\n\nSTDOUT: \n');
console.log('**************************');
console.log('' + data);
console.log('==========================');
});
I created a bundle.js using browserify and my html looks like this.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title></title>
<!--[if IE]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<script src="bundle.js"></script>
<script src="main.js"></script>
</head>
<body>
</body>
</html>
I am trying to avoid running an http server and piping the results to it in the browser. Is there any other way where I can do it ?
Thanks
You should look into hyperwatch, which pipes the server side stdout/stderr to the browser and renders it exactly like it would appear in your terminal (including colors).
If it doesn't exactly solve your problem, reading through the code should at least help you. It uses hypernal under the hood in order to convert terminal output to html.
I dont know if this is to late but i managed to run a program from browser starting from this code that works only on linux (i use ubuntu). You will have to run interactive programs with stdbuf -o0 prefix.
var child = require('child_process');
var myREPL = child.spawn('bash');
process.stdin.pipe(myREPL.stdin);
myREPL.stdin.on("end", function() {
process.exit(0);
});
myREPL.stdout.on('data', function (data) {
console.log(data+'');
});
myREPL.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
An then to make it to work on browser you only need to add socket.io
var myREPL = child.spawn(program);
myREPL.stdin.on("end", function() {
socket.emit('consoleProgramEnded');
});
myREPL.stdout.on('data', function (data) {
socket.emit('consoleWrite',data+'');
});
myREPL.stderr.on('data', function (data) {
socket.emit('consoleWrite',data+'');
});
socket.on('consoleRead',function(message){
console.log("Writing to console:"+message);
myREPL.stdin.write(message.replace("<br>","")+"\n");
});
I hope that will help you.
I think the frontail NPM module can also do what you want to do -
https://github.com/mthenw/frontail
I have used it and it works
Related
Note: When I delete "node_modules\uws\uws_win32_59.node" it works fine. uws is used by engine.io, which is used by socket.io.
I wrote a very basic app to demo the problem. In the below app, with 2 tabs of the index.html open in chrome, clicking "emit" emits the message from the client, but takes a significant, variable amount of time to reach the server. Anywhere from 2-15+ seconds. If I only have 1 index.html page open, it works fine, but once a second one is opened, I encounter the problem. If I delete the above uws_win32_59.node, it works fine with multiple connections.
server.js:
var io = require('socket.io')();
io.on('connection', function(socket){
console.log('connection made');
socket.on('number', function(num) {
console.log(num + ' received on server');
io.emit('number', num);
console.log(num + ' emitted from server');
});
});
io.listen(9001);
index.html:
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.4/socket.io.js"></script>
</head>
<body>
<button id="emit">emit</button>
<p id="nums"></p>
<script>
var socket = io('http://localhost:9001');
socket.on('number', function(num){
console.log(num + ' received on client');
document.getElementById('nums').innerHTML = num;
});
var num = 0;
document.getElementById('emit').addEventListener('click', function(){
num++;
socket.emit('number', num);
console.log(num + ' emitted from client');
});
</script>
</body>
</html>
the default "npm install socket.io" installs that uws module mentioned above, which itself is used by engine.io.
run "node server" and open 2 instances of the index.html and click emit and notice the browser developer tools console logging and the node server console logging to recreate the issue.
EDIT: looks like there is an open issue with uws on windows os: https://github.com/socketio/socket.io/issues/3100
Maybe you need npm install socket.io#1.7.4. I think version 2.0 is significantly slower
When executed in Node context (node-main),
setTimeout(function () {
console.log(nw);
}, 20);
throws
nw is not defined
because WebKit context is not ready (right from the start window is unavailable in NW.js <= 0.12, window.nw in NW.js >= 0.13). And
setTimeout(function () {
console.log(nw);
}, 200);
works just fine but setTimeout looks like a hack, setting it to safe delay value may cause undesirable lag.
How can the availability of WebKit context and nw be checked from Node context? Is there a reasonable way, like an event that could be handled?
The following achieves the same thing but does it the other way around.
In your html file:
<body onload="process.mainModule.exports.init()">
In your node-main JS file:
exports.init = function() {
console.log(nw);
}
Here, init function is only called when Webkit context/DOM is available.
You could use pollit :) ...
var pit = require("pollit");
foo = function(data) {
console.log(nw);
};
pit.nw('nw', foo);
I've tested it and it works for me :). This modularizes the solution that I give near the end of this.
The nw object does not exist until webkit is up and running ie the browser
window has been created. This happens after Node starts up which is why you're
getting this error. To use the nw api you either create events that can be
listened to or call global functions the former being preferable. The following code will demonstrate both and should give you a good idea of how Node and WebKit are interfacing with each other.
This example creates a Window, opens devtools and allows you to toggle the
screen. It also displays the mouse location in the console. It also demonstrates how to send events using the DOM ie body.onclick() and attaching events from within Node ie we're going to catch minimize events and write them to the console.
For this to work you need to be using the SDK version of NW. This is my package.json
{
"name": "hello",
"node-main": "index.js",
"main": "index.html",
"window": {
"toolbar": true,
"width": 800,
"height": 600
},
"dependencies" : {
"robotjs" : "*",
"markdown" : "*"
}
}
The two files you need are index.html
<!DOCTYPE html>
<html>
<head>
<script>
var win = nw.Window.get();
global.win = win;
global.console = console;
global.main(nw);
global.mouse();
var markdown = require('markdown').markdown;
document.write(markdown.toHTML("-->Click between the arrows to toggle full screen<---"));
</script>
</head>
<body onclick="global.mouse();">
</body>
</html>
and index.js.
var robot = require("robotjs");
global.mouse = function() {
var mouse = robot.getMousePos();
console.log("Mouse is at x:" + mouse.x + " y:" + mouse.y);
global.win.toggleFullscreen();
}
global.main = function(nw_passed_in) {
global.win.showDevTools();
console.log("Starting main");
console.log(nw_passed_in);
console.log(nw);
global.win.on('minimize', function() {
console.log('n: Window is minimized from Node');
});
}
When running this I used
nwjs --enable-logging --remote-debugging-port=1729 ./
You can then open up the browser using
http://localhost:1729/
for debugging if needed.
If you want to do something as soon as the nw object exists you can poll it. I'd use eventEmitter, if you don't want to use event emitter you can just as easily wrap this in a function and call it recursively. The following will display how many milliseconds it took before the nw object was setup. On my system this ranged between 43 - 48 milliseconds. Using a recursive function was no different. If you add this to the code above you'll see everything logged to the console.
var start = new Date().getTime();
var events = require('events');
var e = new events.EventEmitter();
var stop = 0;
e.on('foo', function() {
if(typeof nw === 'undefined') {
setTimeout(function () {
e.emit('is_nw_defined');
}, 1);
}
else {
if(stop === 0) {
stop = new Date().getTime();
}
setTimeout(function () {
console.log(stop - start);
console.log(nw);
e.emit('is_nw_defined');
}, 2000);
}
});
e.emit('is_nw_defined');
Solution 1:
You can use onload, see reference.
main.js:
var gui = require("nw.gui"),
win = gui.Window.get();
onload = function() {
console.log("loaded");
console.log(win.nw);
};
index.html:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="main.js"></script>
</head>
<body></body>
</html>
package.json:
{
"name": "Freebox",
"main": "index.html"
}
Solution 2:
(To prevent issue, but it is not necessary).
var gui = require("nw.gui"),
win = gui.Window.get();
onload = function() {
console.log("loaded");
var a = function () {
if (!win.nw) return setTimeout(a, 10);
console.log(win.nw);
};
};
The solution I've initially come up looks like
app-node.js
process.once('webkit', () => {
console.log(nw);
});
app.html
<html>
<head>
<script>
global.process.emit('webkit');
</script>
...
I would be glad to know that there is already an event to listen, so cross-platform client scripts could omit NW-related code.
I am trying to update the bust variable in require.js so the browser is forced to re-fetch instead of loading resources from cache. I found here that several people have asked similar question. I am trying to try with a simple piece of code.
<html>
<head>
<title>jQuery+RequireJS Sample Page</title>
<!-- This is a special version of jQuery with RequireJS built-in -->
<script>
var require = {
urlArgs : "bust="+getRandom()
};
</script>
<script data-main="scripts/main" src="scripts/require-jquery.js"></script>
<script>
function getRandom() {
var buildNumber;
$.get("/resource/buildNumber", function(data) {
buildNumber = data;
});
return buildNumber;
}
</script>
</head>
<body>
<h1 id="heading">jQuery+RequireJS Sample Page</h1>
<p>Look at source or inspect the DOM to see how it works.</p>
</body>
I am trying to get the value of my build number from a properties file on the server. But I get the following error:
Uncaught ReferenceError: getRandom is not defined
So I tried this:
<script data-main="scripts/main" src="scripts/require-jquery.js"></script>
<script>
var require = {
urlArgs : "bust="+getRandom()
};
function getRandom() {
var buildNumber;
$.get("/resource/buildNumber", function(data) {
buildNumber = data;
});
return buildNumber;
}
</script>
But I get this error:
Uncaught TypeError: Property 'require' of object [object Object] is not a function
It looks like the bust variable has to be set even before require-jquery.js is declared but how do I access server side APIs without access to jquery libraries? I want to update the bust variable for every build.
Any pointers in the right directions would be really appreciated.
Thanks.
I'm using Yeoman to scaffold my project. It comes with several handy things, including a PhantomJS-based test runner.
My problem is that while my tests run correctly in the browser, they time out while trying to run them with PhantomJS in the CLI.
Here's how my test index.html looks like:
<!doctype html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Mocha Spec Runner</title>
<link rel="stylesheet" href="lib/mocha/mocha.css">
</head>
<body>
<div id="mocha"></div>
<script src="lib/mocha/mocha.js"></script>
<!-- assertion framework -->
<script src="lib/chai.js"></script>
<!-- include source files here... -->
<script data-main="scripts/main" src="scripts/vendor/require.js"></script>
<script>
mocha.setup({ui: 'bdd', ignoreLeaks: true});
expect = chai.expect;
should = chai.should();
require(['../spec/map.spec'], function () {
setTimeout(function () {
require(['../runner/mocha']);
}, 100);
});
</script>
</body>
</html>
Here's map.spec.js:
require(['map'], function (Map) {
describe('Choropleth Map Generator', function() {
describe('Configure the map', function () {
it("should enforce mandatory parameters in the configuration", function () {
var config = {title: 'test configuration'};
var map = new Map(config);
(function () {
map.getConfig();
}).should.throw(Error);
});
});
});
});
Now, when I do yeoman test, I get this:
Running "server:phantom" (server) task
Starting static web server on port 3501
[...]
Running "mocha:all" (mocha) task
Testing index.html
<WARN> PhantomJS timed out, possibly due to a missing Mocha run() call. Use --force to continue. </WARN>
Aborted due to warnings.
As I said, yeoman server:test shows my assertions correctly in the browser.
I'm using Yeoman 0.9.6 and PhantomJS 1.7.0. Any help is appreciated.
I solved that same warning by double-checking the path to mocha in test/index.html
<link rel="stylesheet" href="lib/mocha/mocha.css">
</head>
<body>
<div id="mocha"></div>
<script src="lib/mocha/mocha.js"></script>
What is your mocha config In Gruntfile. Do you have something like:
mocha: {
all: ['http://localhost:3501/index.html']
},
codes in file main.js is like this:
phantom.injectJs("libs/require-1.0.7.js");
require.config(
{
baseUrl: ""
}
);
require([], function(){});
when i run "phantomjs main.js" in the commandline, requirejs doesn't work well in the main.js. I know how to use requirejs in the page running in the browser(including phantomjs' way: page.open(url, callback)), but not like above. I tries using requirejs like the main.js, it is a popular problem, i think. Thank you!
I just struggled for some time. My solution is not clean, but it works, and I'm happy with that due to the unfinished api documentation from phantomjs.
Wordy explanation
You need three files. One is your amd phantomjs test file which I'll call "amd.js". The second is your html page to load which I'll name "amd.html". Finally the browser test which I called "amdTestModule.js".
In amd.html, declare your script tag per normal:
<script data-main="amdTestModule.js" src="require.js"></script>
In your phantomjs test file, this is where it gets hacky. Create your page, and load in the 'fs' module. This allows you to open a relative file path.
var page = require('webpage').create();
var fs = require('fs');
page.open('file://' + fs.absolute('tests/amd.html'));
Now since requirejs loads files asynchronously, we can't just pass in a callback into page.open and expect things to go smoothly. We need some way to either
1) Test our module in the browser and communicate the result back to our phantomjs context. Or
2) Tell our phantomjs context that upon loading all the resources, to run a test.
#1 was simpler for my case. I accomplished this via:
page.onConsoleMessage = function(msg) {
msg = msg.split('=');
if (msg[1] === 'success') {
console.log('amd test successful');
} else {
console.log('amd test failed');
}
phantom.exit();
};
**See full code below for my console.log message.
Now phantomjs apparently has an event api built in but it is undocumented. I was also successfully able to get request/response messages from their page.onResourceReceived and page.onResourceRequested - meaning you can debug when all your required modules are loaded. To communicate my test result however, I just used console.log.
Now what happens if the console.log message is never ran? The only way I could think of resolving this was to use setTimeout
setTimeout(function() {
console.log('amd test failed - timeout');
phantom.exit();
}, 500);
That should do it!
Full Code
directory structure
/projectRoot
/tests
- amd.js
- amdTestModule.js
- amd.html
- require.js (which I symlinked)
- <dependencies> (also symlinked)
amd.js
'use strict';
var page = require('webpage').create();
var fs = require('fs');
/*
page.onResourceRequested = function(req) {
console.log('\n');
console.log('REQUEST');
console.log(JSON.stringify(req, null, 4));
console.log('\n');
};
page.onResourceReceived = function(response) {
console.log('\n');
console.log('RESPONSE');
console.log('Response (#' + response.id + ', stage "' + response.stage + '"): ' + JSON.stringify(response, null, 4));
console.log('\n');
};
*/
page.onConsoleMessage = function(msg) {
msg = msg.split('=');
if (msg[1] === 'success') {
console.log('amd test successful');
} else {
console.log('amd test failed');
}
phantom.exit();
};
page.open('file://' + fs.absolute('tests/amd.html'));
setTimeout(function() {
console.log('amd test failed - timeout');
phantom.exit();
}, 500);
amd.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<script data-main='amdTestModule.js' src='require.js'></script>
</body>
</html>
amdTestModule.js
require([<dependencies>], function(<dependencies>) {
...
console.log(
(<test>) ? "test=success" : "test=failed"
);
});
console
$ phantomjs tests/amd.js
amd test successful
you are misunderstanding webpage.injectJs()
it's for injecting scripts into the page you are loading, not into the phantomjs runtime environment.
So using .injectJs() is making requirejs load up into your page, not into phantomjs.exe.
That said, phantomjs's runtime environment has an aproximation of commonjs. RequireJs will not run on there by default. If you felt especially (VERY) motivated, you could attempt porting the require-shim made for nodejs, but it doesn't work out of the box, and would require an incredibly deep understanding of the runtimes. for more details: http://requirejs.org/docs/node.html
a better idea:
probably you should make sure you have commonjs versions of your javascript you wish to run. i personally write my code in typescript so i can build for either commonjs or amd. i use commonjs for phantomjs code, and amd for nodejs and browser.