Skip to content

Instantly share code, notes, and snippets.

@lenards
Last active January 13, 2016 19:34
Show Gist options
  • Save lenards/711efb16001d02152598 to your computer and use it in GitHub Desktop.
Save lenards/711efb16001d02152598 to your computer and use it in GitHub Desktop.
A small outline for a tech talk to help explain the *future* usage of ES6 modules in the troposphere UI...

ES6 Modules

Providing a saner, flexible module format

Ultimately, ECMAScript 6 gives us an export and import that allow use to define modules. There is no module keyword.

TL;DR

... this will replace the AMD-style format used within troposphere.

Let's expand on this ...

Using the Babel loader, our webpack configuration can transpile the ES6 syntax to JavaScript more widely supported within browsers (giving us assurations that our code runs where it needs to run).

When defining a module, we do not need to wrap our code in functions (aka IIFE) to get the scope we want (*not global). We control what gets exported to the public via export and we bring in dependent code via import (like a real programming language).

(what are the benefits? Exploring JS goes over them here)

But I got ahead of myself...

Existing manner of exporting

Our existing codebase utilizes the following approach:

define(
  [
    'react',
    'backbone'
  ],
  function (React, Backbone) {

    return React.createClass({

      propTypes: {
        provider: React.PropTypes.instanceOf(Backbone.Model).isRequired
      },

      render: function () {
        var provider = this.props.provider;
        return (
          <div className="row">
            <h2>{provider.get('name')}</h2>
          </div>
        );

      }

    });

  });

Or, simply using a function passed to define with require as an argument:

define(function (require) {
  var React = require('react/addons'),
    Backbone = require('backbone'),
  
  return React.createClass({
  // ... omitted 
  });
});

The bottom line is that we are exporting one for each file via the return here. We are getting the desired thanks to the function we close our code in.

Also, we include "use scrict"; - sometimes - but not always:

define(function (require) {
  "use strict";

  var React = require('react/addons'),
    Backbone = require('backbone'),
    Router = require('react-router');

  return React.createClass({

    mixins: [Router.State],

    propTypes: {
      instance: React.PropTypes.instanceOf(Backbone.Model).isRequired
    },

    render: function () {
      var instance = this.props.instance,
          name = instance.get('name').trim() || "[no instance name]";

      if (!instance.id) {
        return (
          <span>{instance.get('name')}</span>
        );
      }

      return (
        <Router.Link to="project-instance-details" params={{projectId: this.getParams().projectId, instanceId: instance.id}}>
          {name}
        </Router.Link>
      );
    }

  });

});

source: components/projects/detail/resources/tableData/instance/Name.react.js

By default, ES6 modules have "use strict"; behavior when transpiled (you can see this in the Babel REPL)

The current approach in troposphere makes transitioning to ES6 modules straightforward.

Each file defines a single component. That exact component is what coding importing the module wants to use.

So when defining a React component, we change from:

  ...
  // old-style
  return React.createClass({ /* ... */ });

  // to
  export default React.createClass({ /* ... */ }); 

With the export, you can export any object, function, class, var, let, or const.

But if we keep it simple (for now) - we would just be exporting the component defined in the file:

// example: BadgeConstants.js

export default {
    GRANT_BADGE: "GRANT_BADGE"
};

source: constants/BadgeConstants.js

Using modules via import

So if we consider the following define(...):

define(function (require) {
  var React = require('react/addons'),
    Backbone = require('backbone'),
  //...
});

That would turn into:

import React from 'react/addons';
import Backbone from 'backbone';
...

What is happening?

We are asking to evaluate the code in 'react/addons' and assign the object exported via export default to the variable React. Just like do assign it with require: var React = require('react/addons'),

But ES6 import can handle much than just that. However, that's all you need to know for the troposphere codebase.

You can so all the pieces of a simplified view put together here:

import React from 'react/addons';
import Backbone from 'backbone';
import Router from 'react-router';

export default React.createClass({
    displayName: "Name",

    propTypes: {
        provider: React.PropTypes.instanceOf(Backbone.Model).isRequired
    },

    render: function () {
        let provider = this.props.provider;
        return (
            <div className="row">
                <h1>{provider.get('name')}</h1>
                <Router.Link className=" btn btn-default" to = "all-providers" >
                <span className="glyphico glyphicon-arrow-left"> </span> 
                {" Back to All Providers" }
                </Router.Link>
            </div>
        );

    }

});

source: components/providers/Name.react.js from es6-module-imports branch

Remember though ...

We still have require(...) available to us. And there are places where it will continues to be used, like bootstrapper.js.

But wait - there is more!

We name do named exports and, thus, named imports.

For an example - let's consider some components from troposphere's resource details within Projects: Name, IpAddress, & Provider

If we defined a module, say, project-details, that contained all 3 components.

We can export them, by name:

... 
export {Name, Provider, IpAddress};
...

(note - other forms of export exist ...we can do multiple export lines, one per component - or prefix the var declaration)

This changes how a "consumer" of project-details will import.

Now, you'd no longer be importing the default - but all or some of the exported object.

The syntax for just imported Name looks like:

import {Name} from 'project-details';

You can also import them all:

import * from 'project-details';

You can import them all and namespace them:

import * as details from 'project-details';

// now you'd refer to Name as:
details.Name

This could be used to namespace functions, constants (const), or components.

Mozilla Developer Network has a lengthy entry explain the above and much more: import

Example:

import React from 'react/addons';
import Backbone from 'backbone';
import Router from 'react-router';
import stores from './stores';

var Name = React.createClass({
    displayName: "Name",

    mixins: [Router.State],

    propTypes: {
      instance: React.PropTypes.instanceOf(Backbone.Model).isRequired
    },

    render: function () {
      var instance = this.props.instance,
          name = instance.get('name').trim() || "[no instance name]";

      if (!instance.id) {
        return (
          <span>{instance.get('name')}</span>
        );
      }

      return (
        <Router.Link to="project-instance-details"
          params={{projectId: this.getParams().projectId, instanceId: instance.id}}>
          {name}
        </Router.Link>
      );
    }
});

var IpAddress = React.createClass({
      displayName: "IpAddress",

      propTypes: {
        instance: React.PropTypes.instanceOf(Backbone.Model).isRequired
      },

      render: function () {
        var instance = this.props.instance;

        var address = instance.get('ip_address');

        if (!address || address.charAt(0) == "0") {
            address = "N/A";
        }

        return (
          <span>{address}</span>
        );
      }
});

var Provider = React.createClass({
    displayName: "Provider",

    propTypes: {
      instance: React.PropTypes.instanceOf(Backbone.Model).isRequired
    },

    render: function () {
      var instance = this.props.instance,
        provider = stores.ProviderStore.get(instance.get('provider').id);

      if (!provider) return <div className="loading-tiny-inline"></div>;

      return (
        <span>{provider.get('name')}</span>
      );
    }
});

export {Name, Provider, IpAddress};

Resources / References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment