Code Monkey home page Code Monkey logo

moonboots's Introduction

moonboots

Moonboots makes it incredibly easy to jump into single-page-app development by encapsulating a set of conventions and tools for building, bundling, and serving SPAs with node.js.

Powered by browserify, moonboots gives us a structured way to include non-CommonJS libraries, work in development mode and agressively cache built JS and CSS files for production.

What it does

  1. Saves us from re-inventing this process for each app.
  2. Lets a developer focus on building a great clientside experience, not boiler plate.
  3. Lets you use CommonJS modules to structure your clientside code.
  4. Manages clientside files during development so you can just write code.
  5. Compiles/minifies/uniquely named JS files (and CSS files optionally) containing your application allowing really aggressive caching (since the name will change if the app does).
  6. Plays nicely with express.js, hapi.js, or even straight node http

Why?

  1. Because single page apps are different. You're shipping an application to be run on the browser instead of running an application to ship a view to the browser.
  2. Engineering a good client-side app requires a good set of conventions and structure for organizing your code.
  3. Effeciently building/sending a client-side app to the browser is a tricky problem. It's easy to build convoluted solutions. We want something a bit simpler to use.

How to use it

You grab your moonboots, pass it a config and listen for the ready event. Then tell your http library which urls to serve your single page app at.

That's it.

var express = require('express');
var Moonboots = require('moonboots');
var app = express();

// configure our app
var clientApp = new Moonboots({
    main: __dirname + '/sample/app/app.js',
    libraries: [
        __dirname + '/sample/libraries/jquery.js'
    ],
    stylesheets: [
        __dirname + '/styles.css'
    ]
});

clientApp.on('ready', function () {
    app.get(clientApp.jsFileName(),
        function (req, res) {
            clientApp.jsSource(function (err, js) {
                res.send(js);
            })
        }
    );
    app.get('/app*', clientApp.htmlSource());

    // start listening for http requests
    app.listen(3000);
});

Options

Available options that can be passed to Moonboots:

  • main (required, filepath) - The main entry point of your client app. Browserify uses this to build out your dependency tree.
  • libraries (optional, array of file paths, default: []) - An array of paths of JS files to concatenate and include before any CommonJS bundled code. This is useful for stuff like jQuery and jQuery plugins. Note that they will be included in the order specified. So if you're including a jQuery plugin, you'd better be sure that jQuery is listed first.
  • stylesheets (optional, array of file paths, default: []) - An array of CSS files to concatenate
  • jsFileName (optional, string, default: app) - the name of the JS file that will be built
  • cssFileName (optional, string, default: styles) - the name of the CSS file that will be built
  • browserify (optional, object, default: {debug: false}) - options to pass directly into browserify, as detailed here. Additional options are:
    • browserify.transform (optional, list, default: []) - list of transforms to apply to the browserify bundle, see here for more details.
  • uglify (optional, object, default: {}) - options to pass directly into uglify, as detailed here
  • modulesDir (optional, directory path, default: '') - directory path of modules to be directly requirable (without using relative require paths). For example if you've got some helper modules that are not on npm but you still want to be able to require directly by name, you can include them here. So you can, for example, put a modified version of backbone in here and still just require('backbone').
  • beforeBuildJS (optional, function, default: nothing) - function to run before building the browserify bundle during development. This is useful for stuff like compiling clientside templates that need to be included in the bundle. If you specify a callback moonboots will wait for you to call it. If not, it will be run synchronously (by the magic of Function.prototype.length).
  • beforeBuildCSS (optional, function, default: nothing) - function to run before concatenating your CSS files during development. This is useful for stuff like generating your CSS files from a preprocessor. If you specify a callback moonboots will wait for you to call it. If not, it will be run synchronously (by the magic of Function.prototype.length).
  • sourceMaps (optional, boolean, default: false) - set to true to enable sourcemaps (sets browserify.debug to true)
  • resourcePrefix (optional, string, default: '/') - specify what dirname should be prefixed when generating html source. If you're serving the whole app statically you may need relative paths. So just passing resourcePrefix: '' would make the template render with <script src="app.js"></script> instead of <script src="/app.js"></script>.
  • minify (optional, boolean, default: true) - an option for whether to minify JS and CSS.
  • cache (optional, boolean, default: true) - an option for whether or not to recalculate the bundles each time
  • buildDirectory (optional, string, default: nothing) - directory path in which to write the js and css bundles after building and optionally minifying. If this is set, moonboots will first look in this folder for files matching the jsFileName and cssFileName parameters and use them if present. Those files will be trusted implicitly and no hashing or building will be done.
  • developmentMode (optional, boolean, default: false) - If this is true, forces cache to false, minify to false, and disables buildDirectory
  • timingMode (optional, boolean, default: false) - If set to true, moonboots will emit log events w/ a 'timing' flag at various stages of building to assist with performance issues

