4.9 Node.js, npm וכלי פיתוח פתרון
פתרון - Node.js, npm וכלי פיתוח¶
פתרון תרגיל 1¶
א. calc.js:
const args = process.argv.slice(2);
if (args.length !== 3) {
console.log("Usage: node calc.js <num1> <operator> <num2>");
console.log("Operators: + - x /");
process.exit(1);
}
const num1 = parseFloat(args[0]);
const operator = args[1];
const num2 = parseFloat(args[2]);
if (isNaN(num1) || isNaN(num2)) {
console.log("Error: arguments must be numbers");
process.exit(1);
}
let result;
switch (operator) {
case "+":
result = num1 + num2;
break;
case "-":
result = num1 - num2;
break;
case "x":
result = num1 * num2;
break;
case "/":
if (num2 === 0) {
console.log("Error: division by zero");
process.exit(1);
}
result = num1 / num2;
break;
default:
console.log(`Unknown operator: ${operator}`);
process.exit(1);
}
console.log(parseFloat(result.toFixed(2)));
ב. fileinfo.js:
const fs = require("fs");
const path = require("path");
const filePath = process.argv[2];
if (!filePath) {
console.log("Usage: node fileinfo.js <file-path>");
process.exit(1);
}
try {
const fullPath = path.resolve(filePath);
const stats = fs.statSync(fullPath);
const content = fs.readFileSync(fullPath, "utf-8");
const lineCount = content.split("\n").length;
console.log(`File: ${path.basename(fullPath)}`);
console.log(`Size: ${stats.size} bytes`);
console.log(`Created: ${stats.birthtime.toISOString().split("T")[0]}`);
console.log(`Lines: ${lineCount}`);
} catch (error) {
if (error.code === "ENOENT") {
console.log(`Error: file not found - ${filePath}`);
} else {
console.log(`Error: ${error.message}`);
}
process.exit(1);
}
פתרון תרגיל 2¶
index.js:
const dayjs = require("dayjs");
const chalk = require("chalk");
// current date
const now = dayjs();
console.log(chalk.blue.bold(`Today: ${now.format("dddd, MMMM D, YYYY")}`));
console.log(chalk.blue(`Time: ${now.format("HH:mm:ss")}`));
// days until new year
const nextYear = dayjs(`${now.year() + 1}-01-01`);
const daysUntil = nextYear.diff(now, "day");
console.log(chalk.green(`\n${daysUntil} days until New Year ${now.year() + 1}`));
// last 5 days of the week
console.log(chalk.yellow("\nUpcoming days:"));
for (let i = 1; i <= 5; i++) {
const day = now.add(i, "day");
console.log(chalk.yellow(` ${day.format("ddd DD/MM")} - Day ${i}`));
}
.gitignore:
package.json scripts:
פתרון תרגיל 3¶
אחרי npm create vite@latest todo-app -- --template vanilla:
index.html:
<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Todo App</title>
<link rel="stylesheet" href="/src/style.css">
</head>
<body>
<div id="app">
<h1 id="title"></h1>
<form id="todoForm">
<input type="text" id="todoInput" placeholder="Add a task..." required>
<button type="submit">Add</button>
</form>
<ul id="todoList"></ul>
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
src/main.js:
import "./style.css";
// use env variable
document.getElementById("title").textContent =
import.meta.env.VITE_APP_TITLE || "Todo App";
// load todos from localStorage
let todos = JSON.parse(localStorage.getItem("todos") || "[]");
const form = document.getElementById("todoForm");
const input = document.getElementById("todoInput");
const list = document.getElementById("todoList");
function saveTodos() {
localStorage.setItem("todos", JSON.stringify(todos));
}
function renderTodos() {
list.innerHTML = "";
todos.forEach((todo, index) => {
const li = document.createElement("li");
li.className = todo.completed ? "completed" : "";
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = todo.completed;
checkbox.addEventListener("change", () => toggleTodo(index));
const span = document.createElement("span");
span.textContent = todo.text;
const deleteBtn = document.createElement("button");
deleteBtn.textContent = "Delete";
deleteBtn.className = "delete-btn";
deleteBtn.addEventListener("click", () => deleteTodo(index));
li.appendChild(checkbox);
li.appendChild(span);
li.appendChild(deleteBtn);
list.appendChild(li);
});
}
function addTodo(text) {
todos.push({ text, completed: false });
saveTodos();
renderTodos();
}
function toggleTodo(index) {
todos[index].completed = !todos[index].completed;
saveTodos();
renderTodos();
}
function deleteTodo(index) {
todos.splice(index, 1);
saveTodos();
renderTodos();
}
form.addEventListener("submit", (e) => {
e.preventDefault();
const text = input.value.trim();
if (text) {
addTodo(text);
input.value = "";
}
});
renderTodos();
src/style.css:
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Arial, sans-serif;
max-width: 500px;
margin: 40px auto;
padding: 0 20px;
}
h1 {
text-align: center;
margin-bottom: 20px;
}
#todoForm {
display: flex;
gap: 8px;
margin-bottom: 20px;
}
#todoInput {
flex: 1;
padding: 8px 12px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
}
button {
padding: 8px 16px;
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
ul {
list-style: none;
}
li {
display: flex;
align-items: center;
gap: 8px;
padding: 10px;
border-bottom: 1px solid #eee;
}
li.completed span {
text-decoration: line-through;
color: #999;
}
.delete-btn {
margin-right: auto;
background: #f44336;
padding: 4px 8px;
font-size: 12px;
}
.env:
פתרון תרגיל 4¶
eslint.config.js:
import prettierConfig from "eslint-config-prettier";
export default [
{
rules: {
"no-var": "error",
"eqeqeq": "error",
"no-unused-vars": "warn",
"no-console": "off"
}
},
prettierConfig
];
.prettierrc:
package.json scripts:
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"format": "prettier --write src/"
}
}
קובץ עם שגיאות מכוונות (test-lint.js):
// ESLint should catch these:
var x = 5; // no-var error
let unused = 10; // no-unused-vars warning
if (x == "5") { // eqeqeq error
console.log("bad comparison");
}
פתרון תרגיל 5¶
npm install axios lodash uuid
# check package.json - all three appear in dependencies
npm uninstall axios
# axios removed from package.json and node_modules
rm -rf node_modules
npm install
# node_modules restored with lodash and uuid
index.js:
import { v4 as uuidv4 } from "uuid";
import _ from "lodash";
// generate 5 random IDs
const ids = _.times(5, () => uuidv4());
console.log("Random IDs:");
ids.forEach(id => console.log(` ${id}`));
// lodash utilities
const data = [1, 2, 2, 3, 3, 3, 4];
console.log("\nOriginal:", data);
console.log("Unique:", _.uniq(data));
console.log("Chunk:", _.chunk(data, 3));
console.log("Sum:", _.sum(data));
console.log("Group by count:", _.countBy(data));
פתרון תרגיל 6¶
vite.config.js:
import { defineConfig } from "vite";
import { resolve } from "path";
export default defineConfig({
server: {
port: 3000,
open: true
},
resolve: {
alias: {
"@": resolve(__dirname, "src")
}
},
build: {
sourcemap: true
}
});
src/modules/storage.js:
const STORAGE_KEY = "todos";
export function saveData(todos) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
}
export function loadData() {
const data = localStorage.getItem(STORAGE_KEY);
return data ? JSON.parse(data) : [];
}
export function clearData() {
localStorage.removeItem(STORAGE_KEY);
}
src/modules/utils.js:
export function generateId() {
return Date.now().toString(36) + Math.random().toString(36).slice(2);
}
export function formatDate(date) {
return new Date(date).toLocaleDateString("he-IL");
}
src/modules/ui.js:
export function createTodoElement(todo, onToggle, onDelete) {
const li = document.createElement("li");
li.className = todo.completed ? "completed" : "";
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = todo.completed;
checkbox.addEventListener("change", () => onToggle(todo.id));
const span = document.createElement("span");
span.textContent = todo.text;
const deleteBtn = document.createElement("button");
deleteBtn.textContent = "Delete";
deleteBtn.className = "delete-btn";
deleteBtn.addEventListener("click", () => onDelete(todo.id));
li.appendChild(checkbox);
li.appendChild(span);
li.appendChild(deleteBtn);
return li;
}
export function showMessage(container, message) {
container.textContent = message;
setTimeout(() => {
container.textContent = "";
}, 3000);
}
src/main.js:
import "@/style.css";
import { saveData, loadData } from "@/modules/storage";
import { createTodoElement } from "@/modules/ui";
import { generateId } from "@/modules/utils";
const appTitle = import.meta.env.VITE_APP_TITLE || "Todo App";
const apiUrl = import.meta.env.VITE_API_URL || "http://localhost:3000";
console.log(`Running in ${import.meta.env.MODE} mode`);
console.log(`API URL: ${apiUrl}`);
document.getElementById("title").textContent = appTitle;
let todos = loadData();
const list = document.getElementById("todoList");
const form = document.getElementById("todoForm");
const input = document.getElementById("todoInput");
function render() {
list.innerHTML = "";
todos.forEach(todo => {
const el = createTodoElement(todo, toggleTodo, deleteTodo);
list.appendChild(el);
});
}
function addTodo(text) {
todos.push({ id: generateId(), text, completed: false });
saveData(todos);
render();
}
function toggleTodo(id) {
todos = todos.map(t => t.id === id ? { ...t, completed: !t.completed } : t);
saveData(todos);
render();
}
function deleteTodo(id) {
todos = todos.filter(t => t.id !== id);
saveData(todos);
render();
}
form.addEventListener("submit", (e) => {
e.preventDefault();
const text = input.value.trim();
if (text) {
addTodo(text);
input.value = "";
}
});
render();
.env.development:
.env.production: