Want to Contribute to us or want to have 15k+ Audience read your Article ? Or Just want to make a strong Backlink?

How to bundle a tree-shakable typescript library with tsup and publish with npm

Header picture is generated by Night Cafe’s AI btw.

Considering of the huge frontend ecosystem, for essentially the most half, I don’t have to implement stuff alone. The vast majority of the time, I google a specific drawback and more often than not I discover a library or stack-overflow reply, Bingo. However there are occasions once I discover myself missing one thing distinctive, whether or not it’s a firm job or a hackathon venture, a holistic strategy, an entire new attractive vibe is required certainly.

Lately I used to be attempting to get my head round sports activities analytics and realized that like nearly all of the info science ecosystem, all instruments are written in python. Whereas I used to be re-studying to sharpen my rusted python abilities (no pun meant 🦀), I additionally tried to do some stuff with JS for enjoyable as nicely, which became to a separate library based mostly on D3. On this weblog put up, I gained’t clarify the library itself as a result of that is one other story (P.S watch this house). However this time I will share a really primary setup to allow you to give you your personal with a minimal of fuss. Github hyperlink on the finish 👇

For a JS/TS library you would wish a transpiler/bundler, a module technique, a broadcast model of your lib, and optionally an illustration app.



Bundler (esbuild & tsup)

esbuild claimed to be the quickest within the city in relation to bundlers, and it appears so in keeping with benchmarks as nicely. Much like Parcel, tsup is a zero-config library(in case you don’t need it) based mostly on esbuild. Parcel is predicated on Rust based mostly swc compiler in the meantime esbuild is predicated on Go. Tsup is tailor-made to work seamlessly with TS. Despite the fact that it’s zero config you may change the configs if want be, so we do. Right here is the config I used for this text.

tsup.config.ts

import kind { Choices } from 'tsup';

const env = course of.env.NODE_ENV;

export const tsup: Choices = {
  splitting: true,
  clear: true, // clear up the dist folder
  dts: true, // generate dts recordsdata
  format: ['cjs', 'esm'], // generate cjs and esm recordsdata
  minify: env === 'manufacturing',
  bundle: env === 'manufacturing',
  skipNodeModulesBundle: true,
  entryPoints: ['src/index.ts'],
  watch: env === 'growth',
  goal: 'es2020',
  outDir: env === 'manufacturing' ? 'dist' : 'lib',
  entry: ['src/**/*.ts'], //embrace all recordsdata underneath src
};
Enter fullscreen mode

Exit fullscreen mode

Right here we’re transpiling all our TS recordsdata into JS with the assistance of tsup‘s config. Afterwards we are going to publish it with npm in JS format with kind declarations in order that each JS and TS apps can use it.

I have to make clear just a few issues although.



splitting

That is flagged an experimental characteristic that solely works for esm proper now. You should specify outDir as nicely. With this characteristic, we’re enabling tree-shaking in order that customers do not should obtain shared bits of code time and again after they swap between totally different pages for instance. Look here for additional data.



skipNodeModulesBundle

Skips constructing dependencies for node modules, and use them as they’re imported. As a library builder, you may even bundle and serve your dependencies however that is means past the scope of this text.



dts

Generates typescript declaration recordsdata (e.g. index.d.ts), helpful if you devour libraries with typescript.



entry

See how we allow all recordsdata to be parsed by tsup utilizing a easy regex sample ['src/**/*.ts']

Remainder of the choices just about does the stuff, because the names indicate



Module Technique

Dealing with modules has been a ache within the neck for JS neighborhood for a protracted whereas. Subsequently, there are some totally different methods if you need to distribute your JS bundles. 4 main modules round are:

  1. CommonJS
  2. AMD: Async Module Definition
  3. UMD: Common Module Definition
  4. ES modules

I will not get into the main points of AMD and UMD on this put up. However I might like to speak about CJS and ESM that are the commonest ones. There’s a distinct distinction between CommonJS and ES modules which is Tree shaking. CommonJS modules are the unique method to bundle JavaScript code for Node.js. Up-to-date variations of node.js assist each. The foremost distinction right here is in a CommonJS module your entire fairly js recordsdata are bundled right into a single file. You may bear in mind the syntax in the event you’ve labored on each ends.

ESM (ES modules)

import {john} from "doe";

export jane;
Enter fullscreen mode

Exit fullscreen mode

CJS (CommonJS)

const john = require("doe")

module.exports = jane;

Enter fullscreen mode

Exit fullscreen mode



Tree-shaking

Tree-shaking is eliminating lifeless code and unused exports out of your bundle. With CJS even in the event you solely going to make use of a easy Card part for example, if it’s not tree-shakable library, it’s good to obtain the entire library and add it to your vendor chunk ultimately. For instance probably the most used utility libraries round is lodash and there was a whole lot of drawback due to its enormous bundle dimension within the past For those who use es modules import/exports that are tree-shakable by its nature, bundlers can detect unused exports. Useless codes are eradicated that means.