About Source Maps

Sourcemaps let you send the actual code to the browser along with a mapping to the individual module files. This makes it easier to debug, since you can get relevant line numbers that correspond to your actual source within your modules instead of the built bundle source.

Please note that if you are using libraries your line numbers will be off, because that prepends those files to the main bundle. If it is important for you to maintain line numbers in your source maps, consider using browserify-shim in your transforms to include those non-commonjs files in your app

Methods

moonboots.jsFileName() - returns string of the current js filename.

moonboots.jsSource() - returns compiled (and optionally minified js source)

moonboots.cssFileName() - returns string of the current css filename.

moonboots.cssSource() - returns concatenated (and optionally minified css stylesheets)

moonboots.htmlSource() - returns default html to serve that represents your compiled app w/ a script and optional style tag

moonboots.htmlContext() - returns object w/ jsFileName and cssFileName attributes to pass to your http server's templating engine to build your own html source

Full example

For a working example, check out moonboots-hapi or moonboots-express or even moonboots-static

License

MIT

moonboots's People

Contributors

codeaholics avatar henrikjoreteg avatar hycner avatar johnsoftek avatar latentflip avatar lukekarrys avatar nizaroni avatar philipkobernik avatar powmedia avatar sansthesis avatar spanditcaa avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

moonboots's Issues

Error - too many open files

Running a standard AmpJS app using the CLI.

node server reports app being served at localhost.

Navigate to localhost in Chrome.

Server falls over with Moonboots related error.

Diabetix is running at: http://localhost:3000 Yep. That's pretty awesome.

/Users/davidsmith1/Sites/ampersand-app/node_modules/moonboots-express/node_modules/moonboots/node_modules/browserify/index.js:546
                : opts.basedir || process.cwd()
                                          ^
Error: EMFILE, too many open files
    at globalTr (/Users/davidsmith1/Sites/ampersand-app/node_modules/moonboots-express/node_modules/moonboots/node_modules/browserify/index.js:546:43)
    at makeTransform (/Users/davidsmith1/Sites/ampersand-app/node_modules/moonboots-express/node_modules/moonboots/node_modules/browserify/node_modules/module-deps/index.js:250:21)
    at /Users/davidsmith1/Sites/ampersand-app/node_modules/moonboots-express/node_modules/moonboots/node_modules/browserify/node_modules/module-deps/index.js:225:9
    at Deps.getTransforms (/Users/davidsmith1/Sites/ampersand-app/node_modules/moonboots-express/node_modules/moonboots/node_modules/browserify/node_modules/module-deps/index.js:230:7)
    at /Users/davidsmith1/Sites/ampersand-app/node_modules/moonboots-express/node_modules/moonboots/node_modules/browserify/node_modules/module-deps/index.js:365:24
    at onresolve (/Users/davidsmith1/Sites/ampersand-app/node_modules/moonboots-express/node_modules/moonboots/node_modules/browserify/node_modules/module-deps/index.js:177:14)
    at /Users/davidsmith1/Sites/ampersand-app/node_modules/moonboots-express/node_modules/moonboots/node_modules/browserify/index.js:475:13
    at /Users/davidsmith1/Sites/ampersand-app/node_modules/moonboots-express/node_modules/moonboots/node_modules/browserify/node_modules/browser-resolve/index.js:254:13
    at /Users/davidsmith1/Sites/ampersand-app/node_modules/moonboots-express/node_modules/moonboots/node_modules/browserify/node_modules/resolve/lib/async.js:44:21
    at ondir (/Users/davidsmith1/Sites/ampersand-app/node_modules/moonboots-express/node_modules/moonboots/node_modules/browserify/node_modules/resolve/lib/async.js:187:31)

Any help much appreciated.

tests dont pass on windows

It seems to be just that the expected hashes (for css and js) are different.

It seems to be consistent each time on Windows, thought, but if you load balanced across many servers and there was a mix of linux/windows, this could cause an issue.

