Skip to main content
Redhat Developers  Logo
  • Products

    Featured

    • Red Hat Enterprise Linux
      Red Hat Enterprise Linux Icon
    • Red Hat OpenShift AI
      Red Hat OpenShift AI
    • Red Hat Enterprise Linux AI
      Linux icon inside of a brain
    • Image mode for Red Hat Enterprise Linux
      RHEL image mode
    • Red Hat OpenShift
      Openshift icon
    • Red Hat Ansible Automation Platform
      Ansible icon
    • Red Hat Developer Hub
      Developer Hub
    • View All Red Hat Products
    • Linux

      • Red Hat Enterprise Linux
      • Image mode for Red Hat Enterprise Linux
      • Red Hat Universal Base Images (UBI)
    • Java runtimes & frameworks

      • JBoss Enterprise Application Platform
      • Red Hat build of OpenJDK
    • Kubernetes

      • Red Hat OpenShift
      • Microsoft Azure Red Hat OpenShift
      • Red Hat OpenShift Virtualization
      • Red Hat OpenShift Lightspeed
    • Integration & App Connectivity

      • Red Hat Build of Apache Camel
      • Red Hat Service Interconnect
      • Red Hat Connectivity Link
    • AI/ML

      • Red Hat OpenShift AI
      • Red Hat Enterprise Linux AI
    • Automation

      • Red Hat Ansible Automation Platform
      • Red Hat Ansible Lightspeed
    • Developer tools

      • Red Hat Trusted Software Supply Chain
      • Podman Desktop
      • Red Hat OpenShift Dev Spaces
    • Developer Sandbox

      Developer Sandbox
      Try Red Hat products and technologies without setup or configuration fees for 30 days with this shared Openshift and Kubernetes cluster.
    • Try at no cost
  • Technologies

    Featured

    • AI/ML
      AI/ML Icon
    • Linux
      Linux Icon
    • Kubernetes
      Cloud icon
    • Automation
      Automation Icon showing arrows moving in a circle around a gear
    • View All Technologies
    • Programming Languages & Frameworks

      • Java
      • Python
      • JavaScript
    • System Design & Architecture

      • Red Hat architecture and design patterns
      • Microservices
      • Event-Driven Architecture
      • Databases
    • Developer Productivity

      • Developer productivity
      • Developer Tools
      • GitOps
    • Secure Development & Architectures

      • Security
      • Secure coding
    • Platform Engineering

      • DevOps
      • DevSecOps
      • Ansible automation for applications and services
    • Automated Data Processing

      • AI/ML
      • Data Science
      • Apache Kafka on Kubernetes
      • View All Technologies
    • Start exploring in the Developer Sandbox for free

      sandbox graphic
      Try Red Hat's products and technologies without setup or configuration.
    • Try at no cost
  • Learn

    Featured

    • Kubernetes & Cloud Native
      Openshift icon
    • Linux
      Rhel icon
    • Automation
      Ansible cloud icon
    • Java
      Java icon
    • AI/ML
      AI/ML Icon
    • View All Learning Resources

    E-Books

    • GitOps Cookbook
    • Podman in Action
    • Kubernetes Operators
    • The Path to GitOps
    • View All E-books

    Cheat Sheets

    • Linux Commands
    • Bash Commands
    • Git
    • systemd Commands
    • View All Cheat Sheets

    Documentation

    • API Catalog
    • Product Documentation
    • Legacy Documentation
    • Red Hat Learning

      Learning image
      Boost your technical skills to expert-level with the help of interactive lessons offered by various Red Hat Learning programs.
    • Explore Red Hat Learning
  • Developer Sandbox

    Developer Sandbox

    • Access Red Hat’s products and technologies without setup or configuration, and start developing quicker than ever before with our new, no-cost sandbox environments.
    • Explore Developer Sandbox

    Featured Developer Sandbox activities

    • Get started with your Developer Sandbox
    • OpenShift virtualization and application modernization using the Developer Sandbox
    • Explore all Developer Sandbox activities

    Ready to start developing apps?

    • Try at no cost
  • Blog
  • Events
  • Videos

Rails + jspm >= ECMAScript 6 awesomeness

 

June 26, 2015
Hugo Guerrero Michal Cichra
Related topics:
Containers
Related products:
Red Hat OpenShift

