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
const a = {x: 1};
const b = {...a};
src/cd.ts
const c = {x: 1};
const d = {...c};
dist/ab.js
var __assign = /* Somewhat long function */
var a = { x: 1 };
var b = __assign({}, a);
dist/cd.js
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
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 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
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
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
export const assign = /* implementation */
src/ab.ts
import { assign } from './helpers';
const __assign = assign;
const a = {x: 1};
const b = {...a};
dist/ab.js
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. 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 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?
Top comments (0)