Initial Due Date: 2025-02-20 9:45AM
Final Due Date: 2025-03-06 4:15PM
In this practical, you are going to create a simple JavaScript module to get a feel for some basic JavaScript principles and start getting familiar with our tools.
Make sure you complete the steps on the getting started page. If you are using nvm
and you have installed multiple versions of node, before using node
and associated tools, you need to make sure the correct version is activated (đ» node -v
should return âv22.12â, though the minor and patch numbers after the first dot may vary slightly).
Create a new package by first creating the package directory (call it practical01-js-username
, using your GitHub username) and then running đ» pnpm init
inside the new directory as shown below. As noted in the getting started instructions, create this directory on your local disk, not in a âsyncedâ folder/drive (e.g., iCloud, OneDrive, Dropbox).
đ» mkdir practical01-js-mlinderm
đ» cd practical01-js-mlinderm
đ» pnpm init
The pnpm init
command will create the package.json
file, potentially asking you a series of questions. For most of the questions, you can accept the default. If it asks for the âtest commandâ, type jest
. If your directory is a Git repository (not the case here) pnpm
will automatically pull information from your Git repository to create the package.json
file.
The package.json
a file can be edited like any other text file. You can open it directly in VSCode, but I find it more useful to open the entire directory as a project in VSCode, and then navigate to the file from there. There are two ways to do this. You can use the âOpenâ option in the File menu and open the directory, or, if you have the command line tool installed, you can type đ» code .
in the terminal to open the current directory (in the shell .
is a shortcut for current directory and ..
is a shortcut for the parent directory).
Update the package.json
file to look the following. In particular make sure to add/update the following properties:
"private": true
This prevents you from accidentally publishing this package to the public repositories."type": "module"
This will make your code act as an ES module. The details of this arenât important, but it will mean that you can use the same style of code as we will later in the course."test"
script. If not set during initialization, update the test script to be "jest"
.After your manual editing your initial package.json
file should look something like the following:
Make sure to save package.json before moving on. Subsequent installation steps will update the package.json file for you, and if you havenât saved, you will create edit conflicts.
{
"name": "practical01-js-mlinderm",
"version": "1.0.0",
"private": true,
"type":"module",
"description": "CS312 Javascript practical exercise",
"main": "index.js",
"scripts": {
"test": "jest"
},
"author": "Michael Linderman <mlinderman@middlebury.edu>",
"license": "Apache-2.0"
}
We should get comfortable looking at JSON files. JSON files are plain text file used for structured data (e.g., configurations, messages). It is effectively a JavaScript object written out, the name stands for âJavaScript Object Notationâ. However, there are some subtle differences to be mindful of:
If you even have problems with your package.json
file being parsed, check for the second and third of these.
For most JavaScript projects, you will install a collection of dependant packages. The tool we used above (pnpm
) is the Performant Node Package Manager (it isnât the only option, but it is the one we will be using). You wonât need any packages for this assignment, but I will to evaluate it. So, I am going to have you install it so you get a feel for the process. You are going to install a tool called jest
, which helps run automated tests (we will learn about how to use it shortly).
On the command line, execute đ» pnpm install -D jest
to install Jest in this project.
This will take a few moments, and then you should see a message that looks like (although the details will differ and the version of jest may be newer). Here (and throughout the course) you can ignore the warnings about deprecated subdependencies.
WARNâ 2 deprecated subdependencies found: glob@7.2.3, inflight@1.0.6
Packages: +265
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 265, reused 0, downloaded 265, added 265, done
devDependencies:
+ jest 29.7.0
Done in 4.2s
Now look inside of package.json
again. You should see that it now has a new section:
"devDependencies": {
"jest": "^29.7.0"
}
You should also see a new file pnpm-lock.yaml
and a new sub directory named node_modules
into which jest
and all of its dependencies have been installed.
We used the -D
flag to install the package as a âdevelopment dependencyâ. These are packages that we need when we are building our modules, but should not be included in the deployment.
Letâs write our first Javascript functions. Create a new file named index.js
in the directory (or just open it if it was automatically created for you).
Write a function that sums all of the values in an array (you can assume the values are numerical). For our version, letâs write the code using a conventional for
loop. Create a new function called summationFor
. The export
keyword makes your function visible outside of the file, which we will need for testing.
export const summationFor = (list) => {
// ...
};
For loops in JavaScript look a lot like they do in Java/C/etc... To iterate over the indices of a list, we would do the following. Using this snippet, complete the implementation of your function.
for (let i = 0; i < list.length; i++) {
// ...
}
For our first tests we will run the Node.js interpreter (and it use it like the Python or other interpreters). Invoke đ» node
on the command line.
Tip: VSCode has an integrated command line that you can open at the bottom of the window. You will find it listed as âTerminalâ in the âViewâ menu.
To load your code into node
, type let p1 = await import("./index.js")
(we will learn more about await
later and why we need to use this particular import style in this context). Note that the node interpreter is a a REPL (read-eval-print-loop) and so there is an implicit print wrapped around each operation. In JS, a variable declaration returns and thus prints undefined when evaluated in the REPL (as doing many other statements). It is still performing the correct assignment, e.g., this the expected behavior:
> let p1 = await import("./index.js")
undefined
If you see a message in Node like âWarning: Module type of ... is not specified and it doesnât parse as CommonJS.â and the interpreter seems to âhangâ, that indicates you missed the "type":"module",
field when editing your package.json file. As noted in the warning message and above, this field tells Node to treat this file as a more modern ES module (the style of JS we will use throughout the semester).
This creates a new object called p1
(you can call this anything) and attaches all of your exports to it as properties. You can now run the functions by invoking them as properties of p1
(e.g., p1.summationFor([1,2,3])
). Test it out with a couple of Arrays and make sure it works.
If you make a change to index.js
, you will need to re-import it into node
. One simple approach is just to exit (see below) and restart node. Alternately, to re-import the file we will unfortunately need to work around some internal import caching performed by NodeJS. Since this cache is based on the filename, we can append a changing query parameter to the import file name (which is really a URL) so that it is treated as a different file (with respect the cache), but loads the same file from your computer. For example:
p1 = await import(`./index.js?v=${Date.now()}`)
To exit node
, you can invoke .exit
in the interpreter or use the keyboard shortcut ctrl + d.
As you know from class we wouldnât really use a for
loop for this. Write a second function called summationForEach
. You will replace the for
loop with the forEach
function. The forEach
function is a method on arrays. Recall that it is a higher-order function that takes in a function as an argument. The forEach
function calls the function you pass in once per element of the array, passing the element in as the first argument ot the provided function. Test your function in node
to make sure it works.
Even this approach is more iterative than we really would use for a problem like this. I would like you to write this function one more time, but this time instead of forEach
, you are going to use reduce
. reduce
is another high-order method on arrays. Its job is to run a function on every element of an array, accumulating a result, i.e., the array is reduced down to a single value. The function you pass into reduce
should take two arguments: 1) the accumulated value and 2) the current element in the array.
Write a new function called summationReduce
, which uses the reduce
method to sum the list. Note that reduce
is going to do all of the work, so, summationReduce
could actually use the implicit return
version of the fat arrow syntax (as could the reducer function that you pass to reduce
). Test your new function.
When you are happy that the new function is working, try running it on an empty array. You will probably get something that looks like this:
> p1.summationReduce([])
Uncaught TypeError: Reduce of empty array with no initial value
at Array.reduce (<anonymous>)
at p1.summationReduce (REPL1:1:40)
The reduce
method works by starting with the first element in the array as its initial value. If there isnât one... an error is thrown. We can fix this by adding a second argument to the reduce
function. This becomes the starting value for the reduction. Add in a 0 as the initial value (add it to reduce
, not to the reducer function, which already has two arguments). Try it again, verifying that your function works correctly on empty arrays.
While we are thinking about higher-order functions, letâs try a map
example. Write a decorate
function that returns an array of âdecoratedâ strings.
> p1.decorate(['Sam', 'Penny', 'Jordan'])
[ '-<< Sam >>-', '-<< Penny >>-', '-<< Jordan >>-' ]
Any time that you have an array of values and you need a second array of values reflecting some transformation of each value, you should think map
. The helper function you pass to map
takes in one value and returns the transformed value.
A common error is to think about the array as a whole. With map
you only need to think about what should happen to a single value and write a function to do that. So, start by writing a function that takes in a string and returns a âdecoratedâ string. Then, just pass this new function to map
and it will do the rest, applying it to each value in the array and loading the results into a new array for you.
A helpful tool here is the JavaScript template literal. This allows you to insert JavaScript expressions into a string. To make a template literal, use back ticks (`) instead of single or double quotes. You can then include expressions in the string by surrounding them with ${}
.
const x = 5;
const s = `The value of x is ${x}`;
console.log(s); // this will print out 'The value of x is 5`
Write and test the decorate
function. Donât change the decoration as it will break the automated tests.
Now that you have implemented your module, we want to turn the module into a Git repository.
Git is a distributed version control system (VCS). Git, and its âkiller appâ GitHub, will play a key role in our workflow. At its simplest, a VCS enables us to âcheckpointâ our work (a commit in Git terminology), creating a distinct version of our codebase that that we can return to. The opportunity to track and undo changes makes it easier to find new bugs (by reviewing differences to a prior working version), maintain a clean code base (we donât need to keep commented out code around in case we need it again), confidently make large changes knowing we can always recover a working version, and much more. For these reasons and more solo developers will use a VCS (and so should you!), but it is even more useful in a team environment.
How will you know if you and another developer modify the same file (in potentially incompatible ways)? How do you ensure you donât end up with a teammateâs half-working modifications? We will use the VCS to prevent these problems.
The âdistributedâ in âdistributed VCSâ means that no central repository is required. Each Git repository contains the entire history of the project and thus each developer can work independently, making commits (checkpoints) without interfering with anyone else. Only when you push or pull those changes to another copy of the repository do your changes become visible to anyone else. Further we will use branches to segregate our changes. A branch is just a parallel version of the codebase. This allows you to experiment, while leaving the main branch untouched until your new feature is ready to be merged back into the main.
Git does not require a central repository. However, teams still tend to use a central repository to facilitate their work (we will use GitHub in this role). There isnât anything technically special about the shared repository other than that the team decides to share their changes via that central repository rather than directly with each other.
We will use Git and GitHub (in concert with Gradescope) to submit your work. Keep in mind the âdistributedâ in distributed VCS. Until you have pushed your changes to GitHub (and submitted your repository to Gradescope) your work is not turned in.
We will create a new Git repository with the command line. Make sure that the current working directory of your shell is the project folder, and then execute đ» git init
. This will create a new Git repository in your current directory (stored in a hidden directory called .git
.)
Creating a commit (with new files or changes to existing files) is a two step process. First you stage the changes you want to preserve (đ» git add
) and then you commit the changes, which saves them in the repository (đ» git commit
).
Before we do this with your files, however, I should note that sometimes there are files that we do not want in the repository. These tend to be files that we can recreate later, or, in our case, other peopleâs code that we can always download again (i.e., the contents of node_modules
). We really donât want node_modules
to sneak into the repository. That directory can get quite large, and it fouls up Gradescope to no end if you include it.
As you will see below, if we are careful, we can avoid including node_modules
, but we want a solution that we can set once and forget. We can configure Git with a file called .gitignore
. Git consults this file first before looking for changes in the project.
Create a new file called .gitignore
and add the following to it:
# See https://help.github.com/ignore-files/ for more about ignoring files.
# Dependencies
/node_modules
# Misc
/.vscode
.DS_Store
This will ignore node_modules
as well as some other invisible files that VSCode and MacOS use to store data about the current directory.
Once youâve saved that file, we can walk through the process of adding files to the repository using the command line...
đ» git add .
in the terminal (note the trailing period). This adds all of the files in the current directory. Be careful of this! I usually recommend that you target the files you actually want to stage, but in this case, we already told Git what to ignore and we want everything else. Your files are not yet saved into the repository, they are only staged!đ» git status
. This isnât a required step, but I recommend using it often to see the state of your files. It should show that all of your files are new and staged.đ» git commit -m "My first commit"
. This actually creates and records the commit in your repository. The -m
allows you to add a message associated with your commit. This message should be short but informative so you can tell what you were trying to accomplish with the commit in case you need to go back to it later.Letâs do that one more time
đ» git status
to see that there is a modified (but not staged) file.đ» git add index.js
.đ» git status
again to see how the status has changed.git commit -m "Added name to the top"
.We can also look at the history of the repository with đ» git log
. You should see both of your commits in the log output.
You should work on getting comfortable with the command line, but you will also find that VSCode has a reasonable git client built in. You will find it by clicking the âSource Controlâ icon (it looks like a graph) on the left.
Multiple version of your code are now stored in your local repository. To turn your code in, you need to get a duplicate of your repository onto GitHub. The conventional way to do that would be to go to the GitHub site and create a new repository there. However, for this class we are using GitHub Classroom, which will automate this process of creating repositories for everyone in class
Click through to the GitHub classroom assignment. Clicking through to the assignment will create a private repository for you with the name practical01-js-<Your GitHub username>, e.g. practical01-js-mlinderm.git. You can view your repository at https://github.com/csci312a-s25/practical01-js-<Your GitHub username> (click the link provided by GitHub classroom).
On the front page of your new repository, it may list some options about how to get started. If those instructions appear, follow the directions for existing repositories. If not, follow the steps below.
đ» git remote add origin <repository URL>
, where <repository URL> is the URL for your repository at GitHub (donât be the one that actually types out â<repository URL>â). You can find this URL by clicking on the green âCodeâ button and copying one of the URLs. There are two kinds of URLSs: HTTPS and SSH. We want to use the SSH URL. If you didnât set up an SSH key as part of âGetting Startedâ, do so now.đ» git branch -M main
. This is is not actually an essential step for connecting the two repositories. This renames the primary branch of the repository from the git default of âmasterâ to the more inclusive âmainâ.đ» git pull --rebase origin main
. This retrieves starter code created by GitHub classroom that you donât have (recall the distributed nature of Git). This should not be needed in the future when you start by cloning the repository GitHub classroom creates for you.đ» git push -u origin main
. This tells git to copy changes from your current repository to âoriginâ, which is the repository on GitHub.If you reload the GitHub page, you will see it now lists your code.
Now, you can submit the practical to Gradescope as described here. Successfully submitting your assignment is a multi-step process:
Make sure you complete both steps (in order).
Required functionality:
jest
forEach
and reduce
decorate
using map
Recall that the Practical exercises are evaluated as âSatisfactory/Not yet satisfactoryâ. Your submission will need to implement all of the required functionality (i.e., pass all the tests) to be Satisfactory (2 points).