Practical One

Published

September 11, 2025

DeliverableDue Date
Practical 1September 16, 2025 at 02:15 PM
Practical 1 (revision)October 01, 2025 at 11:59 PM

Goals

  • Get started with basic JavaScript and Node.js
  • Get started with Git, GitHub classroom, and Gradescope

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.

Prerequisite

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, pnpm and associated tools, you need to make sure the correct version is activated if you have several version installed (node -v should return ‘v22.17.1’, though the minor version number may vary slightly).

Creating the module

Create a new package by first creating the package directory (call it practical01-username, using your GitHub username) and then running pnpm init inside the new directory. 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-ChristopherPAndrews
cd practical01-ChristopherPAndrews
pnpm init

The pnpm init command will create the package.json file by asking you a series of questions. For most of the questions, you can accept the default. When it asks for the “test command”, type vitest. 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 "vitest".

After your manual editing your initial package.json file should look something like the following.

Make sure to save package.json before moving on. Installing will try to update package.json for you, and if you haven’t saved, you will create edit conflicts.

{
  "name": "practical-01-ChristopherPAndrews",
  "version": "1.0.0",
  "private": true,
  "type":"module",
  "description": "CS312 JavaScript practical exercise",
  "main": "index.js",
  "scripts": {
    "test": "vitest"
  },
  "author": "Christopher Andrews <candrews@middlebury.edu>",
  "license": "Apache-2.0"
}

You should get used to 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”. There are some subtle differences that may catch you out:

  • the only accepted values (right side of the :) are numbers, Booleans, strings, Arrays, or objects
  • the keys (left side of the :) in a JSON file are always quoted strings (as opposed to JavaScript where you should not use quotes)
  • there are no trailing commas allowed (in JavaScript we can have a trailing comma after the last element of an array or object)

If you even have problems with your package.json file being parsed, check for the second and third of these.

Installing dependencies

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 vitest, which helps run automated tests (we will learn about how to use it shortly).

On the command line, execute pnpm add -D vitest.

VSCode’s integrated Terminal

One of the features of VSCode is the integrated Terminal. You can open it from the View menu or typing `^`` (the control key and the back tick). This will open in your working directory.

This will take a few moments, and then you should see a message that looks like

Packages: +51
+++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 96, reused 46, downloaded 5, added 51, done

devDependencies:
+ vitest 3.2.4

╭ Warning ───────────────────────────────────────────────────────────────────────────────────╮
│                                                                                            │
│   Ignored build scripts: esbuild.                                                          │
│   Run "pnpm approve-builds" to pick which dependencies should be allowed to run scripts.   │
│                                                                                            │
╰────────────────────────────────────────────────────────────────────────────────────────────╯

Done in 1.3s using pnpm v10.15.0

Go ahead and execute pnpm approve-builds. This selects build scripts we are willing for pnpm to execute for us. It isn’t really essential for our purposes now, but again, this is about letting you see the process.

You will see something that looks like:

? Choose which packages to build (Press <space> to select, <a> to toggle all, <i> to invert selection) … 
❯ ○ esbuild

Press <space> to select esbuild and then hit enter. When it asks if you approve, type ‘y’.

Now look inside of package.json again. You should see that it now has a new section:

"devDependencies": {
        "vitest": "^3.2.4"
    }

You should also see a new file pnpm-lock.yaml (which is a list of everything that was installed), a new file called pnpm-workspace.yaml (which is giving future builds permission to run the esbuild script) and a new sub directory called node_modules into which vitest 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.

Writing some functions

Let’s write a couple of functions. Create a new file called index.js in the directory (or just open it if it was automatically created for you).

Summation

Write a function that sums all of the values in an array (you can assume the values are numeric).

For our first pass, 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 use:

for (let i = 0; i < list.length; i++){
}

Using this snippet, complete the implementation of your function, returning the sum of all of the items in the list.

Testing the function

While we installed vitest earlier, we are going focus for the moment on manual testing using the Node.js interpreter (which works similarly to the Python interpreter).

Invoke node in the terminal in the same directory as your file.

Load your code into node with 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
Important

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 <tt>package.json</tt> 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 on 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. The simplest approach is to exit node and restart node. To leave node, you can type .exit or use the keyboard shortcut ^d.

Tip

You can use the up and down arrows to cycle through previously entered commands in the node interpreter, just like you can in the shell. node retains the command history across restarts, so you can use up arrow to return to the command to reload the file.

If you just repeat the line provided above, you will get an error because p1 was already declared. If you leave the let off, the command will complete without error… but it will not do what you want. node does some caching behind the scenes, and will use its cached version of your file instead of the new one. We can trick it by altering the name in a subtle way that doesn’t affect which file is loaded:

p1 = await import(`./index.js?v=${Date.now()}`))

We are taking advantage of the fact that the system is designed to operate on URLs and attaching a changing query string. The query string is ignored, but it does trick node into believing this is a new file.

Summation take 2

As you should know from the lecture, we wouldn’t really use a for loop for this. Write a second function called summationForEach. You will replace the for loop with a forEach. The forEach function is a method of 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 of the provided function.

Test your function in node to make sure it works.

Summation take 3

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 of arrays. Its job is to run a function on every element of an array, accumulating a result – in essence, the array is reduced down to a single value. The function you pass into reduce should have two arguments – the first is the accumulated value and the second is 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 Module.summationReduce (index.js:20:47)

I edited the error message somewhat to not include the full path to index.js

The reduce method works by starting with the first element in the array as its initial value. If there isn’t one… BOOM – 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 both empty arrays and non-empty arrays.

Mapping

While we are thinking about higher-order functions, let’s try a map example.

I would like you to 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. Let that go – the real benefit of using map is that you only really need to think about what happens to a single value and write a function that does 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.

Tip

A helpful tool here is that 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. Keep the decoration the same so that my auto-checker recognizes your answer.

Getting Started with Git

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 type 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). In truth, we really don’t want node_modules to sneak into the repository. The 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.

  1. Make sure your files are all saved
  2. Stage your files with git add .. 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!
  3. Check that the files are staged with 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.
  4. Commit your changes with git commit -m "My first commit". This loads your files into the repository. The -m allows you to add a message associated with your commit. This 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.
  5. Check the status again – there should be no changes.

Let’s do that one more time

  1. Create a comment at the top of the file and add your name. (JavaScript comments are the same as Java and C).
  2. Type git status to see that there is a modified (but not staged) file.
  3. Type git add index.js.
  4. Type git status again to see how the status has changed.
  5. Now commit the changes to the repository with 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 find that VSCode actually 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 something called GitHub Classroom, which will automate this process of creating repositories for everyone in class. This let’s me give you starter code as well as automate the process of giving me read access to your repositories.

Click through to the GitHub classroom assignment. Clicking through to the assignment will create a private repository for you with the name “practical01-<Your GitHub username>.git”, e.g. “practical01-ChristopherPAndrews.git”. You can view your repository at https://github.com/csci312-f25/practical01-<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.

  1. git remote add origin repo-name, where repo-name is the name provided by GitHub (don’t be the one that actually types out “repo-name”). `, 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.
  2. 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”.
  3. 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: 1. Commit and push your changes to GitHub 2. Submit your repository to the Gradescope assignment.

Make sure you complete both steps (in order).

Requirements

  • Create Node.js project
  • Install vitest
  • Write three versions of summation using a loop, forEach and reduce
  • Write decorate using map
  • Setup git and GitHub
  • Submit to Gradescope

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).