You possibly can output totally different module codecs with tsup however cjs file will not be tree-shakable. Do not forget you’re simply making the library tree-shakable. It’s the app that makes use of the library truly shakes and eliminates the lifeless code. However on this article, I need to output two totally different codecs for 2 totally different platforms (internet & node) the previous one can be tree-shakeable. The primary motivation behind it was making a UI library for me however you by no means know which platform your library goes to finish with if it’s not a UI library notably. So why not present it in each codecs only for sake of doing it 🃏 Beneath line will deal with that for us.

format: ['cjs', 'esm']
Enter fullscreen mode

Exit fullscreen mode

We will even change the bundle.json a bit.

  "important": "lib/index.cjs",
  "module": "lib/index.js",
  "sorts": "lib/index.d.ts",
Enter fullscreen mode

Exit fullscreen mode

The module area is used to find out the ES model of the bundle. Additionally, we need to mark our library as an es module. To take action, we’ll add the under line to bundle.json.

"kind": "module",

which impacts tsup compilation as nicely by way of file extensions.

├── index.js          # esm
└── index.cjs         # cjs
Enter fullscreen mode

Exit fullscreen mode

index.js is an es module now. The explanation behind all that is for a UI library we wish its elements/lessons to be importable individually in order that the consumer can import a Part just like the one under in an async method.

image.png

script kind="module" lets the browser deal with this piece of import as a module



Output Directories

For those who ever investigated libraries like Ant Design you may acknowledge that there will likely be a separate lib folder within the bundle, from which you’ll be able to import specific elements from. We’d have each lib and dist libraries. dist would stand for bundle model of your recordsdata and presumably may very well be used if you need to distribute your library over a CDN for instance.



Used and Unused Exports 🤔

Now attempt to have a look at this implementation the opposite means round.

Let’s assume an app has our library as a dependency and this app use webpack or an analogous bundler that is able to tree-shake our library. It could actually detect unused exported modules due to a characteristic referred to as usedExports

However in the event you bundle all of your js right into a single complete bundle will likely be imported anyway. There may be one other idea referred to as sideEffect pops out at this stage. In line with webpack

A “facet impact” is outlined as code that performs a particular habits when imported, apart from exposing a number of exports. An instance of this are polyfills, which have an effect on the worldwide scope and often don’t present an export.

