The Modlet Workflow: Improve Your Development Workflow with StealJS

You've been convinced of the benefits the modlet workflow provides and you want to start building your components with their own test and demo pages. Whether you're starting a new project or updating your current one, you need a module loader and bundler that doesn't require build configuration for every test and demo page you want to make.

StealJS is the answer. It can load JavaScript modules in any format (AMD, CJS, etc.) and load other file types (Less, TypeScript, etc.) with plugins. It requires minimum configuration and unlike webpack, it doesn't require a build to load your dependencies in development. Last but not least, you can use StealJS with any JavaScript library or framework, including CanJS, React, Vue, etc.

In this tutorial, we're going to add StealJS to a project, create a component with Preact, create an interactive demo page, and create a test page.

Article Series:

  1. The Key to Building Large JavaScript Apps: The Modlet Workflow
  2. Improve Your Development Workflow with StealJS (You are here!)

1. Creating a new project

If you already have an existing Node.js project: great! You can skip to the next section where we add StealJS to your project.

If you don't already have a project, first make sure you install Node.js and update npm. Next, open your command prompt or terminal application to create a new folder and initialize a `package.json` file:

mkdir steal-tutorial
cd steal-tutorial
npm init -y

You'll also need a local web server to view static files in your browser. http-server is a great option if you don't already have something like Apache installed.

2. Add StealJS to your project

Next, let's install StealJS. StealJS is comprised of two main packages: steal (for module loading) and steal-tools (for module bundling). In this article, we're going to focus on steal. We're also going to use Preact to build a simple header component.

npm install steal preact --save

Next, let's create a `modlet` folder with some files:

mkdir header && cd header && touch demo.html demo.js header.js test.html test.js && cd ..

Our `header` folder has five files:

  • demo.html so we can easily demo the component in a browser
  • demo.js for the demo's JavaScript
  • header.js for the component's main JavaScript
  • test.html so we can easily test the component in a browser
  • test.js for the test's JavaScript

Our component is going to be really simple: it's going to import Preact and use it to create a functional component.

Update your `header.js` file with the following:

import { h, Component } from "preact";

export default function Header() {
  return (
    <header>
      <h1>{this.props.title}</h1>
    </header>
  );
};

Our component will accept a title property and return a header element. Right now we can't see our component in action, so let's create a demo page.

3. Creating a demo page

The modlet workflow includes creating a demo page for each of your components so it's easier to see your component while you're working on it without having to load your entire application. Having a dedicated demo page also gives you the opportunity to see your component in multiple scenarios without having to view those individually throughout your app.

Let's update our `demo.html` file with the following so we can see our component in a browser:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Header Demo</title>
  </head>
  <body>
    <form>
      <label>
        Title
        <input autofocus id="title" type="text" value="Header component" />
      </label>
    </form>
    <div id="container"></div>
    <script src="../node_modules/steal/steal.js" main="header/demo"></script>
  </body>
</html>

There are three main parts of the body of our demo file:

  • A form with an input so we can dynamically change the title passed to the component
  • A #container for the component to be rendered into
  • A script element for loading StealJS and the demo.js file

We've added a main attribute to the script element so that StealJS knows where to start loading your JavaScript. In this case, the demo file looks for `header/demo.js`, which is going to be responsible for adding the component to the DOM and listening for the value of the input to change.

Let's update `demo.js` with the following:

import { h, render } from 'preact';
import Header from './header';

// Get references to the elements in demo.html
const container = document.getElementById('container');
const titleInput = document.getElementById('title');

// Use this to render our demo component
function renderComponent() {
  render(<Header title={titleInput.value} />, container, container.lastChild);
}

// Immediately render the component
renderComponent();

// Listen for the input to change so we re-render the component
titleInput.addEventListener('input', renderComponent);

In the demo code above, we get references to the #container and input elements so we can append the component and listen for the input's value to change. Our renderComponent function is responsible for re-rendering the component; we immediately call that function when the script runs so the component shows up on the page, and we also use that function as a listener for the input's value to change.

There's one last thing we need to do before our demo page will work: set up Babel and Preact by loading the transform-react-jsx Babel plugin. You can configure Babel with StealJS by adding this to your `package.json` (from Preact's docs):

  ...

  "steal": {
    "babelOptions": {
      "plugins": [
        ["transform-react-jsx", {"pragma": "h"}]
      ]
    }
  },

  ...

Now when we load the `demo.html` page in our browser, we see our component and a form to manipulate it:

Great! With our demo page, we can see how our component behaves with different input values. As we develop our app, we can use this demo page to see and test just this component instead of having to load our entire app to develop a single component.

4. Creating a test page

Now let's set up some testing infrastructure for our component. Our goal is to have an HTML page we can load in our browser to run just our component's tests. This makes it easier develop the component because you don't have to run the entire test suite or litter your test code with .only statements that will inevitably be forgotten and missed during code review.

We're going to use QUnit as our test runner, but you can use StealJS with Jasmine, Karma, etc. First, let's install QUnit as a dev-dependency:

npm install qunitjs --save-dev

Next, let's create our `test.html` file:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Header Test</title>
  </head>
  <body>
    <div id="qunit"></div>
    <div id="qunit-fixture"></div>
    <script src="../node_modules/steal/steal.js" main="header/test"></script>
  </body>
</html>

In the HTML above, we have a couple of div elements for QUnit and a script element to load Steal and set our `test.js` file as the main entry point. If you compare this to what's on the QUnit home page, you'll notice it's very similar except we're using StealJS to load QUnit's CSS and JavaScript.

Next, let's add this to our `test.js` file:

import { h, render } from 'preact';
import Header from './header';
import QUnit from 'qunitjs';
import 'qunitjs/qunit/qunit.css';

// Use the fixture element in the HTML as a container for the component
const fixtureElement = document.getElementById('qunit-fixture');

QUnit.test('hello test', function(assert) {
  const message = 'Welcome to your first StealJS and React app!';

  // Render the component
  const rendered = render(<Header title={message} />, fixtureElement);

  // Make sure the right text is rendered
  assert.equal(rendered.textContent.trim(), message, 'Correct title');
});

// Start the test suite
QUnit.start();

You'll notice we're using Steal to import QUnit's CSS. By default, StealJS can only load JavaScript files, but you can use plugins to load other file types! To load QUnit's CSS file, we'll install the steal-css plugin:

npm install steal-css --save-dev

Then update Steal's `package.json` configuration to use the steal-css plugin:

{
  ...
  "steal": {
    "babelOptions": {
      "plugins": [
        ["transform-react-jsx", {"pragma": "h"}]
      ]
    },
    "plugins": [
      "steal-css"
    ]
  },
  ...
}

Now we can load the test.html file in the browser:

Success! We have just the tests for that component running in our browser, and QUnit provides some additional filtering features for running specific tests. As you work on the component, you can run just that component's tests, providing you earlier feedback on whether your changes are working as expected.

Additional resources

We've successfully followed the modlet pattern by creating individual demos and test pages for our component! As we make changes to our app, we can easily test our component in different scenarios using the demo page and run just that component's tests with the test page.

With StealJS, a minimal amount of configuration was required to load our dependencies and create our individual pages, and we didn't have to run a build each time we made a change. If you're intrigued by what else it has to offer, StealJS.com has information on more advanced topics, such as building for production, progressive loading, and using Babel. You can also ask questions on Gitter or the StealJS forums!

Thank you for taking the time to go through this tutorial. Let me know what you think in the comments below!

Article Series:

  1. The Key to Building Large JavaScript Apps: The Modlet Workflow
  2. Improve Your Development Workflow with StealJS (You are here!)