This project:
- is a case-study for browserifying anything
- part of a dream to create virtual Node.js development environment
- likely a major time-sink!
starting out:
mkdir browser-npm && cd browser-npm
npm install npm
npm init
make an index.js
var npm = require('npm')
debugger
and an index.html
<body> b r o w s e r - n p m <script src="bundle.js"></script></body>
ok simple enough lets see what happens next
browserify index.js >| bundle.js
didn't think it would be that easy did you? I get:
Error: module "os" not found from "/Users/kumavis/Dropbox/Development/Node/browser-npm/node_modules/npm/lib/utils/error-handler.js"
ok, what is this os
module? in Node I can see that it is provided out of the box, I would consider it a core-module:
> require('os')
{ hostname: [Function],
loadavg: [Function],
uptime: [Function],
freemem: [Function],
totalmem: [Function],
cpus: [Function],
type: [Function],
release: [Function],
networkInterfaces: [Function],
arch: [Function],
platform: [Function],
tmpDir: [Function],
getNetworkInterfaces: [Function: deprecated],
EOL: '\n' }
if its a core module, why didn't browser-resolve supply it?
ls /usr/local/share/npm/lib/node_modules/browserify/node_modules/browser-resolve/builtin
assert.js events.js net.js querystring.js sys.js tty.js
child_process.js fs.js path.js stream.js timers.js url.js
dgram.js https.js process.js string_decoder.js tls.js util.js
Oh well we can make our own as needed. skimming the results of npm search browser os
I found os-browserify. It will do! I am going to add it browserify's browser-resolve's deps,
"dependencies": {
"resolve": "0.3.1",
"console-browserify": "0.1.6",
"vm-browserify": "0.0.1",
"crypto-browserify": "0.2.1",
"http-browserify": "0.1.11",
"buffer-browserify": "0.0.5",
"zlib-browserify": "0.0.1",
"os-browserify": "0.1.0"
},
navigate to browserify/node_modules/browser-resolve/
and npm install
.
Next add core['os'] = require.resolve('os-browserify');
to browserify/node_modules/browser-resolve/index.js
oh! I almost forgot. We need to tell browser-resolve
to use its own list of core modules, instead of asking regular resolve
. Replace this line with this
if (core.hasOwnProperty(id)) {
returning to our browser-npm
directory, lets try bundling again...
/usr/local/share/npm/lib/node_modules/browserify/node_modules/insert-module-globals/node_modules/lexical-scope/node_modules/astw/node_modules/esprima/esprima.js:3859
throw e;
^
Error: Line 9: Illegal return statement
at throwError (/usr/local/share/npm/lib/node_modules/browserify/node_modules/insert-module-globals/node_modules/lexical-scope/node_modules/astw/node_modules/esprima/esprima.js:1161:21)
at throwErrorTolerant (/usr/local/share/npm/lib/node_modules/browserify/node_modules/insert-module-globals/node_modules/lexical-scope/node_modules/astw/node_modules/esprima/esprima.js:1172:24)
at parseReturnStatement (/usr/local/share/npm/lib/node_modules/browserify/node_modules/insert-module-globals/node_modules/lexical-scope/node_modules/astw/node_modules/esprima/esprima.js:2476:13)
at parseStatement (/usr/local/share/npm/lib/node_modules/browserify/node_modules/insert-module-globals/node_modules/lexical-scope/node_modules/astw/node_modules/esprima/esprima.js:2752:24)
at parseIfStatement (/usr/local/share/npm/lib/node_modules/browserify/node_modules/insert-module-globals/node_modules/lexical-scope/node_modules/astw/node_modules/esprima/esprima.js:2191:22)
at parseStatement (/usr/local/share/npm/lib/node_modules/browserify/node_modules/insert-module-globals/node_modules/lexical-scope/node_modules/astw/node_modules/esprima/esprima.js:2750:24)
at parseSourceElement (/usr/local/share/npm/lib/node_modules/browserify/node_modules/insert-module-globals/node_modules/lexical-scope/node_modules/astw/node_modules/esprima/esprima.js:3044:24)
at parseSourceElements (/usr/local/share/npm/lib/node_modules/browserify/node_modules/insert-module-globals/node_modules/lexical-scope/node_modules/astw/node_modules/esprima/esprima.js:3082:29)
at parseProgram (/usr/local/share/npm/lib/node_modules/browserify/node_modules/insert-module-globals/node_modules/lexical-scope/node_modules/astw/node_modules/esprima/esprima.js:3096:19)
at parse (/usr/local/share/npm/lib/node_modules/browserify/node_modules/insert-module-globals/node_modules/lexical-scope/node_modules/astw/node_modules/esprima/esprima.js:3843:23)
ouch! right in the esprima! ok lets see whats going on here. Well apparently esprima is an "ECMAScript parsing infrastructure". Clearly browserify is using this to seek require
calls and bundle dependencies. But something has gone awry - search reveals that others are getting this unhelpful error message, the posted issue unresolved at the time of this writing.
In order to find out what code is being parsed when the error thrown, I add process.stderr.write(source);
right above the throwErrorTolerant({}, Messages.IllegalReturn);
line, browserify index.js >| bundle.js
again, take the console output and use it to do a project-wide search in browser-npm/
. Don't forget to undo changes to esprima.js!
It is from browser-npm/node_modules/npm/node_modules/child-process-close/index.js
. The above github issue says that you can avoid this Illegal return statement
error by wrapping your code in a closure, like so:
(function(){
< original index.js content >
})();
This is a good time to stretch, get a glass of water. Ready?
browserify index.js >| bundle.js
Error: ENOENT, open 'constants'
Not a lot of details, but browserify is trying to open a file 'constants' and failing. In node, require('constants')
resolves to a Hash of integers. This looks relevant, but I'm just going to figure this one is a core module and save [this output}(https://gist.github.com/kumavis/5704326) from the require('constants')
to browserify/node_modules/browser-resolve/builtin/constants.js
. Since we changed browser-resolve/index.js
to rely on its own understanding of core
, we should be good.
Note: these numbers may be machine specific. You may want to use what is output by your machine instead. I don't really know. We'll figure it out later if we need to.
Continuing onward!
browserify index.js >| bundle.js
/Users/kumavis/Dropbox/Development/Node/browser-npm/node_modules/npm/bin/npm-cli.js:1
(function(process){#!/usr/bin/env node
^
ParseError: Unexpected token ILLEGAL
Hmmm, there's a bit of preprocessor directive or something at the head of npm-cli.js
. I'm not exactly sure what that is or what its for, but I'll just comment it out for now.
browserify index.js >| bundle.js
Error: ENOENT, open 'dns'
Can't find the file 'dns'. I'm going to guess its another core module. Node? Node agrees, Lets npm search browser dns
. Hmmm nothing. We'll need to roll our own. I'm just going to do a copy-paste-hack job here. It will likely break if you try to actualy use it. Save this as browserify/node_modules/browser-resolve/builtin/dns.js
What's next?
[kumavis:...evelopment/Node/browser-npm]$ browserify index.js >| bundle.js
[kumavis:...evelopment/Node/browser-npm]$
woah. cool. But I wouldn't get too excited, this journey has just begun. Opening up index.html
I can use the debugger to see what the first problem is.
Uncaught TypeError: Cannot read property '_ansicursor' of undefined bundle.js:12175
this error originates from the middle line
var ansi = require('ansi')
log.cursor = ansi(process.stderr)
log.stream = process.stderr
which in turn comes from a half-hearted implementation of process
> process.stderr
undefined
The docs say process.stderr
should be a stream
I assumed that this was coming from browserify/node_modules/browser-resolve/builtin/process.js
but apparently it actually comes from browserify/node_modules/insert-module-globals/node_modules/process/browser.js
. Add the following:
Stream = require('stream');
and
process.stderr = new Stream();
// copy pasta from `process.stderr.write.toString()`
process.stderr.write = function (data, arg1, arg2) {
var encoding, cb;
// parse arguments
if (arg1) {
if (typeof arg1 === 'string') {
encoding = arg1;
cb = arg2;
} else if (typeof arg1 === 'function') {
cb = arg1;
} else {
throw new Error('bad arg');
}
}
if (typeof data === 'string') {
encoding = (encoding || 'utf8').toLowerCase();
switch (encoding) {
case 'utf8':
case 'utf-8':
case 'ascii':
case 'ucs2':
case 'ucs-2':
case 'utf16le':
case 'utf-16le':
// This encoding can be handled in the binding layer.
break;
default:
data = new Buffer(data, encoding);
}
} else if (!Buffer.isBuffer(data)) {
throw new TypeError('First argument must be a buffer or a string.');
}
// If we are still connecting, then buffer this for later.
if (this._connecting) {
this._connectQueueSize += data.length;
if (this._connectQueue) {
this._connectQueue.push([data, encoding, cb]);
} else {
this._connectQueue = [[data, encoding, cb]];
}
return false;
}
return this._write(data, encoding, cb);
}
If you browserify
and run you'll find that it isn't able to get the Stream package for you. browserify/node_modules/insert-module-globals/index.js
needs you to manually set deps. Adjust line 59 as below:
deps: {"stream": path.join(__dirname+"../../browser-resolve", 'builtin/stream.js')}
Running again, we hit our debugger statement in our own index.js
! Home sweet home. We're clearly making progress. Of course node is very async, so now we have to catch up with all the work various modules have set aside to be done, and for us that means more debugging and patching holes. Let's remove the debugger statement before we carry on.
next we're on browser-npm/node_modules/npm/node_modules/graceful-fs/graceful-fs.js
// lchmod, broken prior to 0.6.2
// back-port the fix here.
if (constants.hasOwnProperty('O_SYMLINK') &&
process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
Its trying to compensate for old node versions and process.version
is undefined. We'll just use Node's output. Add this to browserify/node_modules/insert-module-globals/node_modules/process/browser.js
.
process.version = 'v0.8.21';
Next we have something trying to readFileSync /node_modules/npm/node_modules/request/node_modules/mime/types/mime.types
using my virtual file system! (1) My file system isn't quite ready for use (2) Its empty! There aren't any files stored on it. Sounds like we'll need to seed our file system with some basic node environment.
See you next time!
Things to add: