Check out a free preview of the full Polyglot Programming: TypeScript, Go, & Rust course

The "Projector Object in TypeScript" Lesson is part of the full, Polyglot Programming: TypeScript, Go, & Rust course featured in this preview video. Here's what you'd learn in this lesson:

ThePrimeagen demonstrates how to build the Projector object in TypeScript. This will require a struct that takes a defined config, a possible present working directory, operation, and arguments.


Transcript from the "Projector Object in TypeScript" Lesson

>> All right, well, we're back and we're gonna continue on. I'm gonna make a slight modification to the operations and orderings. We're gonna go into part two right here, which is gonna be converting the options into something that we can use to get everything to make project work, right?

So we did the first part, which is just simply getting this thing out. Now, we're gonna create the projector config. I kinda like this like I said, I keep things really simple, makes it easy for me to understand. And so we are right here. If there was a URL right here, it would be right there.

And so we're gonna first do the course in TypeScript. After we do in TypeScript, we'll do it in Go, after we do it in Go, we will do it in Rust. And then we'll kinda discuss how does it feel, what do we think about it? And depending on how fast we fly to this will add tests or not, cuz I think testing, very important.

If you're not unit testing, I don't know what kinda crazy life you live. But I can't believe that I can keep making changes to products that doesn't ultimately end with me being sad and production being out. So I believe in the unit test lifestyle, but I'm not one that writes tests before I write code.

I write the code, write the test, and when I'm exploring, I don't even write tests until I'm really ready. I am gonna use classes from time to time, I know that people are horrified about classes in the TypeScript community. But there is ways to make JavaScript super-duper faster by using classes.

So I even have a little sweet YouTube video right here, which I thought had the greatest boxer of all time, but we won't look at it right now. But just effectively, you can do some neat things if you have access to classes that you can't quite do when you only have functions.

And so we'll see how this goes. All right, so let me just make sure, let's see, yep, right now we do have, of course, the arguments, the present working directory, the config path. And so now, we need to do something with it. So effectively what I think we need to do is we need to create a struct that takes a config, a defined config, exactly where we want to look, right?

Cuz there may or may not be one provided. We need a present working directory, there may or may not be one provided. We're definitely gonna want the operation we're gonna do, and then, of course, any additional arguments as well. So there is some manipulation to what was passed that then needs to happen now.

So we're gonna do each one of those, and let's start with config.ts. Everyone loves a little TypeScript, don't we? All right, so let's go to TypeScript, let's jump in here, and let's create a config.ts. Awesome, right, actually I wanna put this one as index.ts, there we go. All right, awesome, hopefully everyone's there, I know I'm moving fast, remember, dev, productivity on Frontend Masters.

[LAUGH] Sorry, guys, slightly weird. Well, let's keep on going. So let's first create the enum, cuz I feel like that's probably a good plan, right? We needed a probably an Operation enum. Let's export this. And this represent what type of operations are we doing? We of course have a Print, an Add, and a Remove, right?

That's pretty much all we're really needing to do with this library. We add stuff ,we remove stuff, or we print out stuff. All right, let's create the actual config now. So let's export type, we'll just call config. We could call it projector config, but it's a lot of words and then the word projector shows up everywhere.

Then you'll have visual or auditorial sensation or satiation, terrible thing to have. So let's do this, let's have args, and this will be a concrete string of arguments, right? These are things that are gonna be used In our operation. We should have an operation, which is gonna be an operation.

We're gonna need a config, which will be an actual string path. This is the actual path to the item. Let's not hydrate it yet, let's not go out and get it. Let's just first make sure we have the correct place. And then lastly, we're also gonna need the present working directory, which will also be a string, cuz before we may or may not have any of those items.

So the first thing I'm gonna do is, I'll like to create the actual function that we're gonna be using for this. So export, default function and getConfig, and this is gonna take in an Opts,. And of course, I don't know, my autocomplete, is it off the screen? Do you guys see it?

All right, we'll just say yes to that one. Where did it go? That was funny. Anyways, so I'll just go like this and we'll figure it out. And so boom, and import {Opts} from, I think I can just do this, right? I don't know, everyone's gonna be just making fun of my LSP all night, okay?

I swear it's not me. And let's return a config, awesome. So there we go, this all looks to be pretty normal. So in reality, we only need a couple items here, right? We need to return a present working directory. And so I'll just create the function get present working directory, and I'm just gonna pass in the opts.

Exact same thing with config, exact same thing with args, getArgs, and exact same thing with operation, right? Just make it real simple. There you go, we have these four things. They're each gonna go out and do what they need to do, and we'll return it right here. So as long as these things all work out, we're actually gonna be building something that's good.