Share:

    From the buzz on Twitter and blog posts, you could feel that ECMAScript 6 was finally coming. It has many things we’ve wanted for years, so it makes sense to start new projects with it in mind.

    ECMAScript 6

    Others have written in depth about various ECMAScript 6 features. I’d like to focus just on one: module loading. There is no common way to load your ES6 modules natively in the browsers. For example babel, has support for three different module loaders. There was a System dynamic module loader included in the ES6 specification, but in the end it was removed and work continued as WhatWG loader spec. Yes, you can define modules, classes, export them, and import them, but there is no way how to load them across files. This also means that the import {} from 'file.js' does not work.

    However, we can use System module loader now, via awesome polyfill es6-module-loader. On top of that, there is SystemJS – universal dynamic module loader which loads basically everything you can think of: ES6 modules, AMD, CommonJS and global scripts in the browser and NodeJS.

    It sounds like we could actually use those ECMAScript 6 modules now thanks to SystemJS.

    jspm

    Frictionless browser package management

    • jspm is a package manager for the SystemJS universal module loader, built on top of the dynamic ES6 module loader
    • Load any module format (ES6, AMD, CommonJS and globals) directly from any registry such as npm and GitHub with flat versioned dependency management. Any custom registry endpoints can be created through the Registry API.
    • For development, load modules as separate files with ES6 and plugins compiled in the browser.
    • For production (or development too), optimize into a bundle, layered bundles or a self-executing bundle with a single command.

    If you’ve used tools like browserify or webpack, you know how it is to run a precompiler, add options to the compiler when you want to use JSX and do other chores. With jspm, the experience is very different. You install and initialize it. And it works. In the browser! No process running on your machine is needed to compile things. See the guides on jspm.io for more details. I really recommend the talk from London React Meetup.

    Rails

    Yes, even in 2015 Ruby on Rails is still a thing. Unfortunately Rails tooling for ES6 is still very young – but hey, we’ve got jspm. These tools (sprockets-es6 for example) also require up to date (>= 3.0) sprockets, which is available on Rails 4.x. Some of us have to work with Rails 3 applications, so there has to be a way how to make it work even without server side compilation. You’d need a module loader with Rails 4 anyway because it is not part of the specification.

    Rails + jspm

    I’ll use Rails 4 in this example, but it really doesn’t matter which version it is. I’m also using latest stable version of jspm (0.15.7). You might run into problems that Rails 3 wants to minify your source maps or ECMAScript 6 files. We have solved that by naming them *.es6 and disabling source maps.

    New Rails App

    $ rails new jspm_example
    $ cd jspm_example
    $ rails generate controller welcome index --no-helper --no-assets

    And you’ll have to add root route root 'welcome#index' to config/routes.rb.

    Install jspm

    If you don’t have jspm, install it as described in Getting Started.

    $ npm install jspm -g

    Then inside of the generated application, generate jspm configuration:

    $ cd jspm_example
    $ jspm init
    Would you like jspm to prefix the jspm package.json properties under jspm? [yes]: yes
    Enter server baseURL (public folder path) [./]: ./assets/
    Enter jspm packages folder [assets/jspm_packages]:
    Enter config file path [assets/config.js]:
    Configuration file assets/config.js doesn't exist, create it? [yes]:
    Enter client baseURL (public folder URL) [/]: /assets/
    Which ES6 transpiler would you like to use, Traceur or Babel? [babel]: babel
    ok   Verified package.json at package.json
         Verified config file at assets/config.js
         Looking up loader files...
           system.js
           system.src.js
           system.js.map
           es6-module-loader.src.js
           es6-module-loader.js
           es6-module-loader.js.map
    
         Using loader versions:
           es6-module-loader@0.16.6
           systemjs@0.16.11
         Looking up npm:babel-core
         Looking up npm:core-js
         Looking up npm:babel-runtime
         Updating registry cache...
    ok   Installed babel as npm:babel-core@^5.1.13 (5.5.6)
         Looking up github:jspm/nodelibs-process
         Looking up github:jspm/nodelibs-fs
         Looking up github:systemjs/plugin-json
    ok   Installed github:jspm/nodelibs-process@^0.1.0 (0.1.1)
         Looking up npm:process
    ok   Installed npm:process@^0.10.0 (0.10.1)
    ok   Installed babel-runtime as npm:babel-runtime@^5.1.13 (5.5.6)
    ok   Installed github:jspm/nodelibs-fs@^0.1.0 (0.1.2)
    ok   Installed github:systemjs/plugin-json@^0.1.0 (0.1.0)
    ok   Installed core-js as npm:core-js@^0.9.4 (0.9.16)
    ok   Loader files downloaded successfully
    
    ok   Install complete.

    Note that I used ./assets/ as public folder path and /assets/ as public folder URL (server by webserver). I think it is really nice to separate modern JS from its older forms to two very different folders. However, you can come up with your own convention like app/assets/jspm.

    Now, there is a small issue with the latest stable version of jspm (0.15.7): it automatically adds .js to all loaded files. That means if you try to load ‘main’ it will load ‘main.js’. But also if you load ‘main.js it will try ‘main.js.js’. Fortunately, the fix is really easy. Add "*.js": "*.js", to “paths” in assets/config.js.

    Next, let’s ignore jspm modules, generated assets and source maps from git.

    $ echo '/assets/jspm_packages/*/**' >> .gitignore
    $ echo '/public/assets/' >> .gitignore
    $ echo '/assets/**/*.map' >> .gitignore

    This will keep jspm and SystemJS in the git, so other developers won’t have to install jspm to just use the app in development environment.

    And add our new assets folder as a load path to Rails Asset Pipeline.

    $ echo "Rails.application.config.assets.paths << 'assets'" >> config/initializers/assets.rb

    That way you’ll have nicely split assets managed by jspm (in ECMAScript 6) and normal Rails assets (together with your old javascripts).

    Teaspoon

    Because we are all agile, tests are needed for JavaScript too. And Teaspoon is a really nice toolkit to test your scripts. First add it to the Rails Gemfile.

    group :test, :development do
      gem 'teaspoon-jasmine' # can be also -mocha or -qunit
    end

    Following Installation guide the next step is to initialize the project.

    $ rails generate teaspoon:install
          create  spec/teaspoon_env.rb
           exist  spec/javascripts/support
           exist  spec/javascripts/fixtures
          create  spec/javascripts/spec_helper.js
    +============================================================================+
    Congratulations!  Teaspoon was successfully installed.  Documentation and more
    can be found at: https://github.com/modeset/teaspoon

    To make ES6 work in the tests, you need to initialize SystemJS in spec/javascripts/spec_helper.js.

    Let’s create assets/jspm.js with following contents:

    //= require jspm_packages/es6-module-loader.js
    //= require jspm_packages/system.js
    //= require config.js

    And require it in spec/javascripts/spec_helper.js by replacing //= require application with //= require jspm. Because ES6 module system does not pollute global namespace and SystemJS allows to asynchronously load modules in the browser, you don’t need to load application.js. That would load all global jQuery or what you might end up having there.

    PhantomJS

    By default, Teaspoon is using PhantomJS. You get several options how to install it:

    # on OSX (if you use Homebrew)
    brew install phantomjs
    # the rest
    npm install -g phantomjs
    # last resort
    echo "gem 'phantomjs', group: :test" >> Gemfile
    bundle install

    greeter_spec.js

    Let’s create sample test using ES6 modules. It will be a Greeter that will greet someone with Hello. Create spec/javascripts/greeter_spec.js with following contents:

    import {Greeter} from 'greeter.js';
    
    describe("Greeter", function() {
        const greeter = new Greeter();
    
        it('greets', function(){
            expect(greeter.greet('Someone')).toBe("Hello Someone!")
        });
    });

    But when executing tests, there is an syntax error:

    $ rake teaspoon
    Starting the Teaspoon server...
    Teaspoon running default suite at http://127.0.0.1:54124/teaspoon/default
    SyntaxError: Parse error
    
    
    
    Finished in 0.00100 seconds
    0 examples, 0 failures

    Why? Because browsers do not support ES6 natively yet. Jspm to the rescue!

    Teaspoon + jspm

    Jspm uses SystemJS – asynchronous module loader. Notice the asynchronous? Teaspoon needs to execute loaded tests, but how does teaspoon know that the tests are loaded when it is asynchronous? The default is window.onload which does not work in this case as the scripts can be loaded after that event. It is very similar case to RequireJS with Teaspoon.

    1. Configure a partial in spec/teaspoon_env.rb
    config.suite do |suite|
      suite.boot_partial = '/boot_system_js'
    end
    1. Create the partial in spec/javascript/fixtures/_boot_system_js.html.erb
    <%= javascript_include_tag @suite.helper %>
    
      Teaspoon.onWindowLoad(function () {
        System.register('teaspoon', , function() {
          return {
            setters: [],
            execute: function() { }
          }
        });
    
        System.import('teaspoon').then(Teaspoon.execute, Teaspoon.execute);
      });
    
    1. Precompile spec_helper.js
      Teacup requires spec_helper.js to be in scripts to precompile.
      Enable it just for test and development environment by adding config.assets.precompile += %w( spec_helper.js )
      to config/environments/development.rb and config/environments/test.rb
    2. Fire up rails server and open http://localhost:3000/teaspoon/default.
      You should see error in console: GET http://localhost:3000/assets/greeter.js 404 (Not Found).
      If you see greeter.js.js you are not drunk, just missed a spot in install jspm (about adding "*.js": "*.js").
    3. Now to fulfill the failing test, you should create assets/greeter.js.
      After creating empty file, you can run rake teaspoon to see how the test failure changes:
    $ rake teaspoon
    Starting the Teaspoon server...
    Teaspoon running default suite at http://127.0.0.1:55128/teaspoon/default
    F
    
    Failures:
    
      1) Greeter encountered a declaration exception
         Failure/Error: TypeError: 'undefined' is not a constructor (evaluating 'new Greeter()') (line 12)
    
    Finished in 0.00300 seconds
    1 example, 1 failure
    
    Failed examples:
    
    teaspoon -s default --filter="Greeter encountered a declaration exception"
    rake teaspoon failed
    1. Sweet. Let’s implement the Greeter.
    export class Greeter {
        greet(person) {
            return `Hello ${person}!`;
        }
    }

    Execute the tests again and voila!

    $ rake teaspoon
    Starting the Teaspoon server...
    Teaspoon running default suite at http://127.0.0.1:55161/teaspoon/default
    .
    
    Finished in 0.00300 seconds
    1 example, 0 failures

    jspm bundle

    Now the part I like the most. There is no way I’m going to install NodeJS on our servers just to compile few assets. We can keep the bundled version in the repository and force people to keep track of it. As described in jspm’s Wiki Production Workflows, you can build bundles from modules and include all dependencies there. Let’s create simple application that will print our greeting to the console. Start by creating assets/welcome.js with following contents.

    import {Greeter} from "greeter.js";
    const greeter = new Greeter();
    
    export function welcome(name) {
        alert(greeter.greet(name));
    }

    Let’s boot the rails server and open http://localhost:3000 to see if there are any errors. There shouldn’t be any, so continue with actually loading our welcome module and executing it. Add following snippet to app/views/welcome/index.html.erb:

    
      System.import('welcome').then(function(m){
        m.welcome('Someone');
      });
    

    Refresh the browser and see Uncaught ReferenceError: System is not defined. Hmm. Right… Let’s load it in app/assets/application.js by replacing the whole file by just //= require jspm.

    Remember jspm.js? It’s that little file that requires ES6 Module Loader and SystemJS. It was created when setting up teaspoon.

    After refreshing the browser again, you should see alert dialog saying “Hello Someone!”. You might notice that it took a while.

    What did we do here? Using System.import you can load any module exported by ES6. So here we import the welcome function and execute it. Modules should not have side effects, so just requiring them should not do anything. That’s why exporting a function is a good idea. Also, this way you can pass some parameters down to the modules, which is handy when you are integrating with existing system and not building single page apps.

    Now to the Production Workflow. Let’s bundle some scripts.

    $ mkdir assets/bundles
    $ touch assets/bundles/.gitkeep
    $ jspm bundle welcome.js assets/bundles/welcome.js
         Building the bundle tree for welcome.js...
    
           greeter.js
           npm:babel-runtime@5.5.6/core-js/object/define-property
           npm:babel-runtime@5.5.6/helpers/class-call-check
           npm:babel-runtime@5.5.6/helpers/create-class
           npm:core-js@0.9.16/library/fn/object/define-property
           npm:core-js@0.9.16/library/modules/$
           npm:core-js@0.9.16/library/modules/$.fw
           welcome.js
    
    ok   Built into assets/bundles/welcome.js with source maps, unminified.

    Now the last step is to actually load all files from app/bundles.

    echo '//= require_tree ./bundles/' >> app/assets/javascripts/application.js

    Ok. If you refresh the browser again, it should be much faster and not load scripts one by one.

    Workflow

    However, now you get precompiled versions all the time when you load it in the browser. There is simple workflow trick.

    1. rm assets/builds/*.js
    2. Do your changes. Try it in the browser.
    3. Commit your changes.
    4. git status will complain about assets/builds/*
    5. jspm bundle welcome.js assets/bundles/welcome.js (or codify it as a rake task)
    6. Let your CI to verify that assets/bundles/*.js is always the latest version.

    You can also sprinkle it with some git pre-commit hooks to automatically verify it on development machine. There are two reasons why we want to have compiled copy in the git:

    • we don’t want to require Node on our production servers
    • some of our developers might not have Node on development machines

    Unless you are a developer working on ES6, you don’t have to care about all the buzzwords and everything just works. Maybe time will prove it too hard to follow or too error prone, but the joys of simple deployment are greater than the risks.

    People behind this

    • jspm and SystemJS – Guy Bedford
    • babel – Sebastian McKenzie

    When you see them, give them a hug. They are doing awesome work to bring sanity into our development.

    Source

    You can find individual steps as commits in the mikz/rails-jspm-es6-example.

    Last updated: September 19, 2023

    Recent Posts

    • Storage considerations for OpenShift Virtualization

    • Upgrade from OpenShift Service Mesh 2.6 to 3.0 with Kiali

    • EE Builder with Ansible Automation Platform on OpenShift

    • How to debug confidential containers securely

    • Announcing self-service access to Red Hat Enterprise Linux for Business Developers

    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Products

    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform

    Build

    • Developer Sandbox
    • Developer Tools
    • Interactive Tutorials
    • API Catalog

    Quicklinks

    • Learning Resources
    • E-books
    • Cheat Sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site Status Dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit
    © 2025 Red Hat

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility

    Report a website issue