Skip to content

Instantly share code, notes, and snippets.

@Rich-Harris
Last active October 12, 2020 15:09
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Rich-Harris/51e1bf24e7c093469ef7a0983bad94cb to your computer and use it in GitHub Desktop.
Save Rich-Harris/51e1bf24e7c093469ef7a0983bad94cb to your computer and use it in GitHub Desktop.
Don't ship untranspiled code

When Babel 6 came out, it was hard for a lot of packages to upgrade because it was essentially an entirely different category of thing than Babel 5. So what happened was that some packages upgraded, and some didn't — at least not straight away.

Some projects took the prima facie enlightened view that packages should expose untranspiled code, so that the consumers of that code could determine for themselves what needed to get transpiled based on the environments they supported.

That was a costly decision. If I was the author of an app that was using Babel 6, I couldn't import a library that was still using Babel 5 and shipping untranspiled code (because the configs were completely incompatible), and vice versa. Frankly, it was a bloody nuisance. We are bad at anticipating these sorts of issues. It will happen again at some point.

Adding a few extra bytes to pkg.main or pkg.module is a small price to pay for things just working. As well as avoiding the aforementioned headaches, it means that your builds are a lot quicker. I don't want to run my node_modules through Babel.

But it might not be a few bytes!

Right. So you're a library author, and you want to use async/await and all that stuff. You have a few options here:

  1. Don't. It transpiles badly. Honestly, we managed for long enough without it — it's not that hard.
  2. Say that you don't support environments without async/await. That's totally fine. I don't support environments without Array.prototype.filter.
  3. Include your src code in the npm package. If someone really wants to use your futuristic code in an environment you don't support, let them. Just make sure you also include pre-transpiled bundles.

The golden rule

A lot of developers believe that the priority is to ship the leanest, most optimised code possible. I disagree. The priority is to ensure that people can consume code with the least amount of faffery. A novice developer should not have to learn about transpilers in order to use your library.

Shipping code that works out of the box is just basic politeness. I've said this in the past, and I stand by it:

It’s the difference between giving someone raw ingredients and a cooked meal — if you went to a restaurant and ordered a burger, you’d be pretty mad if they gave you half a pound of minced beef and a frying pan instead.

Shipping the leanest, most optimised code is something experts care about. Experts have the knowledge and incentives to invest time in the kinds of workflows that involve transpiling other people's code. That should not be the default.

@aickin
Copy link

aickin commented Apr 12, 2017

Thanks for writing this!

It's funny: I feel like I agree with 98+% of this, but my headline for it would be: "Libs should start shipping untranspiled code". Why? Because I think your option #3 is a good way forward for the community, and I care about the possible optimizations we can get from untranspiled code in the majority of browsers that support the new ES features.

(To be honest, it never even occurred to me that "shipping untranspiled code" meant not also shipping transpiled code and putting that entry point in for pkg.main. This is why communication over Twitter is terrible!)

I do, however, think that making option #3 truly workable probably requires some new package.json entry that would indicate what level of ES the code is written in. That would allow bundlers/tooling to effectively choose when to include untranspiled code, when to partially transpile the untranspiled code, and when to fall back to fully pre-transpiled code. Does that make sense?

Thanks for breaking out of 140 chars and for your nearly unending service to the JS world!

@Rich-Harris
Copy link
Author

That makes total sense, yeah. It gets a little tricky when you start talking about e.g. stage <=3 features, or things like TypeScript — do you transpile to a baseline of (albeit futuristic) JavaScript that a bundler could intelligently transpile given the appropriate information, and then transpile further for main/module? (Not a rhetorical question!)

@fregante
Copy link

fregante commented Apr 12, 2017

Definitely libs should be compatible with most engines out of the box, but you're still leaving a loophole in there, the number 2. .filter can be easily polyfilled, async needs build tool support.

Ok you lib author decided that your lib is ES2017 only, that's not gonna work for most people. So where does your well-intentioned "I'll cook your burger" go?

Lib authors should probably always ship a (IE11/Node4, today) widely-compatible version in the main/module/browser fields and a latest standard ES version if it differs. If the real source uses any non-standard syntax, it should be handled as "private API."

The real problem with this is that best practices change and poor articles are written and read years later. This info should be on the bundlers' repos and it could be shown as part of npm publish itself.

@matthewp
Copy link

I'm definitely on the side of (2) here. The features I would transpile are:

  1. Non-standard features like JSX.
  2. import/export, since no runtime supports that yet (bundling to umd or iife).

Everything else I would leave in. I feel my job as a library author is to deliver on the purpose of the library. To do the thing I advertise that it does. I don't feel it is my job to figure out how to ship it in your unique environment constraints. And just about every application has its own unique constraints.

There are a lot of tools out there to help you with that problem. But my library that creates ascii cat pictures? That's not its domain.

I feel we as a community spend too much time catering to the lowest common denominator. To people who are just starting out and really don't know what they are doing. I feel for these people and am glad that there are resources and communities who do cater to them. But I don't think every library or every framework needs to be that.

@maple3142
Copy link

For me, I will only transpile es6+ things such as async/await and es6 modules.
es6 without modules is my currently lowest common denominator, because IE and old version node should be abandoned in my opinion. Including other old browsers which can't support them correctly.

@sancarn
Copy link

sancarn commented May 6, 2019

@maple3142

because IE and old version node should be abandoned in my opinion

you know there are good reasons many devlopers still use Internet Explorer. It's not for fun or because they are stubborn. In windows software development if ever you want an easy to make, easily integratable GUI which comes native to windows for all users, using the IE ActiveX object is a real winner. It saves you having to ship with e.g. chromium and also gives you an expansive API which allows you to truly integrate every part of IE into your application. Bare in mind, Microsoft could easily fix this for everyone by making an ActiveX edge control, but yeah, in the end if we could migrate away from IE we would. It isn't as simple as it may seem from a web-only developer's perspective :)

@robertknight
Copy link

Don't. It transpiles badly. Honestly, we managed for long enough without it — it's not that hard.

I think that might be an issue with Babel's default approach to transpiling async/await (regenerator) rather than an unavoidable problem. The TypeScript compiler and babel-plugin-async-to-promises can both transpile async/await down to a fairly modest amount of code which isn't a complete PITA to debug.

That said, if a library doesn't make heavy use of async code then just writing code with Promises directly would probably be better.

@steve-taylor
Copy link

steve-taylor commented Jan 3, 2020

Babel 7's lack of support for targeting specific ES versions forces me to go with option #3 sans pre-transpiled bundles. As a library author, the decision about which browsers to support is not mine to make.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment