BattlefyBlogHistoryOpen menu
Close menuHistory

Removing assertions from production build with Vite

Ronald ChenJuly 25th 2022

Previously we talked about refactoring lying TypeScript type assertions into real assertions. But we left out one critical piece.

Assertions bloat the JavaScript bundle

As one creates a rich library of assertions and use them liberally throughout a project, this increases the JavaScript bundle size. Unfortunately, these assertions remain even in the production build. Worse, since assertions don't have a return value, they are strictly worthless in production.

Assertions can be safely removed

The assertions have done their job by the time the code path was run during development. If there was any violation, the assertion would have already tripped. This means it is safe to remove them from the production build.

We are not relying on assertions for correctness, only to detect issues. Issues which are no longer possible in production.

The problem

Here is a short example that highlights the problem. Our sample problem contains a single usage of assertInstanceOf.

function assertInstanceOf<T>(
  value: any,
  expectedClass: new () => T
): asserts value is T {
  if (!(value instanceof expectedClass)) {
    throw new Error(
      `Expected value to be a ${expectedClass.name}, but was ${value.constructor.name}`
    );
  }
}

document.body.insertAdjacentHTML('beforeend', '<h1>Hello</h1>');

const main = document.querySelector('h1');
assertInstanceOf(main, HTMLHeadingElement);
main.textContent += ' world!';

After Vite performs a build, we can inspect the output. Note, minification has been turned off to make it easier to understand what Vite is doing.

function assertInstanceOf(value, expectedClass) {
  if (!(value instanceof expectedClass)) {
    throw new Error(
      `Expected value to be a ${expectedClass.name}, but was ${value.constructor.name}`
    );
  }
}

document.body.insertAdjacentHTML("beforeend", "<h1>Hello</h1>");
const main = document.querySelector("h1");
assertInstanceOf(main, HTMLHeadingElement);
main.textContent += " world!";

Vite has transpiled TypeScript down to JavaScript, however assertInstanceOf is still present.

Vite will tree shake dead code, thus we need to some how turn assertInstanceOf into dead code. The simplest way to achieve this is to use Vite environment variables.

Tree shaking with Vite environment variables

We can use import.meta.env.PROD which is false during development and true during production build. But the thing to remember is these are not normal JavaScript variables. The entire expression is statically replaced during the build. That is a word salad but will become clear with a simple example.

When we add this single one line at the beginning of assertInstanceOf:

function assertInstanceOf<T>(...): asserts value is T {
  if (import.meta.env.PROD) return;
  ...
}

When Vite does a production build, import.meta.env.PROD is statically replaced into true. Thus the code becomes:

function assertInstanceOf<T>(...): asserts value is T {
  if (true) return;
  ...
}

import.meta.env.PROD doesn't actually exist at runtime!

This means in a production build, the rest of the body of assertInstanceOf is dead code. Furthermore, we can see the entire assertInstanceOf function is dead code, therefore we can remove calls to it as well!

Finally, a fully tree shaken code becomes:

document.body.insertAdjacentHTML("beforeend", "<h1>Hello</h1>");
const main = document.querySelector("h1");
main.textContent += " world!";

Full demo

Are you interested in optimizing production builds? You're in luck, Battlefy is hiring.

2022

Powered by
BATTLEFY