Giter VIP home page Giter VIP logo

ezel's Introduction

Ezel

image

A boilerplate for Backbone projects that share code server/client, render server/client, and scale through modular architecture. Used at Artsy to bootstrap new projects, see our blog post on it.

Introduction

Ezel makes it easy to write and maintain Backbone apps that run in the browser and on the server using Node.js. Built on popular libraries like Express, Backbone, and Browserify, Ezel isn't a framework or library of its own, but rather a boilerplate of libraries and patterns that can be leveraged or abandoned as needed.

Ezel has three main philosophies...

Modularity

Instead of managing growing complexity in projects by imposing rigid monolithic structure, Ezel encourages breaking your project up into smaller pieces that are easy to maintain and refactor independently.

Flexiblity

Don't get locked into choosing between single page app or fully server-side rendered pages. Ezel's modular structure and shared server/client code makes it easy to decide what patterns and tools are best on a case by case basis.

Run on Both Sides

Ezel shares javascript modules that run in the browser and on the server. This means you can optimize initial page load and SEO by sharing templates that can render on the server or client. This also makes it easy to test all of your code in Node.js using benv and zombie for robust, fast, and easy to set up tests.

Getting Started

Installation

  1. Install Node.js
  2. Install the Ezel project generator globally npm install -g ezel
  3. Generate your project ezel myapp (Use ezel --coffeescript myapp for coffeescript) and cd to the directory.
  4. Install node modules npm install
  5. Run the server make s
  6. Visit localhost:4000 and see an example that uses the GitHub API.

Overview

First it would be good to familiarize yourself with the tools Ezel is built on.

At its heart Ezel is just a Backbone app and therefore relies on an external API as its data source. This can come in a variety of forms and it's up to you to choose the best technology to serve your data over HTTP.

Once you understand how the above tools work, diving into Ezel is just a matter of understanding its patterns. After this, when you're ready, you can generate a new clean project without the Github example code by using ezel --clean.

Project vs. Apps vs. Components

Monolithic frameworks tend to organize your code by type such as /views, /stylesheets, /javascripts, etc. As your app grows larger this becomes an awkward and unmaintainable way to delineate parts of your project. Ezel encourages grouping files into conceptual pieces instead of by type.

There are three different levels of this organization:

Project

Refers to the root, "global", level and contains the initial setup/server code and project-wide modules such as models, collections, and libraries. Setup code is extracted into /lib/setup to encourage modularizing and testing your setup code.

Apps

Apps are small express applications that are mounted into the main project. What distinguishes apps from one another is that they conceptually deal with a certain section of your website, and are often separated by a full page-refresh. As such an app could be a complex thick-client "search" app, or a simple static "about" page.

Apps should strive to be self-contained and shouldn't require into other apps. However, apps will often need project-level modules so requiring into components, models, collections and libraries are fine. It's encouraged to namespace your CSS classes inside an app by the app name to avoid conflicts, e.g. apps/user may use h1.user-header. It's also encouraged to use app-level public folders for static assets and namespace your filenames by app name e.g. /apps/user/public/images/user-icon.png which can be referenced normally in templates and stylesheets e.g. /images/user-icon.png.

The organization of these apps are up to you, for a simple app you may put all of your code into one express instance exported in a single index.js file. More complex apps may have their own /routes, /stylesheets, etc. folders or even look like its own Ezel project with components and sub-apps.

Large web projects often have a wide range of needs on a case by case basis. Instead of trying to solve every problem with the same architecture, Ezel remains flexible and modular so you can pick the right tools and patterns for the job.

Components

Components are portions of UI re-used across apps and are simply a folder containing a mix of stylesheets, templates, and client-side code that can be required piece-meal. These can be thought of like a jQuery UI widget, Bootstrap component, Backbone view, or component.js component. Components can be as simple a stylesheet and template, more complex like an autocomplete widget, or even a massive modal window pieced together from smaller components.

Components should strive to be a library of self-contained UI and therefore shouldn't require into apps, however, it's totally fine to require into /lib or other components. It's encouraged to namespace your CSS classes in a component by the component name to avoid conflicts, e.g. components/autocomplete may use li.autocomplete-list-item. It's also encouraged to use component-level public folders for static assets and namespace your filenames by component name e.g. /components/autocomplete/public/images/autocomplete-icon.png which can be referenced normally in templates and stylesheets e.g. /images/autocomplete-icon.png.

Models & Collections

Model code is meant to work on the server and client so it must strictly be domain logic around your data. Model code can't use APIs only available to the browser or node such as accessing the file system or the XMLHttpRequest object.

