Huỳnh Minh Tân Bài viết sẽ giúp đọc giả có được những kiến thức cơ bản về RequireJS, trả lời được những câu hỏi như RequireJS là gì, tại sao phải sử dụng, cách module hóa JavaScript, làm thế nào để tạo module với RequireJS và cách ứng dụng nó vào dự án.
Nội dung bài viết

Giới thiệu

Bài viết sẽ giúp bạn đọc có những kiến thức cơ bản về RequireJS, trả lời được những câu hỏi như RequireJS là gì, tại sao phải sử dụng, cách module hóa JavaScript, làm thế nào để tạo module với RequireJS và ứng dụng nó vào dự án.

Cùng với những ví dụ cụ thể kết hợp với chú thích rõ ràng, bài viết sẽ giúp bạn đi từ những khái niệm mang tính trừu tượng cho đến việc bạn có thể tự tay áp dụng RequireJS vào dự án. Mỗi phần sẽ có source code demo các bạn có thể tải về tham khảo.

Tiền đề bài viết

Trong quá trình tìm hiểu framework DurandalJS để tạo Single-page app (SPA). DurandalJS được tạo từ 3 thành phần chính, sử dụng RequireJS cho việc hiện thực module, sử dụng KnockoutJS để bindding dữ liệu và dùng jQuery cung cấp các tiện ích. 

Đối tượng hướng đến

Yêu cầu phải có kiến thức cơ bản về JavaScript (function, object) và HTML. Biết những khái niệm về Bootstrap và jQuery.

RequireJS là gì?

logo

