 Do you want to... Oh, backpack off you. Okay, so... Hello everybody. Welcome to our workshop. We're going to be talking about NAPI and new stuff going on in there and sort of introducing the concept of native modules and porting native modules to NAPI. So let's introduce ourselves. So I'm Gabriel. I work at Intel. And on NAPI, by the way, I'm also part of the technical steering committee and these are some of the things that I implemented when we were developing NAPI. I'm Michael Dawson, IBM's community lead for Node.js. That means I get to spend a lot of time working in the community. I'm on the technical steering committee and in particular work as part of the NAPI team as well. And I also get to work with a lot of great teams within IBM. We do things like keep Node.js running on our platforms, make sure our cloud is a great place to deploy Node, and use NAPI in building some of our modules. And I'm Jim Schlatt. I'm the head of Inspiredware, small consultancy in Ashland, Oregon. We've been in the web space since 2003, web exclusive, and early on we were implementing in C&C++ and have since moved to Node. Therefore, NAPI is of interest to us as a bridge between our legacy code and Node. My participation is primarily on the build tools and documentations as part of the NAPI team. Our company is particularly interested in the adoption of Node and JavaScript in the scientific and engineering communities. And to that end, we are working on an automated tool, a tool that will automate the creation of NAPI bindings. So if that's of interest to your company, we should really talk. Oops. Hi, everybody. Thank you to be here. My name is Nicola Del Gobbo. I work as a developer at Bakri, a small company in the south of Italy. For the most part of the time, I take care of implementing backend systems. To do that, there's a lot of Node.js, JavaScript, and sometimes C or C++. In my spare time, I try to give my contribution to Open Source Project for more in part of NAPI team. Recently, I started working on the possibility to write and divide down for Node.js using primary language different from C and C++. And that's it. This is why I'm in very short terms. And I guess, you know, we just want to acknowledge that we're not the only ones contributing. We're just the ones who happen to be able to make it here to do the workshop with you. So there's been contributions from a number of other people. It's been actually quite a good number of years where we've had, like, you know, people showing interest, getting involved from all sorts of different companies. And I just even say, like, you know, I think it was two or three years ago that we did a presentation similar to this or more of an introduction to NAPI at Node.js Interactive. And it ended up with Jim and Nicola getting a lot more involved. And so if you're wondering how you get involved and help out with NAPI, this is a great starting point. This is me. All right, so here are the objectives of today's workshop. So we'd like to give you a brief orientation to NAPI, the background on NAPI, why we have it. And then we're hoping that you leave with an awareness of the available tools. And we hope today, through the workshop, you get a good start on your own projects or, say, porting a current module. So what we will do is speak briefly to give you a live presentation about NAPI and its counterpart Node add-on NAPI. And then what you will have is a chance to work on online tutorials at your own pace. So if you're a beginner, there'll be options for you. If you're already in NAPI and interested in some of the newer features, the tutorials will guide you through that. You'll also have a chance to work on individual projects, or if you're interested, we have modules that are available to be ported into NAPI. And then at the end, we'd like to do a wrap-up and an assessment. Okay, so the purpose of this workshop is to teach you how to implement NAPI don't using NAPI, but before to start, you need to know what is NAPI don't. We can refer to this definition that came from the Node.js documentation. And NAPI don'ts are defined like dynamical link-share object that can be loaded as any other just in module through required function, and they are written in CNC++. This definition is very complete and precise because they ask that NAPI don't are written in CNC++ and you need to compile your code. And at the end of this process, you will obtain a shared library that Node will be able to load using required function. So you don't need any boilerplate code to load this particular module. Usually native-gen are used to bind CNC++ libraries or applications and to speed up your Node.js application, especially if you want to execute CPU-bound operation like image processing. But how is it possible to call native code directly from JavaScript? The answer is another question. What is Node.js? Node.js is just your runtime. It uses just the engine to parse and execute your JavaScript application. And there is a binding between the API exported by the engine. In the case of Node, the engine is V8. And this binding allows you to expose CNC++ function as function inside JavaScript. In two words, you can use the API exported by V8 that our CNC++ API to create any kind of just object like you would in plain JavaScript. So you can understand that native-edown mechanism gives us great powers, but from great powers comes great responsibility. I'm saying this because there are some problems that we need to know about native-edown development. The ability to develop native add-ons has been part of Node since the very beginning. The issue with that tool called NAND was that it was tied very tightly to the V8 API. One of the motivating factors for creating an API is that what we found is that 30% of the modules depend on a native add-on. So, for example, Node SAS, which is listed here, which is a native add-on, has 4 million downloads per week. So NAND was the original way. It, as I said before, it's tightly bonded with the V8 engine. And it was an initial effort to create an API that would allow you to generate a native add-on. So the problem with NAND is that it was so tightly tied to the V8 engine that when there were changes in the V8 engine, it could potentially break the ABI stability of your native add-on. And at that point, your users would see a message like the one shown there, which is not understandable to them, which would then land as an issue for maintainers. So this fragility in the ABI was one of the primary motivating factors for creating an API, which we have today. Okay, so what exactly is an API? That's what we're going to be talking about. What we did was, instead of trying to basically expose the V8 C++ API to native add-ons and have people use that directly, or through NAND, which is basically the same but with a few more abstractions, what we decided was that we're going to abstract away everything, the entire JavaScript engine and a lot of the Node.js APIs, things like Buffer and AsyncWorker, these things are not part of the V8 engine. They're part of Node.js, so they are Node.js-provided tools rather than engine-provided tools. And because we wanted this to be as binary stable as possible, meaning that once you've compiled your add-on, Node.js versions come and go and you move into the future, and your add-on, as compiled 10 years ago, continues to work today against the latest and greatest version of Node.js. In order to achieve that, what we decided to do was to create a C API, a plain C API that covers basically all the JavaScript language features and, of course, some of the Node APIs. So think of an API, something like the Win32 API or the POSIX API on Linux. The C library, that kind of thing. Basic, basic functions, but they cover pretty much everything that you'd want to do in a native add-on. And so that's how we sought to achieve this binary stability. So if you look at a plain Windows application, it was written for Windows 95 and it still runs today. It will not fail because the Windows API has remained so stable. The same thing can be said with UNIX apps. They will still run today because all they do is things like fOpen and IOControl and these things. And those things have been around forever and they haven't changed and they haven't broken in 30, 40 years. Well, we don't yet have 30, 40 years under our belt, but that's the idea, basically. That's why we wrote it in C. So when you have an API in the picture, basically instead of going directly, you can see here from the V8 engine to the V8 public API, instead of doing that, your code talks to an API instead and we do the talking to the V8 engine. And so if the V8 engine changes, we have the opportunity to change the implementation of an API without actually changing the interface of an API. So you still call the same function with the same parameters that have the same meanings and you get the same return value as you did in an earlier version of Node. So your binary interface doesn't change at all, but we change our implementation. So that's the difference between NAN. With NAN, the interface remains the same, but once you compiled your code, the compiled code would only work with a certain version of Node.js because the meanings of things change, function parameters change, and so you have to recompile your code when the next version of Node comes out. Although you don't have to change your code anymore, because NAN changes, your code doesn't change, you still have to recompile. With any API, you don't even have to recompile. You just release it and everybody uses it for the next eternity, basically. So this is an illustration of that. So without any API, this is what happens. This is Node.js version 6, V8 version 5.1, and then the Node module version is 48. So what happens? A new version of Node comes out and the ABI is completely different and it no longer matches. So in the best case, you get something like unresolved symbol, blah, blah, blah. I can no longer reload your module. In the worst case, you get something like, your module works perfectly, but in this very obscure corner case, because the meaning of this parameter changed, you're passing zero, which used to be an enum value, meaning X. Now it's an enum value, meaning Y. The engine does something else and you set fault. It's like, why did that happen? So to avoid these mysterious sort of failures, we have Node module version, which is basically like, okay, look, I'm going to fail you because I can no longer trust that your ABI matches this one, even though it looks like it does. We know that the ABI has changed, therefore we're going to increment this Node version, Node major version, or Node module version, and then we will simply refuse to load one that was compiled with the lower one. So this was a conscious decision on the part of Node.js. So with an API, what happens is, the initial state is the same. I mean, when you build your module, it's freshly built. Obviously it's going to run against the version against which it was built, right? But the difference is because you now have the C API, which we have promised will not change in meaning or in structure. We can later add new APIs, but we cannot change the existing ones, right? So what we do when we add newer ones is we do this, right? You have the surface that remains the same, but now there's other functionality, right? Like in NAPI version one, there was no thread safe function, right? So in NAPI version four, now we have a thread safe function. It's a new Node.js interface. It's great, but anybody who hasn't used it will not be affected by it because it doesn't change things like how you create an integer. That doesn't change, right? How do you create an object? That hasn't changed, right? It's the same C function. So if you wrote the module when NAPI three came out and you created an object, the code with which you created that object will continue to run without any recompilation. It will continue to correctly create an object in Node version 12, 14, 16, and wherever going forward, right? So, but one thing did change, especially in the last year, and it's called worker threads, right? So Node.js used to be a runtime that was basically start the process, require, require, require, require, and then run forever, right? In practice, that means run until something crashes, you shut it down, you start another instance, you start 15 other instances, it doesn't matter, right? It's like a microservice. So you don't have to worry about what happens to the process. It's basically throw away once it starts, as long as it works correctly while it runs. So this allowed great freedom because what do you do in practice? All these requires, they may load these native modules, they may not, right? If they did, and they compiled, and they ran, everything, everybody was happy. But all these native modules, they had this freedom where they could just allocate memory and not worry about the life cycle of the module because the process would clean it all up for you anyway when it died, right? Like, you know, technically, before you return zero out of main, you're supposed to free everything you allocated, right? But sometimes you don't do that, and that's okay, that really is okay in production, because you have to think about the bigger picture, right? There is such a thing as a process, and the kernel will clean it up for you, right? So as long as you don't leave like network sockets open which may crash a server half a world away and that kind of stuff, you're actually okay leaving some memory around. But not with workers, right? With workers, worker turns on, loads a bunch of modules, and then turns off again. And then another worker starts up because somebody decided to use worker threads, and they load a bunch of modules, right? And the worker shuts down, and you can have this any number of times, so you end up with this, right? Whereas before, you had these little chunks of memory that were left over when the process died, now you end up with a bunch of leaks in your modules because they don't clean up absolutely everything after themselves, right? So how do you go about cleaning stuff up? So we have several mechanisms, right? First of all, in the core, well, unload the module, right? Because if you don't unload the module, the module will be loaded. So that's one of the most important things. The other thing is you have to tell the module, hey, you're about to die, you know? You better do stuff. Because so far we didn't have such a mechanism. We had like the init, everybody knows if you've done any native add-ons, you know that there's an init function in which you get passed, it's kind of like a common JS, you have like module.exports, right? So you have a bunch of nice little properties which are all implemented in your C or C++ code, and then you give back the object. And then whatever somebody calls a function on that object, it actually goes into your C, C++ code, right? And that's all done in init. But what about like, you know, on init, you know, like die or whatever, it's like, we didn't have that because we didn't need to, right? But now we need to. And this requires buying from all the native add-on maintainers because all of them need to be aware that these add-ons can be removed now. And so, you know, if you have like, I don't know, like a static giant structure pointer equals null at the top of your file and in init, you say new giant structure, you know, then that's going to stick around, right? If nobody tells you to kill it, right? Now, there are ways that, no, JS informs you of late that things are dying, right? So, one thing you can do in init is to add a cleanup hook. And cleanup hook basically means, okay, this node.js instance is about to die. So, you know, you may want to, like, you know, do something or not, up to you. And that's now part of NAPI 5. It's stable, so you can just do that thing you're in it and then inside it, you get a nice snappy env. So, you know, you can still call functions in NAPI because although the environment is dying, it's not dead yet, so you can still, like, do some cleanup. I don't believe you can run JavaScript anymore at that point, but you can do things like NAPI remove wrap, you know, to get rid of native pointers, that kind of thing. Now, the other thing that's experimental now is this thing called NAPI set instance data, right? And this is a little bit easier to NAPI add and clean up hook for technical reasons. The technical difference between the two is that when you add a cleanup hook, all it does for you is just, you know, you give it a pointer and at the end of the environment, it gives you the pointer back and says, hey, this was the pointer you gave me, so, you know, you better clean it up. That's it, right? But so what, right? Like, that pointer is not going to be available in your binding, right? And you don't want to store it statically because then, you know, if you have five instances and five different threads, all those static pointers are going to be clobbering each other, right? So then what do you do, right? If all you have is NAPI add and clean up hook. Well, when you create the binding, you can pass a void start there, which it gives you back when it gets called. So that's a nice way of sort of threading this global state through instead of using a global static, right? You can thread it through. You can pass it to async workers and so forth, but you still have to sort of pass it around and you have to, like, change your whole init function. Now, it's like, you know, NAPI create function. That's what you had so far. Now, you have NAPI create function giant structure pointer and you have to do that for everything, right? So to avoid having to do all this threading by hand, we have NAPI set instance data, which is now experimental and going towards becoming part of NAPI 6. And that's basically like, hey, we know that the NAPI environment that you get, like, you know, the NAPI N that everybody uses inside NAPI is a different one for every module instance. So you might as well just hook this pointer on it with NAPI set instance data and then anywhere you can just say, hey, give me the data and then it gives you back the pointer, right? And when you hooked it, you, by the way, you can attach a finalizer so that when the environment goes down, you can delete it, right? So it's a little bit cleaner than the cleanup hook and it's also more spec compliant in terms of, like, ECMA script. So, okay, but there are still some things that are not covered by this. So, for example, references. References are one thing that is not covered by this. If you hook data to individual JavaScript objects and the lifetime of your native data is supposed to equal the lifetime of the JavaScript object, then what happens when you have these JavaScript objects left over when the engine dies, right? If they don't go out of scope, even if they do go out of scope, if the garbage collector hasn't had chance to say, hey, I'm cleaning you up now, and the engine dies, then they will never be cleaned up even though they were already out of scope, right? And if they don't go out of scope, it's even worse because the garbage collector never even considered cleaning them up because there were still references to them, right? So then what happens to those pointers, right? We actually talked about this in TC39 and they said we can't clean that up. You can't just say, okay, the environment is dying, so garbage collector, go do one last garbage collection and everything is going out of scope. You can't do that for very complicated reasons which I don't fully understand, but I took their answer for granted and I said, okay, fine, yes, sir. And so we have to do it. And they said explicitly that the implementers of the environment, not even the engine, the engine can't handle it. The environment in which the engine run has to, the engines run have to handle this, right? So what we did was, okay, fine, let's do that. Let us do that final run because we know the references that are hooked up in native objects, we hooked them up, right? So all you have to do is keep a tally, like, okay, this thing's still hooked up, this thing's still hooked up. Environment dying, no problem. Go die, die, die, die, die, die, die, die, basically, right? And so we tell everybody to die. So this is also a new feature and this can also break things because, believe it or not, people were not cleaning these up, surprise, right? So now that we are calling these functions, which we were never calling before because the garbage collector wasn't calling them, now we're finding things like, huh, wait a second, oh, you want me to close the database, but wait, I have a query handle open still. So I need to close the query handle, then the database handle. But what if the finalizer gets called in the wrong sequence? Oh, right? So I think we leveled down, we have some of these issues, but we're working on them. So this was sort of the cleanup and for the history, I hand it over. Okay, so now I hope briefly, sorry, of NAPI. From the beginning until NAPI has been promoted like stable API. NAPI was kicked off for the first time at VM Summit in April 2016. And in this summit core collaborator discussed about the VM support for the Node.js. And NAPI was identified as core areas to improve the native DOM mechanism. After all regas that we received from native DOM maintainers about the effort to keep up with the changes in the V8 engine. And then we started to work to shape this new API. And most important was the collaboration with the Chakra core team because we started to validate and test the API using two different implementations. And in December 2016, we shipped the enhancement proposal for NAPI. Then we started to port some of the key modules for the ecosystem. Like, for example, Node.js port, Node.js, CQlite. And then we fixed something in the API. And in May 2016, NAPI has been released like experimental API with Node.8. After that in July at the new VM Summit, we agreed on which should be the criteria to exit from the experimental status. And having met these criteria, we got the agreement from the TSE to exit from the experimental status in March 2018. And then we started to back port NAPI to all LTS race lines. And in the middle of this long process, we started working on support pre-built tools like Node.projip. Because we believe that the distribution of NAPI is as important as its implementation. If you want to create NAPI with NAPI, you have two choices. You can use the plain C API exported directly from the core of Node.js. You need only to include in your code Node underscore api.tich and start writing your native add-on. Or maybe you can use Node.API. It's not part of the ABI stable API and it's distributed through NPM. Node.API is a C++ wrapper over the C API exported by the core of Node. And it's a collection of functions, classes and macros that help you to simplify your code using C++. All the code is in line. This means that when you compile your native add-on, the compiler will replace all the code with the NAPI function. And the most important thing is that Node.API depends only on NAPI. Today we have 300,000 downloads per week and we are at version two that we released three weeks ago. And Node.API helps you on error rendering. Because if you enable the C++ exception mechanism, every NAPI failures will be dispatched as just exception through C++ exception. Here I reported the usage of these two APIs. On your left you can see the C++ API provided by Node.API and I'm creating an object. The API is very intuitive and simple and then I'm attaching this object property who and set string value bar. All this code will be expanded in a 7-til high-end code of C API and in this code you can recognize a pattern. Every NAPI function takes as parameter our environment value and returns a status. We have always to check the status to intercept if there was an error or not and react. For example throwing an error or you can do what is best for your use case. Jim, before talking about NAND and now I introduced Node.API. Both of these libraries are C++ wrapper and both of these libraries provide a single surface API to write an native at home but they are very different because NAND has a transparent layer over V8 API and it uses V8 types so it's strongly bonded with V8 and instead Node.API depends on only NAPI and this guarantees the same promise of NAPI, compile once and run on multiple versions of Node.js and this reduced the likelihood to change your code switching to a different version of Node.js. We always work on improved NAPI and all the native at home ecosystem and I won't tell what happened in the last year. We shipped two new versions of NAPI to help you to call the main thread from an external thread and we found API 5 we added the data object we promoted like stable API the finalizer callback added the function to clean up the memory native at home and made the callback for thread safe function in Node.API we did a lot we started from the documentation then we added new synchronous API and the thread safe function wrapper and thanks to feedback that we received from other developers we worked a lot on async worker API and if you are curious you can see all what we did on the issue reported in the slide and then we added a new async worker called async progress worker that internally is implemented using the thread safe function and help you to report on the main thread the progress that happened in an external thread and then we added a wrapper for the data object outside of Node itself we also improved some of the build tools so in the past year we added NAPI support for pre-build pre-build is a tool that will compile your native add-on into a binary that gets uploaded to GitHub as a release and we've also added support for NAPI builds in CMake.js when you start a new project for native add-on this project will be composed by two parts one written in JavaScript and the other written in C and C++ and usually the C++ part will be used by the JavaScript part to expose some future here I reported the structure that we used on the example tutorials and it's a normal JavaScript project but there is something new there is a folder called src where there is the native code CSC++ code and a special folder called build that contains intermediary and finally build product and then we have BindingJP that is a special file that contains all the settings to build your native add-on BindingJP is used by NodeJP and internally it is used by JIP that is the default building tool that Node use to build native add-on BindingJP is a file that uses JSON syntax and allows you to set all the flags, libraries that you need to build your native add-on and the important things here are target underscore name that represents the name of your native add-on and sources that is a list of your native files that you need to build your native add-on some developers don't like JIP even if because it has been dismissed by Google and created we have to use a CMake file to build an add-on for all the tutorials today we use NodeJP we want to use modules which have been ported to NAPI so we came up with this concept of being able to add badges to your project so that people can find modules which are going to more easily use be able to be used with different versions of Node and keep working when you upgrade so we have these badges we've created for the different versions it's as simple as adding the URL into your Readme and really you want to start with what's the earliest version of NAPI that you support with NAPI version 3 being a good choice because that's the one that was supported by all the existing LTSs and so unless you need to use some of the newer features there's just some of the URLs that you may need and so when we send out the slide deck you'll be able to get those directly this concludes the introduction part we now have available for you to work on at your own pace sort of online tutorials this slide shows some of the topics that are available under the tutorials but I think what I'd like to do is bring up the slide that has the link to the tutorials alright so on the left is the URL if you'd like to start looking at the online tutorials so when you bring it up you'll see on the left there's a table of contents and if you're brand new to NAPI there are some topics for getting started but if you're actually doing NAPI work and you're interested in or the advanced topics for example the context awareness then you'll see that there are advanced topics further down so the four of us are here ready to help you if you're working through a tutorial if you're working through your own project and you have a question or you have a suggestion please signal one of us and we'll be happy to work with you if you don't have your own project and you'd like to contribute to the Node project then on the right is a list of existing native modules that are eligible to be converted to NAPI and some are easy and some are very complex so depending on your level of commitment to the project you could pick one of those and we're all here to help you