Closed kitchen-boy closed 2 years ago
Could consider this application complete.
Created a Node.js application that asks for your input & creates an HTML page => the main objective at the the outset of this project.
If we open the generated HTML file in a browser the output looks like the following image:
NEXT: To complete this portfolio is to add a style sheet, as per the final GitHub issue.
In this lesson, TO DO: -- Understand how to use the fs module to copy files from one location to another. -- Create JavaScript Promises to handle asynchronous functionality in a cleaner way.
NOTE: Up until now, only worked with executing asynchronous JavaScript code that returns a Promise, but haven't really looked to see how Promises work.
Will create our own PROMISES to clean up & handle asynchronous code.
Download the style sheet it to the <src> folder in the project's directory to keep it in the same place as the HTML functionality: https://static.fullstack-bootcamp.com/module-9/module-9-stylesheet.zip
Should look at how we expect the finished output to work, before we write the function for copying this CSS file.
Currently, we're writing the finished HTML file to the root of the application. Need to create a subdirectory for the final output, because we will have a finished product with 2 files.
Create a folder called <dist> in the project's directory. This stands for "distribrution", which is a common name used in web development to indicate that the contents of that folder are exactly what we need for the final product.
We only need the HTML & CSS FILES, & none of the JavaScript we used to create them, for the deployment of the portfolio site. -- This <dist> folder can help us distinguish the production-ready code to deploy from the working code that gets us there.
Change the <fs> code in <app.js> to have the file written to this path instead of <index.html>.
Update code as follows:
The <index.html> file will be created in the <dist> folder, when we run the code.
IMPORTANT: The <dist> folder needs to exist for a file to be created inside it. If it doesn't exist, you'll get an error. The fs.writeFile() method WILL NOT CREATE a directory to place the file in if it doesn't already exist. When using <fs.writeFile()>, we need to make sure the directory we're writing a file to already exists. This can be done manually or by using the <fs.existsSync()> method. -- The <fs> library has functionality to create a directory, however, so you can always read the documentation for it and implement such a system yourself!
Add the functionality to copy the <style.css> file from <src> to <dist> every time we create a new portfolio.
Use <.copyFile()>, another method of the <fs> library. Refer to the Node.js API documentation for the File System library: https://nodejs.org/docs/latest-v12.x/api/fs.html#fs_fs_copyfile_src_dest_flags_callback
<.copyFile()> method works similarly to the <.writeFile()> method ==> Provide 3 sets of data as function arguments:
The implementation of <.copyFile()> will look like the following:
When we place this code into the application & run it, it will find <style.css> in the <src> directory & create a copy of it in the <dist> directory.
Rename it to <style.css> because we're working with a style sheet - can rename it to whatever we want upon copying.
Then if there's an error, we'll let the user know & stop the <.copyFile()> method from running with a <return> statement.
It should probably go in the same <.then()> callback as the <.writeFile()> method -- so that it doesn't execute until we're done answering the prompts.
When dealing with asynchronous code: Must consider where to place code -- can have both <fs> functions run concurrently (because one isn't dependent on the other), but it makes it more difficult to determine what went wrong if an error occurs.
To execute the code in order: Have the <fs.copyFile()> functionality occur inside the callback function for <fs.writeFile()>. -- this way, we know that the <.writeFile()> method successfully created the HTML file before we attempt to copy the CSS file.
Update the <.fs.writeFile()> in <app.js>:
We wait for confirmation that the <.writeFile()> method ran successfully.
When we get that confirmation (which in this case, means not receiving an error), we then execute the <.copyFile()> method. -- this isn't mandatory for app to work, but it does make the app run its functions one at a time, making it easier to track what's going on when.
Run <app.js> & answer all the required prompts from the command line.
Both <index.html> & <style.css> should be created in <dist>.
If any error printed to command line/it doesn't look like this ==> check that <dist> folder is created in the correct location, directly at the root of the project's directory.
If run successfully, open <index.html> in the browser.
See new portfolio - should look like following:
DONE!
Current code in <app.js>: a mix of callback functions & Promises.
Both are completely valid ways to handle asynchronous functionality, but putting them together makes the code harder to read & understand (esp. in the <fs> methods (where we're executing a callback function inside of a callback function, which also occurs inside of a callback function. -- aka the Pyramid of Doom or Callback Hell. -- https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Introducing#promises
Refer to following code:
Note: <writeFile()> & <copyFile()> functions - where did they come from?? We will create these functions in a new file called <generate-site.js> in a new <utils> subdirectory.
Go to next page: 9.5.4 Write a File Using Promise.
(callback function vs a Promise-based function):
Lesson Goal: Learn how to set up this type of functionality.
Create a <utils> subdirectory & a file called <generate-side.js> that goes inside it - in the application's root directory. -- (naming subdirectory <utils> because the functionality has to do with utilities used to get the app working.
Port all of the <fs> functionality (currently used in app.js) over to the file <generate-site.js>.
Note: To use the <fs library in a file, import it using the Node.js <require()> function.
We need to create a new Promise object using the JavaScript keyword <new>.
This Promise object acts like a container that allows us to run code that at some point will be in a status of "pending", which means that the code has started to run but is waiting for a response.
This could be for HTTP requests like <fetch()> or even timed events like <setTimeout()>.
Inside the parentheses for the new Promise, we provide it with a function that accepts 2 functions as parameters: <resolve> and <reject>.
From there, we can write whatever asynchronous functionality we need to execute, & run: -- the <resolve() function when the code executes successfully or -- <reject()> when it fails to execute successfully.
Update our new <writeFile> function to actually write a file using <fs>:
We're using the same <fs.writeFile()> function that we have in
This time, we're contextualizing it to a Promise and accepting the HTML file's content as a parameter.
If we were to execute it, it would look like the following code:
IMPORTANT Feel free to execute this code in generate-site.js. Just know that it will look for a dist folder inside the utils folder, and may throw an error if it doesn't find one. It's a good way to test the .catch() functionality, though!
Compare how the code looks executed in <app.js> earlier, then look at how the <fs.copyFile() method's code looks.
How writeFile was executed in app.js earlier:
Current writeFile () in <generate-site.js>
Need to create one more function:
need to take the <fs.copyFile()> functionality & turn it into a Promise-based function.
just like the previously created <writeFile()> function.
Hints to create this function (refer to the <writeFile()> function that was just created in <generate-site.js>:
Make <copyFile()> return a new Promise object, like we do with <writeFile()> above.
Move the <fs.copyFile()> code form <app.js> into the returning Promise object's function into the <copyFile()**> function:
If there's an error, <reject()> the error and return out of the function:
If it's successful, <resolve()> it with a success message:
Done with writing this JavaScript Promise for <copyFile()> function!
Now we have the 2 functions for handling the project's output.
We need to export it from <generate-site.js> and import it into <app.js> to include in the Promise chain at the bottom!
Earlier, in <page-template.js>, Used <module.exports> to export only one function.
Need to export 2 functions both at once, using <module.exports>.
<module.exports> supports exporting data any way we need it to be exported.
module.exports can only be used once in a file. If we need to export multiple functions or data in a file, we need to set module.exports to an object or array.
Place the following code at the bottom of <generate-site.js>:
We're exporting an object with the two functions, <writeFile()> and <copyFile()>, used as methods, <writeFile> and <copyFile>. Very confusing & redundant to use the name of the function as both the method name and the value of the method.
Can simplify this by using another feature from ES6, as follows:
-- This is aka shorthand property names: if we have a property key name with the same name as the value we're associating with it (e.g., <writeFile: writeFile>, we can just say
Now we have the 2 functions (writeFile, copyFile) being exported from <generate-site.js>.
We can import them wherever we need them, i.e. <app.js>.
In <app.js>: -- Remove <const fs = require('fs')> at the top of <app.js> , as we're no longer going to use the <fs> library in this file.
Add the following line to the top instead:
This will import the exported object from <generate-site.js> allowing us to use <generateSite.writeFile()> and <generateSite.copyFile()>.
NOW, we can export & import multiple sets of functionality or data between files.
Change <generateSite> const variable to look as follows:
We exported an object from <generate-site.js>, so we can use object destructuring to create variables out of those properties instead of having to use dot notation. -- Keep code looking clean: use object destructuring instead of using <objectName.methodName()> notation to run the functionality.
Double-check the Inquirer prompt Promise chain at the bottom of <app.js> and confirm that we're now using these new functions instead of the <fs> methods.
this returns all of the data as an object in a Promise.
We recursively call <promptProject()> for as many projects as the user wants to add.
Each project will be pushed into a <projects> array in the collection of portfolio info, and when we're done, the final set of data is returned to the next <.then()>.
NOTE:
(a recursive function can be defined as a routine that calls itself directly or indirectly)
SUMMARY: PROMISES
IMPORTANT:
Commit and push your feature branch (feature/finished-site) to GitHub.
Merge your local feature branch (feature/finished-site) into your local develop branch and push develop to GitHub.
Close your GitHub issue regarding this feature.
We now know how to use the <fs> module to copy files from one location to another.
We've created our own JavaScript Promises to handle asynchronous functionality in a cleaner fashion.
We understand the purpose of Node.js and some of its uses in web development.
We know how to create and execute a Node.js application.
We learned a bit about the process for new features to be introduced into JavaScript and who is in charge of it (Ecma).
We can use new means of creating variables and functions in JavaScript.
We know a new way to concatenate variable data into strings using template literals along with some other new ES6 features such as destructuring and spread/rest operators.
We can modularize the Node.js applications to enhance code readability and reusability.
We know that Node.js has built-in tools and how to use them if we need to, such as the <fs> library.
We understand how and why to use the <npm init> command to create Node.js packages that are dependent on outside tools and libraries such as Inquirer.
We can use new array methods with object destructuring and -arrow function implicit returns_ to create succinct and readable code.
We can identify where the asynchronous functionality can be cleaned up and how to turn callback functions into JavaScript Promise objects.
*In the next module, we'll learn about two very important concepts in web development:
Description