Optimizations#

Aside from just bundling your module into different formats, DTS comes with some optimizations for your convenience. They yield objectively better code and smaller bundle sizes.

After DTS compiles your code with TypeScript, it processes your code with 3 Babel plugins:

Dev-only Expressions#

babel-plugin-annotate-pure-calls + babel-plugin-dev-expressions work together to fully eliminate dead code (aka treeshake) development checks from your production code. Let's look at an example to see how it works.

Imagine our source code is just this:

tsx
1
// ./src/index.ts
2
export const sum = (a: number, b: number) => {
3
if (process.env.NODE_ENV !== 'production') {
4
console.log('Helpful dev-only error message');
5
}
6
return a + b;
7
};
js
1
// Entry File
2
// ./dist/index.js
3
'use strict';
4
5
// This determines which build to use based on the `NODE_ENV` of your end user.
6
if (process.env.NODE_ENV === 'production') {
7
module.exports = require('./mylib.cjs.production.js');
8
} else {
9
module.exports = require('./mylib.cjs.development.js');
10
}
js
1
// CommonJS Development Build
2
// ./dist/mylib.cjs.development.js
3
'use strict';
4
5
const sum = (a, b) => {
6
{
7
console.log('Helpful dev-only error message');
8
}
9
10
return a + b;
11
};
12
13
exports.sum = sum;
14
//# sourceMappingURL=mylib.cjs.development.js.map
js
1
// CommonJS Production Build
2
// ./dist/mylib.cjs.production.js
3
'use strict';
4
exports.sum = (s, t) => s + t;
5
//# sourceMappingURL=test-react-dts-cli.cjs.production.js.map

As you can see, DTS stripped out the development check from the production code. This allows you to safely add development-only behavior (like more useful error messages) without any production bundle size impact.

For ESM build, it's up to end-user to build environment specific build with NODE_ENV replace (done by Webpack 4 automatically).

Rollup Treeshaking#

DTS's rollup config removes getters and setters on objects so that property access has no side effects. Don't do it.

Advanced babel-plugin-dev-expressions#

DTS will use babel-plugin-dev-expressions to make the following replacements before treeshaking.

__DEV__

Replaces

ts
1
if (__DEV__) {
2
console.log('foo');
3
}

with

js
1
if (process.env.NODE_ENV !== 'production') {
2
console.log('foo');
3
}

IMPORTANT: To use __DEV__ in TypeScript, you need to add declare var __DEV__: boolean somewhere in your project's type path (e.g. ./types/index.d.ts).

ts
1
// ./types/index.d.ts
2
declare var __DEV__: boolean;

invariant

Replaces

js
1
invariant(condition, 'error message here');

with

js
1
if (!condition) {
2
if ('production' !== process.env.NODE_ENV) {
3
invariant(false, 'error message here');
4
} else {
5
invariant(false);
6
}
7
}

Note: DTS doesn't supply an invariant function for you, you need to import one yourself. We recommend https://github.com/alexreardon/tiny-invariant.

To extract and minify invariant error codes in production into a static codes.json file, specify the --extractErrors flag in command line. For more details see Error extraction docs.

warning

Replaces

js
1
warning(condition, 'dev warning here');

with

js
1
if ('production' !== process.env.NODE_ENV) {
2
warning(condition, 'dev warning here');
3
}

Note: DTS doesn't supply a warning function for you, you need to import one yourself. We recommend https://github.com/alexreardon/tiny-warning.

Using lodash#

If you want to use a lodash function in your package, DTS will help you do it the right way so that your library does not get fat shamed on Twitter. However, before you continue, seriously consider rolling whatever function you are about to use on your own. Anyways, here is how to do it right.

First, install lodash and lodash-es as dependencies

bash
1
yarn add lodash lodash-es

Now install @types/lodash to your development dependencies.

bash
1
yarn add @types/lodash --dev

Import your lodash method however you want, DTS will optimize it like so.

tsx
1
// ./src/index.ts
2
import kebabCase from 'lodash/kebabCase';
3
4
export const KebabLogger = (msg: string) => {
5
console.log(kebabCase(msg));
6
};

For brevity let's look at the ES module output.

js
1
import o from"lodash-es/kebabCase";const e=e=>{console.log(o(e))};export{e as KebabLogger};
2
//# sourceMappingURL=test-react-dts-cli.esm.production.js.map

DTS will rewrite your import kebabCase from 'lodash/kebabCase' to import o from 'lodash-es/kebabCase'. This allows your library to be treeshakable to end consumers while allowing to you to use @types/lodash for free.

Error extraction#

After running --extractErrors, you will have a ./errors/codes.json file with all your extracted invariant error codes. This process scans your production code and swaps out your invariant error message strings for a corresponding error code (just like React!). This extraction only works if your error checking/warning is done by a function called invariant.

Note: We don't provide this function for you, it is up to you how you want it to behave. For example, you can use either tiny-invariant or tiny-warning, but you must then import the module as a variable called invariant and it should have the same type signature.

⚠️Don't forget: you will need to host the decoder somewhere. Once you have a URL, look at ./errors/ErrorProd.js and replace the reactjs.org URL with yours.

TODO: Simple guide to host error codes to be completed

Optimizations