JavaScript Compilation with Babel

Matt Zeunert speaking at JS Monthly London in July, 2016
185Views
 
Great talks, fired to your inbox 👌
No junk, no spam, just great talks. Unsubscribe any time.

About this talk

Most people use Babel to compile ES2015 or JSX code. But it’s actually a multi-purpose JavaScript compilation tool. This talk by Matt Zeunert will show how you can use Babel to create your own code transformation rules.


Transcript


[00:00:05] Hi everyone. I’m going to talk a little bit about Babel. Originally, Babel was known as 6to5 because the primary purpose of Babel is that it allows you to compile you ES6 code down to ES5. For example, if you have an arrow function, in all the browsers it’s not going to work so you need 6to5 to convert it to ES5 code that the browser actually supports. Babel or 6to5 can allow you to write ES6 code even though browsers don’t support it yet. Now 6to5 is called Babel and it’s more of a general purpose JavaScript compilation tool and it works with plugins to allow you to make certain confirmations on your JavaScript code. For example, you can still use a compiled ES6 code just like with 6to5, but if you wanted to use certain features from ES7, you can also do that using different Babel plugins, or if you’re using React you can use Babel to compile JSX to ES5 so you can run it in a browser. Adding all this flexibility has a downside from user perspective because now Babel by default doesn’t do anything at all. You have to tell it exactly what to do. If you run 6to5, you just give it your file and it’s going to output the compiler code, but with Babel you have to create this .babelrc file and install a bunch of Node modules to actually tell it exactly what I want it to do. [00:01:28] For example, if you wanted to compile ES6 code, you will [need?] all of these plugins. That’s a lot of plugins so that’s not what you actually need to do. There is something called “presets” which is a collection of plugins. Rather than listing all of these plugins I had on the previous slide, I just use this preset called “ES 2015” and then that’s going to load all of the plugins for me. What does Babel actually do? Basically, there are three steps that Babel takes when you run Babel. First of all, it looks at your code, parses it to generate the parse tree. It then goes through the parse tree and makes changes to it, based on the plugin code, and then it finally looks again at the parse tree and generates the updated code. The place where the plugins come in is really in this middle bit where we make the change to our code. The parsing or generation or generating source amounts is handled exclusively by Babel. Let’s look at a very simple example. I have this variable declaration called greeting = "hello" and I want to change it to greeting = "hi". First of all, I have to look at what my syntax is and I’m using a tool called, “AST Explorer” which allows me to view the syntax tree of my JavaScript code. You can see it’s a variable declaration and on that I’ve got the variable name, I’ve got the identifier, and on the right I have the initial value with the string literal. [00:02:59] To write our plugin there are two important bits of information here. One is the type of the node that I’m working with. In the case that was the literal. The other bit that’s important is the value because the value is what I want to modify. Right now the value is "hello" but I want it to be "hi". That allows me to write my Babel plugin. It’s just a basic node module, the really important bit here is the visitors. The visitors are called and Babel traverses through the syntax tree. I give it the node type I’m interested in, so in this case the literal and then I can make… you just change that value. You make a change to the syntax tree by reassigning that value. In this case, it’s a very simple change to the syntax tree and really changing one value. If I wanted to, I could just add a function call or add a complex object into my code. This is what the updated syntax tree or parse tree looks like. What Babel does is it’s going to take that syntax tree and compile it back into code for me. At the bottom you can see the generated code from that. This isn’t very useful at all because it’s just going to replace all of your literals with "hi", but if you want to make something more useful, I’ll just show you a little demo. I’ve got a problem with my app that I’m working on, so that’s this app. It consists of just two files, one is the app.js and one is my utils file. My app.js is inside a subfolder, so in order to import g-tools, I have to use these two dots to go up in the directory tree. [00:04:51] My application works perfectly fine but the problem is that doing it this way with relative import makes it difficult for me to actually move my app.js file around or re-factor my code. For example, if I wanted to create another subfolder further down and move my app file there, that means I also have to update the import declaration to have this two dots to go for that directory tree. Likewise, if I got rid of my subfolder entirely, I, again, I have to modify my app.js file to update the import statements to match the current relative part. Every time I move my file around it requires a bunch of changes. Really, what I would like to do is I want to have an import that’s relative to the root of my project. Can I use this app/utils rather than using a relative part? I want to show how you can build that. Still using the utils file and the relative import. If I run this script, which basically uses Webpack, and then that return is called Babel to compile my code and then it passes it into a Node program to run my code. You can see it works fine and I get “successfully imported to utils” at the bottom. I want this code to look like this. Webpack is going to complain that it can’t find this file using this app import because that file just doesn’t exist. I’m getting this error, “Cannot find module utils”. I need to write my Babel plugin to actually change that part to something else. To begin doing that, I need to first figure out what my code looks like in a syntax tree. [00:06:51] Let me go in this tool called “AST Explorer”. You can see two bits in the body of my program. One is the import declaration which is what I care about and then there is this expression statement, which is just the console or node call. Just by knowing that’s an import declaration, that allows me to write the handler to pass into the Babel plugin. I can go into the plugin and select import declaration and pass it and handle it. The handler is going to receive the path and the path does two things, the path has path of node which is my import declaration, and path to parent which is the program. If I look a bit more of the import declaration, I need to figure out what it is I actually want to change. You can see that the import declaration has a property called “source” which in turn has a value which is my add utils. I want to change the value of my import string. To do that, just to restore the serious behaviour as an example, I can do path.node.source.value. This is just the same path that I had before. Now, if I try to run it again, hopefully, it’s going to work fine. Okay. You can see it can find the file again because of my plugin. Obviously, that’s not really useful because not every import you make in your application is going to be to the utils file, which is not probably what you want. [00:08:30] You need add some more logic to really think about what you want this to do. Let’s start by getting the import path in the variable and then I only want to actually change the import path if it’s an import path that’s relative to the project root. I’m using the app character to indicate that the path is relative to the project root. I’m going to do import path zero, if it does not equal to the character then I want to return because I don’t want to change any of the existing relative path. I don’t know this bit. Now I’ve got my import path. Now it starts with an @ and it’s going to look something like @/utils. The @ isn’t meaningful in any way. It’s just an indicator, that’s it. A path that’s relative to the project root. I want to convert this to take out the @, to be just that utils. To do that, my import path equals importpath.substring1, which just keeps everything except for the first character. We’re running this now and can replace the import path with that. I would just be importing from the root of my file system, which, again, isn’t what I want. What I want is I want to look at the current working directory. The current working directory is similar to doing PWD in the command line. You’ve got this Babel plugin demo form that I’m working in. The way to do that in Node is process.cwd, so for the current working directory. [00:10:12] My project root isn’t actually the current working directory. I’ve got the Babel plugin demo but inside my import demo isn’t actually project root, so I’m going to add that to here and then I’m going to add the import path that I had from the file. Okay. If I run it again now, I think, it’s going to use the import part as well to do project root, so it makes it easier to import certain files, especially utils files that are in a different root folder. For example, your components or your models or something like that. It makes working with the app a bit easier. Yes, that’s all I have to say. If you want to try it yourself, I’ve got the slides online, I’ve got the demo code online. Thanks.