Tree-Shaking Without a Bundler? Here's How Snowpack Does It!
Tags: npm, github, productivity, cicd
Published: 2020-01-16 00:10:20

Until [Snowpack](https://www.snowpack.dev/) came on the scene, application bundling was the only way to do tree-shaking (dead-code elimination) on the web.

But Snowpack is the unbundler. It runs only on your dependencies without ever touching your application code. So how could it tree-shake? 

Here's a quick post about how we made this production optimization happen for faster production apps, thanks to some essential help from the [RollupJS](http://rollupjs.org/) team 🙌



## The Setup

Bundlers "tree-shake" your web application by finding and removing unused code, shaving off valuable bytes when you deploy to production.

Since Snowpack never touches your application code, how could we do the same? We had to get creative. 

Snowpack v1.0 added an initial "scan" phase behind the `--optimize` flag. It runs a quick scan of your app to learn how you use each dependency.

... and that's it! We pass that info to our internal installer (powered by Rollup) and eliminate any dead code at install time. 

## The Challenge 

"Passing that info to our Rollup-powered installer" was an interesting challenge: How do you tell Rollup that code in your top-level entrypoints is unused?

This was an unusual request, and something the Rollup team (rightfully!) didn't want to build into the bundler itself. 

We [created an issue](https://github.com/rollup/rollup/issues/3047) on the Rollup repo to outline the problem, and with [@lukastaegert](https://twitter.com/lukastaegert)'s help came up with a really clever solution via a new plugin...

![issue screenshot](https://thepracticaldev.s3.amazonaws.com/i/a3eipsbyqo9davqoyzny.png)

**Here's how we solved it:** We create an in-memory "envelope" wrapper for Rollup of every dep & fill it with the exact imports used by your app.

Rollup sees the envelope, reads the scanned imports to the real dep, & then automatically tree-shakes out everything not found in the ✉️ 
You can see exactly how it works here, coming in at [only ~50 LOC](https://github.com/pikapkg/snowpack/blob/a05263ab9bb22ae1f79fc084dd88d65cd462432c/src/rollup-plugin-treeshake-inputs.ts#L7-L12).

![plugin screenshot](https://thepracticaldev.s3.amazonaws.com/i/mrh8obkfxhj19wnktjz4.png)

This all runs behind the production-only `--optimize` flag, so you can add & remove imports as you develop your app without worrying about tree-shaking or needing to re-install as your code changes. 

But even when optimizing for production, just reading & scanning your application code should be a hell of a lot faster than reading, scanning AND BUNDLING your app code with a traditional bundler (so you can still expect fast production deployments).

## What's Next?

We're experimenting with replacing our current import scanner with the even faster [es-module-lexer](https://github.com/guybedford/es-module-lexer) by [@guybedford](https://twitter.com/guybedford), a WASM-powered scanner written in C.

{% github guybedford/es-module-lexer %}


## Thanks for Reading!

**Enjoyed this post?** Throw Snowpack a 🌟on GH to help spread the word:

{% github pikapkg/snowpack %}

- Learn more about Snowpack: https://www.snowpack.dev/
- Follow us on Twitter for updates: https://twitter.com/pikapkg
- Read & share the original Twitter thread that this was based on: https://twitter.com/pikapkg/status/1217509391668133888