All right, so let's do function, getConfig. Let's start with present working directory first, cuz it's in order. If it wasn't in order, it would be a little bit frustrating. So let's start here. All right, so what do we need to do here? Well, the first thing we have to do is go like this, if (opts.pwd), well, let's just return that, right?

It's already there. There's nothing we need to do, right? We just simply need to return it. We're gonna trust that they provide us something that makes sense. If it's not there, how do we get the present working directory? Well, if I'm not mistaken, our process has current working directory.

Yeah, there we go. So this thing simply returns your current working directory, right? And so that's where node was executed from, awesome. That's it, that's all we have to do, simple, easy win. Let's do another one, function, getConfig. Now, I feel like this is gonna be more about how do you properly place configs than it is about getting configs.

So let's go opts, and I'm not gonna use a library. There is a library to do this. We're not gonna use a library to accomplish this. We're just going to write it out ourselves. So exact same thing, let's jump up here. Let's paste those things in, let's take present working directory, replace it with config, right?

If there is a config, we're gonna trust that they've handed us the correct config, right? Now, when it comes to configuration, Macs have a specific location, right? It's like /applications/ something else. It has a location that you should probably be using. I assume there's even some sort of XDG config home or something like that on Macs, that's like an environment variable that gives you that exact path.

So here would probably be a pretty good place to use that, is at least how I think about it. So I'm on Ubuntu, I'm just gonna program for Ubuntu. So if you have Windows, I think it's /users username. Each one has kind of their own location. So just follow along using your operating system's best to find location.

If I'm not mistaken, though, I believe it's xdg. Yeah, there is an npm package called xdg-basedir, which just goes through and make sure this works. You should not use this on Mac OS or Windows. Okay, so this only works for me, now even for you guys. But you can imagine that there's this whole, there you go, end of the paths.

There we go, this would be the one that you'd want to do that goes and gets the correct location. But we're just gonna program our own little one right here cuz I feel like it's a little bit more, I don't know, it's a little bit more fun, plus you don't need to always have a module for everything.

So let's do it right here. I'm gonna go const config = process.env. And I'm gonna do XDG_CONFIG_HOME. And so if this thing exists, well, that's where I want to be. And so we'll set that right now. So I'll go like this. I have the word config. I should stop using the word config.

We'll use location, if there is a location, then we're gonna return that. We'll do that here in one second. Or we can technically use home, right? So I could use something like const home = process.env [HOME], correct? That does work out, right? So we could do these two things.

So what I'm gonna do is I'm just gonna take this and do something that I didn't do in practice, which is just do this, right? So I'm gonna grab one of these two. So hopefully, one of them should exist. If nothing exists, we probably actually have an unrecoverable error, right?

So I'm gonna do something like this. If there is no location, well, then throw new Error ("unable to determine config location"). All right, that's pretty nice. Else, I have these two locations, but we're gonna need to do something a little bit unique with this one. So I'm gonna first like this, const home = f over here, grab that, Do this and go, all right, I might as well just have broken it up into two.

There's a reason why I broke it up into two, I know, I now remember, right? But we'll go like this, if loc === home, well we should return a path.join. So if you don't know path, path is the pathing utility that's available in Node js. It just has a bunch of convenient things.

You should never create your own paths, let the operating system do it, let the environment do it, let the runtime do it. It is just really annoying to do it. So I'm gonna import it then, there you go, import path from "path". Go back down here, and I'm gonna go path.join.

Now, go location and projector.json, right? So this looks pretty close to what you would expect in your home directory, a hidden file called projector.json. All right, else, if you're in xdg, usually you have a little bit more, so we'll do the exact same thing here. I'll go location, "projector", projector.json.

So if you don't know what xdg is, let me just give you a quick example of it, echo $XDG_CONFIG_HOME. So for me, it's my .config folder. So that's where I have all my editors configuration. It's likely if you're using Ubuntu, that's likely where most of your configuration is actually under .config or .local, depending on where you're using it.

Or there's also like a dot something else, I forget what it is. But there's a lot of little XDGs type items. So there we go, this looks really good. I mean, I think this looks pretty good, everything we need to do. We have a get config duplicate item, so this is problematic.

So let's call this, what should we call this thing? It's gonna call config. Now, look how beautiful that is. There we go, we produced the config. So we have the present working directory, we now have get config working, let's get the args, right? One at the time, function [SOUND] getArgs (opts: Opts) and we're gonna want this, right?

This makes sense. So if opts.args, if there is no args, or opts.args.length === 0, well, we can just return that, right? There's nothing there, there's no arguments that we're gonna be using, awesome. But if there is something, we actually kinda need to think about this. Now, it makes sense, if you were to add a key, how many arguments do we need?