Webpack can determine if it ought to run this optimization by checking a customized area referred to as sideEffects inside bundle.json. Preserving module construction and serving library with tiny modules allow bundlers to remove the lifeless code.
When you have a specific module that you simply need to be bundled whether or not it’s used or not, it is best to add it to the sideEffect checklist. This contains scss or different recordsdata as nicely. A potential sideEffects configuration you could add to your bundle.json is

  "sideEffects": [
    "dist/*",
    "lib/**/style/*",
    "*.scss"
  ],```



Within the above instance, you are telling webpack to not clear modules thought of to be lifeless code and this is sensible as a result of underneath a dist folder presumably you may have a single minified bundle together with all wanted utility features, and so forth. It may be even a CJS module by which tree-shaking isn't potential. The identical is true to your type recordsdata, you presumably want all of them, it could be deceptive to inform webpack to skip the entire module/subtree of scss. 

### The best way to take a look at if `sideEffects`truly works
Principally trying on the bundle. Let's assume a really primary library 

![Screen Shot 2022-09-08 at 23.38.40.png](https://cdn.hashnode.com/res/hashnode/picture/add/v1662669737424/_piV3_JUo.png align="left")

`index.ts`


Enter fullscreen mode

Exit fullscreen mode

import { sum } from ‘./Sum’;

const sampleJson = [
{
id: ‘f5e7457f-467d-4e37-9652-f2fb1b51c712’,
first_name: ‘John’,
last_name: ‘Doe’,
},
];

export { sampleJson, sum };



and inside `Sum` we're simply exporting a primary sum methodology
`Sum/index.ts`


Enter fullscreen mode

Exit fullscreen mode

const sum = (a: quantity, b: quantity) => a + b;

export { sum };



After we transpile with `tsup` all of the recordsdata will likely be added to the bundle. Be aware that we're preserving folder construction underneath lib.

![Screen Shot 2022-09-08 at 23.40.33.png](https://cdn.hashnode.com/res/hashnode/picture/add/v1662669755836/ce6t5YIdq.png align="left")

Let's soar into to the app and domestically hyperlink the library for testing. I will use this webpack starter https://github.com/wbkd/webpack-starter

### Testing with npm hyperlink

The best method to take a look at your library is to register it domestically with `npm hyperlink` or `yarn hyperlink`.




Enter fullscreen mode

Exit fullscreen mode

yarn hyperlink v1.22.19
success Registered “tsup-library-template”.
data Now you can run npm hyperlink "tsup-library-template" within the tasks the place you need to use this bundle and it is going to be used as a substitute.




Then go to the app you need to use the library then `yarn hyperlink <your-library>`


Enter fullscreen mode

Exit fullscreen mode

yarn hyperlink v1.22.19
success Utilizing linked bundle for “tsup-library-template”.
✨ Finished in 0.02s.




That is the quickest method to set you up. If you wish to handle your lib and examples in the identical monorepo take a look on the [yarn workspaces](https://yarnpkg.com/options/workspaces) 

### Let's use the library
Contained in the app we are going to use library like this 

`someApp.js`


Enter fullscreen mode

Exit fullscreen mode

import {sum} from ‘tsup-library-template’;

console.log(sum);



despite the fact that library has two anticipated members we solely used one methodology which is `sum` and that displays on webpack bundler.


`someAppsBundle.js`


Enter fullscreen mode

Exit fullscreen mode

!operate(){“use strict”;console.log(((o,c)=>o+c))}();
//# sourceMappingURL=app.5712c325.js.map




Now import the `sampleJson` and use it as nicely


Enter fullscreen mode

Exit fullscreen mode

import {sum, sampleJson} from ‘tsup-library-template’;

console.log(sum, sampleJson);




Take a look at the bundle generated by the webpack
 it's bigger as a result of it know contains two imported members `sum` and `sampleJson`.


Enter fullscreen mode

Exit fullscreen mode

!operate(){“use strict”;console.log(((e,f)=>e+f),[{id:”f5e7457f-467d-4e37-9652-f2fb1b51c712″,first_name:”John”,last_name:”Doe”}])}();
//# sourceMappingURL=app.b0051b82.js.map




We are able to even solely import a module inside utilizing the under syntax


Enter fullscreen mode

Exit fullscreen mode

import {sum} from ‘tsup-library-template/lib/Sum’;

console.log(sum);



output will likely be similar with the primary instance.


Enter fullscreen mode

Exit fullscreen mode

!operate(){“use strict”;console.log(((o,c)=>o+c))}();
//# sourceMappingURL=app.5712c325.js.map



A few of you would possibly assume at this stage "is not it simply common module import/export". Sure, you're completely proper, the one distinction right here is we let the bundler to try this within an npm bundle. We're finished with the constructing, time to ship it to the npm 🚀

### Versioning and Releasing


Enter fullscreen mode

Exit fullscreen mode

npm model main // improve main model

npm model minor // improve minor model

npm model patch // improve patch model



All the above feedback set the brand new model, updates the bundle json and commits to git



Enter fullscreen mode

Exit fullscreen mode

npm login“`

to login your npm account, then run

npm publish
Enter fullscreen mode

Exit fullscreen mode

on terminal. Test you npm account to see bundle is prepared with the model you specify inbundle.json



Abstract

tsup is a bundler based mostly on arguably the quickest bundler: esbuild. You do not have to switch configs, however you may compose the best way you need for various outputs. ESM is the popular module technique for internet and UI libraries. The primary distinction between a CJS and ES module is that the latter is tree-shakable makes it extra environment friendly and requires much less time to obtain, parse and execute for browsers. We offer the library with kind declarations in JS format. Add sideEffects:false to your bundle.json for additional optimization when different bundlers use the library. Shortly take a look at with npm hyperlink



Additional Studying

I’ve discovered all of those sources very helpful. Shout out to all of them 🙌



 Github Hyperlink

In case you need to use this strategy as a template
👉 https://github.com/orabazu/tsup-library-template



Webpack Error

For those who see this error if you use the library

ModuleNotFoundError: Module not discovered: Error: Cannot resolve './Sum' in '/Customers/hunor/workspace-zhunor/js/tsup-template.js/lib'
Did you imply 'index.js'?
BREAKING CHANGE: The request './Sum' didn't resolve solely as a result of it was resolved as absolutely specified
Enter fullscreen mode

Exit fullscreen mode

By default webpack expects you to supply the file extension when importing a module in .mjs recordsdata or some other .js recordsdata when their nearest dad or mum bundle.json file incorporates a “kind” area with a price of “module”, in any other case webpack would fail the compiling with a Module not discovered error.

To be able to skip this you would possibly have to set fullySpecified: false in webpack configuration just like under.

{
  take a look at: /.m?js/,
  resolve: {
    fullySpecified: false
  }
}
Enter fullscreen mode

Exit fullscreen mode

Add a Comment

Your email address will not be published. Required fields are marked *

Want to Contribute to us or want to have 15k+ Audience read your Article ? Or Just want to make a strong Backlink?