Optimizing bundle size with TSLib helper functions
Tags: typescript, javascript, webdev
Published: 2021-01-27 17:31:57

# Who may find it useful
The post describes techniques useful for libs creators. For client apps `importHelpers` (see below) is just fine.

# An issue with helper functions
When transpiling a modern TS code into ES5, the compiler emits helper functions like `__assign`, `__extend`, `__importStar` etc. to emulate ES6+ features in ES5.

### Good news 🙂
A helper function defined once may be used several times in a file.

### Bad news 😥
If multiple files need the same function, it's inserted into each of them. Let's see:

#### src/ab.ts
```typescript
const a = {x: 1};
const b = {...a};
```
#### src/cd.ts
```typescript
const c = {x: 1};
const d = {...c};
```
#### dist/ab.js
```javascript
var __assign = /* Somewhat long function */
var a = { x: 1 };
var b = __assign({}, a);
```
#### dist/cd.js
```javascript
var __assign = /* Damn! Literally the same function 😥 */
var c = { x: 1 };
var d = __assign({}, c);
```

# So what to do with helper functions? First, official ways
### 1. [Import helpers](https://www.typescriptlang.org/tsconfig#importHelpers)
With this option you need declaring a (peer-)dependency on `tslib`. Now, instead of emitting function bodies, TS will just import them from `tslib`.

The lib [is quite small](https://bundlephobia.com/result?p=tslib@2.1.0) yet it's tree-shakeable which means good bundler won't use any unneeded functions. But if you are authoring the lib you can't be sure: perhaps the user doesn't have any bundler at all 😕 Yet, for small libs having no deps looks more attractive.

### 2. [No emit helpers](https://www.typescriptlang.org/tsconfig#noEmitHelpers)
With this option TS emits neither helpers bodies nor their imports — it implies all needed helpers are available globally, i.e. you have somewhere like

```javascript
var globalObj = typeof window === 'object' ? window : global;
globalObj.__assign = /* ... */
```

That's not fun 😥 What good is polluting the global scope with your lib internals?

# Defining helpers in a module — not official way
I find this way the best for libs; however, because it's not official, I should warn you it's a bit fragile — it may stop working in some TS version. Hope that times there will be a better official way 😉

The trick works with `noEmitHelpers` but you don't put anything into the global object — you just create module-scoped variable with the name TS expects, and assign it the function imported from wherever you want.

#### src/helpers.ts
```typescript
export const assign = /* implementation */
```

#### src/ab.ts
```typescript
import { assign } from './helpers';
const __assign = assign;
const a = {x: 1};
const b = {...a};
```

#### dist/ab.js
```javascript
var helpers_1 = require("./helpers");
var __assign = helpers_1.assign;
var a = { x: 1 };
var b = __assign({}, a);
```

# Voila!
* Helper function is defined exactly once
* Your lib doesn't have any deps and doesn't imply bundler
* You don't pollute the global scope

# Notes

#### Renaming imported object
It would be nice to have just `import { __assign } from './helpers'` or `import __assign from './helpers'` but, unfortunately, this doesn't work for CommonJS — objects are imported under generated names. So, `const __x = x` is unavoidable.

#### When it may break?
The trick works because TS doesn't change the name of `const __assign`. Is it reliable? I don't know. Do you?

#### Where to take helper functions implementations?
You may just copy them from [tslib](https://github.com/microsoft/tslib/). It's 0BSD-licensed which doesn't even require attribution; but having the link somewhere in your comments is at least polite 🙂

#### Example
I applied this approach in my [Fluent Streams](https://github.com/aleksei-berezkin/fluent-streams) lib, and that's not the only bundle-size-trick I used here. The next post is coming 😄

____

*Thanks for reading this. Do you know some tricks to easily reduce bundle size?*