screen shot 2014-09-17 at 12 59 21 pm

moonboots-static?

One thing I liked in v1 was the ability to write all 3 files (html, js, css) to a dir.

moonboots/index.js

Lines 421 to 444 in 987cc04

// Build kicks out your app HTML, JS, and CSS into a folder you specify.
Moonboots.prototype.build = function (folder, callback) {
var self = this;
self._ensureReady(function () {
async.parallel([
function (cb) {
self.sourceCode(function (err, source) {
if (err) return cb(err);
fs.writeFile(path.join(folder, self.jsFileName()), source, cb);
});
},
function (cb) {
self.cssSource(function (err, source) {
if (err) return cb(err);
fs.writeFile(path.join(folder, self.cssFileName()), source, cb);
});
},
function (cb) {
fs.writeFile(path.join(folder, 'index.html'), self.getTemplate(), cb);
}
], callback);
});
};

This is different than the current buildDirectory which solves a different (and much bigger) problem of making deploys quicker.

I'm thinking about making a moonboots-static module that will do just that. Good idea? Or would others use this enough that it should be in core?

Document how to add middleware with express apps using moonboots.

Figured out my file upload issue: express middleware has to be added before routes are defined. I was adding middleware after initializing moonboots, and I hadn't clocked that moonboots would be adding a route handler, which means I can't add middleware after moonboots.

insert semi-colon to prevent 'undefined is not a function'

Certain libraries don't play nice with moonboots' approach to library and browserified-app concatenation. The safest approach is as follows:
The line at index.js#L350 should be modified from

                self.result.js.source = self.result.js.source + js;

to

                self.result.js.source = self.result.js.source + ";" + js;

The lines at index.js#L428-433 should be modified from

// a few helpers
function concatFiles(arrayOfFiles) {
    return arrayOfFiles.map(function (fileName) {
        return fs.readFileSync(fileName);
    }).join('\n');
}

to:

// a few helpers
function concatFiles(arrayOfFiles) {
    return arrayOfFiles.map(function (fileName) {
        return fs.readFileSync(fileName);
    }).join(';\n'); 
}

Problems with Express 4 applications

I have been trying to get MoonBoots to work with a fresh scaffolded Express 4 application. Here are the steps I took:

  1. Called "express" in my test folder to generate a fresh Express 4 app.
  2. Called "npm install moonboots --save
  3. Added "public/javascripts/jquery.js" and "public/javascripts/app.js" files
  4. Edited the autogenerated Express 4 app.js to import and configure moonboots, here:
var express = require('express');
var path = require('path');
var favicon = require('static-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');
var users = require('./routes/users');

var Moonboots = require('moonboots');
//console.log('__dirname', __dirname);

var clientApp = new Moonboots({
    main: __dirname + '/public/javascripts/app.js',
    libraries: [
        __dirname + '/public/javascripts/jquery.js'
    ],
    stylesheets: [
        __dirname + '/public/stylesheets/style.css'
    ]
});


var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use(favicon());
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', routes);
app.use('/users', users);

/// catch 404 and forward to error handler
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

/// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});


app.get(clientApp.jsFileName(),
    function (req, res) {
        clientApp.jsSource(function (err, js) {
            res.send(js);
        })
    }
);
app.get('/app*', clientApp.htmlSource());


module.exports = app;

The application crashes with the following error if I try to fire it up

Users/kokujin/Development/Research/moonboots_test/node_modules/express/node_modules/path-to-regexp/index.js:34
    .concat(strict ? '' : '/?')
     ^
TypeError: Cannot call method 'concat' of undefined
    at pathtoRegexp (/Users/kokujin/Development/Research/moonboots_test/node_modules/express/node_modules/path-to-regexp/index.js:34:6)
    at new Layer (/Users/kokujin/Development/Research/moonboots_test/node_modules/express/lib/router/layer.js:21:17)
    at Function.proto.route (/Users/kokujin/Development/Research/moonboots_test/node_modules/express/lib/router/index.js:382:15)
    at Function.app.(anonymous function) [as get] (/Users/kokujin/Development/Research/moonboots_test/node_modules/express/lib/application.js:406:30)
    at Object.<anonymous> (/Users/saina/Development/Research/moonboots_test/app.js:73:5)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Module.require (module.js:364:17)

npm ERR! [email protected] start: `node ./bin/www`
npm ERR! Exit status 8

