Browserify makes heavy use of the file system, as it implements its own module resolution that uses the same algorithm as Node's require
. To get Browserify to bundle using in-memory source files, one solution would be to monkey patch the file system - in a manner similar to the mock-fs
module that's useful for testing.
However, you really only want to do this for the source files. Browserify uses its module resolution for other files that are included in the bundle and having to add those to the in-memory file system would be tedious. The patched file system functions should check for calls that involve in-memory source files, handle them accordingly and forward other calls to the original implementations. (mock-fs
does something similar in that it detects file system calls that occur within require
and forwards those calls to the original implementations.)
'use strict';
const fs = require('fs');
const path = require('path');
const toStream = require('string-to-stream');
// Create an object hash that contains the source file contents as strings,
// keyed using the resolved file names. The patched fs methods will look in
// this map for source files before falling back to the original
// implementations.
const files = {};
files[path.resolve('one.js')] =
'console.log("Hello from one.js");' +
'var two = require("./two");' +
'exports.one = 1;';
files[path.resolve('two.js')] =
'console.log("Hello from two.js");' +
'exports.two = 2;';
// The three fs methods that need to be patched take a file name as the
// first parameter - so the patch mechanism is the same for all three.
function patch(method, replacement) {
var original = fs[method];
fs[method] = function (...args) {
var name = path.resolve(args[0]);
if (files[name]) {
args[0] = name;
return replacement.apply(null, args);
} else {
return original.apply(fs, args);
}
};
}
patch('createReadStream', function (name) {
return toStream(files[name]);
});
patch('lstat', function (...args) {
args[args.length - 1](null, {
isDirectory: function () { return false; },
isFile: function () { return true; },
isSymbolicLink: function () { return false; }
});
});
patch('stat', function (...args) {
args[args.length - 1](null, {
isDirectory: function () { return false; },
isFile: function () { return true; }
});
});
// With the fs module patched, browserify can be required and the bundle
// can be built.
const browserify = require('browserify');
browserify()
.require(path.resolve('one.js'), { entry: true, expose: 'one' })
.require(path.resolve('two.js'), { expose: 'two' })
.bundle()
.pipe(process.stdout);
require
and expose
In your question, you mentioned expose
. The one.js
and two.js
modules were bundled using require
, so they can be required in the browser using the name specified in the expose
options. If that's not what you want, you can just use add
instead and they will be modules internal to the bundle.
<!doctype html>
<html>
<head>
<title>so-39397429</title>
</head>
<body>
<script src="./bundle.js"></script>
<script>
// At this point, the bundle will have loaded and the entry
// point(s) will have been executed. Exposed modules can be
// required in scripts, like this:
console.log(require('one'));
console.log(require('two'));
</script>
</body>
</html>
I spent some time looking into Browserify's require
and expose
options when investigating this tsify question. The options facilitate the requiring of modules from outside the bundle, but I'm not at all sure they have any influence on requires between modules within the bundle (which is what you need). Also, their implementation is a little confusing - particularly this part in which the exposed name sometimes has a slash prepended.
Vinyl streams
In your question, you used vinyl
. However, I wasn't able to use vinyl
for the read streams. Although it does implement pipe
, it's not an event emitter and doesn't seem to implement the on
-based the pre-Streams 2 API - which is what Browserify expects from a createReadStream
result.