How to move from Ember-Rails to Ember-CLI

TL;DR; Make use of sprocket's ES6 precompilation and few ember-cli libraries to put in place a hybrid ES6 module - Globals App for incremental transition to ES6 modules.

So you want the magic of Ember-CLI?

Well, you came to the right place. I am going to be documenting how to move over a large Ember-Rails app to Ember-CLI. Before you start reading too far, Yeah, it's going to take awhile.

code refactoring

So since a "stop the world" for refactoring was probably not on the table, I got creative: Let's have a hybrid globals and ES6 module approach for incremental conversion.

Before we start though...

Take a look at ember-cli-migrator. It didn't work for us, but it sure as hell might work for you and save you a ton of work.

Bower > Asset Gems

Bower is going to be your new best friend. If you don't know what bower is, I recommend you watch a bower tutorial and check out bower's website.

This write up is about getting you started with the smallest amount of effort, so the packages you will need are:

I highly recommend migrating your assets/asset-gems used exclusively by your ember app to bower as soon as possible. This includes Ember and Ember-Data (though ember-rails has a dependency on it ember-source, so you may want to wait).

But what versions do I use?

versions are changing quickly, so what version you may need will depend on what version of Ember you are running and what version of Ember-cli you are targeting to move to. Take a look at the ember-cli repo, and under blueprints/app/files/bower.json will have various versions.

Once your bower.json is ready to go, bower install.

One more thing...

Add your bower_components path to sprockets so you can start requiring files from there.

# config/application.rb or config/environments/*.rb
# snip...
  # use bower directory path
  config.asset.paths << Rails.root.join('bower_components')
# snip...

Setup The File Structure

Your Ember Rails app is probably structured like this and is under app/assets/javascripts/old-app. If your entire ember app is not already in a subfolder under javascripts, put it in one now and make any changes to make the subfolder work before going on. While not necessary, I highly recommend you doing this for clarity of what is converted and what isn't.

Finished? Good. Now create a new folder under app/assets/javascripts and use this new file structure.

# Ember CLI
# example path: <rails app>/app/assets/javascripts/new-app

├── adapters
├── components
├── controllers
├── helpers
├── initializers
├── mixins
├── models
├── routes
├── serializers
├── templates
│   └── components
├── transforms
├── utilities
└── views

This will allow you to start conforming to Ember-CLI naming conventions.

ES6/ES2015/ insert new name here Modules

I'm not going to spend anytime here explaining ES6 modules or syntax, as there are many more, well qualified sources out there. I recommend you start at or with this blog post.

Housekeeping time. In your Gemfile add the following, and bundle

gem 'es6_module_transpiler-rails'  
# if you use coffeescript, do this too
gem 'sprockets-es6-coffee'  

Now a new initializer:

This will essentially help craft your module names. Remember, ES6 modules are not supported in the wild right now. Under the covers they are getting transpiled into AMD modules, which are then loaded by loader.js.

The only thing you should care about is that the module names should come out to be something like <your app name>/models/user. If you are ever unsure of your modules names, you can check requirejs.entries, where loader.js stores a dictionary of the modules.

Anything with the extension js.es6 will be transpiled. add .coffee to the end if you need to use coffeescript, and make sure to wrap those imports and exports in backticks.

Is it conversion time yet?

Before you jump head first into conversion, you are going to need a custom resolver that will resolve classes from both globals and AMD modules. You can go take a look at either the Ember-CLI resolver or extend Ember's default resolver, but I am going to do you one better:

WARNING: I would be weary of using this without regression testing.

This extends Ember-Resolver from Ember-CLI, adds a global resolver method and a resolveTemplate method that resolves templates compiled to Ember.TEMPLATES, including the barber gem. Since the Ember CLI Resolver extends Ember.DefaultResolver, I actually don't know if you need to use the resolveGlobal method. I recommend diving into the resolver code a bit and seeing what meets your needs.

Tomster at Work

Tips from the Front Lines

Convert Simple, but Vital Files First

This gives you the benefit of failing fast™, so you know if things are really broken before handing them over to QA or worse, merging them. Some examples of simple but vital might be custom transforms, models, components, ect.

Don't be afraid of the source code

The source for Ember.DefaultResolver and Ember Resolver aren't that complex if you know even a little bit about the container. Hopefully you won't need to dive into it more than I did.

Dasherize your file and folder names

Ember CLI's site says to do it. so just do it.

Note: Ember-Qunit >=0.1.8 will not always play nicely with dasherized file names.

Create a strategy with your team to convert files

I implemented a "touch the file, convert the file" rule for new pull requests. It's had it's bumps, but has been relatively successful. Depending on how much free time you have between features and how fast you want to move fully over to Ember CLI will determine a good strategy for your team.

Handle any deprecations along the way

I'm looking at you, global lookup of views in templates. Also, all helpers and components should have a dash in the name to make your life easier.

When migrating, make sure to reference the Ember Deprecations Guide.

Use the hacky require sparingly

If all else fails, use require to bring a converted module back into the global namespace. Use this when you have so many places the global is referenced directly that converting over all the files in one shot would be tough. Don't let it allow you to get lazy though

// old-app/models/user.js
var App.User = require('new-app/models/user')['default'];  


Just get started

The resolver will probably fail. You will probably forget to name your export and realize it 30 minutes later. But you gotta start somewhere. We all know your PM isn't going to give you 3 weeks to migrate over the code. Get to it!


comments powered by Disqus