Master ES6 module system with import/export statements for building scalable, maintainable JavaScript applications.
ES6 modules revolutionized JavaScript development by introducing a native module system that enables you to organize code into reusable, maintainable units. Before ES6, JavaScript lacked a built-in module system, forcing developers to rely on external tools and patterns. ES6 modules provide a clean, standardized way to import and export functionality between files, promoting code organization, reusability, and maintainability.
The module system allows you to split your application into smaller, focused files, each handling specific functionality. This modular approach makes code easier to understand, test, and maintain. ES6 modules also provide static analysis capabilities, enabling bundlers and tools to optimize your code through tree-shaking and dead code elimination.
Understanding ES6 modules is crucial for modern JavaScript development, as they're used extensively in frameworks like React, Vue, Angular, and Node.js applications. They form the foundation of modern JavaScript tooling and build systems.
Export statements make functions, classes, objects, or primitive values available for import in other modules. ES6 provides several export syntaxes to handle different scenarios.
Named Exports:
Named exports allow you to export multiple values from a module. Each export has a specific name that must be used when importing.
1// math.js - Named exports
2export const PI = 3.14159;
3export const E = 2.71828;
4
5export function add(a, b) {
6 return a + b;
7}
8
9export function multiply(a, b) {
10 return a * b;
11}
12
13export class Calculator {
14 constructor() {
15 this.result = 0;
16 }
17
18 add(value) {
19 this.result += value;
20 return this;
21 }
22
23 getResult() {
24 return this.result;
25 }
26}
27
28// Alternative syntax - export list
29const subtract = (a, b) => a - b;
30const divide = (a, b) => a / b;
31
32export { subtract, divide };Default Exports:
Default exports allow you to export a single main value from a module. Each module can have only one default export.
1// user.js - Default export
2export default class User {
3 constructor(name, email) {
4 this.name = name;
5 this.email = email;
6 }
7
8 getInfo() {
9 return `${this.name} <${this.email}>`;
10 }
11}
12
13// Alternative default export syntax
14class Product {
15 constructor(name, price) {
16 this.name = name;
17 this.price = price;
18 }
19}
20
21export default Product;
22
23// Exporting values directly
24export default {
25 apiUrl: 'https://api.example.com',
26 timeout: 5000,
27 retries: 3
28};Mixed Exports:
You can combine named and default exports in the same module.
1// api.js - Mixed exports
2const API_BASE = 'https://api.example.com';
3
4export const endpoints = {
5 users: '/users',
6 posts: '/posts',
7 comments: '/comments'
8};
9
10export function createApiUrl(endpoint) {
11 return API_BASE + endpoint;
12}
13
14// Default export
15export default class ApiClient {
16 constructor(baseUrl = API_BASE) {
17 this.baseUrl = baseUrl;
18 }
19
20 async get(endpoint) {
21 const response = await fetch(this.baseUrl + endpoint);
22 return response.json();
23 }
24
25 async post(endpoint, data) {
26 const response = await fetch(this.baseUrl + endpoint, {
27 method: 'POST',
28 headers: { 'Content-Type': 'application/json' },
29 body: JSON.stringify(data)
30 });
31 return response.json();
32 }
33}Export statements must be at the top level of modules and cannot be inside functions, blocks, or conditional statements.
Import statements allow you to use functionality exported from other modules. ES6 provides flexible import syntax to handle different export types.
Named Imports:
Import specific named exports using curly braces. The names must match the exported names exactly.
1// app.js - Importing named exports
2import { add, multiply, PI, Calculator } from './math.js';
3
4console.log(add(5, 3)); // 8
5console.log(multiply(4, 6)); // 24
6console.log('Pi value:', PI); // 3.14159
7
8const calc = new Calculator();
9console.log(calc.add(10).add(5).getResult()); // 15
10
11// Importing with aliases
12import { subtract as minus, divide as div } from './math.js';
13
14console.log(minus(10, 3)); // 7
15console.log(div(15, 3)); // 5
16
17// Importing all named exports
18import * as MathUtils from './math.js';
19
20console.log(MathUtils.add(2, 3)); // 5
21console.log(MathUtils.PI); // 3.14159
22const calculator = new MathUtils.Calculator();Default Imports:
Import the default export without curly braces. You can use any name for the default import.
1// main.js - Importing default exports
2import User from './user.js';
3import ApiClient from './api.js';
4import config from './config.js';
5
6const user = new User('John Doe', '[email protected]');
7console.log(user.getInfo()); // John Doe <[email protected]>
8
9const api = new ApiClient();
10console.log('API timeout:', config.timeout); // 5000Mixed Imports:
Import both named and default exports in a single statement.
1// index.js - Mixed imports
2import ApiClient, { endpoints, createApiUrl } from './api.js';
3
4const client = new ApiClient();
5const usersUrl = createApiUrl(endpoints.users);
6
7client.get(endpoints.users)
8 .then(users => console.log('Users:', users))
9 .catch(error => console.error('Error:', error));Dynamic Imports:
Import modules conditionally or lazily using the import() function, which returns a Promise.
1// Dynamic imports for code splitting
2async function loadMathUtils() {
3 const { add, multiply } = await import('./math.js');
4 return { add, multiply };
5}
6
7// Conditional imports
8if (process.env.NODE_ENV === 'development') {
9 import('./debug-tools.js').then(debugTools => {
10 debugTools.initializeDebugger();
11 });
12}
13
14// Lazy loading based on user interaction
15document.getElementById('chartButton').addEventListener('click', async () => {
16 const ChartLibrary = await import('./chart-library.js');
17 const chart = new ChartLibrary.default();
18 chart.render();
19});Import statements are hoisted and executed before any code in the module runs.
Effective module organization is crucial for maintainable applications. Understanding common patterns helps you structure your codebase efficiently.
Barrel Exports (Re-exports):
Create index files that aggregate and re-export related modules, providing a single entry point.
1// utils/index.js - Barrel export
2export { default as dateUtils } from './date-utils.js';
3export { default as stringUtils } from './string-utils.js';
4export { default as arrayUtils } from './array-utils.js';
5export * from './math-utils.js';
6export * from './validation-utils.js';
7
8// Alternative syntax
9import dateUtils from './date-utils.js';
10import stringUtils from './string-utils.js';
11import arrayUtils from './array-utils.js';
12
13export {
14 dateUtils,
15 stringUtils,
16 arrayUtils
17};1// Usage - Clean imports from barrel
2import { dateUtils, stringUtils, arrayUtils, add, multiply } from './utils';
3
4console.log(dateUtils.formatDate(new Date()));
5console.log(stringUtils.capitalize('hello world'));
6console.log(arrayUtils.unique([1, 2, 2, 3, 3, 4]));Feature-Based Organization:
Organize modules by features rather than file types.
1// features/user/
2// - user-model.js
3// - user-service.js
4// - user-validator.js
5// - index.js
6
7// features/user/user-model.js
8export default class User {
9 constructor(data) {
10 this.id = data.id;
11 this.name = data.name;
12 this.email = data.email;
13 }
14
15 validate() {
16 return this.name && this.email;
17 }
18}
19
20// features/user/user-service.js
21import User from './user-model.js';
22
23export class UserService {
24 async getUser(id) {
25 const response = await fetch(`/api/users/${id}`);
26 const data = await response.json();
27 return new User(data);
28 }
29
30 async createUser(userData) {
31 const user = new User(userData);
32 if (!user.validate()) {
33 throw new Error('Invalid user data');
34 }
35
36 const response = await fetch('/api/users', {
37 method: 'POST',
38 headers: { 'Content-Type': 'application/json' },
39 body: JSON.stringify(userData)
40 });
41
42 return response.json();
43 }
44}
45
46// features/user/index.js - Feature barrel
47export { default as User } from './user-model.js';
48export { UserService } from './user-service.js';
49export { validateUser } from './user-validator.js';Configuration and Constants:
Centralize configuration and constants in dedicated modules.
1// config/database.js
2export const dbConfig = {
3 host: process.env.DB_HOST || 'localhost',
4 port: process.env.DB_PORT || 5432,
5 database: process.env.DB_NAME || 'myapp',
6 ssl: process.env.NODE_ENV === 'production'
7};
8
9// config/api.js
10export const apiConfig = {
11 baseUrl: process.env.API_BASE_URL || 'http://localhost:3000',
12 timeout: 10000,
13 retries: 3
14};
15
16export const endpoints = {
17 auth: '/auth',
18 users: '/users',
19 posts: '/posts'
20};
21
22// config/index.js
23export { dbConfig } from './database.js';
24export { apiConfig, endpoints } from './api.js';
25export { default as appConfig } from './app.js';Service Layer Pattern:
Create service modules that encapsulate business logic and external API interactions.
1// services/http.js - HTTP service
2export class HttpService {
3 constructor(baseUrl, options = {}) {
4 this.baseUrl = baseUrl;
5 this.options = options;
6 }
7
8 async request(endpoint, options = {}) {
9 const url = this.baseUrl + endpoint;
10 const config = { ...this.options, ...options };
11
12 const response = await fetch(url, config);
13
14 if (!response.ok) {
15 throw new Error(`HTTP error! status: ${response.status}`);
16 }
17
18 return response.json();
19 }
20
21 get(endpoint) {
22 return this.request(endpoint, { method: 'GET' });
23 }
24
25 post(endpoint, data) {
26 return this.request(endpoint, {
27 method: 'POST',
28 headers: { 'Content-Type': 'application/json' },
29 body: JSON.stringify(data)
30 });
31 }
32}
33
34// services/user-service.js
35import { HttpService } from './http.js';
36import { apiConfig } from '../config';
37
38export class UserService extends HttpService {
39 constructor() {
40 super(apiConfig.baseUrl);
41 }
42
43 async getUsers() {
44 return this.get('/users');
45 }
46
47 async getUser(id) {
48 return this.get(`/users/${id}`);
49 }
50
51 async createUser(userData) {
52 return this.post('/users', userData);
53 }
54}This modular approach promotes code reusability, testability, and maintainability.
ES6 modules provide advanced features and following best practices ensures optimal performance and maintainability.
Tree Shaking & Dead Code Elimination:
Structure exports to enable effective tree shaking by bundlers.
1// Good for tree shaking - Named exports
2// utils.js
3export function formatDate(date) {
4 return date.toLocaleDateString();
5}
6
7export function formatTime(date) {
8 return date.toLocaleTimeString();
9}
10
11export function formatDateTime(date) {
12 return `${formatDate(date)} ${formatTime(date)}`;
13}
14
15// Usage - Only imports what's needed
16import { formatDate } from './utils.js'; // Only formatDate is bundled1// Less optimal for tree shaking
2// utils.js
3export default {
4 formatDate(date) {
5 return date.toLocaleDateString();
6 },
7 formatTime(date) {
8 return date.toLocaleTimeString();
9 },
10 formatDateTime(date) {
11 return `${this.formatDate(date)} ${this.formatTime(date)}`;
12 }
13};
14
15// Usage - Entire object might be bundled
16import utils from './utils.js';
17utils.formatDate(new Date()); // Whole utils object includedCircular Dependencies:
Handle circular dependencies carefully to avoid runtime errors.
1// Problematic circular dependency
2// a.js
3import { b } from './b.js';
4export const a = 'value from a';
5console.log('In a.js:', b); // undefined at runtime
6
7// b.js
8import { a } from './a.js';
9export const b = 'value from b';
10console.log('In b.js:', a); // undefined at runtime1// Solution 1: Restructure to avoid circularity
2// shared.js
3export const config = {
4 baseUrl: 'https://api.example.com'
5};
6
7// a.js
8import { config } from './shared.js';
9export const serviceA = {
10 url: config.baseUrl + '/a'
11};
12
13// b.js
14import { config } from './shared.js';
15export const serviceB = {
16 url: config.baseUrl + '/b'
17};Module Side Effects:
Be aware of code that runs when modules are imported.
1// analytics.js - Module with side effects
2console.log('Analytics module loaded'); // Runs on import
3
4let pageViews = 0;
5
6export function trackPageView() {
7 pageViews++;
8 console.log(`Page view #${pageViews}`);
9}
10
11// Auto-start tracking
12window.addEventListener('load', trackPageView);
13
14// main.js
15import { trackPageView } from './analytics.js'; // Side effects run here
16// Console: "Analytics module loaded"Module Loading Performance:
Optimize module loading for better performance.
1// Code splitting with dynamic imports
2class App {
3 async loadFeature(featureName) {
4 try {
5 switch (featureName) {
6 case 'charts':
7 const { ChartManager } = await import('./features/charts.js');
8 return new ChartManager();
9
10 case 'analytics':
11 const { AnalyticsService } = await import('./features/analytics.js');
12 return new AnalyticsService();
13
14 default:
15 throw new Error(`Unknown feature: ${featureName}`);
16 }
17 } catch (error) {
18 console.error('Failed to load feature:', error);
19 }
20 }
21}
22
23// Preloading modules
24const preloadModules = [
25 import('./critical-feature.js'),
26 import('./important-utils.js')
27];
28
29Promise.all(preloadModules).then(() => {
30 console.log('Critical modules preloaded');
31});Best Practices:
1// Good
2export function formatCurrency(amount) { ... }
3export function validateEmail(email) { ... }1// Good
2export default class UserManager { ... }1// kebab-case for file names
2import { UserService } from './user-service.js';
3import { ApiClient } from './api-client.js';1// features/auth/index.js
2export { AuthService } from './auth-service.js';
3export { AuthGuard } from './auth-guard.js';
4export { LoginComponent } from './login-component.js';1// Bad
2import { helper } from '../../../utils/helpers/string-helper.js';
3
4// Good - use barrel exports
5import { stringHelper } from '@/utils';Understanding these advanced features and following best practices ensures your modules are efficient, maintainable, and performant.
Create a modular task management application demonstrating various module patterns, organization strategies, and best practices.
1// === File Structure ===
2// src/
3// āāā models/
4// ā āāā task.js
5// ā āāā user.js
6// ā āāā index.js
7// āāā services/
8// ā āāā api.js
9// ā āāā task-service.js
10// ā āāā user-service.js
11// ā āāā index.js
12// āāā utils/
13// ā āāā date-utils.js
14// ā āāā validation.js
15// ā āāā index.js
16// āāā config/
17// ā āāā app-config.js
18// ā āāā index.js
19// āāā app.js
20
21// === models/task.js ===
22export default class Task {
23 static #nextId = 1;
24 #id;
25 #createdAt;
26 #updatedAt;
27
28 constructor(title, description = '', priority = 'medium') {
29 this.#id = Task.#nextId++;
30 this.title = title;
31 this.description = description;
32 this.priority = priority;
33 this.status = 'pending';
34 this.#createdAt = new Date();
35 this.#updatedAt = new Date();
36 this.assigneeId = null;
37 this.tags = [];
38 }
39
40 // Getters for private fields
41 get id() { return this.#id; }
42 get createdAt() { return this.#createdAt; }
43 get updatedAt() { return this.#updatedAt; }
44
45 // Status management
46 updateStatus(newStatus) {
47 const validStatuses = ['pending', 'in-progress', 'completed', 'cancelled'];
48 if (!validStatuses.includes(newStatus)) {
49 throw new Error(`Invalid status: ${newStatus}`);
50 }
51
52 this.status = newStatus;
53 this.#updatedAt = new Date();
54 return this;
55 }
56
57 // Assignment
58 assignTo(userId) {
59 this.assigneeId = userId;
60 this.#updatedAt = new Date();
61 return this;
62 }
63
64 // Tags
65 addTag(tag) {
66 if (!this.tags.includes(tag)) {
67 this.tags.push(tag);
68 this.#updatedAt = new Date();
69 }
70 return this;
71 }
72
73 removeTag(tag) {
74 this.tags = this.tags.filter(t => t !== tag);
75 this.#updatedAt = new Date();
76 return this;
77 }
78
79 // Serialization
80 toJSON() {
81 return {
82 id: this.#id,
83 title: this.title,
84 description: this.description,
85 priority: this.priority,
86 status: this.status,
87 assigneeId: this.assigneeId,
88 tags: this.tags,
89 createdAt: this.#createdAt,
90 updatedAt: this.#updatedAt
91 };
92 }
93
94 // Static factory methods
95 static fromJSON(data) {
96 const task = new Task(data.title, data.description, data.priority);
97 task.status = data.status;
98 task.assigneeId = data.assigneeId;
99 task.tags = [...data.tags];
100 return task;
101 }
102
103 static createQuickTask(title) {
104 return new Task(title, '', 'low');
105 }
106
107 static createUrgentTask(title, description) {
108 return new Task(title, description, 'high');
109 }
110}
111
112// === models/user.js ===
113export default class User {
114 constructor(name, email, role = 'user') {
115 this.id = User.generateId();
116 this.name = name;
117 this.email = email;
118 this.role = role;
119 this.createdAt = new Date();
120 this.isActive = true;
121 }
122
123 static generateId() {
124 return 'user_' + Math.random().toString(36).substr(2, 9);
125 }
126
127 static createAdmin(name, email) {
128 return new User(name, email, 'admin');
129 }
130
131 static createManager(name, email) {
132 return new User(name, email, 'manager');
133 }
134
135 deactivate() {
136 this.isActive = false;
137 return this;
138 }
139
140 activate() {
141 this.isActive = true;
142 return this;
143 }
144
145 hasPermission(permission) {
146 const permissions = {
147 user: ['read'],
148 manager: ['read', 'write'],
149 admin: ['read', 'write', 'delete']
150 };
151
152 return permissions[this.role]?.includes(permission) || false;
153 }
154
155 getDisplayName() {
156 return `${this.name} (${this.role})`;
157 }
158}
159
160// === models/index.js (Barrel Export) ===
161export { default as Task } from './task.js';
162export { default as User } from './user.js';
163
164// === utils/date-utils.js ===
165export function formatDate(date, format = 'short') {
166 const options = {
167 short: { year: 'numeric', month: 'short', day: 'numeric' },
168 long: {
169 year: 'numeric',
170 month: 'long',
171 day: 'numeric',
172 weekday: 'long'
173 },
174 time: {
175 hour: '2-digit',
176 minute: '2-digit',
177 second: '2-digit'
178 }
179 };
180
181 return new Intl.DateTimeFormat('en-US', options[format]).format(date);
182}
183
184export function getDaysDifference(date1, date2) {
185 const diffTime = Math.abs(date2 - date1);
186 return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
187}
188
189export function isOverdue(date) {
190 return new Date() > new Date(date);
191}
192
193export function getRelativeTime(date) {
194 const now = new Date();
195 const diffMs = now - date;
196 const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
197
198 if (diffDays === 0) return 'Today';
199 if (diffDays === 1) return 'Yesterday';
200 if (diffDays < 7) return `${diffDays} days ago`;
201 if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`;
202 return `${Math.floor(diffDays / 30)} months ago`;
203}
204
205// === utils/validation.js ===
206export function validateEmail(email) {
207 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
208 return emailRegex.test(email);
209}
210
211export function validateTask(task) {
212 const errors = [];
213
214 if (!task.title || task.title.trim().length === 0) {
215 errors.push('Title is required');
216 }
217
218 if (task.title && task.title.length > 100) {
219 errors.push('Title must be less than 100 characters');
220 }
221
222 const validPriorities = ['low', 'medium', 'high'];
223 if (!validPriorities.includes(task.priority)) {
224 errors.push('Priority must be low, medium, or high');
225 }
226
227 return {
228 isValid: errors.length === 0,
229 errors
230 };
231}
232
233export function validateUser(user) {
234 const errors = [];
235
236 if (!user.name || user.name.trim().length === 0) {
237 errors.push('Name is required');
238 }
239
240 if (!user.email || !validateEmail(user.email)) {
241 errors.push('Valid email is required');
242 }
243
244 const validRoles = ['user', 'manager', 'admin'];
245 if (!validRoles.includes(user.role)) {
246 errors.push('Role must be user, manager, or admin');
247 }
248
249 return {
250 isValid: errors.length === 0,
251 errors
252 };
253}
254
255// === utils/index.js ===
256export * from './date-utils.js';
257export * from './validation.js';
258
259// === config/app-config.js ===
260export const appConfig = {
261 name: 'Task Manager Pro',
262 version: '1.0.0',
263 api: {
264 baseUrl: process.env.API_BASE_URL || 'http://localhost:3000/api',
265 timeout: 5000,
266 retries: 3
267 },
268 features: {
269 enableNotifications: true,
270 enableAnalytics: process.env.NODE_ENV === 'production',
271 maxTasksPerUser: 100
272 },
273 ui: {
274 theme: 'light',
275 defaultPageSize: 20,
276 animationDuration: 300
277 }
278};
279
280export const statusConfig = {
281 pending: {
282 color: '#fbbf24',
283 label: 'Pending',
284 icon: 'ā³'
285 },
286 'in-progress': {
287 color: '#3b82f6',
288 label: 'In Progress',
289 icon: 'š'
290 },
291 completed: {
292 color: '#10b981',
293 label: 'Completed',
294 icon: 'ā
'
295 },
296 cancelled: {
297 color: '#ef4444',
298 label: 'Cancelled',
299 icon: 'ā'
300 }
301};
302
303export const priorityConfig = {
304 low: {
305 color: '#6b7280',
306 label: 'Low',
307 weight: 1
308 },
309 medium: {
310 color: '#f59e0b',
311 label: 'Medium',
312 weight: 2
313 },
314 high: {
315 color: '#dc2626',
316 label: 'High',
317 weight: 3
318 }
319};
320
321// === services/api.js ===
322export class ApiClient {
323 constructor(baseUrl, options = {}) {
324 this.baseUrl = baseUrl;
325 this.timeout = options.timeout || 5000;
326 this.retries = options.retries || 3;
327 this.headers = {
328 'Content-Type': 'application/json',
329 ...options.headers
330 };
331 }
332
333 async request(endpoint, options = {}) {
334 const url = this.baseUrl + endpoint;
335 const config = {
336 timeout: this.timeout,
337 headers: this.headers,
338 ...options
339 };
340
341 for (let attempt = 1; attempt <= this.retries; attempt++) {
342 try {
343 const response = await fetch(url, config);
344
345 if (!response.ok) {
346 throw new Error(`HTTP ${response.status}: ${response.statusText}`);
347 }
348
349 return await response.json();
350 } catch (error) {
351 if (attempt === this.retries) {
352 throw error;
353 }
354
355 // Exponential backoff
356 await this.delay(Math.pow(2, attempt) * 1000);
357 }
358 }
359 }
360
361 delay(ms) {
362 return new Promise(resolve => setTimeout(resolve, ms));
363 }
364
365 get(endpoint) {
366 return this.request(endpoint, { method: 'GET' });
367 }
368
369 post(endpoint, data) {
370 return this.request(endpoint, {
371 method: 'POST',
372 body: JSON.stringify(data)
373 });
374 }
375
376 put(endpoint, data) {
377 return this.request(endpoint, {
378 method: 'PUT',
379 body: JSON.stringify(data)
380 });
381 }
382
383 delete(endpoint) {
384 return this.request(endpoint, { method: 'DELETE' });
385 }
386}
387
388// === services/task-service.js ===
389import { ApiClient } from './api.js';
390import { Task } from '../models/index.js';
391import { validateTask } from '../utils/index.js';
392import { appConfig } from '../config/index.js';
393
394export class TaskService {
395 constructor() {
396 this.api = new ApiClient(appConfig.api.baseUrl, {
397 timeout: appConfig.api.timeout,
398 retries: appConfig.api.retries
399 });
400 }
401
402 async getAllTasks() {
403 try {
404 const data = await this.api.get('/tasks');
405 return data.map(taskData => Task.fromJSON(taskData));
406 } catch (error) {
407 console.error('Failed to fetch tasks:', error);
408 throw new Error('Unable to load tasks. Please try again.');
409 }
410 }
411
412 async getTask(id) {
413 try {
414 const data = await this.api.get(`/tasks/${id}`);
415 return Task.fromJSON(data);
416 } catch (error) {
417 console.error(`Failed to fetch task ${id}:`, error);
418 throw new Error('Task not found.');
419 }
420 }
421
422 async createTask(taskData) {
423 // Validate task data
424 const validation = validateTask(taskData);
425 if (!validation.isValid) {
426 throw new Error(`Invalid task data: ${validation.errors.join(', ')}`);
427 }
428
429 try {
430 const task = new Task(taskData.title, taskData.description, taskData.priority);
431 const createdData = await this.api.post('/tasks', task.toJSON());
432 return Task.fromJSON(createdData);
433 } catch (error) {
434 console.error('Failed to create task:', error);
435 throw new Error('Unable to create task. Please try again.');
436 }
437 }
438
439 async updateTask(id, updates) {
440 try {
441 const updatedData = await this.api.put(`/tasks/${id}`, updates);
442 return Task.fromJSON(updatedData);
443 } catch (error) {
444 console.error(`Failed to update task ${id}:`, error);
445 throw new Error('Unable to update task. Please try again.');
446 }
447 }
448
449 async deleteTask(id) {
450 try {
451 await this.api.delete(`/tasks/${id}`);
452 return true;
453 } catch (error) {
454 console.error(`Failed to delete task ${id}:`, error);
455 throw new Error('Unable to delete task. Please try again.');
456 }
457 }
458
459 async getTasksByStatus(status) {
460 try {
461 const data = await this.api.get(`/tasks?status=${status}`);
462 return data.map(taskData => Task.fromJSON(taskData));
463 } catch (error) {
464 console.error(`Failed to fetch tasks with status ${status}:`, error);
465 throw new Error('Unable to load tasks. Please try again.');
466 }
467 }
468
469 async assignTask(taskId, userId) {
470 try {
471 const updatedData = await this.api.put(`/tasks/${taskId}/assign`, { userId });
472 return Task.fromJSON(updatedData);
473 } catch (error) {
474 console.error(`Failed to assign task ${taskId}:`, error);
475 throw new Error('Unable to assign task. Please try again.');
476 }
477 }
478}
479
480// === app.js (Main Application) ===
481import { Task, User } from './models/index.js';
482import { TaskService } from './services/index.js';
483import { formatDate, validateTask, validateUser } from './utils/index.js';
484import { appConfig, statusConfig, priorityConfig } from './config/index.js';
485
486class TaskManagerApp {
487 constructor() {
488 this.taskService = new TaskService();
489 this.currentUser = null;
490 this.tasks = [];
491 this.users = [];
492
493 this.init();
494 }
495
496 async init() {
497 console.log(`š Starting ${appConfig.name} v${appConfig.version}`);
498
499 // Initialize demo data
500 this.setupDemoData();
501
502 // Load tasks
503 try {
504 await this.loadTasks();
505 console.log(`š Loaded ${this.tasks.length} tasks`);
506 } catch (error) {
507 console.error('Failed to load tasks:', error.message);
508 // Use demo data as fallback
509 this.createDemoTasks();
510 }
511
512 // Setup UI event handlers
513 this.setupEventHandlers();
514
515 console.log('ā
Application initialized successfully');
516 }
517
518 setupDemoData() {
519 // Create demo users
520 this.users = [
521 User.createAdmin('Alice Johnson', '[email protected]'),
522 User.createManager('Bob Smith', '[email protected]'),
523 new User('Charlie Brown', '[email protected]', 'user'),
524 new User('Diana Prince', '[email protected]', 'user')
525 ];
526
527 // Set current user
528 this.currentUser = this.users[0]; // Alice (admin)
529
530 console.log(`š„ Created ${this.users.length} demo users`);
531 console.log(`š Current user: ${this.currentUser.getDisplayName()}`);
532 }
533
534 createDemoTasks() {
535 // Create various types of tasks
536 const demoTasks = [
537 Task.createUrgentTask(
538 'Fix critical security vulnerability',
539 'Patch the authentication system to prevent unauthorized access'
540 ),
541 new Task('Implement user dashboard', 'Create a comprehensive dashboard for user analytics', 'medium'),
542 Task.createQuickTask('Update documentation'),
543 new Task('Design mobile app', 'Create responsive design for mobile application', 'high'),
544 new Task('Setup CI/CD pipeline', 'Implement automated testing and deployment', 'medium')
545 ];
546
547 // Assign tasks and update statuses
548 demoTasks[0].assignTo(this.users[1].id).updateStatus('in-progress');
549 demoTasks[1].assignTo(this.users[2].id).updateStatus('pending');
550 demoTasks[2].assignTo(this.users[0].id).updateStatus('completed');
551 demoTasks[3].assignTo(this.users[3].id).updateStatus('pending');
552 demoTasks[4].assignTo(this.users[1].id).updateStatus('in-progress');
553
554 // Add tags
555 demoTasks[0].addTag('security').addTag('urgent');
556 demoTasks[1].addTag('frontend').addTag('dashboard');
557 demoTasks[2].addTag('documentation');
558 demoTasks[3].addTag('design').addTag('mobile');
559 demoTasks[4].addTag('devops').addTag('automation');
560
561 this.tasks = demoTasks;
562 console.log(`š Created ${this.tasks.length} demo tasks`);
563 }
564
565 async loadTasks() {
566 this.tasks = await this.taskService.getAllTasks();
567 }
568
569 setupEventHandlers() {
570 // In a real application, these would be connected to UI elements
571 console.log('šļø Event handlers setup complete');
572 }
573
574 // Task management methods
575 async createTask(title, description, priority) {
576 try {
577 const validation = validateTask({ title, description, priority });
578 if (!validation.isValid) {
579 throw new Error(validation.errors.join(', '));
580 }
581
582 const task = await this.taskService.createTask({
583 title,
584 description,
585 priority
586 });
587
588 this.tasks.push(task);
589 console.log(`ā
Created task: ${task.title}`);
590 return task;
591 } catch (error) {
592 console.error('ā Failed to create task:', error.message);
593 throw error;
594 }
595 }
596
597 getTasksByStatus(status) {
598 return this.tasks.filter(task => task.status === status);
599 }
600
601 getTasksByPriority(priority) {
602 return this.tasks.filter(task => task.priority === priority);
603 }
604
605 getTasksByAssignee(userId) {
606 return this.tasks.filter(task => task.assigneeId === userId);
607 }
608
609 generateReport() {
610 const report = {
611 totalTasks: this.tasks.length,
612 byStatus: {},
613 byPriority: {},
614 byAssignee: {}
615 };
616
617 // Count by status
618 Object.keys(statusConfig).forEach(status => {
619 report.byStatus[status] = this.getTasksByStatus(status).length;
620 });
621
622 // Count by priority
623 Object.keys(priorityConfig).forEach(priority => {
624 report.byPriority[priority] = this.getTasksByPriority(priority).length;
625 });
626
627 // Count by assignee
628 this.users.forEach(user => {
629 report.byAssignee[user.name] = this.getTasksByAssignee(user.id).length;
630 });
631
632 return report;
633 }
634
635 printSummary() {
636 console.log('\nš === TASK MANAGER SUMMARY ===');
637 console.log(`Application: ${appConfig.name} v${appConfig.version}`);
638 console.log(`Current User: ${this.currentUser.getDisplayName()}`);
639 console.log(`Total Tasks: ${this.tasks.length}`);
640 console.log(`Total Users: ${this.users.length}`);
641
642 console.log('\nš Tasks by Status:');
643 Object.keys(statusConfig).forEach(status => {
644 const count = this.getTasksByStatus(status).length;
645 const config = statusConfig[status];
646 console.log(` ${config.icon} ${config.label}: ${count}`);
647 });
648
649 console.log('\nš„ Tasks by Priority:');
650 Object.keys(priorityConfig).forEach(priority => {
651 const count = this.getTasksByPriority(priority).length;
652 const config = priorityConfig[priority];
653 console.log(` ${config.label}: ${count}`);
654 });
655
656 console.log('\nš„ Tasks by Assignee:');
657 this.users.forEach(user => {
658 const count = this.getTasksByAssignee(user.id).length;
659 console.log(` ${user.name}: ${count} tasks`);
660 });
661
662 console.log('\nš
Recent Tasks:');
663 this.tasks
664 .sort((a, b) => b.createdAt - a.createdAt)
665 .slice(0, 3)
666 .forEach(task => {
667 const assignee = this.users.find(u => u.id === task.assigneeId);
668 const statusInfo = statusConfig[task.status];
669 console.log(` ${statusInfo.icon} ${task.title} - ${assignee?.name || 'Unassigned'} (${formatDate(task.createdAt)})`);
670 });
671
672 console.log('\n=== END SUMMARY ===\n');
673 }
674}
675
676// === Application Startup ===
677async function startApplication() {
678 try {
679 const app = new TaskManagerApp();
680
681 // Demonstrate the application
682 setTimeout(() => {
683 app.printSummary();
684 }, 1000);
685
686 return app;
687 } catch (error) {
688 console.error('š„ Failed to start application:', error);
689 }
690}
691
692// Auto-start the application
693startApplication().then(app => {
694 console.log('š Task Manager application is running!');
695
696 // Make app available globally for testing
697 window.taskManagerApp = app;
698});
699
700/*
701šÆ Module Organization Highlights:
702
7031. **Clear Separation of Concerns**:
704 - Models: Task and User classes with business logic
705 - Services: API communication and data management
706 - Utils: Reusable helper functions
707 - Config: Application configuration and constants
708
7092. **Barrel Exports**:
710 - Each feature directory has an index.js for clean imports
711 - Makes refactoring easier and imports more readable
712
7133. **Static Factory Methods**:
714 - Task.createQuickTask(), Task.createUrgentTask()
715 - User.createAdmin(), User.createManager()
716
7174. **Service Layer Pattern**:
718 - TaskService encapsulates all task-related API calls
719 - Proper error handling and validation
720
7215. **Configuration Management**:
722 - Centralized configuration with environment support
723 - Separate configs for different aspects (status, priority)
724
7256. **Modular Utilities**:
726 - Date formatting and validation functions
727 - Reusable across the application
728
7297. **Private Fields & Methods**:
730 - Task class uses private fields for id, timestamps
731 - Proper encapsulation and data protection
732
733This architecture demonstrates how ES6 modules enable building
734maintainable, scalable JavaScript applications with clear
735organization and separation of concerns.
736*/Your progress is saved in this browser, on this device only.