An experimental prototype for running Node.js scripts in Google Chrome
JavaScript initially made its mark in the browser. Node.js popularized running JavaScript on the desktop (and npm for package management). browserify enabled using much of the code and tooling written for Node/NPM on any web browser.
But browserify creates an unusual situation when developing web-based applications. There are two completely separate JavaScript engines and runtimes and platforms: Node.js used for tooling and command-line testing, and the browser used for everything else. Nodeachrome attempts to reconcile this discrepancy by allowing a (hopefully) useful subset of Node scripts to run directly within the Google Chrome web browser.
Nodeachrome is packaged as a Chrome extension (in app/
), with a
native messaging host component
(in host/
) to provide native OS functionality where necessary. You can interact with
it through the Developer Tools console (TODO: native command-line interface via host).
Currently, Node.js is still required for bootstrapping, but in principle if this project
is successful it should be possible to self-host Nodeachrome without Node.js, at some point.
NPM and browserify are the primary targeted use cases.
-
Build the Chrome extension:
cd app npm install npm start
-
Install the Chrome extension by navigating to chrome://extensions, checking "Developer mode", then selecting "Load unpacked extension..." and choose the "app" folder you just built.
-
Configure the native host. Edit
host/io.github.deathcap.nodeachrome.json
and specify the full path to nodeachrome.js in thepath
field (it is set for my system, but your path will differ), and update theallowed-origins
field to the chrome-extension:// URL matching the ID shown in chrome://extensions/. Finally, edithost/nodeachrome.js
and specify the full path tonode
in the#!
line. -
Register the native host running
sudo host/register.sh
, or copying the .json file to/Library/Google/Chrome/NativeMessagingHosts
(on OS X, may differ other systems, see here). -
Launch the Chrome extension app in chrome://extensions click "Launch". This opens up a debugging window for sending commands to the native host. For the full JavaScript environment: Inspect views: main.html, open the JavaScript console. Use
evalsb()
to evaluate code in the eval sandbox and interact with the console by typing JavaScript, see below for APIs.
The Nodeachrome app is split into "kernel" and "userland", similar to textbook operating system architecture. The kernel manages userland "processes", holding additional privileges over userland (access to the native host, Chrome platform API, arbitrary XHR), but most code is expected to execute in userland processes.
When the kernel boots up, it will spawn()
its first process. This creates a new HTML5 iframe with a unique
origin, aka sandbox. JavaScript code within the
process runs within its own isolated context, independent from other processes and the kernel, even having
its own global variables. It can only communicate by posting messages to the kernel, or writing to the
HTML document within its iframe.
You can debug the kernel by invoking the JavaScript console on kernel.html, or on a userland process by right-clicking an iframe and chosing "Inspect". Note that userland processes are much more interesting, the kernel is very minimal, by design.
Node.js process API is partially supported, using the default browserify process/browser, plus augmented with additional functionality in ./process2.js.
process.pid
is the process ID in userland, uniquely identifying each sandbox. Starts at 1, increments sequentially.
process.env
is an environment variable dictionary, accessible in the kernel, and inherited in
userland processes.
process.stdout
and process.stderr
log to console.log
and console.error
, respectively,
similar to browser-stdout. They also write to the
iframe document body, as preformatted text.
The sandbox/
directory is exposed through the Chrome extension via the native host. You can use
your normal text editors and file management tools to manipulate this directory, and use it from
Nodeachrome with (a subset of, the mostly-compatible) Node fs API.
There is no "web filesystem", or localStorage, or a virtual cloud filesystem here, you get real
filesystem access, within the sandbox directory.
Asynchronous methods use the aforementioned sandbox/
directory on disk, via the native host.
Synchronous methods are special-cased to return hardcoded results.
shellasync is included on the global object providing
ls()
, cat()
, etc. shell-like functions, for interactive convenience in using fs
The Node.js net socket API is not supported, but apps should be able to interact with the network using the http and https APIs, through the browserify default replacements stream-http and https-browserify, implemented using XMLHttpRequest and/or fetch
Unlike websites, Chrome extensions are not necessarily bound by the Same-Origin Policy on network
requests. Nodeachrome's manifest.json
specifies permissions http://*/
, https://*/
, so it can
access third-party websites like the NPM registry server. Sandboxes do not have this privilege, so
the window.fetch API is proxied to the kernel fetch API to retain this privilege. This allows the NPM
registry client to function, example:
new Process().exec(['npm', 'view', 'ucfirst'])
- Most browserify builtins are exposed on the global object
- require.resolve(x) returns x, modified browserify prelude in prelude2.js
Bundled programs intended to run under this project: (note: may be broken)
- browserify API (example:
browserify('foo.js').bundle().pipe(process.stdout)
) - npm API
See app/userland/bin
for all included.
When Nodeachrome is running, it can be interacted with on the Unix command-line using cli.js
, for example:
./host/cli.js -e '3+4'
./host/cli.js npm view ucfirst
Each call spawns a new process and runs the command-line program.
MIT