StealJS 2.0 Released

Matthew Phillips

StealJS 2.0 is out, bringing best-in-class tree shaking support, native promises used by default, simplified demo pages, increased dev code removal, and an eye towards the native-module future. Continue reading to learn more.

posted in StealJS ,DoneJS ,Development on July 24, 2018 by Matthew Phillips


StealJS 2.0 Released

Matthew Phillips by Matthew Phillips

StealJS 2.0 is out and available on npm! 🎆 Check out the migration guide to help you upgrade.

This release includes:

StealJS' mission is to make it cheap and easy to do the right thing. Doing the right thing, when building for the web, includes things such as writing tests and breaking your applications into smaller mini-applications (modlets) that can be composed together.

Steal 2.0 expands on this mission while minimizing the number of changes you need to make to your app. Even for big apps the upgrade can be done in an afternoon.

Like other DoneJS projects, we added these features based on our community survey results.

 

Tree Shaking

This has been the top requested feature from the community survey for quite a while, and something we get asked about in Gitter, at meetups, and anywhere else we are discussing DoneJS.

Tree Shaking is a bundling optimization, a form of dead code removal, that examines a dependency graph based on the use of exports. When it encounters an unused export (one that is not used by any parent modules) it can remove that code. The follow example has code that can be removed:

math.js

export function add(a, b) {
  return a + b;
};

export function subtract(a, b) {
  return b - a;
};

 

main.js

import { add } from './math';

add(2 ,3);

 

In the above example, StealJS will perform the following steps:

  1. Examine math.js and see that it exports add and subtract.
  2. Walk up the parents of math.js, in this case only main.js, and see which of those functions are used.
  3. Since subtract is not used, its code, and any code it depends on that is not used elsewhere, can be removed.

The final bundled output will be something like:

define("math", [], function(exports, module, require){
  exports.add = function(a, b) {
    return a + b;
  };
});

define("main", ["./math"], function(exports, module, require){
  var _add = require("./math").add;

  _add(2, 3);
});

 

StealJS does tree shaking both in the client (in steal), and during the build (with steal-tools). We do tree shaking in the client to avoid loading entire modules, sometimes entire packages, that are not used by an application.

This is how StealJS is able to tree-shake CanJS. The can package contains a module that re-exports from a bunch of sub-packages. It looks a little like:

can.js

export { default as Component } from "can-component";
export { default as DefineMap } from "can-define/map/map";
export { default as stache } from "can-stache";
export { default as fixture } from "can-fixture";

Our app then uses it:

main.js

import { Component } from "can";

Component.extend({
  tag: 'my-app',
  view: `Hello, this is an app`,
  ViewModel: {}
});

 

Here we can see that only Component is used, which means only the can-component package is used.

Steal is able to see this and recompile can.js to be:

export { default as Component } from "can-component";

This is a big win, saving us from having to fetch the package.json, the main, and likely many other modules from each of those unused packages.

Later, if another parent of can is detected, steal will re-perform the same operation and, if needed, recompile and re-execute the can.js module.

Without tree-shaking the above example would result in an optimized build output of 134kb. With tree-shaking it comes to 60.9kb; that’s less than half the size!

 

Native Promises

More and more teams have dropped support for IE and only support browsers supporting native Promises. Since the Promise polyfill included in steal.js in 1.x was quite large, we added the steal-sans-promises.js script in steal 1.5.

In 2.0 we thought it would be a good time to flip this; now steal.js does not contain the Promise polyfill and we’ve created steal-with-promises.js which does. All of our documentation and examples use steal.js since we assume most people getting started are using modern browsers for development.

If you want to support IE11, just change your script tag to use the new promises-included script:

<script src="./node_modules/steal/steal-with-promises.js" main="~/app">
</script>

Likewise, when you build out your project with steal-tools it will no longer include the version of steal that contains the Promise polyfill, so if you need that you can add this flag to your build options:

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

stealTools.build({}, {
  bundlePromisePolyfill: true
});

 

Support for .mjs extension

Now that native modules have landed in browsers, we're starting to see some libraries ship native module compatible builds with the .mjs extension. This article explains the reasoning behind the new extension in detail. Google's Chrome team also recommends using this extension on the web to differentiate module from non-module scripts.

We are planning on making StealJS work directly with native modules in the future, but in the meantime steal 2.0 can now import modules with the .mjs extension:

import * as math from "./math.mjs";

math.add(2, 3);

 

Simplified demo pages

Steal has always automatically loaded the main module when it boots up. This makes getting started super simple: just add a script tag pointing to steal.js. However once applications grow and you add more and more pages, most pages are not utilizing the app’s main,. To prevent loading the main module, ou would need to do weird things like:

<script src="node_modules/steal/steal.js" main="@empty"></script>

Here @empty is a special module defined in steal; it's essentially a noop. Once you understand that it makes sense but is a bit difficult to explain to new users.

With that being the case Steal 2.0 no longer automatically loads the main module. We feel that sacrificing a tiny bit of DX in getting started is worth it to make things easier once your app grows. And this makes things a bit more consistent; steal only loads the config by default now. You have to tell it what you want to load. You can do that by:

Providing a main

Explicitly specifying a module to load:

<script src="node_modules/steal/steal.js" main="~/app"></script>

Or using the new main boolean attribute to load the package.json main:

<script src="node_modules/steal/steal.js" main></script>

Using a steal-module

<script src="node_modules/steal/steal.js"></script>
<script type="steal-module">
import { Component } from "framework";
// ...
</script>

Use the dynamic import API

<script src="node_modules/steal/steal.js"></script>
<script>
steal.import("~/app").then(function() {
  // ...
});
</script>

 

Removal of development code

steal-tools will already remove development code that uses steal-remove-start/end comments like so:

//!steal-remove-start
console.warn("Don't do that.");
//!steal-remove-end

However this only works in steal. Many frameworks such as React use a different approach. They check the process.env.NODE_ENV global like so:

if(process.env.NODE_ENV !== "production") {
  console.warn("Don't do that.");
}

This is supported in steal-tools 1.x but you need to pass the --envify flag in order to enable it. Because this is so widely used we thought it would be a good idea to enable it by default in steal-tools 2.0, so we did!

What’s Next?

This is an important release of StealJS by making defaults out of some of the recent features we have recently completed. The next version of steal and steal-tools will likely be a much bigger change, but we’re still thinking about the direction it should go.

In the meantime with StealJS 2 and CanJS 5 out, we need a new release of DoneJS supporting all of these. Look for DoneJS 3 in the near future, to include:

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