Skip to content

Instantly share code, notes, and snippets.

@justinbmeyer
Last active June 24, 2016 20:00
Show Gist options
  • Save justinbmeyer/9b2b300dc300b9b29e5d90ec8d00b3ff to your computer and use it in GitHub Desktop.
Save justinbmeyer/9b2b300dc300b9b29e5d90ec8d00b3ff to your computer and use it in GitHub Desktop.
StealJS Training

Example App

Install Prerequisites

Window Setup

  1. Install NodeJS 6.
  2. Install chocolatey, python, windows sdk, and visual studio as described here.

Linux / Mac Setup

  1. Install NodeJS 6.

Setup a new project

Create a new project folder

> mkdir myhub
> cd myhub
> npm init

Create and host dummy page

Create myhub.html with:

<!DOCTYPE html>
<html lang="en">
  <head></head>
  <body>
    Hello World!
  </body>
</html>

Install and run local fileserver:

> npm install http-server -g
> http-server

Open http://127.0.0.1:8080/myhub.html.

Install steal, steal-tools, and jquery

> npm install steal --save
> npm install steal-tools --save-dev
> npm install jquery --save

Import your first module

Create the module

Create myhub.js with the following:

import $ from "jquery";

$("body").html("<h1>Goodbye script tags!</h1>");

Point your page at steal.js

Update myhub.html with:

<!DOCTYPE html>
<html lang="en">
  <head></head>
  <body>
    Hello World!
    <script src="./node_modules/steal/steal.js"></script>
  </body>
</html>

Point package.json to the right main.

Update package.json to:

{
  "name": "myhub",
  "version": "1.0.0",
  "description": "",
  "main": "myhub.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "jquery": "^3.0.0",
    "steal": "^0.16.21"
  },
  "devDependencies": {
    "steal-tools": "^0.16.5"
  }
}

Reload myhub.html to see your changes.

Import styles

Create and import a less file

Create myhub.less with:

body h1 {
    color: #2193C4;
}

Import it with the following updated myhub.js:

import $ from "jquery";
import "./myhub.less";

$("body").html("<h1>Goodbye script tags!</h1>");

Review module identifiers and module names.

Install and import bootstrap

Install bootstrap with:

> npm install bootstrap --save

Update the myhub.html to use bootstrap with:

<!DOCTYPE html>
<html lang="en">
  <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <script src="./node_modules/steal/steal.js"></script>
  </body>
</html>

Import it and use it with the following updated myhub.js:

import $ from "jquery";
import "./myhub.less";
import "bootstrap/dist/css/bootstrap.css";

$("body").append(
    "<div class='container'>"+
    "<h1>Goodbye script tags!</h1>"+
    "</div>");

Create an ES6 exporting module and modlet

Create the demo page

Create repos/repos.html with:

<!DOCTYPE html>
<html lang="en">
  <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <div id="repos"/>
    <script src="../node_modules/steal/steal.js" main="@empty">
        import repos from "myhub/repos/repos";
        repos("#repos");
    </script>
  </body>
</html>

Create the module

Create repos/repos.js with:

import $ from "jquery";
import "bootstrap/dist/css/bootstrap.css";

export default function(selector){

    $(selector).html("Loading...")
    $.ajax({
        url: "https://api.github.com/users/justinbmeyer/repos",
        jsonp: "callback",
        dataType: "jsonp",
        success: function( response ) {
            var defs = response.data.map(function(repo){
                return `<dt><a href="${repo.url}">${repo.name}</a></dt><dd>${repo.description}</dt>`
            });
            $(selector).html("<dl class='dl-horizontal'>"+defs.join("")+"</dl>");
        }
    });
}

Create the test page

Create repos/repos-test.html with:

<title>myhub/repos/repos</title>
<script src="../node_modules/steal/steal.js" 
        main="myhub/repos/repos-test"></script>
<div id="qunit-fixture"></div>

Create the test

Install steal-qunit with:

> npm install steal-qunit --save-dev

Create repos/repos-test.js with:

import QUnit from "steal-qunit";
import repos from "./repos";

QUnit.module("myhub/repos/");

QUnit.test("basics", function(){
    stop();
    var fixtureEl = document.getElementById("qunit-fixture");

    repos(fixtureEl);

    QUnit.equal(
        fixtureEl.innerHTML,
        "Loading...", "starts with loading");

    var interval = setInterval(function(){
        var dl = fixtureEl.getElementsByTagName("dl");
        if(dl.length === 1) {
            QUnit.ok(true, "inserted a dl");
            QUnit.start();
            clearInterval(interval);
        }
    },100);
});

Use the module

Update myhub.js to:

import $ from "jquery";
import "./myhub.less";
import "bootstrap/dist/css/bootstrap.css";
import repos from "./repos/repos";

$("body").append(
    "<div class='container'>"+
    "<h1>Goodbye script tags!</h1>"+
    "<div id='repos'/>"+
    "</div>");

repos('#repos');

Create test with dependency injection

