Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Browserify's custom dependency name is not working

I am trying to get browserify's custom dependency name working with memory streams. The reason I am using a memory stream is because this code is destined to run inside an AWS Lambda that will be receiving multiple "files" as inputs and will not be available to the lambda via the file system.

According to the documentation at https://github.com/substack/node-browserify, it seems like this should be possible:

b.require(file, opts)

file can also be a stream, but you should also use opts.basedir so that relative requires will be resolvable.

Use the expose property of opts to specify a custom dependency name. require('./vendor/angular/angular.js', {expose: 'angular'}) enables require('angular')

Code:

const browserify = require('browserify')
const path = require('path')
const File = require('vinyl')

const jsIndex = new File({
    file: path.resolve('index.js'),
    contents: new Buffer('var two = require("./two")')})
const jsTwo  = new File({
    file: path.resolve('two.js'),
    contents: new Buffer('console.log("Hello from two.js")')})

browserify({
        entries: [ jsIndex ],
        require: [ jsTwo ],
        basedir: path.resolve('.')
    })
    .bundle((err, res) => {
        if (err) console.log(err.message)
    })
    .pipe(process.stdout)

Error:

Cannot find module './two' from '...'

Edit:

Another individual has posted a similar issue on the node-browserify github page here: https://github.com/substack/node-browserify/issues/1622

like image 304
joelnet Avatar asked Sep 08 '16 18:09

joelnet


1 Answers

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.

like image 110
cartant Avatar answered Sep 28 '22 09:09

cartant