Backbone.sync is used as a layer over HTTP accessible on both sides. Any HTTP requests made in model and collection code therefore must be wrapped in a Backbone class or used by an anonymous instance e.g. new Backbone.Model().fetch({ url: '/api/system/up', success: //... }).

Libraries

Libraries are a place to store modules that are used across apps and don't pertain to domain logic or UI that can be better handled by models or components. These can be server only such as a library zipping uploaded files, browser-only such as an HTML5 Canvas library, or even shared such as a date parsing library that can be used on both the server and client.

Testing

Tests are broken up into project-level, app-level, and component-level tests that are run together in make test. This boilerplate comes stocked with a suite of tests for the Github API example, so please take a look around for examples.

Project-level Tests

Project-level tests involve any library, model, or collection tests. Because Ezel model code can run on the server you can easily test it in node without any extra ceremony and testing these parts should be straight-forward. In the case that you need to test model or collection fetching/persisting it's encouraged to stub Backbone.sync.

Component-level Tests

Components should have tests inside their own /test folders to try to be self-contained. Because components contain view code meant to run in a browser you can use benv to set up a fake browser environment and require these modules for unit testing like any other module.

App-level Tests

App-level tests can come in a number of different forms, but often involve some combination of route, template, client-side, and integration tests. Like components, apps should have their own tests under /test folders. Given that apps can vary in complexity and number of components they use, it's up to you to decide how to structure and test their parts.

Some common practices are to split up your route handlers into libraries of functions that pass in stubbed request and response objects. Templates can be directly compiled with jade and asserted against the generated html. Client-side code can be unit tested in node using benv (Backbone views can help wrap code into testable methods). Finally a suite of integration tests use Zombie and boot up a version of the project with a fake API server found under /test/helpers/integration.

All of these techniques ensure your code remains decoupled, your tests run fast, and you stay happy and productive.

Build Scripts & Configuration

Ezel uses simple configuration and build tools that are standard with most environments.

A Makefile designates build commands. When more complex build scripts are needed it's encouraged to wrap them in libraries that can be run via node lib/script.js.

Configuration is handled entirely by environment variables. For ease of setup there is a .env file that declares sensible defaults for development. For non-sensitive config data like NODE_ENV it's encouraged to use sharify (e.g. var sd = require('sharify').data; console.log(sd.NODE_ENV)) which you can add to in lib/setup. For sensitive data on the server simply use process.env. Be careful not to add sensitive data to sharify as it will expose it on the client.

Asset Pipeline

Ezel's asset building is mostly handled by Browserify and Stylus with middleware for development and a make assets task to output more production ready files to public/assets. Place your asset packages in /assets, point your script and style tags to /assets/ in your views, and Ezel will wire the rest up for you.

Ezel's focus on modularity makes it easy to build up light-weight asset packages that are focused for your needs. This combined with rendering on the server makes it a great option for optimizing initial page load.

A common pattern would be to build asset packages per-app. For instance you may have a "search" app that uses assets from a layout component, an autocomplete component, and the search app's own client-side code and stylesheets. In this case you would create an /assets/search.styl that imports stylesheets from components/layout, components/autocomplete and app/search/stylesheets, and ditto for javascripts. In the view of the search app you would include script( src='/assets/search.js' ) and link( href='/assets/search.css' ) and Ezel will wire up the rest.

ezel's People

Contributors

bxjx avatar craigspaeth avatar micrypt avatar neilkinnish avatar oxaudo 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  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

ezel's Issues

make test issue

Not sure if anyone else is getting this or not but on the latest version of master, running 'make test' outputs this:

Listening on port 5000

queryAaaa ENOTFOUND Error: queryAaaa ENOTFOUND
  at errnoException (dns.js:37:11)
  at Object.onanswer [as oncomplete] (dns.js:149:18)

․․․․

  3 passing (1s)
  1 failing

  1) commits displays the list of commits:
     Uncaught AssertionError: expected '<html><head></head><body>queryAaaa     ENOTFOUND</body></html>' to include 'Adding a README'

I was able to resolve this by changing 'localhost' to '127.0.0.1' in apps/commits/test/integration.coffee on line 26. (let me know if you need the output of my envvars)

Consider structure that makes it easier to mount sub-ezel projects

See: https://twitter.com/craigspaeth/status/489061988257910784

Currently it's difficult b/c we override Backbone's sync at the larger project level, then expect models/collections to use that one backbone module. However, sub-apps under node_modules can have their own dependencies and can use their own version of Backbone without a modified sync. In this case we want a sane approach to have both apps use a modified sync. This kind of isolation/vs. shared setup conundrum can also occur in the need to share config/sharify data.

Options could include:

  • Pass in Backbone/config/sharify in a sort-of dependency injection manner to sub-apps. e.g. app.use(require('ezel-sub-app')(Backbone, config, sharify));
  • Sub-apps require backwards into the main project e.g. Backbone = require('../../node_modules/backbone')
  • Some kind of mount helper function that can modify a sub-app based on the project setup.

None of these seem too appealing though.

Memory leak in example project

You might want to review this part of your example project:

var Commits = require('../../collections/commits');

exports.index = function(req, res, next) {
  var commits = new Commits(null, {
    owner: 'artsy',
    repo: 'flare'
  });
  commits.fetch({
    success: function() {
      res.locals.sd.COMMITS = commits.toJSON();
      res.render('index', { commits: commits.models });
    },
    error: function(m, err) { next(err.text); }
  });
};

We followed your example and stumbled across some serious problems concerning memory consumption for high traffic apps. Creating Backbone collections for every request and using them to fetch (and rendering) data seems to be problematic, because the GC won't clean them up and the Node process will allocate more and more memory.

memory

I haven't yet fully understood if this is an Backbone issue or maybe caused by your backbone-super-sync module.

Headers not honored by Model on fetch

/** Model Def */
var Backbone = require('backbone');

module.exports = Picture = Backbone.Model.extend({
  url : function() { return '/api/picture/' + this.get('id'); }
});

/** Declared in a route */
picture.fetch({
  success: function(model, response, options) { /** success */ },
  error: function(model, response, options) { /** error */ },
  headers: {
    'Authorization' : 'Bearer ' + token
  }
});

When I execute the fetch, the header is not included and I am given an error every time. Checking the network traffic shows that the header is in fact missing from the request. As this is standard Backbone implementation, shouldn't this work here as well?

Remove "self-contained components" suggestion

Often times you have a small widget like a follow button that lives in a larger reusable widget like a list of things that can be followed. Apps shouldn't require into each other but components are just a library of things.

More component docs

Components should have their own tests because they can get really complex. Also should note that components shouldn't drill back and depend on each other. If they did then that would mean there's a larger UI that the two depend on, and those components should be sub-components of the larger one. Which also notes that components can have sub-components. This also means there's three levels of testing "project", "app", and "component".

It might even be good to write an example component like a modal window or something simple.

Also should indicate that CSS should be name-spaced by app name and component name to ensure no conflicts and clear separation between each other.

Generators

Would be nice to have some generators to initialize some boilerplate for generating a new sub-app

Modular static assets

It would be nice if Ezel made it easy to use public folders inside their respective apps/components and potentially leave out a root public folder all-together.

'make test' issues (coffeescript)

Running 'make test' throws:

/home/jordan/projects/Node/ezel-coffeescript/test/models/commit.coffee:1
(function (exports, require, module, __filename, __dirname) { # 
                                                          ^
SyntaxError: Unexpected token ILLEGAL

I was able to resolve this issue by replacing a line in mocha.opts:

--compilers coffee:coffee-script to --compilers coffee:coffee-script/register

and now two other issues are showing up (the first is regarding the integration test with zombie and the second I believe has to do with jQuery not being exposed properly with benv / no simulated window object being present).

Thoughts on build tools/config

rant

Obviously ppl are encouraged to use whatever build/config approach they like the most (Grunt/Gulp/Broccoli/Make/Jake/Cake/sh/npm run/whatever + nconf/package.json/config.json/config.js/.env/whatever). For Ezel I want to keep it as basic as possible. I think keeping all config in environment variables is a good idea and using a config.js file makes it easy to accidentally require on the client-side.

So I'm sold on all config being in a .env file & preferably checked out of the repo. Using a .env loader is a decent approach but not so nice for running scripts outside of your web server which can get a little messy.

Foreman is kinda to best tool for this setup b/c a Procfile can take the place of the Makefile (hmm actually maybe not b/c it seems like foreman start tries to run all tasks), it loads the .env file automatically, and it makes it easy to deploy to the most popular PAAS—Heroku. It keeps the .env file loading outside of your scripts so you can easily run foreman run migrate. Downside however is mainly that globally installing Foreman makes it a not so nicely self-contained project or the default way to run tasks out of the box is an ugly node_modules/.bin/foreman run start—although I guess that could be mitigated by npm start npm test. It also adds a Procfile to your app which is just awkward to rip out for hosting on other popular PAAS like Nodejitsu/OpsWorks.

I guess in the end I believe a .env file for config is a good opinion to encourage but the build tool I could care less about. It might be best to just drop the Makefile b/c it can throw the grunt/gulpers off and use npm run instead. Then clearly state that build/config is totally up to you but if you want Ezel's advice use Foreman and modularize complex build tasks into /lib or extracted node modules.

Update integration test helper

It could be simpler and more helpful using a .env.test file and child.stdout.pipe process.stdout

e.g.

spawn = require("child_process").spawn
express = require "express"

# Stubbed API
@api = express()

# Spawns a child process with test .env.test variables
@startServer = (callback) =>
  return callback() if @child?
  @child = spawn "make", ["test-s"],
    customFds: [0, 1, 2]
    stdio: ["ipc"]
  @child.on "message", -> callback()
  @child.stdout.pipe process.stdout

# Closes the server child process, used in an `after` hook and on
# `process.exit` in case the test suite is interupted.
@closeServer = =>
  @child?.kill()
  @child = null

process.on "exit", @closeServer

ReferenceError: $ is not defined

I get this error for both versions unless I add the following line to the top of assets/commits.coffee (for example):

$ = require 'components-jquery'

Versioning + Changelog

Next version should...

  • Encourage superagent as the layer over HTTP rather than Backbone.sync
  • Iterate over components to allow their own "static" or "public" directories so their image/whatever assets are grouped together.
  • Consider a branch that uses director to share routing code and act more like a thick-client app after initial page load.

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.