So I had the daunting task of converting some Coffeescript to JavaScript (AngularJS more specifically). Here are some notes on the process which might help you if you ever face the same task.
Some tools to help:
decaffeinate
decaffeinate Online tool
Depercolator
Steps I did:
Run decaffeinate directory by directory (or file/file)
– refactored to use Angular class definitions
– use ‘ngInject’ (ng-annotate) + add missing ngInject’s
– Fix function arrows
– remove unnecessary return statements
– replace __guard__ functions and use lodash _.get instead
– use es6 param defaults – replace if (initialValue == null) { initialValue = undefined; } with
– replaced Array.from usage into forEach loops (or such)
– removed unessarry anonymous functions
– removed unessarry const declared functions
– fixed screwed up for loops
– ensured singleton services have $get functions
– Fixed broken tests (if you have after every conversion)
– Use eslint after to find/fix errors
– Update build steps to use browserify/babel/jest
– update package.json to contain browserify transform config
– fix broken spec tests (function declarations)
Some Useful Links:
Open source Angular biker router by Console: https://github.com/consoleau/bicker
Tips on coffee to js conversions https://blog.bugsnag.com/converting-a-large-react-codebase-from-coffeescript-to-es6/
Setting up grunt with balel and browserify https://mitchgavan.com/es6-modules/
CoffeeScript to Angular https://github.com/CaryLandholt/ng-classify
ng-annotate https://github.com/olov/ng-annotate
Before And After Code Snippets
Delcaring angular providers.
Before
1 2 3 4 5 6 7 |
angular.module('bicker_router').provider('State', function() { this.$get = (WatchableListFactory) => { return WatchableListFactory.create(); } }); |
After
1 2 3 4 5 6 7 8 |
class StateProvider { $get(WatchableListFactory) { return WatchableListFactory.create(); } } angular.module('bicker_router').provider('State', new StateProvider); |
Array.from usage
Before
1 2 3 4 5 6 7 |
applyCommonRequiredState = (bindings, commonRequiredState) -> for binding in newBindings if not (binding.requiredState instanceof Array) binding.requiredState = [binding.requiredState] binding.requiredState = binding.requiredState.concat commonRequiredState |
After
This is ES5 blocked scope function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
let applyCommonResolve = (bindings, commonResolve) => (() => { let result = []; for (let binding of Array.from(newBindings)) { if (!('resolve' in binding)) { binding.resolve = {}; } result.push(_.defaults(binding.resolve, commonResolve)); } return result; })() ; |
es5 conversion __guard__ function
Before
1 2 3 4 5 6 7 8 |
function __guard__(value, transform) { return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; } if (!tokens[targetKey] || __guard__(types[tokens[targetKey] != null ? tokens[targetKey].type : undefined], x => x.regex.test(value))) { ... |
After
1 2 3 4 |
const tokenTypeName = tokens[targetKey] ? _.get(tokens[targetKey], 'type') : undefined; if (!tokens[targetKey] || (types[tokenTypeName].regex.test(value))) { |
$get functions
1 2 3 |
$get($location, State, $injector, $q) { |
An AngularJS service is a singleton object created by a service factory. These service factories are functions which, in turn, are created by a service provider. The service providers are constructor functions. When instantiated they must contain a property called
$get, which holds the service factory function.
TypeError: Cannot convert undefined or null to object
1 2 3 |
for (let url of Array.from(this.urls)) { |
es6 default params
Before
1 2 3 4 |
registerUrl(pattern, config) { if (config == null) { config = {}; } |
After
1 2 3 |
registerUrl(pattern, config = {}) { |
const assigned functions
Before
1 2 3 4 5 6 7 8 9 |
const getMatchingBinding = function(bindings) { for (const binding of Array.from(bindings)) { if (hasRequiredData(binding)) { return binding; } } return undefined; }; |
After
1 2 3 4 5 6 7 8 9 |
function getMatchingBinding(bindings) { for (const binding of Array.from(bindings)) { if (hasRequiredData(binding)) { return binding; } } return undefined; } |
screwed up for loops.
Before
1 2 3 4 5 6 7 |
for (let index = 0, end = this.watchers.length-1, asc = 0 <= end; asc ? index <= end : index >= end; asc ? index++ : index--) { if (this.watchers[index].handler !== watcher) { newWatchers.push(this.watchers[index]); } } |
After
1 2 3 4 5 6 7 |
_.each(this.watchers, thisWatcher => { if (thisWatcher.handler !== watcher) { newWatchers.push(thisWatcher); } }); |
bizzare anonymous self executing functions
Before
1 2 3 4 5 6 7 8 9 |
_notifyWatchers(changedPath) { return (() => { ... } return result; })(); } |
After
1 2 3 4 5 6 |
_notifyWatchers(changedPath) { ... return result; } |
Array.from
“The Array.from() method creates a new Array instance from an array-like or iterable object.”
Before
1 2 3 |
for (const watcher of Array.from(this.watchers)) { |
After
1 2 3 |
_.each(this.watchers, watcher => { |
Spacing is tabs and not characters
IntelliJ > Code > Reformat Code

angular inject module error
missing 2 things.
- ngInject as the first line inside the functions which inject modules
- browserify config (below) in the package.json (yes that’s weird place to put it but it is what it is).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
"browserify": { "transform": [ [ "babelify", { "presets": [ "es2015" ], "plugins": [ [ "angularjs-annotate", { "explicitOnly": true } ] ] } ] ] } |
TypeError: Cannot read property ‘alpha’ of undefined
alphanumeric regex
1 2 3 |
provider.registerType('alpha', {regex: /[a-zA-Z]+/}); |
Array.from / anonymous fn / spread operator
Before
1 2 3 4 5 6 7 8 9 10 11 12 13 |
setPersistentStates(...stateList) { return (() => { let result = []; for (let state of Array.from(stateList)) { let item; if (!Array.from(persistentStates).includes(state)) { item = persistentStates.push(state); } result.push(item); } return result; })(); }, |
After
1 2 3 4 5 6 7 8 9 |
setPersistentStates(...stateList) { _.forEach(stateList, (state) => { if (!persistentStates.includes(state)) { persistentStates.push(state); } }); }, |
fix broken spec tests (function declarations)
COFFEE
1 2 3 4 |
controller = ($element) -> $element.$on('$destroy', () -> destroyCalledCounter += 1) |
BEFORE
1 2 3 |
let controller = $element => $element.$on('$destroy', () => destroyCalledCounter += 1); |
AFTER
1 2 3 4 5 |
function controller($element) { $element.$on('$destroy', () => destroyCalledCounter += 1); } |
Search Terms
“convert coffeescript to javascript export statements”
“convert coffeescript to angular”
javascript “$get function”
javascript “$get function” angularjs providers
Interesting to read, Thank you Angularjs4u for helping me learn AngularJs