esify
esify
is a combination of various tools with the purpose of automatically translating Shopify’s CoffeeScript to ESNext. Unless you work at Shopify, you probably don’t need this.
Installation
npm install -g esify
Limitations
The tools on which esify
is build have certain limitations that prevent us from providing the ideal conversion in some cases. We strongly recommend you have our linting configuration, eslint-plugin-shopify
, set up for your project before beginning to translate in order to easily identify small translation errors (unused or missing references, indentation, etc). Below is a list of limitations that you should check for in the code you are converting:
- All comments will be removed in the transformed output (including Sprockets directives)
- CoffeeScript soak calls with embedded methods (e.g.,
foo.bar()?.baz
) will compile to JavaScript that is hard to read - Assignment to a global outside of the file creating that global will result in incorrect exports (e.g.,
Shopify.UIPopover.foo = 'bar'
outside the file declaring Shopify.UIPopover.foo
) - Strings and regular expressions with complex escapes might be converted improperly
- Multiline CoffeeScript strings become a single-line string with newlines inserted as needed
- Object keys that use interpolation are not handled correctly.
Our CoffeeScript to JavaScript converter also makes a few assumptions that allow us to convert more files without user intervention, but which may not be true for your codebase:
-
Private variables inside of a class declaration are moved to the top of the scope in which the class is defined because JavaScript does not allow variables to be scoped to a class.
class A
b = 123
c = () ->
Becomes:
var b = 123;
var c = function() {};
class A {}
-
Function calls executed in a class block are moved to the bottom of the scope, after the class’s definition. This can cause problems if the function call was made with the assumption that the prototype of the class has not yet been set up, as the JavaScript conversion will run after the class has been fully constructed.
class A
_.extend(@prototype, B)
Becomes:
class A {}
_.extend(A.prototype, B);
Usage
From the root of the Shopify directory, run this script with a single, relative CoffeeScript file, or a glob pattern. Wait for it to finish, and marvel at the clean ESNext code that is spit out beside the original file! Note this script does not delete the original CoffeeScript file — you should review the output before pushing any changes.
esify app/assets/javascripts/admin/lib/*.coffee
You can provide custom options to esify
by adding an esify.config.js
file to the directory from which you are running the esify
command. An example configuration is shown below:
var path = require('path');
module.exports = {
appGlobalIdentifiers: ['Shopify'],
javaScriptSourceLocation: path.join(__dirname, 'app/assets/javascripts'),
printOptions: {
quote: 'single',
trailingComma: true,
tabWidth: 2,
wrapColumn: 1000,
},
testContextToGlobals: {
testClock: {
properties: ['clock'],
replace: true,
},
sandbox: {
properties: ['spy', 'stub', 'mock', 'server', 'requests'],
},
},
globalIdentifiers: {
_: 'lodash',
$: 'jquery',
moment: 'moment',
},
renameIdentifiers: {
jQuery: '$',
},
renameProperties: {
_: {
first: 'head',
each: 'forEach',
eachRight: 'forEachRight',
entries: 'toPairs',
entriesIn: 'toPairsIn',
extend: 'assignIn',
extendWith: 'assignInWith',
},
},
methodsThatIgnoreReturnValues: [
{
object: '_',
methods: ['each'],
},
{
object: /.*/,
methods: ['forEach'],
},
],
methodsReturningVoid: [
{
object: 'console',
methods: ['log', 'warn'],
},
{
object: /^(e|evt|event)$/,
methods: ['preventDefault'],
},
{
object: /.*/,
methods: ['forEach'],
},
{
object: '_',
methods: ['each'],
},
],
}