If you've tried the Quick Start guide you've seen a taste of what it's like to use StealJS.
This guide expands upon the quick start guide to give a full example of using Steal's primary feature, progressive loading, to build a single page application that loads only what is necessary.
This will be demonstrated by creating a small sample application called myhub.
import $ from "jquery";
import "./myhub.less";
$("body").html("<h1>Goodbye script tags!</h1>");
Each string used to import such as "jquery" and "./myhub.less" are called module identifiers. They identify a module to be imported within the context of the module that is importing them. That means that when you import a module like "./myhub.less" you are importing that module relative to the current module (in this case it is your myhub.js module).
Internally Steal resolves all module identifiers into moduleNames, which it uses as the key to look up modules. This allows you to load modules from many different places in the application and have them all resolve to the same module.
Dependency injection is a technique used to improve testing in your application.
Steal provides dependency injection through its module loading system using steal-clone.
steal-clone allows you to create a cloned loader with stubs for modules that you want to fake.
We'll create a new test and use steal-clone to provide our own fake version of jQuery that will let us simulate a service request, so we can test that the rest of our app behaves correctly.
In the case of the test below, the app should list the single forecast it is given.
Update weather/weather-test.js with:
import QUnit from "steal-qunit";
import weather from "./weather";
import clone from "steal-clone";
import $ from "jquery";
QUnit.module("myhub/weather/");
QUnit.test("basics", function(assert){
var done = assert.async();
var fixtureEl = document.getElementById("qunit-fixture");
weather(fixtureEl);
assert.equal(
fixtureEl.innerHTML,
"Loading...", "starts with loading");
var interval = setInterval(function(){
var ul = fixtureEl.getElementsByTagName("ul");
if(ul.length === 1) {
assert.ok(true, "inserted a ul");
clearInterval(interval);
done();
}
},100);
});
QUnit.test("basics with dependency injection", function(assert){
var done = assert.async();
var jQuery = function(selector){
return $(selector)
};
jQuery.ajax = function(options){
var dfd = new $.Deferred();
setTimeout(function(){
dfd.resolve({
query: {
results: {
channel: {
item: {
forecast: [{
date: new Date(),
text: "Sunny",
high: "72",
low: "58"
}]
}
}
}
}
}).then(function(){
var html = $("#qunit-fixture").html();
assert.ok(/Sunny/.test(html),
"updated with request");
done();
});
},1);
return dfd;
};
clone({
"jquery": {"default": jQuery}
}).import("myhub/weather/weather").then(function(module){
var weather = module["default"];
var fixtureEl = document.getElementById("qunit-fixture");
weather(fixtureEl);
});
});
Import a global script in a CommonJS modlet
Steal supports all of the most common module formats: ES modules, CommonJS , and AMD . This means your project can contain multiple formats which can be useful if, for example, you are using one module format in your project (like ES modules) but a package you want to depend on expects another module format (like CommonJS).
Some libraries on the web are still distributed as globals. Including such a library sets a property on the global window object, instead of exporting a value for use with one of the module formats mentioned above.
Steal is able to detect and deal with globals by default, but it's often necessary to add some configuration for correctness. The configuration guide goes into greater depth on how to configure globals in more complex situations, but configuring the globals will be simple for our example.
Install the package containing a global script
Justified Gallery is a library for displaying a gallery of images. Unfortunately, the library is distributed as a global; so we'll need to add some configuration.
Use npm to get the justifiedGallery package into your project:
Required justifiedGallery in the puppies CommonJS module.
Getting functionality out of a global script from an npm package and into a modlet is easy as that, thanks to Steal.
Update app to change pages
Now that we've created puppies, the app needs to be updated so that it will toggle between the weather and puppies pages when using the navigation. More specfically, we will do this by looking at the location.hash of the page.
Update myhub.js to:
import $ from "jquery";
import "./myhub.less";
import "bootstrap/dist/css/bootstrap.css";
import weather from "./weather/weather";
import puppies from "./puppies/puppies";
$("body").append(`
<div class="container">
<h1>Goodbye script tags!</h1>
<a href="#weather">Weather</a> <a href="#puppies">Puppies</a>
<div id="main"/>
</div>`);
var modules = {
weather: weather,
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();
There's a lot going on there, so you might want to re-read that file a couple of times to make sure you understand it.
Build a production app
Now that we've created our application, we need to share it with the public. To do this we'll create a build that will concat our JavaScript and styles down to only one file, each, for faster page loads in production.
Build the app and switch to production
When we first installed our initial dependencies for myhub, one of those was steal-tools. steal-tools is a set of tools that helps with bundling assets for production use.
Note that it is usually recommended not to include link tags for stylesheets in the head as it blocks the page from rendering until those styles are fetched. For this small demonstration we'll do it anyways. See PageSpeed Tools for more information.
Now if you reload the page you'll notice that only a few resources are downloaded.
Bundle steal.js
You'll notice in the above screenshot that we are loading two JavaScript files. myhub.js and steal.production.js. We can avoid loading both by bundling Steal along with your app's main bundle.
Update your build script to add the --bundle-steal flag:
For this size app we're in a good spot. For larging apps you want to avoid bundling your entire site into 1 JavaScript and one CSS file. Instead you should progressively load your app based on which page the user is viewing.
In the above code we have a div #main that each page renders into. Based on the location.hash, dynamically import the page being requested. So when the hash is #weather use steal.import to import the weather modlet; if the hash is #puppies use steal.import to import the puppies modlet.
Update bundles to build
Using bundle we can specify each page of our application and steal-tools will build out separate bundles.
Using our existing npm run build command we create a build using the default build options. In many cases you might want to customize these, so creating a small script allows you to do that more easily.
Create build.js:
var stealTools = require("steal-tools");
stealTools.build({}, {
bundleSteal: true
});
If you've tried the Quick Start guide you've seen a taste of what it's like to use StealJS.
This guide expands upon the quick start guide to give a full example of using Steal's primary feature, progressive loading, to build a single page application that loads only what is necessary.
This will be demonstrated by creating a small sample application called myhub.
Install Prerequisites
Window Setup
Linux / Mac Setup
Setting up a new project
Create a new project folder
Create a new folder for your project and then run
npm init
. Answer all questions with their defaults.Create and host the main page
Create myhub.html with:
Next install and run a local fileserver. http-server handles our basic needs. We'll install it locally and then and it to our npm scripts:
Next edit your
package.json
so that the start script looks like:This allows us to start the server with:
Open http://127.0.0.1:8080/myhub.html. You should see the Hello world! test.
Install steal, steal-tools, and jquery
Installing these 3 dependencies gives us everything we need to build our application.
Import your first module
Create the module
Create myhub.js with the following:
Use steal.js in your page
Update myhub.html with:
Update package.json with the right main
Update package.json to:
Reload http://127.0.0.1:8080/myhub.html to see your changes.
Import styles
What's an application without a little bit of flare? Steal allows using less through steal-less, which we installed earlier.
Update package.json
We need to update our package.json to specify the plugins that need to be loaded:
Create and import a less file
Create myhub.less with:
Import it with the following updated myhub.js:
Each string used to import such as
"jquery"
and"./myhub.less"
are called module identifiers. They identify a module to be imported within the context of the module that is importing them. That means that when you import a module like"./myhub.less"
you are importing that module relative to the current module (in this case it is your myhub.js module).Internally Steal resolves all module identifiers into moduleNames, which it uses as the key to look up modules. This allows you to load modules from many different places in the application and have them all resolve to the same module.
Install and import bootstrap
Next, install bootstrap:
Update the myhub.html to use bootstrap:
Import bootstrap and use it with the following updated myhub.js:
Steal is able to load npm packages as modules thanks to the npm plugin that comes with Steal by default.
If the package uses a module format, all you have to do is
import
in the.js
file(s) where that module needs to be used.Create a modlet
Steal encourages the use of modlets as a unit of functionality in your application.
A modlet is a folder that contains:
Using modlets helps to ensure that your application is well tested.
For example, instead of something like:
With modlets we will have exactly this:
Use this workflow to create the
weather
modlet:Create the demo page
Create weather/weather.html with:
Add the weather styles
Create weather/weather.css with:
Create the module implementation
Create weather/weather.js with the following code:
Update the
city
variable with your city so the weather page will display your city's weather.Open http://127.0.0.1:8080/weather/weather.html to see the weather widget's demo page.
Create the test page
Create weather/weather-test.html with:
Create the test
Install
steal-qunit
with:Create weather/weather-test.js with:
Open http://127.0.0.1:8080/weather/weather-test.html to run the weather tests.
Use the module
Update myhub.js to:
Open http://127.0.0.1:8080/myhub.html to see the application using the weather widget.
Create a test with dependency injection
Dependency injection is a technique used to improve testing in your application.
Steal provides dependency injection through its module loading system using steal-clone.
steal-clone allows you to create a cloned loader with stubs for modules that you want to fake.
We'll create a new test and use steal-clone to provide our own fake version of jQuery that will let us simulate a service request, so we can test that the rest of our app behaves correctly.
In the case of the test below, the app should list the single forecast it is given.
Update weather/weather-test.js with:
Import a global script in a CommonJS modlet
Steal supports all of the most common module formats: ES modules, CommonJS , and AMD . This means your project can contain multiple formats which can be useful if, for example, you are using one module format in your project (like ES modules) but a package you want to depend on expects another module format (like CommonJS).
Some libraries on the web are still distributed as globals. Including such a library sets a property on the global
window
object, instead of exporting a value for use with one of the module formats mentioned above.Steal is able to detect and deal with globals by default, but it's often necessary to add some configuration for correctness. The configuration guide goes into greater depth on how to configure globals in more complex situations, but configuring the globals will be simple for our example.
Install the package containing a global script
Justified Gallery is a library for displaying a gallery of images. Unfortunately, the library is distributed as a global; so we'll need to add some configuration.
Use npm to get the
justifiedGallery
package into your project:Create a modlet for puppies
Create puppies/puppies.html:
Create puppies/puppies.js in CommonJS format:
Open http://127.0.0.1:8080/puppies/puppies.html to see that requiring
justifiedGallery
fails.Configure
package.json
for loading the global justifiedGallery packageConfiguration in Steal is usually done in the
package.json
, under thesteal
object.map maps the
"justifiedGallery"
identifier to the JavaScript file location.meta specifies that:
format
).deps
)justifiedGallery.less
).Update package.json to:
Use justifiedGallery
Now that justifiedGallery is installed and configured, we need to
require()
it in our puppies CommonJS module.Change puppies/puppies.js to:
Open http://127.0.0.1:8080/puppies/puppies.html to see the puppies widget demo page.
At this point, we've done the following:
package.json
.justifiedGallery
in the puppies CommonJS module.Getting functionality out of a global script from an npm package and into a modlet is easy as that, thanks to Steal.
Update app to change pages
Now that we've created puppies, the app needs to be updated so that it will toggle between the weather and puppies pages when using the navigation. More specfically, we will do this by looking at the location.hash of the page.
Update myhub.js to:
There's a lot going on there, so you might want to re-read that file a couple of times to make sure you understand it.
Build a production app
Now that we've created our application, we need to share it with the public. To do this we'll create a build that will concat our JavaScript and styles down to only one file, each, for faster page loads in production.
Build the app and switch to production
When we first installed our initial dependencies for myhub, one of those was steal-tools. steal-tools is a set of tools that helps with bundling assets for production use.
In your package.json
"scripts"
section add:And then you can run:
To use the production artifacts rather than the development files we need to update our index.html to load them.
Create index.html with:
By using
steal.production.js
instead ofsteal.js
Steal will know to load the production files we just built.Preload css
To prevent flash of unstyled content (or FOUC) we can add a link tag to the top of the page.
Update index.html to:
Now if you reload the page you'll notice that only a few resources are downloaded.
Bundle steal.js
You'll notice in the above screenshot that we are loading two JavaScript files. myhub.js and steal.production.js. We can avoid loading both by bundling Steal along with your app's main bundle.
Update your
build
script to add the--bundle-steal
flag:Run:
Update index.html to:
Build a progressive loading production app
For this size app we're in a good spot. For larging apps you want to avoid bundling your entire site into 1 JavaScript and one CSS file. Instead you should progressively load your app based on which page the user is viewing.
Steal enables this with bundle configuration.
Make the app progressively load
Update myhub.js to:
In the above code we have a div
#main
that each page renders into. Based on the location.hash, dynamically import the page being requested. So when the hash is#weather
use steal.import to import the weather modlet; if the hash is#puppies
use steal.import to import the puppies modlet.Update bundles to build
Using bundle we can specify each page of our application and steal-tools will build out separate bundles.
Update package.json to:
Run:
Make a build script
Using our existing
npm run build
command we create a build using the default build options. In many cases you might want to customize these, so creating a small script allows you to do that more easily.Create build.js:
Run the build script with:
Export modules to other formats
Create an export script
Create export.js with:
Run:
Test the standalone module
Create weather/weather-standalone.html with: