Modules là gì?
ES6 Modules là cơ chế chuẩn để chia code JavaScript thành các files riêng biệt, có thể import/export giữa chúng. Modules giúp code dễ maintain, tái sử dụng và tránh pollution global scope.
Trước khi có Modules
Vấn đề với Global Scope
<!-- index.html -->
<script src="utils.js"></script>
<script src="app.js"></script>
<!-- utils.js -->
<script>
var myVar = 'hello'; // Global variable - nguy hiểm!
function myFunction() {
// ...
}
</script>
<!-- app.js -->
<script>
// Có thể truy cập myVar và myFunction
// Nhưng dễ bị conflict nếu có tên trùng!
console.log(myVar);
</script>
Solutions cũ
IIFE (Immediately Invoked Function Expression):
// utils.js
var Utils = (function() {
var privateVar = 'private';
return {
publicMethod: function() {
return privateVar;
}
};
})();
CommonJS (Node.js):
// utils.js
module.exports = {
myFunction: function() {}
};
// app.js
const utils = require('./utils');
ES6 Modules
Export
Named Exports
// math.js
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
// Hoặc export tất cả cùng lúc
const PI = 3.14159;
function add(a, b) { return a + b; }
function multiply(a, b) { return a * b; }
export { PI, add, multiply };
Default Export
Mỗi module chỉ có 1 default export:
// calculator.js
export default class Calculator {
add(a, b) {
return a + b;
}
}
// Hoặc
class Calculator {
// ...
}
export default Calculator;
// Hoặc với function
export default function calculate(a, b) {
return a + b;
}
Mix Default và Named
// user.js
export default class User {
constructor(name) {
this.name = name;
}
}
export const ADMIN_ROLE = 'admin';
export const USER_ROLE = 'user';
Import
Import Named Exports
// app.js
import { add, multiply, PI } from './math.js';
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159
Import với Alias
import { add as sum, multiply as mult } from './math.js';
console.log(sum(2, 3)); // 5
Import tất cả
import * as Math from './math.js';
console.log(Math.add(2, 3));
console.log(Math.PI);
Import Default Export
// Tên import tùy ý
import Calculator from './calculator.js';
import Calc from './calculator.js'; // Cũng OK
const calc = new Calculator();
Import Mix
import User, { ADMIN_ROLE, USER_ROLE } from './user.js';
const admin = new User('John');
console.log(ADMIN_ROLE);
Import chỉ để thực thi
import './analytics.js'; // Chỉ chạy code, không import gì
Dynamic Import
Import trong runtime (async):
// Static import (compile-time)
import { add } from './math.js';
// Dynamic import (runtime)
button.addEventListener('click', async () => {
const { add } = await import('./math.js');
console.log(add(2, 3));
});
Use cases:
- Code splitting
- Lazy loading
- Conditional loading
// Conditional import
if (userPrefersDarkMode) {
const theme = await import('./dark-theme.js');
theme.apply();
} else {
const theme = await import('./light-theme.js');
theme.apply();
}
// Lazy loading
async function loadFeature() {
const module = await import('./heavy-feature.js');
module.initialize();
}
Sử dụng Modules trong Browser
HTML
<!-- Cần type="module" -->
<script type="module" src="app.js"></script>
<!-- Inline module -->
<script type="module">
import { add } from './math.js';
console.log(add(2, 3));
</script>
Lưu ý:
- Module scripts mặc định
defer - Module scripts có strict mode
- Module scope riêng biệt
Module vs Script
<!-- Module Script -->
<script type="module">
import { add } from './math.js';
// Strict mode tự động
// Top-level 'this' là undefined
// Có thể import/export
</script>
<!-- Regular Script -->
<script>
// Không strict mode
// Top-level 'this' là window
// Không thể import/export
</script>
Module Patterns
1. Single Responsibility
// user-service.js
export class UserService {
getUser(id) {
// ...
}
}
// user-repository.js
export class UserRepository {
findById(id) {
// ...
}
}
// Mỗi file một responsibility
2. Facade Pattern
// api/index.js - Export tất cả APIs
export { UserAPI } from './user-api.js';
export { ProductAPI } from './product-api.js';
export { OrderAPI } from './order-api.js';
// app.js - Import tập trung
import { UserAPI, ProductAPI, OrderAPI } from './api/index.js';
3. Barrel Exports
// components/index.js
export { Button } from './Button.js';
export { Input } from './Input.js';
export { Modal } from './Modal.js';
// app.js
import { Button, Input, Modal } from './components/index.js';
4. Constants Module
// constants.js
export const API_URL = 'https://api.example.com';
export const TIMEOUT = 5000;
export const MAX_RETRIES = 3;
export const STATUS = {
PENDING: 'pending',
SUCCESS: 'success',
ERROR: 'error'
};
5. Utilities Module
// utils.js
export function formatDate(date) {
// ...
}
export function debounce(func, delay) {
// ...
}
export function throttle(func, limit) {
// ...
}
Circular Dependencies
Vấn đề
// a.js
import { b } from './b.js';
export const a = 'A';
console.log(b); // undefined!
// b.js
import { a } from './a.js';
export const b = 'B';
console.log(a); // undefined!
Giải pháp
1. Refactor để tách dependencies:
// shared.js
export const shared = 'shared';
// a.js
import { shared } from './shared.js';
export const a = 'A';
// b.js
import { shared } from './shared.js';
export const b = 'B';
2. Dùng function thay vì value:
// a.js
import { getB } from './b.js';
export const a = 'A';
export function getA() { return a; }
// b.js
import { getA } from './a.js';
export const b = 'B';
export function getB() { return b; }
// Gọi function để lấy giá trị
console.log(getA()); // Works!
Tree Shaking
Module system cho phép bundlers loại bỏ unused code:
// math.js
export function add(a, b) { return a + b; }
export function subtract(a, b) { return a - b; }
export function multiply(a, b) { return a * b; }
export function divide(a, b) { return a / b; }
// app.js
import { add } from './math.js';
// Chỉ add được bundle, 3 functions kia bị loại bỏ!
Best practices cho tree shaking:
- Dùng named exports thay vì default
- Tránh side effects trong modules
- Sử dụng pure functions
CommonJS vs ES Modules
CommonJS (Node.js)
// Export
module.exports = { add, multiply };
// hoặc
exports.add = add;
// Import
const math = require('./math');
const { add } = require('./math');
ES Modules
// Export
export { add, multiply };
// Import
import { add, multiply } from './math.js';
Differences
| Feature | CommonJS | ES Modules |
|---|---|---|
| Syntax | require()/module.exports |
import/export |
| Loading | Synchronous | Asynchronous |
| When | Runtime | Parse-time |
| Tree Shaking | ❌ | ✅ |
| Browser | ❌ | ✅ |
| Node.js | ✅ (default) | ✅ (.mjs hoặc type: "module") |
ES Modules trong Node.js
Cách 1: File extension .mjs
// math.mjs
export function add(a, b) {
return a + b;
}
// app.mjs
import { add } from './math.mjs';
Cách 2: package.json
{
"type": "module",
"name": "my-app"
}
// Bây giờ .js files là ES modules
// math.js
export function add(a, b) {
return a + b;
}
// app.js
import { add } from './math.js';
Import CommonJS trong ES Module
// CommonJS module
// lib.js
module.exports = { hello: 'world' };
// ES Module
// app.mjs
import lib from './lib.js'; // Default import
console.log(lib.hello);
Ví dụ thực tế
1. Service Architecture
// services/api.js
class API {
async get(url) {
const response = await fetch(url);
return response.json();
}
}
export default new API();
// services/user-service.js
import api from './api.js';
export class UserService {
async getUser(id) {
return api.get(`/users/${id}`);
}
}
// app.js
import { UserService } from './services/user-service.js';
const userService = new UserService();
const user = await userService.getUser(1);
2. Config Management
// config/development.js
export default {
apiUrl: 'http://localhost:3000',
debug: true
};
// config/production.js
export default {
apiUrl: 'https://api.example.com',
debug: false
};
// config/index.js
const env = process.env.NODE_ENV || 'development';
const config = await import(`./${env}.js`);
export default config.default;
// app.js
import config from './config/index.js';
console.log(config.apiUrl);
3. Plugin System
// plugins/analytics.js
export function initialize() {
console.log('Analytics initialized');
}
// plugins/auth.js
export function initialize() {
console.log('Auth initialized');
}
// app.js
const plugins = ['analytics', 'auth'];
for (const plugin of plugins) {
const module = await import(`./plugins/${plugin}.js`);
module.initialize();
}
Best Practices
- Một file, một concern
// ✅ GOOD: Mỗi class một file
// Button.js
export class Button {}
// Input.js
export class Input {}
- Named exports cho utilities
// ✅ GOOD
export function formatDate() {}
export function parseDate() {}
// ❌ BAD
export default {
formatDate() {},
parseDate() {}
};
- Default export cho main component
// ✅ Component chính
export default class UserProfile {}
// Named exports cho helpers
export function formatUserName() {}
- Đặt tên file theo export
// UserService.js
export class UserService {}
// constants.js
export const API_URL = '...';
- Tránh circular dependencies
// ✅ Tách shared code ra file riêng
Kết luận
ES6 Modules mang lại:
- Code organization tốt hơn
- Encapsulation và reusability
- Tree shaking cho bundle nhỏ hơn
- Static analysis (type checking, linting)
- Native browser support
Modules là nền tảng của JavaScript hiện đại, giúp xây dựng ứng dụng scalable và maintainable!