RequireJS (https://requirejs.org/) là một thư viện JavaScript dùng để cải thiện tốc độ thực thi và tối ưu source code được viết bằng JavaScript nhờ việc áp dụng tư tưởng module hóa, nó cũng là một trong những thành phần chính (RequireJS, KnockoutJS) có trong framework DurandalJS – dùng để tạo single-page aplication (SPA). Tiếp cận RequireJS giúp cho các bạn có được những khái niệm cơ bản từ đó dễ dàng tìm hiểu các framework khác như Angular, ReactJS,.. 

Để hiểu RequireJS là gì cách tốt nhất ta nên xem qua những lợi ích mà nó mang lại cũng như cách thức để hiện thực nó.

Tại sao phải sử dụng RequireJS?

Một cách khác, bạn có thể hiểu dưới đây là các chức năng hay các công dụng của RequireJS mang lại:

  • Modules hóa
  • Optimizer
  • Testing

Để có cái nhìn sâu hơn, chúng ta sẽ đi chi tiết qua từng chức năng.

Modules hóa

Với phong cách xây dựng website truyền thống, khi muốn thực thi JavaScript thì chúng ta dùng thẻ <script> nhúng chúng vào trong phần head hoặc cuối phần body

<script src="../assets/js/libs/jquery.js"></script>
<script src="../assets/js/libs/bootstrapjs" ></script>
<script src="../assets/js/components/zoom-image.js"></script>
<script src="../assets/js/components/search-products.js"></script>
<script src="../assets/js/main.js"></script>

Việc làm như vậy chỉ phù hợp với các hệ thống website vừa và nhỏ, còn nếu muốn áp dụng cho hệ thống phức tạp thì sẽ gặp phải nhiều khó khăn:

  • Sự phụ thuộc (dependencies): mặc định trình duyệt sẽ thực thi các file .js của chúng ta một cách đồng bộ (async=false) theo trình tự, tức là file script nào được viết trước thì sẽ được thực thi hoàn chỉnh rồi đến file kế tiếp. Như vậy, một cách rõ ràng khi muốn dùng Bootstrap thì phải thực thi jQuery trước đó. Xét trường hợp file main.js có sử dụng lại các phương thức đã được định nghĩa trong zoom-image.js thì tương tự như vậy zoom-image.js phải được đặt trước file main.js. Và cứ thế, khi có nhiều script thì chúng ta phải ghi nhớ thứ tự để thực thi một các chính xác và sẽ gây ra nhiều khó khăn khi mở rộng hoặc muốn điều chỉnh tính năng nào đó.
  • Tải các script không cần thiết: khi hệ thống có nhiều trang mỗi trang thực hiện các tính năng riêng biệt khi đó sẽ cần nhiều file JavaScript để xử lý riêng phù hợp cho từng trang. Cách xử lý truyền thống như trên sẽ bị tình trạng tốn nhiều tài nguyên tải (bandwidth) và thực thi (ram) các script không cần thiết. Xét ví dụ bên trên, tại trang chi tiết sản phẩm sẽ có file zoom-image.js xử lý việc zoom ảnh đại diện cho sản phẩm, còn file search-products.js xử lý các tác vụ ở trang tìm kiếm. Hai file này hoạt động riêng lẻ không ảnh hưởng lẫn nhau nên việc tải và xử lý zoom-image.js ở trang tìm kiếm và ngược lại là điều thật sự không cần thiết.

Để giải quyết các vấn đề này và giúp code tối ưu hơn nữa, chúng ta sẽ tiếp cận khái niệm module hóa JavaScript (javascript modules). Có nhiều cách để hiện thực, đối với RequireJS thì sử dụng phương pháp AMD (Asynchronous Module Difinitions), đôi khi còn có thể sử dụng phương pháp của CommonJS.

Việc hiện thực AMD có nhiều lợi ích:

  • Cải tiến hiệu suất webiste. Bằng việc chia nhỏ thành nhiều file JavaScript thực hiện các chức năng riêng lẻ, chỉ tải và thực thi những script nào thật sự cần thiết.
  • Hạn chế lỗi. Cho phép quản lý sự phụ thuộc giữa các file JavaScript một cách dễ dàng điều này sẽ giảm tối đa việc gọi thực thi các thuộc tính hay các phương thức mà chưa được định nghĩa.
  • Dễ sửa lỗi, cập nhật và duy trì. Việc chia nhỏ các chức năng thành từng module sẽ có rất nhiều thuận lợi cho việc xác định, thu hẹp và vá các lỗi về logic. Dễ dàng thêm mới tính năng mà không làm ảnh hưởng đến cả hệ thống. Khi một module bị lỗi thì các module sử dụng nó sẽ không được thực thi, chương trình một phần nào sẽ được duy trì “ổn định”.

Trong phạm vi bài viết này, chúng ta sẽ không tập trung vào các phương pháp hiện thực JavaScript Modules mà thay vào đó sẽ cùng tìm hiểu cách module hóa code JavaScript với RequireJS bằng việc áp dụng các phương pháp mà RequireJS có thể hổ trợ (AMD, CommonJS).

Opimize 

Tối ưu hóa là một tính năng cũng khá hữu ích của RequireJS dành cho các hệ thống lớn khi có nhiều tập tin JavasSript và CSS

  • Kết hợp (combine) những đoạn JavaScript liên quan với nhau thành từng file rồi tự động nén chúng lại (minify). Điều này giúp giảm băng thông do chỉ cần tải một vài tập tin cần thiết.
  • Tối ưu CSS bằng cách tự động minify (xóa khoảng trắng và các chú thích) giúp giảm dung lượng, tăng tốc độ tải trang.

Testing 

Việc kiểm thử tính đúng đắng của chương trình được diễn ra một cách thuận lợi bởi RequireJS có thể tương thích với nhiều framework testing javascript như busterjs, mocha, nodeunit, qunit và jasmine.

Tóm lại, RequireJS sẽ giúp chúng ta chi nhỏ thành các module JavaScript, dễ dàng quản lý sự phụ thuộc và mối liên hệ giữa chúng; giúp cải thiện đáng kể tốc độ cũng như tối ưu các đoạn code JavaScript; và dễ dàng kiểm thử chương trình. Sau khi đã hiểu rõ được những lợi ích mà RequierJS mang lại, những nội dung tiếp theo từng bước từng bước một giúp các bạn có thể tự tay áp dụng RequireJS vào dự án của chính mình.

Cách module hóa sử dụng RequireJS

Để có cái nhìn trực quan, chúng ta sẽ hiện thực tính năng phân chia module bởi công việc này đồi hỏi nhiều kỹ thuật của lập trình viên và cũng là tính năng quan trọng hơn hết khi nói đến đến thư viện RequireJS.

Xét một ví dụ đơn giản, sử dụng thư viện jQuery và Bootstrap để xây dựng một trang có hiệu ứng tooltip khi hover qua button. Tôi đã thêm file xử lý của RequireJS (require.js), cuối cùng sẽ được cấu trúc thư mục như bên dưới

|   index.html
|
\---assets
    +---css
    |       bootstrap.css
    |
    \---js
        |   main.js
        |
        +---components
        |       tooltip-button.js
        |
        \---libs
                bootstrap.js
                jquery.js
                require.js

Ở phần này, tôi muốn các bạn thấy được kết quả khác biệt giữa phong cách code truyền thống so với việc áp dụng thư viện RequireJS để tối ưu vì vậy tôi sẽ không chú trọng đến những vấn đề khác. Đôi khi các bạn sẽ không hiểu ở một vài điểm, nhưng đừng quá lo lắng chúng ta sẽ tìm hiểu kỹ hơn ở những nội dụng kế tiếp.

File index.html (cách code truyền thống)

<!DOCTYPE html>

<head>
    <link rel="stylesheet" href="assets/css/bootstrap.css">
</head>

<body>

    <div class="container">
        <h1> Tradition Sample Page: on hover show tooltip button </h1>

        <button class="btn btn-default" data-toggle="tooltip" title="Tooltip on top">
            Hover me
        </button>

    </div>

    <!-- code something -->

    <script src="assets/js/libs/jquery.js"></script>
    <script src="assets/js/libs/bootstrap.js"></script>
    <script src="assets/js/components/tooltip-button.js"></script>
    <script src="assets/js/main.js"></script>

</body>

</html>

Phần này nếu ta vô tình thay đổi thứ tự thực thi của bootstrap.js trước jquery.js thì sẽ gây ra lỗi và được thông báo bởi trình duyệt trong màn hình Console “Uncaught Error: Bootstrap's JavaScript requires jQuery at bootstrap.js:8

ccccc

File index.html (sử dụng RequireJS)

<!DOCTYPE html>

<head>
    <script data-main="assets/js/main" src="assets/js/libs/require.js"></script>
</head>

<body>

    <div class="container">
        <h1> RequireJS Sample Page: on hover show tooltip button </h1>

        <button class="btn btn-default" data-toggle="tooltip" title="Tooltip on top">
            Hover me
        </button>

    </div>

</body>

</html>

File main.js

// code something
// ...

// gọi hàm thực thi tooltip
var b = button();
b.tooltipFunc();

File main.js (sử dụng RequireJS)

// code something
// ...

function loadCss(url) {
    var link = document.createElement("link");
    link.type = "text/css";
    link.rel = "stylesheet";
    link.href = url;
    document.getElementsByTagName("head")[0].appendChild(link);
}

// load bootstrap css
loadCss("assets/css/bootstrap.css")

// cấu hình RequireJS
requirejs.config({

    // thiết lặp đường dẫn thư mục chứa các file javascript cần thực thi
    baseUrl: 'assets/js/libs',

    // gán tên đại diện cho module
    // tên này dùng để gọi sử dụng trong hàm require() và define()
    // nếu không xác định rõ tên thì mặc định sẽ lấy đúng tên file
    // chương trình sẽ tự động thêm thành phần mở rộng (.js)
    paths: {
       jquery: "jquery", // có thể bỏ đi, mặc định sẽ tự lấy đúng tên 
       bootstrap: "bootstrap",
       // gán tên đại diện nằm ở thư mục khác
       tooltip: "../components/tooltip-button"
    },

    // quy định sự phụ thuộc
    // bootstrap chỉ được load sau khi jquery được load thành công
    shim: {
        bootstrap: {
            deps: [ "jquery" ]
        }
    },
});

// gọi hàm thực thi
require(["tooltip"], function(tooltip) {
    tooltip.tooltipFunc();
})

File tooltip-button.js

function button(){
    return {
        tooltipFunc : function () {
            $('[data-toggle="tooltip"]').tooltip()
        }
    }
}

File tooltip-button.js (sử dụng RequireJS)

// Gọi và thực thi các module jquery và bootstrap 
// Biến $ là đối số đại diện cho module jquery,
// Do bootstrap chỉ cần thực thi hàm tooltip() nên không cần thiết
// phải khai báo đối số đại diện
define(['jquery','bootstrap'], function($) {
    'use strict';

    // được định nghĩa tương tự
    return {
        tooltipFunc : function () {
             $('[data-toggle="tooltip"]').tooltip()
       }
    }
});

Kết quả cuối cùng sử dụng 2 phương pháp đều giống nhau

a222

a1111

Giờ đây, chúng ta đã có cái nhìn tổng thể về 2 phương pháp, có khi bạn thắc mắc việc áp dụng RequireJS có vẻ như làm cho quá trình code trở nên phức tạp và khó khăn hơn. Điều đó dường như đúng, lý do là bạn đang tiếp cận những khái niệm mới cũng như khá quen thuộc với cách code truyền thống. Nhưng từ những lợi ích mà RequireJS mang lại, việc áp dụng RequireJS là một lựa chọn cần thiết dành cho hệ thống phức tạp đồi hỏi nhiều phương thức xử lý.

Các bạn có thể tải về 2 phiên bản của ví dụ trên: modules-javascript-example.zip

Kiến thức về RequireJS

Trong phần này, chúng ta sẽ cùng tìm hiểu sâu hơn về RequireJS. Từ việc đầu tiên là cài đặt môi trường, khởi tạo project, thiết lập cấu hình cho đến định nghĩa các module.

Thiết lặp môi trường (load javascript files)

Để sử dụng RequireJS ta chỉ cần có tập tin require.js là đủ. Các bạn có thể tải tại trang chủ https://requirejs.org/docs/download.html. 

Hoặc, có thể cài đặt thông qua NodeJS

  • Tải phiên bản NodeJS mới nhất tại https://nodejs.org/en/download/ (nếu chưa cài đặt)
  • Chạy lệnh command (cmd) trong thư mục muốn cài đặt project: npm install -g requirejs, sau khi chương trình tải về xong chúng ta có được cấu trúc thư mục như sau

+---.bin
|
\---requirejs
    |   package.json
    |   README.md
    |   require.js
    |
    \---bin
            r.js

Điểm bắt đầu chương trình (data-main entry point)

Sau khi có require.js, chúng ta cần thêm nó vào project. Để có thể tiếp cận dễ dàng chúng ta nên bắt đầu từ sự đơn giản nhất, nên tôi có một cấu trúc thư mục như bên dưới dùng để làm ví dụ

|   index.html
|
\---assets
    +---css
    |       bootstrap.css
    |
    \---js
        |   main.js
        |
        +---components
        |       tooltip-button.js
        |
        \---libs
                bootstrap.js
                jquery.js
                require.js

Đầu tiên, chúng ta cần định nghĩa trang index.html để load RequireJS

<!-- // index.html -->
<html>

<head>
    <script data-main="assets/js/main" src="assets/js/libs/require.js"></script>
</head>

<body>
    <h1> RequireJS Sample Page </h1>
</body>

</html>

Nếu bạn muốn sử dụng CDN (Content Delivery Netword) không muốn tải require.js về máy thì có thể sửa lại thành src="https://requirejs.org/docs/release/2.3.5/comments/require.js" chương trình vẫn thực thi một cách chính xác.

Sau khi require.js được thực thi xong, RequireJS sẽ tìm đến thuộc tính data-main, thuộc tính này dùng để xác định điểm bắt đầu của chương trình (entry point). Với trường hợp bên trên thì file “assets/js/main.js” sẽ được load một cách không đồng bộ (async) và là tập tin JavaScript bắt đầu cho chương trình.

Cũng có một cách khác để xác định điểm bắt đầu chương trình là lợi dụng việc load module thông qua định nghĩa module, nhưng cách này không được khuyến khích.

<!-- // index.html -->
<!DOCTYPE html>

<head>
    // không cần thuộc tính data-main
    <script src="assets/js/libs/require.js"></script>

    <script>
        // yêu cầu thực thi file assets/js/main.js
        // có thể thay thế thuộc tính data-main
        require(['assets/js/main'], function () {
            // none
        });
    </script>
</head>

<body>
    <h1> RequireJS Sample Page </h1>
</body>

</html>

Cấu hình chương trình (configuration options)

Trong ví dụ trên, tập tin assets/js/main.js là tập tin JavaScript chính của chương trình, thông thường nó sẽ dùng để cấu hình các đường dẫn và sự phụ thuộc (dependencies) giữa các module cho ứng dụng chúng ta.

// cấu hình RequireJS
requirejs.config({

    // thiết lặp đường dẫn thư mục chứa các file javascript cần thực thi
    baseUrl: 'assets/js/libs',

    // gán tên đại diện cho module
    // tên này dùng để gọi sử dụng trong hàm require() và define()
    // nếu không xác định rõ tên thì mặc định sẽ lấy đúng tên file
    // chương trình sẽ tự động thêm thành phần mở rộng (.js)
    paths: {
        jquery: "jquery",
        // jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min',

        bootstrap: "bootstrap",
        // gán tên đại diện nằm ở thư mục khác
        tooltip: "../components/tooltip-button"
    },

    // quy định sự phụ thuộc
    // bootstrap chỉ được load khi jquery được load thành công
    shim: {
        bootstrap: {
            deps: ["jquery"]
        }
    },
});

RequireJS hỗ trợ các tùy chọn cấu hình sau:

  • baseURL
    • Thiết lặp đường dẫn thư mục chứa các tập tin JavaScript cần thực thi.
    • Nó sẽ không được thực thi đối với các đường dẫn bắt đầu bằng dấu “/” (slash), giao thức http/https (protocol) hoặc kết thúc bằng đuôi mở rộng .js.
    • Nếu baseURL không được sử dụng để khái báo trong hàm requirejs.config() thì đường dẫn mặc định sẽ được lấy từ thuộc tính data-main, như vậy sẽ có kết quả là baseUrl: 'assets/js' đối với ví dụ bên trên.
  •  paths:
    • Dùng để gán (ánh xạ) một thư viện hoặc một module cho một biến mới, biến này sẽ làm đại diện cho thư viện / module đó khi chúng ta muốn gọi nó trong quá trình định nghĩa một module khác.
    • Trong ví dụ trên, biến jquery sẽ là tên đại diện cho thư viện jQuery, biến tooltip sẽ là tên đại diện cho module được định nghĩa trong assets/js/components/tooltip-button.js. Việc tạo biến đại diện này có tác dụng làm đơn giản hơn trong việc gọi thư viện hoặc module khi chúng ta muốn sử dụng chúng trong quá trình định nghĩa module khác.
    • Sử dụng các biến đại diện để gọi thực thi
      • define(['jquery','bootstrap', 'tooltip'], function($, btp, tooltip) {
            // Sử dụng thư viện jquery.
            // Sử dụng thư viện bootstrap.
            // Sử dụng các phương thức đã 
            // được định nghĩa trong moudle assets/js/components/tooltip-button.js.
        });
        
    • Không tạo biến đại diện, phải chỉ rõ đường dẫn để gọi thực thi
      • define(['jquery','bootstrap', '../components/tooltip-button'], 
          function($, btp, tooltip) {
            // Sử dụng thư viện jquery.
            // Sử dụng thư viện bootstrap.
            // Sử dụng các phương thức đã 
            // được định nghĩa trong moudle assets/js/components/tooltip-button.js.
        });
        
    • Đường dẫn chứa thư viện hoặc module trong thuộc tính paths này nó sẽ phụ thuộc vào đường dẫn mà chúng ta đã thiết lập ở thuộc tính baseURL trước đó. Ở ví dụ này, khi đã thiết lập baseUrl: 'assets/js/libs'paths: { jquery: "jquery" } điều này có thể hiểu là biến jquery sẽ đại diện cho assets/js/libs/jquery.js, còn paths: { tooltip: "../components/tooltip-button"} thì biến tooltip sẽ đại diện cho assets/js/components/tooltip-button.js ( dùng “../” để trở về thư mục js ). Các bạn nên xem lại cấu trúc thư mục trong phần ví dụ để hiểu rõ cách xác định đường dẫn.
    •  Có thể sử dụng giao thức http/https (protocol) khi định nghĩa thuộc tính paths
      • paths: {
            // jquery: "jquery", // sử dụng ở local
            // sử dụng CDN, không có thành phần mở rộng .js
            jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min',
        
            bootstrap: "bootstrap",
            tooltip: "../components/tooltip-button"
        },
    • Đường dẫn không cần thành phần mở rộng .js, khi thực thi chương trình sẽ tự động thêm .js vào mỗi tập tin JavaScript.
  • shim:
    • Dùng để thiết lặp các sự phụ thuộc của các thư viện hoặc module theo kiểu đồng bộ (non AMD) và dùng khai báo giá trị toàn cục cho thư viện hoặc module đó (exporting their global values).
    • Ta có ví dụ, giả sử backbone.js, underscore.js, jquery.jsbootstrap.js đã được cấu hình đường dẫn ở thuộc tính baseURL (hoặc paths), các bạn xem comment để hiểu rõ hơn
      • requirejs.config({
          
            shim: {
                'bootstrap': {
                    // Quy định sự phụ thuộc là
                    // jquery.js sẽ thực thi trước bootstrap.js 
                    deps: ['jquery'],
               },
        
               'backbone': {
                    //Các thư viện được khai báo trong thuộc tính deps
                    //sẽ được thực thi trước backbone.js
                    deps: ['underscore', 'jquery'],
                    //Khi thực thi xong, biến toàn cục "Backbone" có thể sử dụng
                    //để gọi thư viện này trong lúc định nghĩa module .
                    exports: 'Backbone'
                },
            }
        });
        
  • map:
    • Dùng để thiết lập cho việc sử dụng module có nhiều phiên bản làm sao phù hợp với từng trường hợp cụ thể.
    • Có ví dụ như sau, dựa vào thư viện Bootstrap để tùy biến và cá nhân hóa lại thanh menu. Tất cả các code xử lý được lưu ở custom-menu-v3.js ứng với phiên bản Bootstrap ver3. Sau này, Bootstrap cập nhật lên ver4, tôi cũng cập nhật theo, vì vậy tạo custom-menu-v4.js ứng với Bootstrap ver4. Tôi sẽ áp dụng thuộc tính map và sẽ có cấu trúc thư mục như bên dưới
    • xxxxxxot_20180810011046
      • requirejs.config({
            map: {
                'menu/custom-menu-v3': {
                    'boostrap': 'bootstrap.v3'
                },
                'menu/custom-menu-v4': {
                    'boostrap': 'bootstrap.v4'
                }
            }
        });
        
        // Định nghĩa ở custom-menu-v3.js
        define(['boostrap'], function (b3) {
            // 'boostrap' -> bootstrap.v3.js
            // Các thuộc tính và phương thức
            // trong bootstrap.v3.js sẽ thực thi 
        })
        
        // Định nghĩa ở custom-menu-v4.js
        define(['boostrap'], function (b4) {
            // 'boostrap' -> bootstrap.v4.js
            // Các thuộc tính và phương thức
            // trong bootstrap.v4.js sẽ thực thi 
        })
        
    • Các bạn có thể thấy, ứng với mỗi tập tin JavaScript tùy biến menu thì RequireJS sẽ thực thi phiên bản Bootstrap một cách tương ứng mà ta chỉ cần gọi tên đại diện chung là bootstrap (xem comment trên code ví dụ). Tất nhiên, bạn cũng có thể gọi như cách thông thường là define([‘bootstrap.v3.js']…) hoặc define([‘bootstrap.v4.js']…) ở mỗi tập tin JavaScript dùng để tùy biến menu, nhưng cách này thật sự không tối ưu đối với chương trình lớn mà lại có nhiều phiên bản khác nhau. Việc sử dụng thuộc tính map giúp chúng ta dễ điều chỉnh, tránh sửa code ở nhiều nơi nếu có vấn đề phát sinh.
    • Các bạn có thể tải ví dụ tại đây: requirejs-map-property-example.zip
  • config:
    • Cách mà RequireJS thiết lặp một thông tin nào đó cho một module.
      • requirejs.config({
            config: {
                // phải tên cùng với tên file contact.js
                'contact': {
                    GoogMapKey: 'ABAB-CD-XX'
                }
            }
        });
        
        // contact.js
        define(['module'], function (module) {
        
            var key = module.config().GoogMapKey;
            // có giá trị là 'ABAB-CD-XX'
        
        })
        
    • Các bạn muốn thiết lập thông tin cho module được định nghĩa trong tập tin contact.js thì phải khai báo cùng tên là contact, sử dụng thông tin được gán thông qua phương thức module.config(), thông tin thực thi thành công chỉ khi được gọi trong module tương ứng.
    • Các bạn có thể tải ví dụ: requirejs-config-property-example.zip

Tôi đã giới thiệu các bạn các thuộc tính thường dùng nhất, có thể tham khảo thêm các thuộc tính khác tại trang chủ https://requirejs.org/docs/api.html#config

Định nghĩa module (define a module)

RequireJS cho phép chúng ta tải và thực thi module một cách nhanh chống, không lo lắng về sự phụ thuộc giữa các module và có thể tải nhiều phiên bản của một module trong cùng một trang. Tùy theo mục đính sử dụng, ta có các cú pháp định nghĩa như sau:

Định nghĩa module cặp Name và Value (simple name/value pairs)

Nếu module bạn chỉ cần tên đại diện và giá trị thì sử dụng cú pháp sau

define({
    studentID: "15526969",
    studentName: "Kong Joo Woo"
});

Định nghĩa như một phương thức (definition functions)

Nếu module bạn không có các phụ thuộc (tức là không sử dụng thư viện hoặc module khác) và muốn định nghĩa như một phương thức thì có cú pháp sau

define(function () {
    //Do setup work here

    return {
        studentID: "15526969",
        studentName: "Kong Joo Woo"
    }
});

Định nghĩa với các phụ thuộc (defining functions with dependencies)

Nếu bạn muốn sử dụng các thư viện và các module khác đã được định nghĩa để tạo ra module mới thì chúng ta sẽ có cú pháp sau

define(["./input", "./insertDatabase"], 
   function(inp, ins) {
        
      return {
        // example
        studentID: "15526969",
        studentName: "Kong Joo Woo",
      
         addStudent: function() {
            inp.inputStudent(this);
            ins.insertToDadabase(this);
         }
      
      }
   }
);

Phương thức define() có 2 tham số, tham số đầu tiên là mảng các thư viện hoặc module cần sử dụng, tham số thứ hai dùng để định nghĩa một phương thức như thông thường có thể trả về là một phương thức (function) hoặc là một đối tượng (object). Các đối số trong phương thức này là đại diện cho từng module hoặc thư viện đã thêm vào ở tham số đầu tiên và nó phải sắp xếp đúng theo đúng thứ tự tương ứng với mảng đã thêm thì mới sử dụng chính xác.

Trong ví dụ trên, đối số inp sẽ đại diện cho module input.js, ins sẽ đại diện cho module insertDatabase.js và phương thức trong module sẽ trả về một đối tượng có 2 thuộc tính (studentID, studentName) và 1 phương thức (addStudent).

Định nghĩa module trả về một phương thức (defining a module as a function)

Khi bạn định nghĩa một module không chỉ trả về đối tượng như ở ví dụ trên mà còn có thể trả về một phương thức.

define(["./getDatabase"],
    function (getD) {
        return function (student) {
            return getD.getStudentName(student.studentID);
        }
    }
);

Định nghĩa theo phong cách CommonJS

Ngoài việc thêm các thư viện hoặc module bằng mảng thì chúng ta có thể sử dụng require() để thực hiện.

define(function (require, exports, module) {
    var ins = require('./input'),
        inp = require('./insertDatabase');

    //Return the module value
    return function (student) {
               inp.inputStudent(student);
               ins.insertToDadabase(student);
    };
});

Định nghĩa với tên module (define a module with a name)

Đối số đầu tiên sẽ dùng để khai báo tên của module, trong ví dụ là utils/add

define("utils/add", ["./input"], function (inp) {
      // something
    }
);

Như vậy, tùy theo mục đích sử dụng các bạn có thể lựa chọn các định nghĩa phù hợp như module chỉ gồm cặp name/value, hoặc có thể trả về một đối tượng hay một phương thức. Có một lưu ý nhỏ, mỗi module chỉ nên định nghĩa trong một tập tin JavaScript riêng biệt, điều này làm cho việc gọi sử dụng module được thuận lợi do chỉ cần xác định đường dẫn (name-to-file-path).

Sử dụng require()define()

Nếu có một chút tinh tế thì sẽ phát hiện trong những ví dụ trên tôi đã sử dụng phương thức require()define(). Hai phương thức này có điểm chung là dùng để tải các phụ thuộc (các thư viện hoặc module), nhưng sẽ có sự khác nhau cơ bản giữa hai phương thức này. Trong đó, require() dùng để gọi thực thi các thuộc tính hay các phương thức trong module đã được định nghĩa và nó thường đặt trong main.js, còn define() sử dụng để định nghĩa module và giúp module có thể tái sử dụng dễ dàng.

Có một lưu ý, sẽ có khi bạn thấy phương thức requirejs() ở đâu đó. Nhưng đừng ngạc nhiên vì phương thức require() và requirejs() đều giống nhau.

require === requirejs // true

Các keyword hữu ích

  • single-page application (SPA)
  • AMD module javascript
  • async sync defer attributes

Kết luận

Như vậy, bài viết đã giới thiệu về RequireJS một cách cơ bản nhất, các khái niệm liên quan cũng như cách để áp dụng vào dự án. Nội dung trong bài viết này cũng là tiền đề tạo cơ sở cho việc tìm hiểu các framework được xây dựng chặt chẽ như Angular, ReactJS, VueJS. Để tiếp cận các kiến thức rộng lớn về việc sử dụng thư viện JavaScript để xây dựng website chúng ta phải tìm hiểu những khái niệm liên quan trước, trong đó RequireJS là một ví dụ.

Do bài viết chỉ mang tính chất giới thiệu tổng quan nhằm tạo ra sự dễ dàng cho những người mới bắt đầu với RequireJS, muốn hiểu rõ hơn cách hoạt động cũng như cách làm việc thì tôi khuyên các bạn tải và chạy thử các ví dụ tại trang chủ, rồi xem lại các phần ở bài viết này nếu gặp khó khăn trong quá trình đọc hiểu các ví dụ đó. Chúc các bạn thành công!

THẢO LUẬN
ĐÓNG