Can someone tell me what I am doing wrong? Thanks

CoffeeScript into Browserify

I've transcribed the "app.js" and "router.js" files into coffeescript as part of creating a purely coffeescript version of the ampersand js sample app. Howerver, when I tried to pass the coffeeify transform into moonboots with:

browserify: {
  debug: config.isDev,
  transform: ['coffeeify']
},

, when I load the page on localhost:3000 it complains that Error: Cannot find module '/Users/gigavinyl/Projects/coffee/client/app.js' from '/Users/gigavinyl/Projects/coffee'. How do I do this properly?

htmlSource/template function argument

After writing another moonboots plugin (this time for just writing files to dir to be served statically) I found myself implementing a template fn as an option again.

I'm starting to think that this belongs in moonboots core. I know @wraithgar has an open issue for something similar in moonboots-hapi wraithgar/moonboots_hapi#23.

I'm thinking something like:

var moonboots = new Moonboots({
  main: 'app.js',
  htmlSource: function (ctx) {
    // Available ctx options:
    // ctx.jsFileName, ctx.cssFileName, ctx.resourcePrefix
    return someHTMLString;
  }
});

And then if the option exists it would just set results.html.source instead of how we build it here https://github.com/HenrikJoreteg/moonboots/blob/master/index.js#L176-L180

Any objections?

Any plan to support code splitting?

Our current bundled javascript file is almost 3MB and it's growing very fast and takes seconds to load. But, not all scripts are being used in all pages and load dependencies on-demand based on the requirement of page is kind of really necessary for javascript heavy project. Any plan to support this in the near future?

3.0.0 - Cannot make sourcemaps and minify work together

sourceMaps is set to true.

If minify is set to true, the JS is correctly minified, but there are no sourcemaps at the end.

If minify is set to false, the JS is bundled (browserify) but not minimised (as expected) and the sourcemaps ARE present.

It seems that setting minify to true somehow disables sourcemaps.

More output in development mode

Not sure if this is a setup issue in andbang, or a moonboots issue, but I didn't realise that moonboots was recompiling my package on every edit+pagerefresh (which is awesome :) ) because there's no output in the console. You just get the first:

Moonboots: app files already written.

so I was killing the server on each change.

Maybe you could log "rebuilt foo.js" on a page refresh when the files have been edited?

CLI

There could be a cli now which, when coupled with buildDirectory, could be a very nice thing indeed.

feature request: allow beforeBuild functions to pass code directly to bundle

Using the browserify require method, you can pass in a stream and an expose parameter to add a module to the bundle.

So, for example templatizer could pass its content directly to browserify instead of writing to a file and then reading from that file. Especially in cases where you don't check in any generated files, I think this can be cleaner.

The API could look like this and then under the hood it could convert the string to stream and expose it as templates.

beforeBuildJS: function (cb) {
  cb(null, {
    templates: {
      content: templatizer('./client-templates'),
      // Other browserify require options
    }
  });
}

This could be extended to beforeBuildCSS by just appending/prepending to the CSS.

Development mode error handling doesn't recover if bundle error on server load.

So @lukekarrys' error handling in development mode is great:

  • If there is a bundle error, it renders the error to your browser and the console.
  • It doesn't crash the server
  • Once you've fixed the error and refresh the browser, everything's back to normal

However,

  • If there is a bundle error on the very first bundle when starting the server, you get nothing at all in the browser (it seems to stop the server working entirely), but the server doesn't actually crash. And when you fix the error, it won't recover without killing and restarting the server.

This may be an &! only issue?

Update to browserify 5

I was just starting up a new Ampersand project, and on first run (npm i && npm start)got this error:
Error: bundle() no longer accepts option arguments.
Move all option arguments to the browserify() constructor.
at Browserify.bundle (/Users/mike/dev/node/datatables/node_modules/moonboots-express/node_modules/moonboots/node_modules/browserify/index.js:564:15)
at async.series.mdeps.resolve (/Users/mike/dev/node/datatables/node_modules/moonboots-express/node_modules/moonboots/index.js:351:20)
at /Users/mike/dev/node/datatables/node_modules/async/lib/async.js:610:21
at /Users/mike/dev/node/datatables/node_modules/async/lib/async.js:249:17
at iterate (/Users/mike/dev/node/datatables/node_modules/async/lib/async.js:149:13)
at async.eachSeries (/Users/mike/dev/node/datatables/node_modules/async/lib/async.js:165:9)
at _asyncMap (/Users/mike/dev/node/datatables/node_modules/async/lib/async.js:248:13)
at Object.mapSeries (/Users/mike/dev/node/datatables/node_modules/async/lib/async.js:231:23)
at Object.async.series (/Users/mike/dev/node/datatables/node_modules/async/lib/async.js:608:19)
at Moonboots.browserify (/Users/mike/dev/node/datatables/node_modules/moonboots-express/node_modules/moonboots/index.js:348:11)

