跳到主要内容

Babel 7 Released

· 阅读需 23 分钟

After almost 2 years, 4k commits, over 50 pre-releases, and a lot of help we are excited to announce the release of Babel 7. It's been almost 3 years since the release of Babel 6! There's a lot of moving parts so please bear with us in the first weeks of release. Babel 7 is a huge release: we've made it faster, created an upgrade tool, JS configs, config "overrides", more options for size/minification, JSX Fragments, TypeScript, new proposals, and more!

If you appreciate the work we're doing on Babel, you can sponsor Babel on Open Collective, support me on Patreon, or get you or your company involved with Babel as part of work. We'd appreciate the collective ownership of this vital project in the JavaScript community!

It's Happening! 🎉

Software is never going to be perfect but we're ready to ship something that's already been used in production for some time now! @babel/core is already at 5.1 mil downloads/month because of its usage in tools like Next.js 6, vue-cli 3.0, React Native 0.56, and even WordPress.com's frontend 🙂!

Babel's Role

I'd like to start this post by re-introducing Babel's role in the JavaScript ecosystem over the last few years.

The initial issue was that unlike server languages, there was no way to guarantee that every user has the same support for JavaScript because users could be using different browsers with varying levels of support (especially older versions of Internet Explorer). If developers wanted to use new syntax (e.g. class A {}), users on old browsers would just get a blank screen due to the SyntaxError.

Babel provided a way for developers to use the latest JavaScript syntax while allowing them to not worry about how to make it backwards compatible for their users by translating it (class A {} to var A = function A() {}).

Because of its ability to transform JavaScript code, it also can be used to implement new features: thus it has become a bridge to help TC39 (the committee that specifies the JavaScript language) get feedback on proposed JavaScript ideas and for the community to have a say in the future of the language.

Babel is fundamental to JavaScript development today. There are currently over 1.3 million dependent repos on GitHub, 17 million downloads on npm per month, and hundreds of users including many major frameworks (React, Vue, Ember, Polymer), and companies (Facebook, Netflix, Airbnb). It has become such a foundation for JavaScript development that many people don't even know that it is being used. Even if you aren't using it yourself, it's highly likely your dependencies are using Babel.

Maintainers are People

Babel has a huge influence on not just the future of the language itself but its community and ecosystem as well. But even with all of this responsibility, Babel is just a community driven project by a couple of volunteers.

It was only just this past year some of the team were able to meet for the first time in person:

As much as this is an announcement post, I'd like to take the opportunity to remind everyone of the state of this project.

