This course has been updated! We now recommend you take the Complete Intro to Real-Time course.
Transcript from the "File IO & Modules" Lesson
[00:00:00]
>> [MUSIC]
[00:00:04]
>> Kyle Simpson: The next thing that we're going to do is deal with File I/O. And we're also gonna deal with modules and other things like that. So this minimist module that we npm installed, that's something that somebody else has provided, but what if we wanted to make our own minimist module?
[00:00:22] I'm gonna go ahead and take away this stuff because we're not going to use that stuff for right now. And I will change this to file, and you'll understand why I'm doing that in just a moment. What if we wanted to make a program that what it did?
[00:00:35] And by the way, I'm gonna save this file off, save as 2.js, that way, I don't lose what I've already been working on. So as we go throughout the rest of today, we're gonna create 11 or 12 different files. So at each kind of intersection junction point, you'll save it is the next number.
[00:00:54] That way you've got all the different versions of your file to go back to things. So we're on to 2.js now, and I'll change this up here to say 2.js. What if I wanted to create a program that would read a file from the file system and pull in its contents and show those contents?
[00:01:22]
>> Kyle Simpson: So I would be able to specify that file from the command line by giving it a dash, dash file flag, for instance. So read the file of NAME and output, for instance, or whatever we wanted to say. [COUGH] All right, so now we could say that we're gonna write all of our code in this one file.
[00:01:49] We have this one file called 2.js, and we wanna put all of our code here. But, in reality, we know that we wanna organize things into different files. So what if I had the task of reading a file, what if I put that into its own module, where I could just use that module?
[00:02:07] So I wanna open up a separate file, and I want you to save this file, and I want you to call it helloworld.js cuz we're gonna create ourselves a module from scratch. And then we're going to load that module, and so before I even create the module I'm just gonna load the module in.
[00:02:49]
>> Kyle Simpson: So we use that require statement like we did before. And you'll notice that when I did require("minimist"), I didn't have to give it a path or a filename, it was just a named module. That's because it was installed in one of the default include locations. But we're gonna put a file directly in our file root that we call helloworld.js so we do kind of need to give it an actual path and file name for it to know where to find it, okay?
[00:03:13] Now, what would our module look like?
>> Kyle Simpson: Our module that we're gonna create inside of helloworld, what we need to do with it is we need to be able to have a function that will read our files out. So just to make sure I don't get too far off track, I'm just gonna have my other one open so I can keep track of myself.
[00:03:43] We're gonna a function who's responsible for pulling in a file by filename.
>> Kyle Simpson: And so if we go back to our our code. Actually, I said earlier that we were gonna switch to 2.js, I think I'm gonna switch back to this still being 1.js. I think we're not quite ready for the switch to two.
[00:04:05] It doesn't really matter, but I'm gonna go back to calling this 1.js just temporarily. So helloworld module is still gonna be in our 1.js file. And you notice that I'm pulling in the module, well, what is my API for my module? Remember, we talked about that in JavaScript yesterday.
[00:04:26] We talked about the module pattern, where you have a wrapping function and it returns some kind of public API. That's not the way it works in CommonJS. In CommonJS, inside of this file, everything that you declare is automatically assumed to be private to a module. So the way that you expose things on your public API is you're given a module.exports object that's already there, it's empty.
[00:04:53] And you can add to that object to add to the public API. So I can say, .say = say.
>> Kyle Simpson: Let me double check just to make sure I'm doing that the same as we did before.
>> Kyle Simpson: [COUGH] I know what I was doing, okay. So rather than calling my module say, I was supposed to call it hello, okay.
[00:05:32] All right, so we're back inside of our little module that we're gonna create. And we need a function that when we pass it a file name, it will read that file. So we're gonna need a facility for reading files. And it turns out that Node has a standard built in module for us called the fs module.
[00:05:49] So if we pull in that module by saying fs = require("fs"), that's gonna allow us to have the file system module available to us. And then inside of our function called say, here on line two, a little bigger what we want to do is we want to read the file.
[00:06:09] So I could say something like the contents of this file is equal to fs.readFileSync and give it a file name that I want it to read.
>> Kyle Simpson: Yes.
>> Speaker 2: So line number seven is basically exposing the API?
>> Kyle Simpson: Yeah, so we could have [COUGH] one or more lines like line number seven, which add things on to our public API.
[00:06:39] Our public API is module.exports. We could replace the object or we could add to it. In this case, we're just gonna add to it.
>> Kyle Simpson: Okay, so readFileSync does exactly what it sounds like is that it's going to read in a file in a synchronous fashion. Most of the time, people will tell you in Node that if you're doing something with a synchronous API, you're probably doing it wrong.
[00:07:07] Because Node is supposed to be asynchronous, and we're supposed to have all kinds of asynchronicity going on, and don't block the event loop, don't use blocking APIs. There's an exception to that rule though, there's more than one, but there's an exception to that rule when you think about the perspective of command line tools.
[00:07:25] Because command line tools are not running thousands of concurrent requests against a web server, it's just a single u in front of a command line. And if it takes an extra couple of half seconds to load up something in a blocking way, that's not a big deal because it's not like you're trying to run the same command line 15 different times at the same time.
[00:07:48] So synchronous file reading, or synchronous blocking, isn't really such a huge sin when we're dealing with a command line tool. But when we start to get into the web world, It's gonna start to be a real problem pretty quick. So let's just test it for right now, let's test our helloworld.
[00:08:05] We've created this module, we have a o1ne.js that uses it. But we need to actually say hello.say cuz we have our hello module and on it the API is say. And we need to pass it to file name. What is the file name? args.file. How did I get that?
[00:08:23] It's because I changed it so that I'm looking for file as one of my parameters.
>> Kyle Simpson: Everybody with me so far?
>> Kyle Simpson: Let's try it. I have
>> Kyle Simpson: A file called test.txt.
>> Kyle Simpson: Which has the helloworld in it. Actually, we need to do one more thing in our code, sorry.
[00:08:58] We need to say that we return the contents from that function for right now. So return those contents rather than saving them into a variable.
>> Kyle Simpson: And then inside of our main program, what we wanna do is we wanna say var contents. And then we wanna console dot log out those contents.
[00:09:32]
>> Kyle Simpson: Just to recap before I run it. We have a helloworld module that does a readfileSync to pull in whatever the contents are from the file that we specify. It returns that value back to us, so we call that. We call hello.say, passing in the file name. We get the contents back, and we print those contents out.
[00:09:54] So when we go to our command line, and we say node 1.js, we should get the help printed because we are already dealing with help. If I say 1.js -- file, I've already provided to you that test.txt. So I just use that, let's call it test.txt. And when you run it, you're gonna get something really strange looking now.
[00:10:19] Did everybody get something that looks roughly like that with the buffer being printed?
>> Speaker 3: Yes.
>> Kyle Simpson: Okay, so I do that on purpose to illustrate another thing that's true about JavaScript. Even when we're dealing with synchronous file reading, but this will be especially true when we get into streams after lunch, everything that is dealing with these data transfers is done in not in terms of strings the way we're normally used to dealing with them.
[00:10:45] It's not pulling in a string, it's creating an array buffer. And an array buffer is kind of an efficient, binary representation of our data. Well, we don't want to look at the hexadecimal representation of binary data, that doesn't help us. So how could we turn our buffer back into something that's useful?
[00:11:04] There's a variety of ways to do it, but the simplest way to illustrate is to call toString on our buffer. So if you come back to line 21 and you add toString, and you call it (), now if we go back to our command line and we run our command line, we pull in the helloworld from the file.