Joomla Bài học 25: Quản lý CSS và JavaScript hiện đại với Web Asset Manager (joomla.asset.json)
Ở các phiên bản Joomla cũ (Joomla 3 trở về trước), lập trình viên thường gọi trực tiếp $doc->addStyleSheet() hoặc $doc->addScript() rải rác khắp các Component, Module và Template. Cách làm truyền thống này dẫn đến một cơn ác mộng về hiệu năng và xung đột: một thư viện (như Bootstrap hay Swiper) có thể bị nạp trùng lặp 2-3 lần từ các nguồn khác nhau, hoặc tệ hơn là file script con (template.js) lại bị trình duyệt tải và thực thi trước cả thư viện lõi mà nó phụ thuộc vào.
Từ Joomla 4 và hoàn thiện mạnh mẽ trên Joomla 5, hệ thống đã giới thiệu Web Asset Manager (WAM) — một bộ quản lý tài nguyên dựa trên đồ thị phụ thuộc (Dependency Graph - DAG), tương tự như cách Composer hay npm quản lý các gói thư viện nhưng dành riêng cho CSS và JavaScript ở Runtime.
1. Bản chất kỹ thuật của Web Asset Manager (WAM)
Thay vì nạp trực tiếp đường dẫn file vật lý trong code PHP, Web Asset Manager hoạt động qua 2 bước tách biệt:
-
Registration (Đăng ký qua
joomla.asset.json): Khai báo danh sách các tài nguyên (style,script,preset) cùng tên định danh duy nhất (Name), đường dẫn file (URI), các thuộc tính HTML (defer,async,type="module") và mảng các tài nguyên mà nó phụ thuộc vào (dependencies). -
Activation & Resolution (Kích hoạt & Phân giải thứ tự): Trong code PHP của Template/Module/Component, bạn chỉ cần gọi lệnh ngắn gọn
$wa->useStyle('ten-asset')hoặc$wa->useScript('ten-asset'). Trước khi render thẻ<jdoc:include type="styles" />và<jdoc:include type="scripts" />, WAM sẽ:-
Tự động duyệt cây
dependenciesđể nạp các thư viện cha trước, file con sau. -
Loại bỏ trùng lặp (Deduplication): Dù có 10 Module cùng gọi
$wa->useScript('bootstrap.modal'), file JS đó cũng chỉ xuất hiện duy nhất 1 lần trong mã nguồn HTML. -
Tự động chọn file nén (
.min.css/.min.js): Nếu bạn khai báotemplate.csstrong JSON và đặt cạnh nó một filetemplate.min.css, khi tắt chế độ Debug System trên Production, WAM tự động nạp bản.min.css! Ngay khi bạn bật Debug System = Yes, nó tự động chuyển về nạp bản chưa néntemplate.cssđể bạn dễ dàng tra cứu dòng code trên DevTools.
-
2. Giải phẫu cấu trúc file joomla.asset.json
Mỗi Template (hoặc Extension) có một file joomla.asset.json nằm ở thư mục gốc của nó (/templates/joomlalab/joomla.asset.json).
Trong WAM, có 3 loại Asset (Types) cốt lõi:
-
style: Đại diện cho một file.css(nội bộ hoặc CDN bên ngoài). -
script: Đại diện cho một file.js(hỗ trợ cả chuẩn ES6 Moduleimport/export). -
preset: Một "gói combo" gộp chung nhiềustylevàscriptlại dưới một cái tên duy nhất. Khi gọi$wa->usePreset('template.joomlalab'), cả CSS và JS bên trong preset sẽ được kích hoạt cùng lúc.
Quy tắc đường dẫn tương đối (uri) trong joomla.asset.json:
Khi bạn khai báo "uri": "template.css" (không có dấu / ở đầu) bên trong file joomla.asset.json của template joomlalab:
-
Đối với
type: "style"WAM tự động tìm trong/media/templates/site/joomlalab/css/template.css. -
Đối với
type: "script"WAM tự động tìm trong/media/templates/site/joomlalab/js/template.js.
3. Thực hành: Viết file joomla.asset.json hoàn chỉnh cho Template joomlalab
Hãy mở file src/templates/joomlalab/joomla.asset.json (mà chúng ta đã tạo khung rỗng ở Bài 23) và cập nhật cấu hình thực tế dưới đây:
JSON
{
"$schema": "https://developer.joomla.org/schemas/json-schema/web_assets.json",
"name": "joomlalab",
"version": "1.0.0",
"description": "Web Assets configuration for JoomlaLab Template",
"license": "GPL-2.0-or-later",
"assets": [
{
"name": "template.joomlalab.base",
"description": "File CSS chính của giao diện JoomlaLab",
"type": "style",
"uri": "template.min.css",
"dependencies": [
"fontawesome"
]
},
{
"name": "template.user",
"description": "File CSS dành riêng cho người dùng ghi đè (luôn nạp sau cùng)",
"type": "style",
"uri": "user.css",
"dependencies": [
"template.joomlalab.base"
]
},
{
"name": "template.joomlalab.script",
"description": "File JavaScript chính của giao diện (chạy với thuộc tính defer)",
"type": "script",
"uri": "template.min.js",
"attributes": {
"defer": true
},
"dependencies": [
"core",
"bootstrap.collapse",
"bootstrap.dropdown"
]
},
{
"name": "template.user",
"description": "File JS tùy chỉnh của người dùng",
"type": "script",
"uri": "user.js",
"attributes": {
"defer": true
},
"dependencies": [
"template.joomlalab.script"
]
},
{
"name": "template.joomlalab",
"description": "Preset nạp trọn bộ CSS và JS tiêu chuẩn cho JoomlaLab",
"type": "preset",
"dependencies": [
"template.joomlalab.base#style",
"template.joomlalab.script#script"
]
}
]
}
Phân tích các điểm đắt giá trong file JSON trên:
-
"dependencies": ["fontawesome"]: Chỉ cần khai báo dòng này trongtemplate.joomlalab.base, Joomla 5 sẽ tự động nạp bộ icon FontAwesome 6 Free có sẵn trong Core vào trang trước khi nạptemplate.min.csscủa bạn! -
"dependencies": ["core", "bootstrap.collapse", "bootstrap.dropdown"]:-
"core": Nạp thư viện lõicore.min.jscủa Joomla (cung cấp đối tượngwindow.Joomla, hàmJoomla.getOptions(),Joomla.Text._()). -
"bootstrap.collapse"&"bootstrap.dropdown": Chỉ nạp đúng 2 module JS của Bootstrap 5 phục vụ cho thanh Menu Mobile và Menu thả xuống, thay vì nạp cả cục Bootstrap nặng nề.
-
-
"attributes": { "defer": true }: Bắt buộc trình duyệt tải file JS song song dưới nền và chỉ thực thi sau khi cây DOM HTML đã dựng xong, giúp điểm số Google PageSpeed (LCP/FCP) không bao giờ bị báo lỗi "Render-blocking resources". -
Cú pháp
#stylevà#scripttrong Preset: Khi mộtpresetgọi các thành phần con, bạn gắn hậu tố#stylehoặc#scriptvào tên asset để WAM phân biệt chính xác loại tài nguyên (đặc biệt hữu ích khi bạn đặt trùng tên nhưtemplate.user#stylevàtemplate.user#script).
4. Cách gọi và điều khiển Web Asset Manager trong index.php
Sau khi đã khai báo trong joomla.asset.json, hãy mở lại file src/templates/joomlalab/index.php (ở Bài 24) và nâng cấp phần khởi tạo $wa như sau:
PHP
/** @var \Joomla\CMS\Document\HtmlDocument $this */
$wa = $this->getWebAssetManager();
// 1. Kích hoạt trọn bộ CSS + JS của template thông qua Preset chỉ với 1 dòng lệnh!
$wa->usePreset('template.joomlalab');
// 2. Kích hoạt file user.css và user.js (WAM sẽ tự kiểm tra: nếu file tồn tại trong
// thư mục /media/templates/site/joomlalab/css/user.css thì mới in thẻ <link>, nếu không có sẽ tự bỏ qua!)
$wa->useStyle('template.user')
->useScript('template.user');
// 3. Đăng ký và nạp nhanh một Google Font hoặc CDN bên ngoài bằng PHP (Dynamic Registration)
$wa->registerAndUseStyle(
'google-font-inter',
'https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap',
[],
['rel' => 'stylesheet']
);
// 4. Huỷ kích hoạt (Disable) một Asset không mong muốn từ Component/Module bên thứ ba
// Ví dụ: Một module cũ tự ý nạp jQuery làm nặng trang, bạn có thể chặn đứng nó:
// $wa->disableScript('jquery');
5. Kỹ thuật truyền dữ liệu từ PHP sang JavaScript chuẩn Joomla 5 (addScriptOptions)
Trong quá trình lập trình Frontend/Backend, bạn thường xuyên cần truyền các biến cấu hình từ PHP (như baseUrl, csrfToken, primaryColor, hay dữ liệu cấu hình Module) xuống cho file template.js xử lý.
Tuyệt đối không dùng cách nối chuỗi <script>var myData = <?php echo ... ?>;</script> vì cách này vừa vi phạm chính sách bảo mật CSP (Content-Security-Policy) mà chúng ta đã học ở Bài 18, vừa chạy trước các file JS có gắn defer.
Thay vào đó, Joomla 5 cung cấp cơ chế truyền dữ liệu qua thẻ <script type="application/json" class="joomla-script-options"> cực kỳ an toàn:
Bước 1: Truyền dữ liệu từ PHP (Trong index.php, Module hoặc View)
PHP
// Truyền một mảng cấu hình xuống JS dưới khóa định danh 'joomlalab.config'
$this->addScriptOptions('joomlalab.config', [
'siteUrl' => Uri::root(),
'stickyOffset' => 80,
'isLogged' => !$app->getIdentity()->guest,
'primaryColor' => $primaryColor,
]);
// Đăng ký các chuỗi ngôn ngữ từ file .ini sang JS để dùng hàm Joomla.Text._()
Text::script('TPL_JOOMLALAB_BACK_TO_TOP');
Bước 2: Đọc dữ liệu trong file /media/templates/site/joomlalab/js/template.js
Nhờ đã khai báo dependency "core" trong joomla.asset.json, ở bên trong file template.js, bạn có thể lấy toàn bộ dữ liệu PHP vừa truyền sang thông qua Joomla.getOptions():
JavaScript
// File: media/templates/site/joomlalab/js/template.js
document.addEventListener('DOMContentLoaded', () => {
// 1. Lấy dữ liệu cấu hình từ PHP một cách an toàn (Chuẩn CSP)
const config = Joomla.getOptions('joomlalab.config', {});
console.log('Site URL từ PHP:', config.siteUrl);
// 2. Lấy chuỗi ngôn ngữ đa quốc gia đã được dịch
const btnLabel = Joomla.Text._('TPL_JOOMLALAB_BACK_TO_TOP', 'Lên đầu trang');
// 3. Lấy CSRF Token của hệ thống khi cần gọi Fetch / AJAX POST
const csrfToken = Joomla.getOptions('csrf.token', '');
});
6. Tổng kết bài học
-
Hiểu rõ kiến trúc đồ thị phụ thuộc (Dependency Graph) của Web Asset Manager giúp giải quyết triệt để bài toán nạp trùng lặp và sai thứ tự CSS/JS.
-
Thành thạo cấu trúc file
joomla.asset.jsonvới 3 kiểustyle,script,preset, cơ chế tự động chuyển đổi file.min.*theo chế độ Debug và thuộc tínhdefer. -
Làm chủ các phương thức
$wa->usePreset(),$wa->useStyle(),$wa->disableScript()và kỹ thuật truyền dữ liệu PHP JS an toàn qua$this->addScriptOptions()kết hợpJoomla.getOptions().
Sau khi đã làm chủ khung giao diện và tài nguyên CSS/JS, làm thế nào để thay đổi hoàn toàn cấu trúc HTML mặc định của trang chi tiết bài viết (com_content) hay thanh Menu (mod_menu) mà không đụng vào code Core của Joomla? Trong Bài học 26, chúng ta sẽ khám phá vũ khí mạnh nhất của lập trình viên giao diện Joomla: Kỹ thuật Template Overrides: Ghi đè giao diện của Component và Module theo thiết kế riêng.
All rights reserved