I wish I could give you more info... I have stopped and restarted the node app and now the error message is no longer displayed. I've tried a number of things, but could not reproduce it. There is some internet chatter about a Browserify API change in their version 5.

Predictable module ordering with browserify

browserify/browserify#355

I think that if developmentMode is true, moonboots should make browserify use predictable module ordering. This helps with things like breakpoints staying on the same line when refreshing a page.

Now that I think about it, should moonboots always make browserify use predictable module ordering? Since moonboots creates a checkSum based on the contents, a rebuild could cause a different checkSum even if nothing has changed.

jadeify works in dev mode but not production

If I follow the suggestion to use jadeify (http://ampersandjs.com/learn/templates), it works with isDev: true but not isDev: false.

I have tried adding the transform to package.json:

  "browserify": {
    "transform": ["jadeify"]
  },

and in Moonboots config:

        browserify: {
            debug: false,
            transforms: ['jadeify']
        },

Both methods give the same error for production mode:

stream.js:94
      throw er; // Unhandled stream error in pipe.
            ^
Error: Parsing file /Users/john/AppDev/amp1/client/vm/collectionDemo/collectionDemo.jade: Line 2: Unexpected identifier
    at parseDeps (/Users/john/AppDev/amp1/node_modules/moonboots-express/node_modules/moonboots/node_modules/module-deps/index.js:256:45)
    at done (/Users/john/AppDev/amp1/node_modules/moonboots-express/node_modules/moonboots/node_modules/module-deps/index.js:236:13)
    at applyTransforms (/Users/john/AppDev/amp1/node_modules/moonboots-express/node_modules/moonboots/node_modules/module-deps/index.js:206:41)
    at /Users/john/AppDev/amp1/node_modules/moonboots-express/node_modules/moonboots/node_modules/module-deps/index.js:178:17
    at fs.js:271:14
    at Object.oncomplete (fs.js:107:15)

It seems that module-deps/index.js is trying to trace dependencies for an untransformed .jade file. If I modify module-deps/index.js to skip .jade files in the same way that it skips .json files, the problem is avoided. But there must be a better fix in Moonboots - or maybe I have not configured browserify/jadeify correctly?

UPDATE

I tried this fix in moonboots/index.js. Change

        mdeps(self.config.main, {
            resolve: hashBundle._resolve.bind(hashBundle)
        })

to this

        var opts = {
            resolve: hashBundle._resolve.bind(hashBundle)
        }
        if (self.config.browserify.transforms) {
            opts.transform = self.config.browserify.transforms
        }
        mdeps(self.config.main, opts)

It works.

sourceMaps option not setting browserify.debug to true

Hello,

I've been having an issue getting the sourceMaps option to work. After digging around a bit I ended up getting it to work properly by explicitly setting browserify.debug = true in the moonboots config.

https://github.com/HenrikJoreteg/moonboots/blob/master/readme.md#options

  • sourceMaps (optional, boolean, default: false) - set to true to enable sourcemaps (sets browserify.debug to true)

Looking into it further, index.js:49 sets browserify.debug = this.config.sourceMaps prior to hydrating this.config on lines 51-53 with the options passed into the constructor, thus this.config.sourceMaps will always equal false when line 49 is executed.

https://github.com/HenrikJoreteg/moonboots/blob/master/index.js#L48-L53

    //Set this but let an explicity set config.browserify.debug override in the next loop
    this.config.browserify.debug = this.config.sourceMaps;

    for (item in opts) {
        this.config[item] = opts[item];
    }

Is the sourceMaps option used to do anything other than set browserify.debug? Is it needed if you can just include { browserify: { debug: true } } as part of the options passed to the constructor?

Thanks for the great work!

Config attribute "server"

I noticed that in an Ampersand app, moonboots, has, and uses an attribute named "server:" . is there documentation on this somewhere? Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.