Update repos/repos-test.js with:

import QUnit from "steal-qunit";
import repos from "./repos";
import clone from 'steal-clone';
import $ from 'jquery';

QUnit.module("myhub/repos/");

QUnit.test("basics", function(){
    QUnit.stop();
    var fixtureEl = document.getElementById("qunit-fixture");

    repos(fixtureEl);

    QUnit.equal(
        fixtureEl.innerHTML,
        "Loading...", "starts with loading");

    var interval = setInterval(function(){
        var dl = fixtureEl.getElementsByTagName("dl");
        if(dl.length === 1) {
            QUnit.ok(true, "inserted a dl");
            QUnit.start();
            clearInterval(interval);
        }
    },100);
});

QUnit.asyncTest("basics with dependency injection", function(){
    var jQuery = function(selector){
        return $(selector)
    };
    jQuery.ajax = function(options){
        setTimeout(function(){
            options.success({
                data: [{
                    url: "http://stealjs.com",
                    name: "StealJS",
                    description: "Futuristic Module Loader"
                }]
            });

            QUnit.equal(
                $("#qunit-fixture").html(),
                '<dl class="dl-horizontal">'+
                '<dt><a href="http://stealjs.com">StealJS</a></dt><dd>Futuristic Module Loader</dd>'+
                '</dl>',
                "updated with request");
            QUnit.start();
        },1);
    };

    clone({
        "jquery": {"default": jQuery}
    }).import("myhub/repos/repos").then(function(module){
        var repos = module["default"];

        var fixtureEl = document.getElementById("qunit-fixture");
        repos(fixtureEl);
    });
});

Import a global script in a CJS modlet

Install the global script

Run:

> npm install justifiedGallery --save

Create the modlet

Create puppies/puppies.html:

<!DOCTYPE html>
<html lang="en">
  <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <div class='container'>
        <div id="puppies"/>
    </div>
    <script src="../node_modules/steal/steal.js" main="@empty">
        var puppies =  require("myhub/puppies/puppies");
        puppies("#puppies");
    </script>
  </body>
</html>

Create puppies/puppies.js:

require("justifiedGallery");

module.exports = function(selector) {
    
};

Configure justifiedGallery to load

Change package.json to:

{
  "name": "myhub",
  "version": "1.0.0",
  "description": "",
  "main": "myhub.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "bootstrap": "^3.3.6",
    "jquery": "^3.0.0",
    "justifiedGallery": "^3.6.2",
    "steal": "^0.16.21"
  },
  "devDependencies": {
    "steal-qunit": "^0.1.1",
    "steal-tools": "^0.16.5"
  },
  "system": {
     "map": {
         "justifiedGallery": "justifiedGallery/src/js/justifiedGallery"
     },
     "meta": {
         "justifiedGallery/src/js/justifiedGallery": {
             "format": "global",
             "deps": ["jquery","justifiedGallery/src/less/justifiedGallery.less"]
         }
     }
  }
}

Use justifiedGallery

Change puppies/puppies.js to:

require("justifiedGallery");
var $ = require("jquery");

module.exports = function(selector) {
    $(selector).html("Loading...");
	$.ajax({
		url: 'https://api.flickr.com/services/feeds/photos_public.gne',
		dataType: 'jsonp',
		jsonpCallback: "jsonFlickrFeed",
		data: {
			"tags": "puppy",
			"format": "json"
		},
		success: function(response) {
			var html = response.items.map(function(item, index) {
				return '<a href="'+item.link+'">'+
                    '<img alt="'+item.title+'" src="'+item.media.m+'"/>'+
                '</a>'
			}).join("");

			$(selector).html(html).justifiedGallery();
		}
	});
};

Build a production app

Update app to change pages

Update myhub.js to:

import $ from "jquery";
import "./myhub.less";
import "bootstrap/dist/css/bootstrap.css";
import repos from "./repos/repos";
import puppies from "./puppies/puppies";

$("body").append(`
    <div class='container'>
        <h1>Goodbye script tags!</h1>
        <a href="#repos">Repos</a> <a href="#puppies">Puppies</a>
        <div id='main'/>
    </div>`);

var modules = {
    repos: repos,
    puppies: puppies,
    "": function(selector){
        $(selector).html("Welcome home");
    }
}

var updatePage = function(){
    var hash = window.location.hash.substr(1);
    modules[hash]("#main");
};

$(window).on("hashchange", updatePage);

updatePage();

Build the app and switch to production

Run:

> ./node_modules/.bin/steal-tools

Create index.html with:

<!DOCTYPE html>
<html lang="en">
  <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <script src="./node_modules/steal/steal.production.js"
        main="myhub/myhub"></script>
  </body>
</html>

Preload css

Update index.html to:

<!DOCTYPE html>
<html lang="en">
  <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <link href="./dist/bundles/myhub/myhub.css" rel="stylesheet">
  </head>
  <body>
    <script>
    steal = {
      instantiated: {
        "bundles/myhub/myhub.css!$css" : null
      }
    }
    </script>
    <script src="./node_modules/steal/steal.production.js"
        main="myhub/myhub"></script>
  </body>
