1. Module là gì?
Trong các bài trước, chúng ta thường viết JavaScript trong một file:
resources/
└── js/
└── app.jsCách này phù hợp với những ví dụ đơn giản.
Nhưng hãy tưởng tượng ứng dụng Laravel của chúng ta có các chức năng:
Quản lý bài viết.
Quản lý danh mục.
Tìm kiếm.
Đăng nhập.
Hiển thị thông báo.
Gọi Laravel REST API.
Nếu tất cả code đều nằm trong app.js, file này sẽ ngày càng dài và khó bảo trì.
Module cho phép chúng ta chia code thành nhiều file, mỗi file đảm nhiệm một nhiệm vụ riêng.
Ví dụ:
resources/
└── js/
├── app.js
├── modules/
│ ├── posts.js
│ ├── users.js
│ ├── search.js
│ └── notification.js
└── services/
└── api.jsMỗi file có thể xuất những thành phần cần sử dụng và nhập các thành phần từ file khác.
Hai từ khóa quan trọng nhất là:
export: xuất dữ liệu, hàm hoặc class để module khác sử dụng.import: nhập dữ liệu, hàm hoặc class từ module khác.
2. Export — xuất dữ liệu từ một module
Có hai cách xuất thường gặp:
Named Export.
Default Export.
2.1. Named Export
Tạo file resources/js/modules/characters.js:
export const doraemon = {
name: 'Doraemon',
type: 'Robot mèo',
color: 'blue'
};
export const nobita = {
name: 'Nobita',
type: 'Học sinh',
color: 'yellow'
};
export function sayHello(name) {
return `Xin chào ${name}!`;
}Trong file này, chúng ta đã xuất ba thành phần:
Biến
doraemon.Biến
nobita.Hàm
sayHello().
Các module khác có thể nhập những thành phần này.
Lưu ý: export ở đây là Named Export vì mỗi thành phần được xuất bằng tên của nó.
2.2. Default Export
Một module cũng có thể xuất một thành phần mặc định.
Tạo file resources/js/modules/pikachu.js:
const pikachu = {
name: 'Pikachu',
type: 'Electric',
power: 100
};
export default pikachu;Ở đây, pikachu được xuất bằng export default.
Mỗi module chỉ có một default export, nhưng có thể có nhiều Named Export.
3. Import — nhập dữ liệu từ module khác
3.1. Import Named Export
Tạo file resources/js/app.js:
import {
doraemon,
nobita,
sayHello
} from './modules/characters.js';
console.log(doraemon);
console.log(nobita);
console.log(sayHello('Nobita'));Kết quả:
{
name: 'Doraemon',
type: 'Robot mèo',
color: 'blue'
}
{
name: 'Nobita',
type: 'Học sinh',
color: 'yellow'
}
Xin chào Nobita!Cú pháp:
import { tenThanhPhan } from './duong-dan-module.js';Tên được import phải khớp với tên Named Export.
Ví dụ, nếu module xuất doraemon, chúng ta không thể import trực tiếp bằng tên robotMeo nếu chưa đổi tên khi import.
3.2. Import Default Export
Để nhập Pikachu:
import pikachu from './modules/pikachu.js';
console.log(pikachu.name);
console.log(pikachu.power);Kết quả:
Pikachu
100Điểm khác biệt: Default Export có thể được nhập bằng một tên khác.
Ví dụ:
import pokemon from './modules/pikachu.js';
console.log(pokemon.name);Code vẫn hoạt động vì pokemon chỉ là tên biến chúng ta chọn khi import Default Export.
4. Phân biệt Named Export và Default Export
| Đặc điểm | Named Export | Default Export |
|---|---|---|
| Số lượng trong một module | Có thể nhiều | Tối đa một |
| Khi import | Thường dùng {} | Không cần {} |
| Tên khi import | Theo tên export hoặc đổi tên rõ ràng | Có thể tự đặt |
| Phù hợp | Nhiều hàm, biến liên quan | Một thành phần chính |
Ví dụ kết hợp cả hai cách.
File resources/js/modules/pokemon.js:
export const pokemonType = 'Electric';
export function attack() {
return 'Thunderbolt!';
}
export default {
name: 'Pikachu',
power: 100
};File resources/js/app.js:
import pikachu, {
pokemonType,
attack
} from './modules/pokemon.js';
console.log(pikachu.name);
console.log(pokemonType);
console.log(attack());Kết quả:
Pikachu
Electric
Thunderbolt!Chúng ta có thể import Default Export và Named Export trong cùng một câu lệnh.
5. Đổi tên khi Import
Giả sử module xuất hàm sayHello():
export function sayHello(name) {
return `Xin chào ${name}!`;
}Khi import, chúng ta có thể đổi tên:
import {
sayHello as greeting
} from './modules/characters.js';
console.log(greeting('Doraemon'));Kết quả:
Xin chào Doraemon!Từ khóa as cho phép đổi tên thành phần khi import.
Cách này hữu ích khi hai module có các thành phần trùng tên hoặc khi muốn tên gọi rõ nghĩa hơn trong file đang sử dụng.
6. Import tất cả bằng dấu *
Giả sử file characters.js có nhiều Named Export:
export const doraemon = {
name: 'Doraemon'
};
export const nobita = {
name: 'Nobita'
};
export const shizuka = {
name: 'Shizuka'
};Chúng ta có thể nhập tất cả Named Export:
import * as characters from './modules/characters.js';
console.log(characters.doraemon.name);
console.log(characters.nobita.name);
console.log(characters.shizuka.name);Kết quả:
Doraemon
Nobita
Shizukacharacters là một namespace chứa các Named Export của module.
Lưu ý: cú pháp import * as ... không nhập Default Export như một thuộc tính mặc định của namespace theo cách này; nếu cần, hãy import Default Export riêng.
7. Module có thể xuất Function, Object, Array và Class
Module không chỉ xuất biến đơn giản.
7.1. Xuất Function
File resources/js/modules/math.js:
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}Sử dụng:
import { add, multiply } from './modules/math.js';
console.log(add(10, 20));
console.log(multiply(10, 20));7.2. Xuất Array
File resources/js/modules/characters.js:
export const characters = [
'Doraemon',
'Nobita',
'Shizuka',
'Pikachu',
'Conan'
];Sử dụng:
import { characters } from './modules/characters.js';
characters.forEach(character => {
console.log(character);
});7.3. Xuất Class
File resources/js/modules/Character.js:
export default class Character {
constructor(name, power) {
this.name = name;
this.power = power;
}
introduce() {
return `${this.name} có sức mạnh ${this.power}`;
}
}Sử dụng:
import Character from './modules/Character.js';
const pikachu = new Character('Pikachu', 100);
console.log(pikachu.introduce());Kết quả:
Pikachu có sức mạnh 1008. Tổ chức JavaScript trong Laravel 13
Đến đây, chúng ta sẽ áp dụng Module vào cấu trúc Laravel thực tế.
Thay vì để toàn bộ code trong app.js, hãy chia thành các file theo nhiệm vụ.
resources/
└── js/
├── app.js
├── modules/
│ ├── characters.js
│ ├── posts.js
│ └── search.js
└── services/
└── api.jsÝ nghĩa:
app.js: điểm khởi đầu của JavaScript.characters.js: dữ liệu minh họa nhân vật.posts.js: xử lý danh sách bài viết.search.js: chức năng tìm kiếm.api.js: nơi tổ chức những hàm gọi API.
Trong ứng dụng thực tế, không nhất thiết phải tạo đủ tất cả file. Chỉ nên tách module khi có chức năng cần tái sử dụng hoặc cần quản lý độc lập.
Bước 1 — Tạo module dữ liệu bài viết
Tạo file resources/js/modules/posts.js:
export const posts = [
{
id: 1,
title: 'Laravel 13',
author: 'Doraemon',
views: 120
},
{
id: 2,
title: 'JavaScript ES6+',
author: 'Nobita',
views: 80
},
{
id: 3,
title: 'Vue cơ bản',
author: 'Pikachu',
views: 200
}
];
export function getPostById(id) {
return posts.find(post => post.id === id);
}
export function getTotalViews() {
return posts.reduce(
(total, post) => total + post.views,
0
);
}Module này xuất:
posts: danh sách bài viết.getPostById(): tìm bài viết theo ID.getTotalViews(): tính tổng lượt xem.
Bước 2 — Tạo module tìm kiếm
Tạo file resources/js/modules/search.js:
export function searchPosts(posts, keyword) {
const normalizedKeyword = keyword.trim().toLowerCase();
return posts.filter(post =>
post.title.toLowerCase().includes(normalizedKeyword)
);
}Hàm tìm kiếm nhận danh sách và từ khóa, sau đó trả về danh sách phù hợp.
Hàm không phụ thuộc vào một Array cố định nên có thể tái sử dụng ở nhiều nơi.
Bước 3 — Import các module trong app.js
Mở resources/js/app.js:
import {
posts,
getPostById,
getTotalViews
} from './modules/posts.js';
import {
searchPosts
} from './modules/search.js';
console.log('Danh sách bài viết:', posts);
console.log('Bài viết số 2:', getPostById(2));
console.log('Tổng lượt xem:', getTotalViews());
console.log('Kết quả tìm kiếm:', searchPosts(posts, 'javascript'));Khi Laravel nạp app.js thông qua Vite, các module được import sẽ được xử lý theo quan hệ phụ thuộc.
Chúng ta không cần đưa từng file module vào Blade bằng một thẻ script riêng.
9. Thực hành: tạo trang tìm kiếm bài viết bằng Module
Bây giờ, hãy xây dựng một ví dụ hoàn chỉnh.
Bước 1 — Tạo file posts.js
Đường dẫn:
resources/js/modules/posts.js
export const posts = [
{ id: 1, title: 'Laravel 13', author: 'Doraemon', views: 120 },
{ id: 2, title: 'JavaScript hiện đại', author: 'Nobita', views: 80 },
{ id: 3, title: 'Vue cơ bản', author: 'Shizuka', views: 200 },
{ id: 4, title: 'React cơ bản', author: 'Pikachu', views: 150 },
{ id: 5, title: 'REST API', author: 'Conan', views: 90 }
];
export function getTotalViews(items) {
return items.reduce((total, post) => total + post.views, 0);
}Bước 2 — Tạo file search.js
Đường dẫn:
resources/js/modules/search.js
export function searchPosts(posts, keyword) {
const query = keyword.trim().toLowerCase();
return posts.filter(post => {
const title = post.title.toLowerCase();
const author = post.author.toLowerCase();
return title.includes(query) || author.includes(query);
});
}Hàm tìm kiếm theo cả tiêu đề và tên nhân vật.
Bước 3 — Tạo file app.js
Đường dẫn:
resources/js/app.js
import { posts, getTotalViews } from './modules/posts.js';
import { searchPosts } from './modules/search.js';
const searchInput = document.querySelector('#searchInput');
const postList = document.querySelector('#postList');
const totalViews = document.querySelector('#totalViews');
function renderPosts(items) {
if (!postList) {
return;
}
postList.replaceChildren();
if (items.length === 0) {
const li = document.createElement('li');
li.textContent = 'Không tìm thấy bài viết.';
postList.append(li);
return;
}
items.forEach(post => {
const li = document.createElement('li');
const title = document.createElement('strong');
const author = document.createElement('span');
title.textContent = post.title;
author.textContent = ` — Tác giả: ${post.author} — ${post.views} lượt xem`;
li.append(title, author);
postList.append(li);
});
}
function updateTotalViews(items) {
if (totalViews) {
totalViews.textContent = getTotalViews(items);
}
}
function handleSearch() {
const keyword = searchInput?.value ?? '';
const filteredPosts = searchPosts(posts, keyword);
renderPosts(filteredPosts);
updateTotalViews(filteredPosts);
}
if (searchInput) {
searchInput.addEventListener('input', handleSearch);
}
renderPosts(posts);
updateTotalViews(posts);Bước 4 — Tạo Blade View
Tạo file:
resources/views/javascript.blade.php
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript Modules - Laravel 13</title>
@vite(['resources/js/app.js'])
</head>
<body>
<h1>Thư viện bài học</h1>
<label for="searchInput">Tìm kiếm:</label>
<input
type="search"
id="searchInput"
placeholder="Nhập tên bài học hoặc nhân vật..."
>
<h2>Danh sách bài viết</h2>
<ul id="postList"></ul>
<p>
Tổng lượt xem:
<strong id="totalViews">0</strong>
</p>
</body>
</html>Bước 5 — Khai báo Route
Trong routes/web.php:
use Illuminate\Support\Facades\Route;
Route::view('/javascript', 'javascript');Nếu đã có Route /javascript từ Bài 02 hoặc Bài 03, hãy sử dụng lại Route đó, không khai báo trùng.
Bước 6 — Chạy ứng dụng
Mở terminal thứ nhất:
npm run devMở terminal thứ hai:
php artisan serveTruy cập:
http://127.0.0.1:8000/javascript
Thử nhập:
LaravelDoraemonVuePikachu
Danh sách và tổng lượt xem sẽ được cập nhật theo kết quả tìm kiếm.
Điều quan trọng: Blade chỉ cần nạp app.js. Các module còn lại được app.js import. Đây là cách tổ chức JavaScript mà chúng ta sẽ tiếp tục sử dụng trong những ứng dụng Laravel lớn hơn.
10. Module và Vite hoạt động như thế nào?
Trong Laravel, Vite giúp xử lý các file JavaScript và quan hệ import giữa chúng.
javascript.blade.php
│
│ @vite()
▼
app.js
│
├── posts.js
│
└── search.js
│
▼
JavaScript chạy
trong trình duyệtTrong chế độ phát triển, Vite hỗ trợ cập nhật nhanh khi chúng ta chỉnh sửa code. Khi build production, Vite xử lý các module để tạo tài nguyên frontend phục vụ triển khai.
Không cần tự nối nội dung tất cả file JavaScript vào một file bằng tay.
Có thể import trực tiếp trong trình duyệt không?
Có. JavaScript hiện đại hỗ trợ ES Modules với type="module":
<script type="module" src="/js/app.js"></script>Tuy nhiên, trong project Laravel sử dụng Vite, hãy ưu tiên:
@vite(['resources/js/app.js'])Vite sẽ đảm nhiệm việc phục vụ và build tài nguyên theo cấu hình của dự án.
11. Một số lỗi thường gặp
Lỗi 1 — Sai đường dẫn import
import { posts } from './post.js';Nhưng file thực tế là posts.js.
Hãy kiểm tra đúng tên file và đường dẫn tương đối tính từ file đang import.
Lỗi 2 — Quên dấu ngoặc nhọn
Nếu module xuất:
export const doraemon = {
name: 'Doraemon'
};Thì cách import đúng là:
import { doraemon } from './modules/characters.js';Lỗi 3 — Dùng sai chữ hoa, chữ thường
import { Doraemon } from './modules/characters.js';Nếu tên export là doraemon, đoạn trên không đúng vì JavaScript phân biệt chữ hoa và chữ thường.
Lỗi 4 — Hai module import lẫn nhau
Ví dụ:
posts.js → search.js
search.js → posts.jsĐây là quan hệ phụ thuộc vòng. Một số trường hợp vẫn hoạt động, nhưng có thể gây lỗi khởi tạo hoặc làm code khó hiểu. Nên thiết kế module theo hướng mỗi module có nhiệm vụ rõ ràng, tránh phụ thuộc vòng không cần thiết.
Lỗi 5 — Import nhưng không Export
Nếu file khác không xuất thành phần mà bạn đang import, JavaScript sẽ báo lỗi module.
Hãy kiểm tra tên và loại export trong file nguồn.
12. Bài tập cuối bài
Bài tập 1 — Tạo module nhân vật
Tạo file characters.js và xuất ba nhân vật:
Doraemon
Nobita
Pikachu
Mỗi nhân vật có name và power.
Bài tập 2 — Import và hiển thị
Trong app.js, import danh sách nhân vật và in tên từng nhân vật ra Console.
Bài tập 3 — Tạo module toán học
Tạo file math.js, xuất hai hàm:
add(a, b)multiply(a, b)
Import vào app.js và kiểm tra kết quả.
Bài tập 4 — Tách chức năng tìm kiếm
Tách hàm tìm kiếm bài viết khỏi app.js thành search.js. Sau đó import lại và đảm bảo chức năng vẫn hoạt động.
Bài tập 5 — Tự tổ chức project
Tạo cấu trúc:
resources/js/
├── app.js
├── modules/
│ ├── characters.js
│ └── posts.js
└── services/
└── api.jsChưa cần gọi API thật. Hãy tập trung vào việc xuất và nhập dữ liệu giữa các file.
13. Tổng kết
Trong bài này, chúng ta đã học:
| Kiến thức | Công dụng |
|---|---|
export | Xuất thành phần từ module |
export default | Xuất thành phần mặc định |
import | Nhập thành phần từ module khác |
as | Đổi tên khi import/export |
import * as | Gom Named Export vào namespace |
| ES Modules | Chia code thành nhiều file |
| Vite | Hỗ trợ phát triển và build frontend trong Laravel |
Ghi nhớ: app.js không nhất thiết phải chứa toàn bộ logic của ứng dụng. Nó có thể đóng vai trò điểm khởi đầu, nhập các module cần thiết rồi khởi tạo chức năng.
Khi chuyển sang Vue và React, chúng ta vẫn sử dụng import và export để tổ chức Component, hàm xử lý dữ liệu và các module dùng chung.
Bài 05 — Promise & Async/Await: học cách xử lý các công việc bất đồng bộ, chờ kết quả API và quản lý lỗi khi giao tiếp giữa JavaScript với Laravel.



Không có nhận xét nào:
Đăng nhận xét