Enhanced Ember Mimosa tooling and a New Ember Skeleton
TL;DR
I'm going to talk through a few new Mimosa modules that solve some tooling gaps with Ember, and then I'll quickly discuss a new Mimosa skeleton that brings it all together. You can, though, skip the jibber-jabber and get straight to the new Ember skeleton and check out the result of all of this coming together.
Mimosa and Ember
In January of this year we made the switch from Backbone to Ember. We needed the power up and the team was ready to move on after two years writing a lot of boilerplate. After the initial, expected, much talked about learning curve was conquered the productivity boosts and code reduction we expected have arrived. When we were getting going, the tooling was already in place for Ember developers to use Mimosa to build their apps.
Several modules catered to doing Ember work.
- An emblem compiler
- An ember-handlebars compiler
- A module that dealt with Ember's r.js build issues
- A transpiler for es6 module syntax which the Ember community adopted very early on
- QUnit testing support which is essential for testing Ember apps
There were also several skeleton apps to get you started with Ember. They usually also include tasks like application packaging and CSS/JS transpiling:
- A port of the peepcode example app which includes CoffeeScript and Emblem compilation among other things
- A GitHub repo browser which included tests via Testem and a dependency bundler written by the skeleton's author
- An example app using Emblem
- A
mimosa new
app modified for Ember - A commonjs/browserify example
Tooling Gaps
I hesitated to attempt to solve any other Ember-specific tooling problems until we had a chance to really get going with a big Ember project. After awhile, we began to see some big gaps that could be filled with some smart modules.
The two big problems that needed module solutions were application wiring and Ember-specific testing support.
Wiring Up an Ember App
It is common to assign your Ember.Application
instance to a global variable, and then define all of the framework factory classes (controllers, routes, views, etc.) as properties of this object:
window.App = Ember.Application.create();
App.PostsRoute = Ember.Route.extend();
App.PostsController = Ember.ArrayController.extend();
Ember will be able to use the right components of your application at the right time. For instance, when you visit /posts, Ember will resolve the corresponding route, controller, view, and template by looking them up on the App namespace.
To achieve this in a modular web application, you can choose to attach your Ember assets from within each individual module, or you can choose to bring all the modules together in one place and attach them all at once.
Bringing the App to the Modules
views/post_view.js
var App = require('app');
App.PostView = Ember.View.extend(...);
In this case we're bringing the Ember application (Ember.Application.create()
) to the PostView
module.
But this option is semantically wrong. A small component of an an application doesn't depend on the larger application. The application depends on it. And something still needs to require
/import
this view. What would do that?
Bringing the Modules to the App
A better approach is to create an application manifest file (of sorts) where the application and its modules are brought together and wired up. When solving this problem, we created a modules.js
file that pulled together all the various Ember assets in one place and attached them to App
. Doing this results in individual assets that know nothing about the larger application and are therefore more modular.
views/post_view.js
var PostView = Ember.View.extend(...);
export default PostView;
Here, rather than attaching to App
, the view just exports itself. Now anything that needs it (multiple apps? test harness?) can import
it without needing the app.
Here's the modules.js
file where we pull the application together.
modules.js
import App from 'app';
import PostView from 'views/post_view';
App.PostView = PostView;
import PostController from 'controllers/post_controller';
App.PostController = PostController;
import Post from 'models/post';
App.Post = Post;
...
All the wiring of the various Ember assets occurs in a single place. No bringing the app in as a dependency to every Ember asset.
But, boilerplate much?
Our production app's modules.js
has around 600 lines and counting. Whenever a developer creates a new asset, they have to remember to go add it to that file. It's not a huge hurdle, but given how boilerplate it is, it begs for a tooling solution...
mimosa-ember-module-import
mimosa-ember-module-import was developed to solve the problem of module
"manifest" creation. With a trivial amount of config (6 lines in the skeleton's case) you can include this module and get your manifest file autogenerated during mimosa build
and mimosa watch
.
The module will output a manifest file in AMD format by default, but it also supports spitting out CommonJS. Here's an example manifest file in AMD.
define( function( require ) {
var _0 = require('./views/post_view');
var _1 = require('./controllers/post_controller');
var _2 = require('./models/post');
var modules = {
PostView: _0 && (_0['default'] || _0),
PostController: _1 && (_1['default'] || _1),
Post: _2 && (_2['default'] || _2),
};
return modules;
});
This file can then be imported and used by Ember during app creation a few different ways.
import modules from 'modules';
import App from 'app'; // class not instance
App.createWithMixins(modules);
...or...
import modules from 'modules';
var App = Ember.Application.extend(modules);
Supports Multiple Apps
Something that is lacking with other tools is an ability to support multiple apps in the same project. We have that case so it was important all the tooling solutions supported it. mimosa-ember-module-import supports multiple applications within a single project with a small tweak to the config.
Config with one app
emberModuleImport: {
apps: [{
namespace: "blogger",
manifestFile: "modules",
additional: ["router"]
}]
}
Config with two apps
emberModuleImport: {
apps: [{
namespace: "blogger",
manifestFile: "blogger-modules",
additional: ["router"]
}, {
namespace: "admin",
manifestFile: "admin-modules",
additional: ["router"]
}]
}
It should be easy to see, adding more applications is as simple as adding another entry to the array.
Testing an Ember App
mimosa-testem-require and its fork mimosa-testem-qunit both solve a lot of problems that come with writing browsers tests. The goal of both is to allow you to Just Write Tests. No need to waste time figuring out how to wire tests up and get the running.
With Ember apps though, there's some additional work to do that is currently beyond those modules' capabilities.
mimosa-ember-test
mimosa-ember-test was created to double the support provided by the modules created before it.
An important note, this module assumes require.js usage. This module is about wiring together not only Ember's tests and testing support, but doing so in a require.js/AMD application.
Below are some of the features of mimosa-ember-test.
Continued from Previous Modules
The previous Mimosa testing modules included:
- Support for running tests during
build
andwatch
- Built on Testem, a first-class test runner.
- Automatic wiring of testing assets into the
.mimosa
directory, far from your application's code. - Automatic detection and inclusion of tests
- Built-in support for Sinon, Require.js (and QUnit in this module's case)
- Command,
mimosa testscript
, to autogenerate script to run Testem interactive client testem ci
support
mimosa-ember-test continues all of this and builds onto it.
ember-qunit
An increasingly popular library to help you unit test your Ember apps is the aptly named ember-qunit. It introduces helper functions which make writing Ember tests easier. Any top notch Ember testing support needs to include ember-qunit, so any module we created to support Ember testing needed to include ember-qunit in its testing scaffolding.
mimosa-ember-test includes ember-qunit in its test scaffolding and makes its functions globally available.
Built-in Bower Support for Testing Assets
We wanted to make sure it was easy to update testing assets. Previous modules would require you to either update specific test assets (like QUnit or Sinon) if you needed newer versions or submit pull requests to the module repo to update assets.
mimosa-ember-test can, when configured (it is by default), utilize the functionality provided by mimosa-bower to Bower in your test assets. Toss the required dependencies in your bower.json
and you are ready to go.
"devDependencies": {
"qunit": "1.14.0",
"requirejs": "2.1.14",
"sinonjs": "1.10.2",
"ember-qunit":"0.1.8"
}
If Bower isn't your thing, then mimosa-ember-test does come with its own versions of the test assets. Turn Bower-ing of assets off (bowerTestAssets:false
) and mimosa-ember-test will copy in the assets it has. Biggest downside to this is that the ember-test module may, over time, fall slightly out of date.
As with previous modules, you can copy in your own assets and tell mimosa-ember-test to not overwrite them.
Multiple Apps
As with the module-import module above, mimosa-ember-test supports multiple apps. It will partition all test assets and scaffolding by app and will run each application's tests separately. Additionally, the mimosa testscript
will kick out scripts capable of running interactive tests for specific apps.
Pulled Together: Ember Skeleton
To show off some of the work we've done and to give ourselves a good starting point for our Ember development, we put together an Ember Skeleton.
Get the Skeleton setup
Just a few steps.
npm install -g mimosa
- git clone https://github.com/dbashford/MimosaEmberSkeleton/
- cd MimosaEmberSkeleton
npm install
mimosa build
mimosa build
will pull in all the Mimosa modules not already in Mimosa.
Module Manifest
mimosa build
will generate a modules.js
file for the app that is configured. Here's that output:
define( function( require ) {
var _0 = require('./controllers/post_controller');
var _1 = require('./helpers/helpers');
var _2 = require('./router');
var _3 = require('./routes/post_route');
var _4 = require('./routes/posts_route');
var modules = {
PostController: _0 && (_0['default'] || _0),
Helpers: _1 && (_1['default'] || _1),
Router: _2 && (_2['default'] || _2),
PostRoute: _3 && (_3['default'] || _3),
PostsRoute: _4 && (_4['default'] || _4)
};
return modules;
});
The skeleton is also all wired up to use the modules.
require(['common'], function() {
require(['app', 'blogger/modules'], function(App, modules) {
window.Blogger = App['default'].createWithMixins(modules);
});
});
Tests
The skeleton app comes with some tests already written. mimosa build
not only runs a full build of the app, it also, by default, executes the tests CI-style. Here's the messaging from the tests.
17:48:08 - Success - 4 of 4 tests passed for .mimosa/emberTest/tests/testem.json.
ok 1 PhantomJS 1.9 - Acceptances - Posts: displays all recent posts
ok 2 PhantomJS 1.9 - Unit - PostController: #init
ok 3 PhantomJS 1.9 - Unit - PostController: #edit
ok 4 PhantomJS 1.9 - Unit - PostController: #doneEdit
1..4
# tests 4
# pass 4
# fail 0
# ok
If you run mimosa testscript
, you can get a script generated that when run will execute the interactive Testem UI.
About require.js
One of the biggest gripes about require.js is the syntax. I hope to address this in a future blog post, but this skeleton is an example of being able to use the best of require.js without a lot of the cruft.
- You aren't coding AMD. It's pure ES6.
- Mimosa understands require.js so that in many cases you do not. This isn't more true than with an Ember app with its very simple dependency tree
- require.js allows you to only concatenate when you build. You can develop with individual assets. It doesn't matter how far source maps have come along, developing with optimized assets just isn't ideal.
- Mimosa manages configuration for concatenation for you. It can figure out most of the configuration. In the case of the skeleton, it just needs a little help to swap in the production version of ember.js
Everything Else, Ember and Not
This skeleton includes plenty of other modules, some of which enable Ember, some which do not. I won't run down them all, but here are the highlights.
- It incorporates ember-handlebars template compilation which, like all Mimosa template compilers, will concatenate all the templates into a single file. Multiple file support is just a few config params away
- The es6-module-transpiler is included and most modules are coded using ES6 module syntax which is compiled to AMD.
- Bower support is included and all vendor and test assets are Bower-ed in.
- JavaScript is hinted and the
.jshintrc
is already set up to expect all of QUnit and ember-qunit's global variables. - SASS!
- An Express server complete with a module that will bounce that server when server code changes. (A server isn't necessary, just included)
- Live Reload with no browser plugins
- Concatenation of vendor CSS
- Minification of JS, CSS and images.
- Packaging support, build your application with deployment in mind.
And this is just what is included in this starter skeleton. Mimosa has plenty of other modules available.
So Little Config
If Ember is your thing, then so are conventions. By sticking to a few simple conventions you can wire your app up with very little configuration. The skeleton has all of 93 lines of config and 30 of it is a very spaced out/commented array. For what this skeleton can do, that's a tiny amount of configuration.
Why Mimosa for Your Ember Application?
Besides the support listed above, in general, why Mimosa?
General Purpose but Customizable
I mentioned we moved from Backbone to Ember. That was a big change for our team. One thing that remained constant after the transition was Mimosa. Before making the switch ourselves, plenty of folks had been using Mimosa for their Ember apps, so all the support we needed to get going was already available.
Mimosa is multi-purpose. It doesn't have anything to do with Ember, but modules can be built to solve any problem set, including Ember's.
A Single Tool
If you need to add Grunt to your Mimosa app, it isn't because Mimosa isn't capable of doing what you need, it is because the module you need hasn't been built. (Try filling the gap? Or bring up your need?)
Stable and Supported
Mimosa may be new to you, but Mimosa isn't new. It predates many of the other tools. And I'm not going anywhere!
Try it out!
If you have any feedback, hit me up.
If you have any ideas about how Mimosa's Ember support could be better, hit me up.
Thanks!
Special thanks to Estelle DeBlois for her help building the above modules and giving this post a thorough sanity check. Her help has been invaluable!
And thanks to my team at Berico for patiently waiting on the above modules to land!