Or if we're gonna add a key and a value, how many arguments? Yeah, two, yeah.
>> Two
>> Key and a value, right? You need to add it, and if you're gonna add a key and a value, you need two arguments, key and value, cuz remember, we go, projector add, foo, bar.

Now, foo is the key, bar is the value. We can also print it out by doing projector foo, it will grab that and go print out bar, right? So it is a key value type system. So that means we're gonna need to do a little kind of stuff here.

So we need to know what type of operation we're having. So before we finish this one, let's go and do getOperation, right? And so we'll return that. And so if opts.args, right, if there is no args, or let's just yank that whole line honestly, or it's empty for whatever reason, let's return, operation.print, there we go, right?

It's a print operation, correct? So how do we know we're doing an add operation? Well, the first argument into our system is just literally the word add. So if opts, oop, wrong language [0]=add, return Operation, Add, and then, of course, fix your typo. There we go, that's pretty much as straightforward as it gets.

I think we all get this, I can do the exact same thing again, right? I could do, if it's rm, it's a remove operation, right? There we go, calling it rm might be a little bit dangerous, because if you forgot the type the word projector and then you access the key that's actually a file, you're gonna delete it.

So should you actually use something with the word rm in it? Probably not. But you know what, we live risky lives out here. So we're gonna keep it going, there we go. So this seems straightforward. We could use a switch, default, whatever good enough for me. I hope everyone's happy with it.

So there we go. This should be your operation. So now, we can go back to our args. And we probably need to make sure we're doing a bit of error checking, cuz this seems like a good place to do the error checking. If you say projector add, foo, bar, bass, well, that's three things.

I don't know how to add three things. I do know how to add two things, right, a key and a value, which means you've messed up and you haven't included the quotes. And so I wanna make sure that you are doing the correct thing, not me trying to be super clever.

For those that have ever built any CLI applications, being dumb with nice error messages is way better than being clever, cuz people usually get upset and disappointed anyways. So let's go like this, let's go const operation = getOperation, and just pass in the opts. Fantastic, so now we know what we have.

At this point, you can use whatever you want to use, I'm just gonna use if statements. I've never liked switch statements in C based languages. They always, I don't know, they always just never feel that great. I always feel like they're just cumbersome if else operations. All right, so if (operation === Operation.Print), well, I need to do some bounds checking at this point, right?

If opts.args, let's see, if( opts.args length) is greater than 1, well, that's problematic, right? I've said you either can have no arguments, which I'll print all the values, or one argument and I'll print just that value. I've never specified any other uses. Now, we could say, well, ergonomically weapon if you want multiple, yeah.

We're not gonna do that right now, we only have two operations. So we're gonna throw an error in here, plus I just think it's kinda nice. So let's just throw a new error, let's see, expected 0 or 1 arguments but got, oop, wrong language, don't do that. Yeah, I forgot about that.

Wrong language altogether, I should be using this, but got $(.opts.args.length), right? That's what we got, and that was very disappointing. And I'm also over 80 characters, also very disappointing right now. I feel very upset at myself, but we're just gonna have to deal with it. All right, so if this is the case, then it should be pretty simple.

I should just be able to return opts.args, right? So whatever it is, that's the arguments you need. Awesome, so let's do it again, fp Add. So if it equals add, and our length do not equals 3, cuz remember, it's add, foo, bar, right? So it's actually three terms, add being one of them, all right?

Then what do we need to do? Well, we can say, expected 2 arguments but got length- 1, right? You don't wanna say expected three arguments, it'll be very confused, people are like, I only provided two, it'll be very, very confusing. So there you go. If that's the case, then we need a slice, starting at 1, right?

We're gonna copy this array out. We're gonna pull out that, so we no longer have add there. Our operation contains the term add, we don't need it. Let's copy that one last time cuz this is gonna be pretty much the last one. So I'll say reduce it down to 2, expected 1.

Fantastic, so if our length does not equal to, we should have expected 1, else we slice it off. That'd be the remove, we only remove one key. There we go, fantastic. Everyone kinda followed along with that? It's just some nice little error throwing and it just feels great, it's just does the right thing.

So hopefully args does all the bounce checking and we don't have to put that somewhere else, cuz it just seems a little silly to me to create a config that is invalid. I'd rather just have the config at least say it's valid, and then from there, I can do the things.

We will do the testing for all this, make sure everything's working, so we're not bamboozled with a lot of bugs getting here early. Plus you get to kinda feel out how testing would work in this crazy world. And yeah, all right, so everyone ready for some Go?
>> Yeah.

>> Yeah, awesome.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now