</html>

Bundle steal.js

Run:

> ./node_modules/.bin/steal-tools build --bundleSteal

Update index.html to:

<!DOCTYPE html>
<html lang="en">
  <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <link href="./dist/bundles/myhub/myhub.css" rel="stylesheet">
  </head>
  <body>
    <script>
    steal = {
      instantiated: {
        "bundles/myhub/myhub.css!$css" : null
      }
    }
    </script>
    <script src="./dist/bundles/myhub/myhub.js"></script>
  </body>
</html>

Build a progressive loading production app

Make the app progressively load

Update myhub.js to:

import $ from "jquery";
import "./myhub.less";
import "bootstrap/dist/css/bootstrap.css";

$("body").append(`
    <div class='container'>
        <h1>Goodbye script tags!</h1>
        <a href="#repos">Repos</a> <a href="#puppies">Puppies</a>
        <div id='main'/>
    </div>`);

var updatePage = function(){
    var hash = window.location.hash.substr(1);
    if(!hash) {
        $("#main").html("Welcome home");
    } else {
        System.import("myhub/"+hash+"/"+hash).then(function(moduleOrPlugin){
            var plugin = typeof moduleOrPlugin === "function" ?
                moduleOrPlugin : moduleOrPlugin["default"];
            plugin("#main");
        });
    }
};

$(window).on("hashchange", updatePage);

updatePage();

Update bundles to build

Update package.json to:

{
  "name": "myhub",
  "version": "1.0.0",
  "description": "",
  "main": "myhub.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "bootstrap": "^3.3.6",
    "jquery": "^3.0.0",
    "jqueryui": "^1.11.1",
    "justifiedGallery": "^3.6.2",
    "steal": "^0.16.21",
    "steal-tools": "^0.16.4"
  },
  "devDependencies": {
    "steal-qunit": "^0.1.1"
  },
  "system": {
    "map": {
      "justifiedGallery": "justifiedGallery/src/js/justifiedGallery"
    },
    "meta": {
      "justifiedGallery/src/js/justifiedGallery": {
        "format": "global",
        "deps": ["jquery","justifiedGallery/src/less/justifiedGallery.less"]
      }
    },
    "bundle": [
      "myhub/puppies/puppies",
      "myhub/repos/repos"
    ]
  }
}

Run:

> ./node_modules/.bin/steal-tools build --bundleSteal

Make a build script

Create build.js:

var stealTools = require("steal-tools");

stealTools.build({
    config: __dirname+"/package.json!npm"
}, {
  bundleSteal: true
});

Run the build script with:

> node build.js

Export modules to other formats

Create an export script

Create export.js with:

var stealTools = require("steal-tools");
stealTools.export({
  system: {
    main: "myhub/repos/repos",
    config: __dirname+"/package.json!npm"
  },
  options: {
    verbose: true
  },
  outputs: {
    "+amd": {},
    "+global-js": {
        exports: {
            "myhub/repos/repos":"repos",
            "jquery": "jQuery"
        },
        dest: __dirname+"/dist/global/repos.js"
    }
  }
});

Run:

> node export.js

Test the standalone module

Create repos/repos-standalone.html with:

<div id='git-repos'/>
<script src="//code.jquery.com/jquery-3.0.0.js"></script>
<script src="../dist/global/repos.js"></script>
<script>
    repos("#git-repos");
</script>
@matthewp
Copy link

./node_modules/.bin/steal-tools build --bundleSteal

"build" is not needed here, it's the default.

@bmomberger-bitovi
Copy link

> npm init

Since this is an interactive command, should have a short blurb that says all of the default options are OK and the user can hit ENTER through all of them.

@bmomberger-bitovi
Copy link

It would be helpful to highlight what changes in the files as we go along

Since the markdown syntax highlighting isn't able to do this (that I know of), we'll have to use our words.

@bmomberger-bitovi
Copy link

We're not implementing any cache-busting here and one of Chrome or http-server seems to be caching aggressively. I'm having to "Empty Cache and Hard Reload" after adding Bootstrap to see the changes. This could put less experienced new users off if they can't figure out why nothing is changing after changes are made.

@bmomberger-bitovi
Copy link

bmomberger-bitovi commented Jun 24, 2016

Tell the user to go to http://127.0.0.1:8080/repos/repos-test.html to run the initial test, just before the "Create test with dependency injection" heading.

EDIT: This is a more general issue, that in a step by step guide with several places to see changes, the user should be prompted to see what's changed before moving on. So between e.g. "Build app and switch to production" and "Preload CSS" it seems pointless to have two versions of index.html unless the reader is guided to see an intermediate step between them.

@bmomberger-bitovi
Copy link

"Configure justifiedGallery" & "Update Bundles to build" sections: jquery-ui is in the dependencies. There hasn't been a prior instruction to install it, and it doesn't seem necessary to run things.

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