How To Conditionally Load Modules with StealJS

Manuel Mujica by Manuel Mujica

How To Conditionally Load Modules with StealJS

Manuel Mujica Learn how to localize your app and load polyfills for only the browsers that need them with steal-conditional.

posted in Open Source ,Development ,StealJS on January 24, 2017 by Manuel Mujica

StealJS makes it easy to bundle all of your JavaScript modules into highly efficient production builds, but sometimes you don’t want to bundle everything together. There are times when a module only needs to be loaded when certain conditions are met; it might be a polyfill for a browser without support for a new API or a translation file based on the user’s preferred language.

Traditionally, conditional module loading involves hacking around a module loader’s configuration options, a complicated build process, and brittle code that’s hard to maintain. steal-conditional provides a declarative syntax that makes conditional module loading easy to use.

In this article, we will:

  • Compare loading modules in webpack (without steal-conditional) to StealJS with steal-conditional
  • Use steal-conditional’s two syntaxes to load polyfills and perform internationalization, and
  • Look at how to create a production build with conditional modules.

Without steal-conditional

Let’s take a look at an example of loading a JavaScript Internationalization API polyfill using webpack.

The application entry point module initially looks like this:

var React = require('react');
var App = require('./App');
var today = new Intl.DateTimeFormat().format(new Date()); React.render(<App today={today} />, document.body);

Nothing fancy here; however, in order to make sure the polyfill is loaded before the application code is executed, the following changes are needed:

function run() {
  var React = require('react');
  var App = require('./App');

  var today = new Intl.DateTimeFormat().format(new Date());
  React.render(<App today={today} />, document.body);
}

// check if polyfill is required
if (!window.Intl) {
  require.ensure([], () => {
    // import the polyfill
    require('intl');

    // then, run the app
    run();
  });
} else {
  // polyfill is not needed, run the app
  run();
}    

This might not look terrible at all, but the devil is in the details: what used to be a simple and synchronous module now includes asynchronous code handling, which is harder to debug and the if/else conditional statement makes the code harder to read and maintain.

It’s also worth pointing out that in most real world applications, the initialization code is a lot more complex, which makes a refactor like this troublesome and prone to cause unexpected bugs.

With steal-conditional

import React from 'react';
import App from './App';

// conditionally import the Intl API only if needed
import 'intl#?requires-polyfill';

var today = new Intl.DateTimeFormat().format(new Date());
React.render(<App today={today} />, document.body);

If you are wondering what’s the magic behind that strange looking import statement, keep reading!

What’s steal-conditional?

steal-conditional is a StealJS extension that provides a declarative syntax which delays module loading to runtime. It integrates with the steal-tools compiler to provide a much better bundling experience without needing extra configuration.

Let’s take a look at the import statement introduced in the example above:

import 'intl#?requires-polyfill';

Wait, what’s that “#?” in the module identifier? Is it a typo?

I’m glad you asked! That’s the boolean conditional syntax, which is ideal for loading polyfills or any other module that either is or is not needed at runtime.

The boolean conditional syntax

Polyfills have become an essential tool to fill in the feature gap between older and newer browser versions, but we only want to load them when they’re needed so we can keep our application code as small as possible.

Let's see how to achieve this using steal-conditional:

import 'intl#?requires-polyfill';

var today = new Intl.DateTimeFormat().format(new Date());

Separated by #? there are two module names on the import statement above: the one on the left, intl, is the JavaScript Internationalization API package name; on the right, requires-polyfill is the condition module name. StealJS will import the condition module first, read its default export value, then proceed to load intl only if the exported value is true.  

steal-conditional-1.pngYou can find the code of an example app using the boolean syntax in this Github repository.

Steal-conditional also supports the string substitution syntax, which is intended for use cases such as internationalization, where multiple modules are available but only one of them is needed at runtime.

The string substitution syntax

Internationalization and environment-dependent configurations are slightly different use cases for conditional module loading. Unlike polyfills, there are usually several modules available to load (e.g. one for each language/country where your application is available) and most of the time, only one of the modules is needed during runtime (e.g. the user's preferred language translation).

import translation from '~/langs/#{lang}';

In this case, the exported value of the condition module (the one inside the curly braces) is used to determine the name of the module that's going to be loaded during runtime. StealJS will load the module lang first, read its default export, then use the value to replace the condition placeholder, #{lang}. The resulting module name will be imported.

Assuming the module lang exports the string en-US, after the string substitution happens, StealJS will load the module with the name langs/en-US.

Note: the tilde at the start of the module identifier is not part of the string substitution syntax but a lookup scheme introduced in StealJS 1.0.

Screen Shot 2017-01-17 at 5.25.23 PM.png

You can find the code of an example app using the string substitution syntax in this Github repository.

Taming the conditional module build

One of StealJS’s goals is to provide fast, reliable and hassle-free builds. There is no configuration needed in order to build an application that conditionally loads modules through steal-conditional; The process can be summarized in the following steps:

  1. Add the condition module to the main bundle.
  2. Glob the file system to detect string substitution variations.
  3. Create a separate bundle for each conditionally loaded module.

Please note that step 2 only applies for applications using the string substitution syntax. Let’s imagine for a moment that your application has the file structure shown below: 

├── index.html
├── index.js
├── lang.js
├── locale
│   ├── ar.js
│   ├── en.js
│   ├── es.js
│   ├── hi.js
│   └── zh.js
└── package.json

index.js is the entry point of the application (the main module) and it has a conditional import that looks like this:

import translations from '~/locale/#{lang}';

Steal-tools will generate the following build artifacts based on the steps mentioned before:

dist
├── bundles
│   └── substitution-example
│       ├── index.js
│       └── locale
│           ├── ar.js
│           ├── en.js
│           ├── es.js
│           ├── hi.js
│           └── zh.js
└── steal.production.js

It’s worth pointing out that the only thing left to get the application ready for production is to make sure steal.production.js is loaded in index.html; no hacking around configuration options, and no complex build scripts required. 

<!doctype html>
<html>
 <body>
   <script src="./dist/steal.production.js"></script>
 </body>
</html>

Try it out today!

If you’re already using StealJS, take a look at the docs for steal-conditional. There are also two example apps to show you how to use steal-conditional: boolean example and substitution example.

If you’re not already using StealJS, take a look at the Quick Start Guide to get started or read more about why you should use StealJS.

If you have any questions, please join us on Gitter or our forums!

Create better web applications. We’ll help. Let’s work together.