Build your own Nx Workspace from Scratch - Nx Conf Lite 2022
hi thank you for watching our talk my name is miroslavianos and i'm a member of the nx core team you all know how amazing annex is and how much heavy lifting it handles for you my colleagues will share a lot of insights with you today on different aspects in which nx can help you we have crossed the 1 million weekly downloads recently people around the world are loving nx and saying they can't imagine a project without it nx does so much for you that it sometimes feels like magic but in the sea of positive feedback we didn't notice the opposite opinions how it's too opinionated that it has a steep learning curve how it's only good for the large enterprise applications or that it's too much to grasp nx has so many features built in that it can be scary for some folks they might feel the project is slipping out of their hands initially jason and i were asked to come up with a talk that would dive even deeper into uncharted territories of annex showing features and possibilities most users don't know about but jason had a different idea you see we had a lot of talks showing the vast complexness and features of nx but none showing how simple it is under the hood so today we'll show you how to build your own nx workspace from scratch using just the nx package we'll go back to the days where you would code in notepad and knew what every line in your project was doing yes the following demonstration we will use no plugins no generators just plain annex my colleague jason will show you what a truly minimal nx setup looks like so make yourselves comfortable and enjoy the ride and i hope you are hungry because we'll be making sandwiches today hey everyone my name is jason i work on the core of nx and i've had the opportunity to work on some pretty massive projects sometimes when a project is that large it's hard to see exactly how annex is used inside that project and how to set it up from scratch so that's what we're going to be doing today we're going to be taking this very minimal project we're going to be adding nx to it and we're going to take a look along the way to see exactly how nx knows the things that it knows so for this project we have a start script that starts an http server and it's just going to serve up all of the files in this directory so i'm going to go ahead and run start and i'm going to take a look at the code so we have a sandwich shop and it's going to bring in some scripts which is going to add a sandwich to the page and this sandwich.js file is going to be our main file for today and if we go to the browser we'll see that we have our title but we have an empty sandwich because we don't have any ingredients for our sandwich yet before i add any ingredients i'm going to start by adding nx to this project to do that i'm going to install the nx package locally as a dev dependency while that happens i'm going to create an nx.json file and i'm going to keep it empty for now this file does two things first it's going to allow annex to know that this workspace actually is an nx workspace the second thing that it does is that it holds a couple of configuration options that we're going to provide later on in this talk but for now it's going to stay empty the first thing that we can already do is that we can run annex graph inside of our directory here we can see all of our projects for which we have none of at the moment let's go ahead and add one i've told us about this sandwich index.js and it's inside this sandwich directory i'm going to tell annex that this is our first project so we have this sandwich directory everything inside of it is going to be its own project it has a project.json file and alex is going to be able to see this project.json file and it's going to be able to show it to us when we run graph so we see when we run graph we can see the sandwich project so by installing nx package adding project json and nx json we can top onto power of the nx for our projects and now that we have one project why don't we add some ingredients into a separate project so i'm going to create a new file it's going to be under ingredients i'm going to create some bread for our sandwich so i have a bread directory and like the sandwich directory i'm going to tell nx that this directory is its own project so the files inside of this directory are going to be all under the bread project if i run next graph i'm going to be able to see that i now have two projects i have a sandwich project and i have the brad project that i just created so we see that just by creating some new files i can create new projects inside of nx now i'm going to write the code for the bread first i'm going to export a function i'm going to call it bread and we're going to create an element for this so red is going to equal document create element and it's going to create a div i'm going to give my bread some color which is going to be its background and i'm going to say it's wheat bread then i'm going to give it some thickness which is going to be its height and that's going to be around 30 pixels and the other thing i'm going to do is i'm going to add a class to my bread and that class is going to give it some padding so that you can see around the bread and the individual slices i'm going to return the bread and we're going to use that inside of our sandwich so i'm going to import the bread function from the ingredients bread directory and as you remember we have this we've defined this directory as a separate annex project so we're inside the sandwich project and we're importing into our bread directory which is a separate nx project this way annex is going to know that a sandwich project is going to depend on our bread so i'm going to add this bread to our sandwich like that i'm gonna give it two slices of bread and first i'm gonna go to the web application we're gonna see that two pieces of bread show up i'm also going to run nx graph and we're going to see that our sandwich project now depends on our bread project and this relationship happens because we made that import before in index.js cool
nx knows that sandwich is depending on ingredient spread because it has analyzed the code and saw that sandwich is importing the file from ingredient spread project this is automatically covered for typescript and javascript projects but if you're using some other programming language you can explicitly set these dependencies so now that we have some bread i'd like to toast the bread before we go and add more ingredients to it this way it's a little bit crunchy so every project in nx has a set of targets that can be run on it you might be familiar with targets such as like test i use just underneath or like build that used webpack underneath but targets can actually be anything we want today we're going to be a little bit silly and we're going to say that our projects can be toasted right so i'm going to name it toast and each target in nx uses an executor to actually run it i'm going to have to write this executor myself because there is no executor out there to toast a project so the way we're going to do that is we're going to create a new directory called executors and we're going to create educators.json file inside of there this file is going to have a map of some executors and i'm going to create a toaster executor every executor annex has two different things one is going to be an implementation and this is going to be a javascript file that's going to tell nx exactly what to run when this executor is used the second thing is going to be a schema file and this is going to tell nx that this executor can accept some options for it so i'm going to go ahead and create these two files and i'm going to go ahead and write the schema first the first thing i'm going to say is that this executor is supposed to be used for nx so i'm going to say that the cli is the nx and i'm going to give it some properties that it accepts i'm going to say that we can set how long we want our bread to be toasted so i'm going to give this a type of number then i'm going to go back and before i write the implementation i'm going to go back to my bread project and i'm going to hook this all up i'm going to say that i should look into my package json and i can define some executors that i can use here and that those are going to live in executors executors.json so now i'm going to go back into my bread project and i'm going to say look in the package json and the root of my directory and use the toaster to toast my bread i'm going to give it some options which are to toast it for a duration of two seconds that should be nice and crunchy now i'm going to go back and write the implementation for the toaster so this is going to be a javascript function and this function takes two things first is going to be those options that we've been talking about which is going to be the duration and also some context about the workspace that we're in right now so i'm going to grab some stuff from our options duration is going to come from options and one of the things that we get from the context is going to be our project name so that's going to come from our context so i'm going to console logout two things um one is going to be toasting our project name for duration divided by a thousand seconds right i was going to print this out now i'm going to wait for the duration so i'm going to create a new promise and it's going to wait for the duration and then i'm going to console out that it's done there we go and the last thing i'm going to do is i'm going to tell nx that this project is actually or this executor has succeeded and the way i do that is i pass it i return success true i'm gonna make this an async function really quick and now i should have everything set up so that if i go to my bread project i'm going to be able to toast it so let's see if i've done everything correctly awesome so it's going to start toasting my bread wait two seconds and then i'm going to have my bread toasted awesome now that i've ran my toaster executor i can reuse this executor through my other projects so i'm going to copy this project json i'm going to also toast my sandwich i'm going to toast my sandwich for a little bit less time than my bread and now just like i can run annex toast ingredients bread i can also run toast sandwich so this is going to toast it for one second and now since we have our toast executor set up for both of our projects we could be using for example nx affected to run the executor only on the affected projects or we could mark the executor to be cacheable in the nx config so next time you would run it you would immediately get the crunchy bread imagine if things work like this in the real life cool so executors don't always have to be the ones that we provide out of nx you can write custom executors for your workspace to do maybe integrate tools that annex doesn't have first party support for or even silly things like toast your bread the last thing that we're going to do today is we're going to create some more ingredients for our sandwich so i'm going to go back to our code here and i'm going to take a look at the bread function a lot of this can be abstracted so i'm going to copy my bread directory and i'm going to say that it's going to be a create ingredient directory and in this project i'm going to say that it has a function called a create ingredient and this is going to take in the color and the thickness of my ingredient so for thickness i'm going to put it down here in pixels and this is going to be my color instead of returning bread i'm going to say returns and ingredients and then inside my bread i'm going to go ahead and use this project so i'm going to say return create ingredient and it's going to be the color of wheats and it's going to be 30 pixels and i have to import this function and again because i'm importing from another nx project it's going nx is going to be able to tell me this relationship so let me go ahead and verify that by running nx graph so if i look at my bread project i can see that my bread depends on this new utility function for creating ingredients and i can still see that my sandwich brings in my bread so if i've done this all correctly and i go back into my web application we should still see our two slices of bread but we know that these are created from our utility function with this utility function i want to tell other developers in my workspace that they should be using this function to create other ingredients so the way i'm going to do that is i'm going to create a custom generator so i'm going to copy over the executors directory and i'm going to name it generators instead is written a lot like executors but instead of an executors.json file we're going to have a generators.json file instead of a map of executors we're going to have generators and i'm going to call our new gender generator a ingredient generator this generator is going to generate the code for our ingredients and so i'm going to name this directory ingredients as well i'm going to define a schema just like i did before for my executor and one of those is going to be the name of the ingredient the next one is going to be the color of my ingredients which is also a string and then it's going to also take a thickness for the ingredients so we have our schema defined and now what we're going to do is we're going to go into the implementation and we're going to erase what we have from the executor and this is going to take in a tree and some options the options we saw that we defined in the schema before and this tree is a representation of our filed tree you've noticed by now how executors and generators look almost the same apart from the different naming the only major difference is the signature of the function of the executor and generator wherefore executor we had context because we are running a command so we need to know in which context it is being run we need to know whether it's running in affected command or normal run or run many what kind of additional parameters we are passing by with generator we are more concerned about generation of the files so we need a tree that will tell us the existing structure of the nx workspace and would allow us to then further generate or change files so we can do things like write to the file tree and we're going to write something at a location that location is going to be ingredients and we're going to need to get something from the options so we're going to say the name comes from our options so it's going to go into the directory that it's named and then the index.js file and the contents of this file is going to be the same as bread.js
so we're going to copy that in there and so whenever we use this generator we're going to be able to create a file that looks just like this instead of being called bread or instead of being called bread is going to be called the name of the ingredients and it's going to have the same color and the thickness that is passed and we're also going to get that from our options okay so we've written our index.js file and we also want to make sure that all of our new ingredients are their separate nx projects so we're also going to write our project.json file so again it's going to go into ingredients the name of the ingredients project.json file and it's going to be an empty project so if i were to generate a bread ingredient i'm going to create an ingredients bread index.js it's going to
export a function named bread it's going to have a color which i might pass wheat and some thickness and it's also going to create the project.json file so now that i've written this generator i'm going to tell nx that it should use this collection whenever it wants to generate new things so i'm going to tell the cli that the default collection that i should use is in generators generators.json so now i know that when i do and i generate ingredients let's say i want some bacon in my sandwich i can run this and it's going to create the bacon directory it has a project.json and it also has
the bacon function i'm going to do this again for several other components or several other ingredients i'm going to generate some lettuce for us it's going to be a nice shade of green yellow and i'm also going to generate a tomato ingredient and it's going to have a color of tomato so now we have some more ingredients to put into our sandwich i'm going to make this a delicious blt sandwich so we're going to copy that a couple times and we have our bacon we have our tomato and we have our lettuce and we're going to go ahead and import those from the different projects bacon tomato lettuce okay so i'm going to check our application to make sure everything was done correctly and we can see that we have some bacon tomato and lettuce inside our sandwich the bread has been toasted and it is now a delicious sandwich that we can consume despite misconception annex is actually not opinionated at all so for example instead of bacon if you're a vegan you can put avocado so you will have alt sandwich or if you're like me you would start with a cheese first so to recap that part we used a generator just like you would generate an application or a library inside of a workspace we have a separate one that uses our create ingredient function here and uses that to create some different ingredients for our sandwiches so i can tell developers to run this command and just pass in the name of the ingredient that they want to add that's about it for today i bet you didn't know nx can be so simple hope you all enjoyed the demo and learned something new today how to build a generator how to build an executor and how nx knows where to find all the projects and identify the connections and calculate the dependencies thank you and enjoy the rest of the conference thank you all so much for listening i'll be sticking around to answer any of your questions later and i hope you all have a good rest of your day bye
2022-05-16 03:31