Favor functions over classes for better minification
As part of the ES2015 specification, JavaScript received support for the class
syntax. This syntax provides familiar
object oriented patterns for authoring JavaScript web apps.
Furthermore, with popular transpilation tools like Babel and TypeScript, the JavaScript class
syntax has become
even more accessible, as it can be easily downleveled for older browsers.
Before you start authoring more JavaScript classes in your codebase, you should understand the performance impact they will have on your web app via bloating resource and transfer sizes.
In this tip, we'll discuss why using JavaScript class
syntax may increase your final payload size, and why you
are likely better off using ordinary function
blocks.
Prerequisites
- You should understand Resource Size and Transfer Size
- You should understand basic JavaScript Minification techniques
An Example Class
Let's start by writing an example class
, called DataFetcher
:
// File DataFetcher.js
class DataFetcher {
constructor(baseUrl) {
this.baseUrl = baseUrl;
}
fetchSomething() {
return fetch(`${baseUrl}/something.json`);
}
fetchSomethingElse() {
return fetch(`${baseUrl}/somethingElse.json`);
}
}
This class
is pretty straightforward -- it exposes methods for acquiring data from a remote endpoint.
We would use it in code like this:
import { DataFetcher } from './DataFetcher';
// ...
const dataFetcher = new DataFetcher('https://www.webperf.tips');
const something = await dataFetcher.fetchSomething();
Minifying DataFetcher
If you applied a standard Terser minifier to our code, it would minify like this:
class a{constructor(e){this.baseUrl=e}fetchSomething(){return fetch(`${baseUrl}/something.json`)}fetchSomethingElse(){return fetch(`${baseUrl}/somethingElse.json`)}}
If we applied beautification to this to make it more readable, it would look like this:
class a {
constructor(e) {
this.baseUrl = e
}
fetchSomething() {
return fetch(`${baseUrl}/something.json`)
}
fetchSomethingElse() {
return fetch(`${baseUrl}/somethingElse.json`)
}
}
Notice, that there is minimal difference from our original code. Notably:
- The
DataFetcher
class
gets renamed toa
- The
constructor
argumentbaseUrl
gets minified toe
The Problem
Ordinary minifiers like Terser only apply safe transformations to source code.
Mangling and Compression
Code that utilizes class
prevents a minifier from safely mangling or compressing:
- Members on
this
within a class. In this example,this.baseUrl
. - Any method name. In this example,
fetchSomething()
andfetchSomethingElse()
Tree shaking
Furthermore, minifiers cannot tree shake methods within a class. This is because
tree shaking operates at the import
and export
level.
While tree shaking can shake out entire unused classes, it cannot shake out unused methods within classes.
As a result, a class with many methods will not only minify poorly, but also likely bring in extraneous methods into the final payload.
The Solution: Functions
If you can, try to use export function
instead of classes. Let's convert the above example into something utilizing ordinary function
blocks:
// File DataFetcher.js
export function fetchSomething(baseUrl) {
return fetch(`${baseUrl}/something.json`)
}
export function fetchSomethingElse(baseUrl) {
return fetch(`${baseUrl}/somethingElse.json`)
}
Let's consider the above JavaScript code in the following snippet:
import { fetchSomething } from './DataFetcher';
// ...
const something = await fetchSomething('https://www.webperf.tips');
If we applied Terser-based resource minification, we would see the following results for DataFetcher
:
function a(n){return fetch(`${n}/something.json`)}
Applying beautification to make it slightly more readable, we see:
function a(n) {
return fetch(`${n}/something.json`)
}
Notice how it's significantly smaller than our class
based DataFetcher
!
Notably:
- The unused
fetchSomethingElse
function has been removed via tree shaking - The function name
fetchSomething
is mangled / compressed toa
- The function argument is mangled / compressed to
n
- There is no un-minifiable assignment to
this
, likethis.baseUrl
A note on Closure
The Closure Compiler in Advanced mode is able to minify classes more aggressively.
With proper Closure minification, our DataFetcher
class would have its members and methods all minified.
That said, it has more onboarding overhead and requires strict source code-level rules that must be followed.
In my experience, onboarding to Closure is more trouble than simply using function
exports, and the results produced are similar.
Conclusion
Although using JavaScript class
is familiar and convenient, overusing class
can lend itself to large, poorly minified payloads
containing extraneous code.
If possible, author JavaScript using export function
blocks to maximize your minifier's potential.
That's all for this tip! Thanks for reading! Discover more similar tips matching JS Optimization.