Week 4 - NPM, Git and Linters - Oh My!
Package.json file
The package.json
file defines all the information about a project.
{
"name": "week4",
"version": "1.0.0",
"main": "lab4.js",
"scripts": {
"test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest",
"func": "run-func lab4.js"
},
"type": "module",
"dependencies": {
"node-fetch": "^3.3.2"
},
"devDependencies": {
"@babel/core": "^7.23.9",
"@babel/preset-env": "^7.23.9",
"cross-env": "^7.0.3",
"eslint": "^8.56.0",
"jest": "^29.7.0",
"prettier": "^3.2.5"
}
}
Project Metadata
The following properties describe information about the project:
- name: Unique identifier of your project, used in package dependencies.
- version: Current project version, following Semantic Versioning (SemVer) standards.
- author: Creator or maintainer of the project, can include name, email, and URL.
- license: License type under which the project is released, e.g., MIT, GPL.
"name": "week4",
"version": "1.0.0",
"author": {
"name": "Brodie Davis",
"email": "[email protected]",
"url": "https://www.mvcc.edu/"
},
"license": "ISC",
Type (optional)
The type
option defines the module format for your project. It has two possible values:
"commonjs"
: This is the default if the type field is not specified. It tells Node.js that.js
files should be treated as CommonJS modules, which is the traditional module format in Node.js.- This is most well known for import statements looking like
const jest = require('jest')
- This is most well known for import statements looking like
"module"
: Setting type to “module” indicates that.js
files in the project should be treated as ECMAScript modules (ES modules or ESM). This allows you to use import and export syntax directly in.js
files without needing to use the.mjs
extension.- This is most well known for import statements looking like
import {Jest} from 'jest'
- This is most well known for import statements looking like
Note: CommonJS vs Module format is one of the biggest pain points of setting up a new project. If you can, use a template fr your type of project and don’t change it!
"type": "module"
Main (optional)
Main is the “entrypoint” for node.js to execute when running your project, if relevant.
"main": "lab4.js"
Dependencies
Dependencies is an object containing all of the packages and their version that your project requires to run.
"dependencies": {
"node-fetch": "^3.3.2"
}
Dev Dependencies
Dev Dependencies is an object containing all of the packages and their version that your project requires to be built and worked on.
"devDependencies": {
"@babel/core": "^7.23.9",
"@babel/preset-env": "^7.23.9",
"cross-env": "^7.0.3",
"eslint": "^8.56.0",
"jest": "^29.7.0",
"prettier": "^3.2.5"
}
Scripts
Scripts contains aliases to command-line scripts used to build, deploy, test, and generally interact with your project. Some keys things to note:
- Scripts can run node packages directly that you cannot from the normal command line.
- Ex, you couldn’t run the command
jest
, but you could runnpm run test
which callsjest
- Ex, you couldn’t run the command
- A common pattern in scripts is to use a
:
after a keyword to group common functionality.- For example,
lint:format
andlint:spellcheck
both perform “linting tasks”, but one runsprettier
and one runscspell
- For example,
- Scripts can call each other to share functionality.
- For example, the command
lint
may just runlint:format && lint:spellcheck
- For example, the command
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "jest",
"lint": "npm run lint:format && npm run lint:spellcheck",
"lint:format": "eslint .",
"lint:spellcheck": "cspell -e **/*.txt .",
},
Package-lock.json
The file package-lock.json
is a manifest of all installed packages and their versions. By storing a snapshot of installed packages:
- We achieve consistency across developer systems and within production
- NPM doesn’t need to resolve all of those dependencies for every installation (resulting in much faster installs)
NPM
NPM is a package manager that can install prebuilt packages for us, and much more!
NPM packages are generally very tiny and do one specific thing. A silly example: is-odd, with 500k weekly downloads.
Oh, and don’t forget is-even:
module.exports = function isEven(i) {
return !isOdd(i);
};
Command Shorthand
Any NPM command can be shorted until the point that it is “ambiguous”. Lets look at the full list of commands:
access, adduser, audit, bugs, cache, ci, completion,
config, dedupe, deprecate, diff, dist-tag, docs, doctor,
edit, exec, explain, explore, find-dupes, fund, get, help,
help-search, hook, init, install, install-ci-test,
install-test, link, ll, login, logout, ls, org, outdated,
owner, pack, ping, pkg, prefix, profile, prune, publish,
query, rebuild, repo, restart, root, run-script, sbom,
search, set, shrinkwrap, star, stars, start, stop, team,
test, token, uninstall, unpublish, unstar, update, version,
view, whoami
There is only one command that starts with i
, so the command npm i
runs npm install
.
Multiple commands start with the letter a
, so npm a
is an error, but npm aud
runs npm audit
.
Common Commands
npm install
To install all of the dependencies for a project, we run npm install
with no additional arguments:
npm i
added 582 packages, and audited 583 packages in 2s
54 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
The command npm install
can also take any number of positional arguments after it to install additional packages to your project:
npm install react node-fetch express
Installing Development Dependencies
The command can optionally accept numerous named arguments as well. The most commonly used is --save-dev
. When running npm install <PACKAGE>
, the package will automatically be saved to your dependencies. When using --save-dev
, the package is saved to your devDependencies
. Dev Dependencies are used for tools used only in development, like linters and compilers.
npm install --save-dev prettier jest cross-env
Installing Specific Versions
If you need to install a specfic version of a package, you can add @version
at the end of a package name. Packages use Semantic Versioning of Major.Minor.Patch
.
You can specify only a major version:
npm install jest@29
Or a Major, Minor and Patch version:
npm install [email protected]
npm run
The command npm run
will be your most executed command. It’s used to execute the scripts defined in the package.json
file.
A very common example is npm run dev
, which will start a development server on the local system. While there is no requirement for this script name, most frameworks use this as a way to run their local development server.
Likewise, most frameworks use npm run build
to create a “production” build for deployment to a server.
The actions performed by any npm run
command are entirely dependent on the package.json
file.
Development Tools
You don’t have to code alone! The use of tools to provide constant feedback is strongly encourage, it makes you a better programmer with each line you write!
Code Analysis
Linting
A “linter” analyzes code for potential errors, stylistic issues, and deviations from coding standards or best practices. Linters can:
- Improve code quality
- Improve code consistency (across a single project and multiple projects)
- Improve readability
ESLint is a common linting tool for Javascript. It supports a ton of plugins and custom rules for configuring it to exactly your project style and tech stack.
Code Formatting
Code format tools simply restructure the spacing of your code to match a given style. They do not change the content of your code.
Prettier is a common code formatting tool for JavaScript.
Unit Tests
Unit tests exist to run specific portions of your code. They are designed to execute as little of your code as possible - typically at the function level.
Sometimes unit tests will “mock” certain function calls, and provide fake return values rather than relying on actual code.
This allows you to test specific edge cases within your code without having to fabricate the full scenario - just the bits that are relevant to a particular test.
Jest is a common unit test framework for JavaScript.
// userService.js
export const getUserById = async (id) => {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
return response.json();
};
// userService.test.js
describe('getUserById', () => {
global.fetch = jest.fn();
it('calls the correct endpoint', async () => {
fetch.mockResolvedValueOnce({
json: () => Promise.resolve({ id: 1, name: 'John' })
});
await getUserById(1);
expect(fetch).toHaveBeenCalledWith(
'https://jsonplaceholder.typicode.com/users/1'
);
});
});
Build Tools
Build tools, sometimes called “bundlers” or (incorrectly) “compilers”, transform source code into another format.
This may be required for several reasons:
- Preprocessing: Source code is written in a language that interpreter will not understand
- Ex, the source is JSX (React) or Typescript and a browser cannot directly execute JSX/Typescript
- Polyfills: more generic code is needed to replace features not fully supported.
- Ex, ES2022 syntax destined for the browser where users may have an outdated browser
- Code Splitting - removing sections of code that aren’t actually used.
- Ex, you import a library with 100 dependencies but you only use one function. We don’t want to include all 100 dependencies in our final bundle!
Many build tools exist, all with overlapping features and specific niches that they were designed for. Some common buld tools include:
Git Source Control
The most common source control software is git
. Git is an extremely powerful tool, and with all power tools, can get extremely confusing. We’re going to focus on a very basic usage of git
for this class to introduce the concepts.
The primary features of git as it’s used for software development include:
- Maintaining a line-by-line history of your code (through commits)
- Allow developing multiple features at one time (through branches)
- Creating a shared method of access for a team of developers (because everyone works from the same “history”)
If life were a video game…
One analogy for git is save files in a video game.
Commits: Each time you reach a new checkpoint in the game, you create a new save/commit. You can go back to any save at any time, and each save is linear in nature - a save depends on all the saves before it.
Branches: Many games have multiple story lines based on your choices. If you weren’t sure how a decision would turn out, you might save (make a commit) just before making a decision, then see how it goes. If you didn’t like the result, you can just load an older save and delete the one that didn’t work out.
The best part is that all the game saves are on a remote server, so anytime that any of your friends want to play a previous save, everyone can access the save they want at any given time!
The commands git pull
and git push
are used to sync your work from and to the remote server, respectively.
Tool Configuration
Each tool typically has it’s own configuration file at the root of the project. This can create some pretty messy root folders!
cspell.yaml
.eslintignore
.eslintrc.cjs
package.json
package-lock.json
postcss.config.cjs
.prettierignore
.prettierrc
svelte.config.js
tailwind.config.cjs
tsconfig.json
vite.config.js
Unfortunately there isn’t much we can do about this. Some tools accept their configuration as part of the package.json file, but not most, and even then the package.json file is very limiting compared to an actual .js
file.
Javascript Modules
Javascript has two different systems for modularizing Javascript code - ECMAScript (ESM) and CommonJS (CJS).
CJS:
Predominantly used in Node.js applications due to its synchronous loading model fitting well for server-side applications where modules are locally available.
ESM:
Designed with the web in mind, supporting asynchronous loading and better optimization capabilities, making it suitable for both browser and modern Node.js applications.
Note: You don’t need to know these systems inside and out, but you should know they exist and what they look like. This article goes into some more detail about the differences if you’re curious and want to know more, but we’ll never use any of that information in class.
CommonJS (CJS)
CommonJS is the “older” way to modularize code. It’s less preferred but still very common is existing libraries. You cannot use a CommonJS library in an ESM application without special configuration.
Importing Code
CJS uses the require
keyword to import code from other modules:
const fetch = require('node-fetch');
const { jest } = require('@jest/globals');
const lab4 = require('./lab4');
Exporting Code
CJS uses the module.exports
object for exporting code for use by other modules.
function myFunction() {
return 'do stuff'
}
module.exports = { myFunction };
// or if the module only exports one thing
module.exports = myFunction;
// or an inline export
module.exports.myFunction = () => {
return 'do stuff'
}
ECMAScript (ESM)
ESM is a newer format, and generally preferred in modern code bases.
Importing code
ESM uses the import
keyword to import code from other modules:
import fetch from 'node-fetch';
import {jest} from '@jest/globals';
import * as lab4 from './lab4';
Exporting Code
ESM uses the export
keyword to export code for use by other modules.
export function myFunction() {
return 'do stuff'
}
// or if the module only exports one thing
export default myFunction() {
return 'do stuff'
}
Introduction to React
What is React? React is “a javascript and XML based library for creating web and native User Interfaces (UIs).”
Thinking of websites like a house:
HTML is the foundation and framing
CSS is the paint and design
Javascript is the light switches - it controls the fixtures in the house
Typescript is the electrical code that prevents safety concerns.
React is pieces of a modular home, you can create/install entire rooms as a module.
Why React?
- It’s declarative and predictable - React’s component-based architecture allows creating encapsulated components that manage their own state.
- It’s reusable - Components can be created once, and used many times. In the same way a function allows for the reuse of logic, components allow for the reuse of UI elements.
- It’s efficient (in comparison to something like jQuery) - the library only updates what needs to be updated on the page.
- Reactivity - Web frameworks like React allow extremely dynamic webpages, with many moving parts and components. This allows for significant flexibility in design.
Why not React?
Every tool has trade-offs. Why might you not want to use React, or a similiar web framework?
- Complexity - It takes several components and technologies working in unison to create a working UI in react. This can create alot of overhead for small projects or simple, static websites.
- Search Engine Optmiziation (SEO) - React sites often cannot be indexed properly by search engines due to the dynamic content creation
- Server Side Rendering (SSR) helps with this.
- Fast-Paced Ecosystem - The React community moves fast, so if you’re trying to design a low-maintence website that will be around for many years, you may lose support for your version of the libraries.
So React is JavaScript?
Weeeeellllllll technically it’s JSX, known formally as “Javascript XML”. It was created specifically for writing React components to support adding HTML elements to the DOM without using the browser functions like createElement
and appendChild
directly.
It is designed as “HTML within Javascript” to simplify readability and create a clear relationship between HTML Elements and the Javascript code that controls them.
There is a variation of Javascript, known as Typescript - and likewise, there is TSX, a variation of JSX.
Components
Functional Components
We can see that tricky anonymous arrow function ((params) => {/* function body */}
) coming back.
// A simple to-do item component, demonstrating props and basic event handling
const TodoItem = ({ todo, toggleComplete }) => {
return (
<li
style={{ textDecoration: todo.isCompleted ? 'line-through' : 'none' }}
onClick={() => toggleComplete(todo.id)}
>
{todo.text}
</li>
);
};
Some things to note about this example:
TodoItem
is a function, but also a component- When we use this as a component it looks like this:
<TodoItem></TodoItem>
- When we use this as a component it looks like this:
TodoItem
takes two parameters,todo
andtoggleComplete
- These props are actually “destructured” from a single object.
- The function returns a “React Element”
- Components must return a single root element.
Component Props
Props can be thought of as “function parameters”. We can pass props like xml/html attributes:
function completeTask(taskId) {
// do some logic to mark the task as complete
}
export App(){
let todo = {id: 1, text: "Clean the dishes", isCompleted: false}
return (
<TodoItem todo={todo} toggleComplete={completeTask} />
)
}
NOTE: All XML must be closed in JSX. The tag can be self-closing or use a separate closing tag, either are valid.
- Correct:
<Todo></Todo>
- Correct:
<Todo />
- Incorrect:
<Todo>
React Elements
React components must always return a single root element or node. This is commonly a div
:
return (
<div>
<h1>Hedy Lamarr's Todos</h1>
<ul>
...
</ul>
<div/>
)
We also have a shorthand called a fragment
we can use:
return (
<>
<h1>Hedy Lamarr's Todos</h1>
<ul>
...
</ul>
</>
)
The only difference is that a fragment is not rendered and leaves no trace in the browser.
<main>
<div> <!-- This line would not exist with the fragment example -->
<h1>Hedy Lamarr's Todos</h1>
<ul>
...
</ul>
<div/> <!-- This line would not exist with the fragment example -->
</main>
Class Based Components
Instead of defining components as a function, we can also define them as a class. In this case, our XML is returned from a render()
function.
class MyComponent extends React.Component {
render() {
// Returns a React element
return <div>Hello, {this.props.name}!</div>;
}
}
Switching between XML and JavaScript
Throughout JSX, you may switch between writing XML and JavaScript at any time.
Within JavaScript, you start an XML document simply by using an opening XML tag
function App() {
// automatically switch to xml at the div
return <div>Hello, World!</div>
}
Within XML, you use a single pair of curly braces {}
to switch back to JavaScript.
function App() {
const message = "Hello, World!"
// automatically switch to xml at the div
return <div>
<!-- Curly braces switch you BACK to Javascript,
where we can access the message variable -->
{message}
</div>
}
Generating XML from a Loop
This can happen as many times as you want. A common example is looping over a JavaScript Array.
NOTE: Each element rendered in a loop in React needs to have a unique key
attribute so React knows how to update it if it changes later.
Take this example of looping over car manufacturers:
function ManufacturerList() {
const manufacturers = ["Chevy", "Ford", "Toyota", "Honda"]
// switch to xml
return <ul>
{/* switch to js */}
{manufacturers.map(m => {
return (
{/* switch to xml */}
<li key={m}>
{/* switch to jjs */}
{m}
</li>
)
})}
</ul>
}
// the preferred, shorthand version
function ManufacturerList() {
const manufacturers = ["Chevy", "Ford", "Toyota", "Honda"]
return <ul>
{manufacturers.map(m => <li key={m}>{m}</li>}
</ul>
}
A Full Example
import React, { useState, useEffect } from 'react';
// A simple to-do item component, demonstrating props and basic event handling
const TodoItem = ({ todo, toggleComplete }) => {
return (
<li
style={{ textDecoration: todo.isCompleted ? 'line-through' : 'none' }}
onClick={() => toggleComplete(todo.id)}
>
{todo.text}
</li>
);
};
// Main to-do list component, demonstrating state, useEffect hook, and rendering a list of components
const TodoList = () => {
const [todos, setTodos] = useState([
{ id: 1, text: 'Learn React', isCompleted: false },
{ id: 2, text: 'Build a React app', isCompleted: false },
]);
const [newTodo, setNewTodo] = useState('');
// useEffect hook to mimic componentDidMount and componentDidUpdate
useEffect(() => {
document.title = `You have ${todos.length} tasks`;
}, [todos]); // Dependency array ensures effect runs only when todos array changes
const addTodo = (text) => {
const newTodos = [...todos, { id: todos.length + 1, text, isCompleted: false }];
setTodos(newTodos);
};
const handleToggleComplete = (id) => {
const newTodos = todos.map((todo) =>
todo.id === id ? { ...todo, isCompleted: !todo.isCompleted } : todo
);
setTodos(newTodos);
};
const handleSubmit = (e) => {
e.preventDefault();
if (!newTodo.trim()) return;
addTodo(newTodo);
setNewTodo('');
};
return (
<div>
<h1>Todo List</h1>
<ul>
{todos.map((todo) => (
<TodoItem key={todo.id} todo={todo} toggleComplete={handleToggleComplete} />
))}
</ul>
<form onSubmit={handleSubmit}>
<input
type="text"
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="Add a new task"
/>
<button type="submit">Add</button>
</form>
</div>
);
};
export default TodoList;
UI Libraries
Many common User Interface libraries exist already for us to utilize.
CSS libraries provide us pre-made CSS classes to work with, like Tailwind or Bootstrap.
Component libraries give us components we can import directly to React. These sometimes build on a CSS library.
- JoyUI (this is what we’re using in this class)
- Material UI (same creators as JoyUI)
- React-Bootstrap
Joy UI
JoyUI is the component library we will be using for our midterm/final project ad through this class.
It’s based on the CSS framework Tailwind CSS. We’ll do a deeper dive into styling the library next week.
Caveats
All libraries have some caveats to be aware of, and I want to outline some you may run into here.
Show code button
You may need to click “show code” on some examples to see the full context about how a component might be used.
JoyUI shares docs with several libraries
Be careful when reviewing and searching the docs for JoyUI. The docs for MUI are on the same site, and sometimes links direct you to MUI instead of JoyUI.
Watch for the JoyUI and MUI indicators. The libraries are very similar but not exactly the same!