Optimized Builds

  • page
StealJS.guides.optimized_builds  

In 1.4.0 StealTools added a new optimize API which aims to replace stealTools.build as the default way of creating a build of a module and all of its dependencies; builds created by stealTools.optimize are smaller and load faster.

Unlike regular builds, optimized builds don't need to load or bundle StealJS at all; a thin wrapper is added instead to the main bundle so the browser can load and execute the modules correctly.

The optimize API is a work in progress, some StealJS features are not supported yet.

In this guide, we'll go through the steps required to create and use an optimized build. We'll be using the myhub application created in the Progressive Loading guide.

Install Prerequisites

Window Setup

  1. Install NodeJS.
  2. Install Chocolatey, Python, Windows SDK, and Visual Studio as described here.

Linux / Mac Setup

  1. Install NodeJS.

Setting up myhub

Clone the Github repo

Run the following command:

> git clone git@github.com:stealjs/myhub.git

Install dependencies

As mentioned before, the optimize API is still in its early days, for that reason we need to use some pre-release packages.

Edit your package.json like:

"devDependencies": {
  ...
  "steal-css": "^1.3.0-pre.0",
  "steal-tools": "^1.4.0"
}

Run npm install to install all the application dependencies.

Make an optimize build script

Create optimize.js:

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

// use the defaults
stealTools.optimize();

The steal-tools optimize CLI can be used instead of NPM scripts.

Run the build script with:

> node optimize.js

Now, start an http server by running npm start and open http://127.0.0.1:8080/ in a web browser. You should see myhub's home page.

One limitation of the optimized loader is that unlike StealJS' loader it does not normalize module identifiers on runtime. For static imports that's not a problem, but it's an issue for dynamic imports (through steal.import), the module identifier needs to match the name set in bundle.

Performance comparison

For most application builds where StealJS is not included in each of the main bundles, optimized builds will have one fewer http request on the initial load which is already a win.

In this example, we are comparing how fast the load event is fired (this event is fired when a resource and its dependent resources have finished loading) when creating the build using stealTools.build (with bundleSteal set to true) and stealTools.optimize.

build

In the screenshot above, the build created by stealTools.build takes 157ms to fire the load event, in contrast the optimized build takes 119ms (24% faster) to fire the load event, see the screenshot below.

google chrome

It's also worth noting that the optimized bundles are smaller; the gzip size of the optimized main bundle is 31.8 kB compared to the 59.5 kB bundle of the regular build (46% smaller!).

Progressive loading and async script tags

So far we have only loaded the main module of the myhub application; one of the features of this application is that it progressively loads the modules needed to render the weather page and the puppies page only when the user navigates to any of these pages.

weather

Once again, the optimized bundle is smaller than the regular one (although the difference is less that 1 kB); but unlike regular bundles loaded with StealJS, the optimized loader can handle bundles loaded using script tags; which means we can take advantage of the browser capability to download the bundle and parse its code without blocking the main thread and without waiting for the user to click any of the navigation links.

Edit index.html to asynchronously load the bundles of the other two pages like this:

<body>
  <div class="container">Hello World.</div>
  <script async src="./dist/bundles/myhub/myhub.js"></script>
  <script async src="./dist/bundles/myhub/weather/weather.js"></script>
  <script async src="./dist/bundles/myhub/puppies/puppies.js"></script>
</body>

optimize-async

If you reload your browser, you'd notice that there are two extra requests and the load event takes more or less the same time to fire as before. The browser downloaded and parsed the bundles without blocking the main thread, instead of waiting for the user to navigate away from the home page, which results in smoother transitions.

Navigate to the puppies page and see it for yourself!

async

iOS builds using Apache Cordova

In this part of the guide we will turn the myhub application into an iOS application that can be tested in the iOS simulator using Apache Cordova.

This guide is targeted to macOS users, please download and install Xcode from the AppStore before moving forward.

To set up the Cordova build, we have to install a couple of packages, run:

npm install --save-dev steal-cordova
npm install --global ios-sim

Update the optimize build script

Once steal-cordova is installed and saved in your package.json file, update optimize.js with the following code:

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

var stealCordova = require("steal-cordova")({
  buildDir: "./build/cordova",
  id: "com.myhub",
  name: "MyHub",
  platforms: ["ios"],
  index: path.join(__dirname, "index.html")
});

stealTools
  .optimize()
  .then(stealCordova.build)
  .then(function() {
    // launch the iOS simulator automatically
    stealCordova.ios.emulate();
  });

Read steal-cordova documentation to get familiar with all the available options.

Run the build script with:

> node optimize.js

screen shot 2017-08-04 at 15 30 42

Congrats! you just created an iOS application!

If you receive the error "Error: Cannot read property 'replace' of undefined", you can work around it by running cd build/cordova/platforms/ios/cordova/ && npm install ios-sim@6 then cd back into the myhub root folder and run the optimize build script again.

Optimized build targets and Cordova builds

The optimize API can be passed a target option to generate code specific to certain platforms, you can read more about it in the steal-tools documentation.

The Cordova build requires the web target output to be passed in, first, make sure your target array includes the "web" option.

Assuming you're creating a build to load on Web Workers as a second target, optimize.js should look like this:

// The code up to this point remains the same
stealTools
  .optimize({}, { target: ["web", "worker"] })
  .then(function(buildResult) {
    return stealCordova.build(buildResult.web);
  })
  .then(function() {
    stealCordova.ios.emulate();
  });

and that's it!

Desktop builds using Electron

In the last part of this guide we will make a desktop build of our myhub application using Electron.

To set up the desktop build, we have to install steal-electron by running:

npm install --save-dev steal-electron

Electron main module

We will need a different entry point for our Electron application, this module will take care of creating windows and handling system events.

Create electron-main.js with the following code:

const url = require("url");
const path = require("path");
const { app, BrowserWindow } = require("electron");

let win;

function createWindow() {
  win = new BrowserWindow({ width: 800, height: 600 });

  win.loadURL(
    url.format({
      pathname: path.join(__dirname, "index.html"),
      protocol: "file:",
      slashes: true
    })
  );

  win.on("closed", () => {
    win = null;
  });
}

app.on("ready", createWindow);

app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit();
  }
});

app.on("activate", () => {
  if (win === null) {
    createWindow();
  }
});

This code was taken from Electron's quick start guide, read their documentation to get familiar with the available APIs and options.

Update the optimize build script

Once steal-electron is installed and saved in your package.json file, update optimize.js with the following code:

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

var electronOptions = {
  buildDir: "./build",
  platforms: ["darwin"],
  main: "electron-main.js",
  electronVersion: "1.6.11",
  files: ["dist/**/*", "index.html"]
};

stealTools
  .optimize()
  .then(function(buildResult) {
    return stealElectron(electronOptions, buildResult);
  });

Read steal-electron documentation to get familiar with all the available options.

Run the build script with:

> node optimize.js

Then open the generated application file by running the following command:

open build/myhub-darwin-x64/myhub.app

screen shot 2017-08-04 at 16 05 41

Congrats, you just created a desktop application!