I myself joined a few months before the 6.0 release, which had its own amount of controversy and backlash. Much of the reception there led to existing maintainer burnout (including Sebastian, Babel's creator) and a few of us that were left took on the reins.

Like many maintainers, we accidentally stumbled into the role. Many of us didn't have any formal training into how compilers worked or how to maintain an open source project. Ironically, I even purposely avoided majoring in Computer Science in college because I didn't want to take classes on compilers or anything low level because it seemed uninteresting and difficult. Yet I found myself drawn to tooling, linters, Babel, and JavaScript as a language.

I'd like to encourage everyone to look into the open source projects that you depend on and to support them (both with time and monetary support if possible).

Many maintainers aren't inherently experts in the things they work on; and there is much to be accomplished from just starting the work first. You will come in with both curiosity and humility, both of which are great attributes to have as a maintainer. My desire is a hope for the vision of the project versus just all of us doing "tasks".

Babel isn't a company, or an open source team at a major company like Facebook. There's only a handful of volunteers working on Babel, and it's only been a few months since I took the leap to leave my job and be the only one so far to work on open source full time. But people can come and go, have lives outside of this "hobby", raise families, move on to different things, switch jobs or are looking for jobs, etc. Are we collectively doing what we can to sustain the things that are so fundamental to how we work, or are we going to allow the foundations to slowly crumble? How do we keep open source welcoming and inclusive but with clear boundaries? Can we learn from the experiences of other maintainers?

Although Open Source has clearly taken over software, can we really consider it to be in a healthy state without taking into account the people behind it?

#BabelSponsorsEverything

Open Source sustainability feels like giving out an offering basket at the moment. It's not difficult to state the value that projects provide to the thousands of people and companies using open source, but yet we don't see that value being shown to the few that are willing to put in the work. There can be so many ways to support open source and yet not all approaches work for each project or people.


Now let's go to the changes!!

Major Breaking Changes

We are documenting these in our Migration Guide. Many of these changes can be done automatically with our new babel-upgrade tool, or can be added in the future.

  • Drop support for un-maintained Node versions: 0.10, 0.12, 4, 5 (details)
  • Move us to the @babel namespace by switching to using "scoped" packages (details). This helps differentiate official packages, so babel-core becomes @babel/core (and no squatting)
  • Remove (and stop publishing) any yearly presets (preset-es2015, etc) (details). @babel/preset-env replaces the need for these as it includes all yearly additions as well as the ability to target a specific set of browsers
  • Also drop the "Stage" presets (@babel/preset-stage-0, etc) in favor of opting into individual proposals. Similarly remove proposals from @babel/polyfill by default (details). Please consider reading the whole post on this for more explanation.
  • Some packages have renames: any TC39 proposal plugin will now be -proposal instead of -transform (details). So @babel/plugin-transform-class-properties becomes @babel/plugin-proposal-class-properties.
  • Introduce a peerDependency on @babel/core for certain user-facing packages (e.g. babel-loader, @babel/cli, etc) (details)

babel-upgrade

babel-upgrade is a new tool we've started that tries to automatically make upgrade changes: currently with dependencies in package.json and .babelrc config.

We recommend running it directly on a git repo with npx babel-upgrade, or you can install it globally with npm i babel-upgrade -g.

If you'd like to modify the files, you can pass --write as well as --install.

Shell
npx babel-upgrade --write --install

Please considering contributing by reporting issues or PRs to help everyone transition to Babel 7! A hope for the future is that we use this same tool for all future breaking changes and create a bot to PR projects to update.

JavaScript Config Files

We are introducing babel.config.js. It isn't a requirement or even a replacement for .babelrc, but having this may be useful in certain cases.

*.js configuration files are fairly common in the JavaScript ecosystem. ESLint and Webpack both allow for .eslintrc.js and webpack.config.js configuration files, respectively.

Below is the case of only compiling with a plugin in "production" (you can do this already with the "env" option in a .babelrc file):

JavaScript
var env = process.env.NODE_ENV;
module.exports = {
plugins: [
env === "production" && "babel-plugin-that-is-cool"
].filter(Boolean)
};

babel.config.js has a different config resolution than a .babelrc. It will always resolve the config from that file versus originally when Babel would do a lookup from each file upward until it found a config. This makes it possible to take advantage of the next feature posted below, overrides.

Selective Configuration with overrides

Recently, I published a post with thoughts on both publishing ES2015+ packages and also consuming/compiling them.

There's a section that goes into using a new key in Babel's config called overrides which allows you to specify different configs per glob.

JavaScript
module.exports = {
presets: [
// default config...
],
overrides: [{
test: ["./node_modules"],
presets: [
// config for node_modules
],
}, {
test: ["./tests"],
presets: [
// config for tests
],
}]
};

This allows an application that requires different compilation configs for its tests, client code, and server code to skip needing to create a new .babelrc file per folder.

Speed 🏎

Babel itself is faster so it should take less time to build! We've made a lot of changes to optimize the code as well as accept patches from the v8 team. We're glad to be a part of the Web Tooling Benchmark alongside many other great JavaScript tools.

Output Options

Babel has supported preset and plugin options for some time now. To recap you can wrap the plugin in an array and pass an options object to the plugin:

{
"plugins": [
- "pluginA",
+ ["pluginA", {
+ // options here
+ }],
]
}

We've made some changes to the loose option of some plugins and added some new options for others! Note by using these options you are opting into non-spec compliant behavior and should know what you are doing; this can become an issue when you switch off of compiling to use the syntax natively. These kinds of options are best used in a library, if at all.

  • For classes, class A {} will now not include the classCallCheck helper.
JavaScript
class A {}
var A = function A() {
- _classCallCheck(this, A);
};
  • There is a new option if every use of a for-of loop is just an array: ["transform-for-of", { "assumeArray": true }]
JavaScript
let elm;
for (elm of array) {
console.log(elm);
}
JavaScript
let elm;

for (let _i = 0, _array = array; _i < _array.length; _i++) {
elm = _array[_i];
console.log(elm);
}
  • We exclude the transform-typeof-symbol plugin in loose mode for preset-env #6831

We've found a lot of libraries doing this already, so we decided to do this by default.

Note that the default behavior is to be as spec compliant as possible so that switching off of Babel or using preset-env is seamless vs. allowing smaller output just to save bytes (there's a tradeoff there that each project can make). We plan to work on better docs and tooling to make that easier.

"Pure" Annotation Support

After #6209, transpiled ES6 classes are annotated with a /*#__PURE__*/ comment that allows gives a hint to minifiers like Uglify and babel-minify for dead code elimination. These annotations are added to other helper functions as well.

JavaScript
class C {
m() {}
}
JavaScript
var C =
/*#__PURE__*/
function () {
// ...
}();

There might be more opportunities for minifier hints and optimizations, let us know!

Syntax

TC39 Proposals Support

I'd like to re-iterate that we've removed the Stage presets in favor of a policy of asking users to explicitly opt-in to proposals < Stage 4.

The concern is that we are automatically opting people into syntax that is not fixed or done with the expectation that it will not change. But this is not the case, especially for proposals that are Stage 0 or 1. This post explains a bit on the kind of thinking behind newer ideas.

Here's a small listing of some of the new syntax Babel supports (keep in mind this feature set is a moving target that could be added/removed/stalled) and which ones have been added in v7:

It's hard for anyone to keep track of all the proposals, so we attempt to do that at babel/proposals.

TypeScript Support (@babel/preset-typescript)

We worked with the TypeScript team on getting Babel to parse/transform type syntax with @babel/preset-typescript, similar to how we handle Flow with @babel/preset-flow.

For more details check out this post from the TypeScript team!

Before (with types):

interface Person {
firstName: string;
lastName: string;
}

function greeter(person : Person) {
return "Hello, " + person.firstName + " " + person.lastName;
}

After (types removed):

function greeter(person) {
return "Hello, " + person.firstName + " " + person.lastName;
}

Both Flow and Typescript are tools that enable JavaScript users to take advantage of gradual typing, and we'd like to enable both in Babel. We plan on continuing to work closely with both of their respective teams at FB and Microsoft (in addition to the community-at-large) to maintain compatibility and support new features.

This integration is fairly new, so it's possible some syntax isn't supported fully. We'd appreciate your help in reporting issues and maybe sending a PR!

JSX Fragment Support (<>)

As mentioned in the React Blog, JSX Fragment support has been available as of beta.31.

JSX
render() {
return (
<>
<ChildA />
<ChildB />
</>
);
}

// output 👇

render() {
return React.createElement(
React.Fragment,
null,
React.createElement(ChildA, null),
React.createElement(ChildB, null)
);
}

Babel Helpers Changes

The babel-upgrade PR is in progress

@babel/runtime has been split up into @babel/runtime and @babel/runtime-corejs2 (PR). The former only contains Babel's helper functions and the latter contains that as well as any polyfill functions (e.g. Symbol, Promise).

Babel may need to inject certain functions into the code that can re-used. We just call these "helper functions" just like functions that are shared between modules.

An example of this is with compiling a class (without loose mode on):

The specification says that you need to call a class with new Person() but if it's compiled to a function you could technically just do Person() so we include a runtime check for this.

JavaScript
class Person {}
JavaScript
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Person = function Person() {
_classCallCheck(this, Person);
};

With @babel/plugin-transform-runtime and @babel/runtime (as a dependency), Babel can extract the individual functions and just require the modular functions to enable smaller output like so:

JavaScript
var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");

var Person = function Person() {
_classCallCheck(this, Person);
};

The same can be done with external-helpers and rollup-plugin-babel. We are looking into seeing if we can automatically do this in the future. Look out for a standalone post on Babel's helpers soon.

Automatic Polyfilling (experimental)

Polyfills are necessary for enabling features like Promise, Symbol in environments that do not have support for them. This is important when differentiating between what Babel does as a compiler (transforms syntax) vs. a polyfill (implements built-in functions/objects).

It's easy enough to just import something that covers everything like @babel/polyfill:

JavaScript
import "@babel/polyfill";

But it includes the whole polyfill, and you may not need to import everything if browsers support it already. This is the same problem that @babel/preset-env tries to solve with syntax, so we apply it here with polyfills. The option useBuiltins: "entry" does this by splitting the original import into only the imports that are necessary.

But we can do better by only importing the polyfills that are used in the codebase. The option "useBuiltIns: "usage" is our first attempt at enabling something like that (docs).

It will run through each file and inject an import at the top of each file if that built-in is "used" in the code. For example:

JavaScript
import "core-js/modules/es6.promise";
var a = new Promise();

The inference isn't perfect so there may be false positives.

JavaScript
import "core-js/modules/es7.array.includes";
a.includes // assume a is an []

Other ideas in this space are to use polyfill.io if you are ok with relying on a service (or read how Kent C. Dodds used it to build a hosted service at PayPal).

Misc

Babel Macros 🎣

One of the best parts of Babel is the pluggability of the tool. Over the years, Babel went from being just a "6to5" compiler into a code transform platform, enabling some fantastic optimizations for both user and developer experience. Tons of Babel plugins have been developed for specific libraries and use cases to improve performance and capabilities of library APIs that would not be possible otherwise (some "libraries" are completely transpiled away resulting in no runtime at all).

Unfortunately, adding these plugins to your codebase requires changing configuration (which some toolkits like create-react-app don't allow) and it adds complexity to your code because developers have to know what Babel plugins are operating on a file to know what will happen to the code they're writing. These problems have been solved by babel-plugin-macros by Kent C. Dodds!

Once babel-plugin-macros has been installed and added to your config (it's included in create-react-app v2), you don't need to bother with your configuration to use any macros. In addition, it's even easier to write your own custom transforms for use cases specific to your app or one part of your code.

Learn more about babel-plugin-macros in our original post "Zero-config code transformation with babel-plugin-macros".

Module Targeting

Babel has always attempted to balance the size impact of transformations and capabilities they provide to JavaScript authors. In Babel 7, it has become much easier to configure Babel to support the growing popularity of the module/nomodule pattern.

Notably, several CLI tools for popular web frameworks (1, 2) are already leveraging support leading to a roughly 20% reduction in shipped JavaScript to consumers of applications transpiled by Babel.

Caller Metadata and Better Defaults

We've added a caller option to @babel/core so our tooling can pass metadata to presets/plugins. For example: babel-loader can add something like this so that preset-env can automatically disable the module transformation (same with rollup:

JavaScript
babel.transform("code;", {
filename,
presets: ["@babel/preset-env"],
caller: {
name: "babel-loader",
supportsStaticESM: true,
},
});

This is exciting since it enables a way for tooling to provide better defaults and less configuration! For the case of webpack/rollup: we can automatically defer to using their module transformation instead of Babel's (same with import("a")). Look out for more tooling to take advantage of this in the future!

class C extends HTMLElement {}

One of our oldest issues gets it's own heading (details)

Babel has always had the caveat where it could not support extending native built-ins (Array, Error, etc) and now it can! We've gotten a lot of issues about this; 🎉 you should celebrate like Andrea!

This change was made to the class plugin so it should be automatically enabled if you are using preset-env.

Website Changes 🌏

We've moved our site from Jekyll to Docusaurus!

We're still in the process of setting up translations with Crowdin, and with Babel 7 out, we will be in a better place to start that process.

REPL

We've rewritten the REPL as a React Component, and have been working with Ives on integrating better with CodeSandbox. This allows you to install any plugin or preset from npm in the REPL as well as get any updates that CodeSandbox gets.

We're participating in Rails Girls Summer of Code again! This time, Gyujin and Sujin have been working hard to integrate Boopathi's babel-time-travel into the REPL which you can already test now!

There are so many opportunities here to get involved to make Babel, ASTs, and other tools work better!

We Have A Song 🎶

Hallelujah—In Praise of Babel

One day, Angus graciously imparted to us a song which I thought was amazing, so why not make it our "official" song? And Shawn made a brilliant cover here.

You can find it in our repo at SONG.md. We hope to see other projects follow up on this!

What's Next?

  • Babel is inherently tied to what it compiles: JavaScript. As long as there are new additions to propose/work on there is work to be done there. That includes the time/effort to implement and maintain the syntax even before it becomes "stable". We care about the whole process: the upgrade path, education of new features, teaching of standards/language design, ease of use, and integration with other projects.
    • Related: we are almost finished implementing the new decorators proposal in Babel thanks to Nicolò. It has been a long journey (it has taken more than one year!) because the new proposal is completely different and much more powerful than the old one, but it is almost there 🎉. You can expect it to be released in one of the next minor versions, alongside with a blog post which will explain the changes in detail.
  • Boopathi has been diligently maintaining babel-minify, so we'll being doing a 1.0 for that next!
  • There are a lot of new features in the works: plugin ordering, better validation/errors, speed, re-thinking loose/spec options, caching, using Babel asynchronously, building against itself from CI, smoke tests, running test262. Check out this roadmap doc for some more possible ideas!

We have no secret plans: we're trying the best we can with what we have to serve this community.

Open Source is a Mirror

I'd love if we'd have the time and resources to accomplish all of these ideas and to do it well. But as we've shown with this current release, things take much longer than expected!

Why do these releases take so long anyway? Is it from the feature creep, both from ourselves and our users? Was it because we don't understand how to prioritize among all the possible things to add or fix? Deciding to fix low-hanging fruit vs. fundamental issues until the end? Or "distractions" from helping others on GitHub, Slack, Twitter? Are we just bad are estimating our time, understanding the complexities of these issues, overcommitting as volunteers?

Or are we just setting too high of an expectation on ourselves, or feel so pressured by others to perform and fit to their needs at the harm of ourselves? I can only describe it as dread when seeing a message from someone online wondering why something hasn't been released while another asks why this bug isn't fixed yet. I want to just rush it out and be done with it but I also have a desire to take this seriously.

I've tried to express some of these thoughts and struggles in my talk last week at React Rally: Through the (Open Source) Looking Glass, which I hope you can take an opportunity to listen to. The question I ask myself: What can I do about the inevitable cycle of maintainer burnout, constant anxiety, and unrealistic expectations?

Like much of life, the things we do reflect our character and show us how we really are. The actions we take can in themselves can change us, for better or for worse. If we are to take our work seriously, we should keep one another accountable in these habits that seem so embedded in our culture: of instant gratification, success in terms of metrics, entitlement vs. gratitude, and pride in overworking.

But despite all of it, working towards this release has been so worth it.

Thanks

This is truly a really exciting release, not only through looking back at what we've accomplished and enabled, but much more just knowing how much time and heart was put into making it happen over the last year. It's hard to believe the opportunities and experiences that have happened along the way: interacting with and helping companies from all over the world, finding friends in almost any city I've visited, and speaking honestly about the unbelievable journey this group has taken up together.

Personally, I've never really put so much of my mental energy into anything of this magnitude and I'd like to thank so many people for lifting us up along the way. Shoutouts in particular to Logan Smyth who has spent countless time to change so much of how the core works and always takes time to be so helpful in our Slack while also working freelance and Brian Ng who has stepped up in such a big way to continue to maintain Babel as well as review all my blog posts and talks. Daniel Tschinder, Sven Sauleau, Nicolò Ribaudo, and Justin Ridgewell have all just been instrumental in making this release possible and enjoyable to work on.

Shoutout to all the many community members on Slack, Twitter, and across all the projects on GitHub that also have to understand what we are doing for their own users. I'd like to give a huge thanks to my friends at Behance/Adobe for sponsoring me for almost a year to work on Babel half time before deciding to go full-time (as well as helping test Babel 7 in production throughout the whole time I was there). Big thanks to all of our sponsors for our Open Collective, especially Trivago and Handshake. And thanks to our friends and family for all their love and support.

We're all pretty tired at this point, but it's truly been an honor and privilege to serve in this way, so we hope you appreciate the release!