Một bài viết chi tiết hơn về ESM và CommonJS modules trong Node.js

Một bài viết chi tiết hơn về ESM và CommonJS modules trong Node.js

Tin ngắn hàng ngày dành cho bạn
  • Manus đã chính thức mở cửa cho tất cả người dùng rồi đấy mọi người. Cho những ai chưa biết thì đây là một công cụ viết báo cáo (làm mưa làm gió) giống như Deep Research của OpenAI á. Mỗi ngày được miễn phí 300 Credits để nghiên cứu. Mỗi lượt nghiên cứu tiêu tốn tuỳ thuộc vào độ phức tạp của yêu cầu. À với cả họ đang có chương trình tặng miễn phí Credits hay sao á. Như mình thì vào thấy được hẳn 2000.

    Mình dùng thử, so sánh với cùng một lệnh giống như đợt trước dùng bên Deep Research thì nội dung khác biệt nhau hoàn toàn. Manus báo cáo như kiểu viết văn hơn so với OpenAI là các gạch đầu dòng và bảng biểu.

    À lúc đăng ký xong có bắt nhập số điện thoại để xác minh, nếu lỗi thì các bạn đợi qua ngày thử lại xem có được không nhé.

    » Xem thêm
  • Mọi người chắc nghe nhiều về xu hướng tìm kiếm thông tin bằng AI chứ không cần công cụ tìm kiếm như Google nữa rồi đúng không? Không đâu xa ánh xạ vào bản thân thì thấy đúng thật, thi thoảng mới tìm kiếm thôi chứ còn đâu toàn hỏi tụi AI.

    Ngay từ đầu viết blog, thứ mà mình hướng đến là chia sẻ kinh nghiệm chứ không phải là những bài mang nặng tính kỹ thuật, máy móc, hướng dẫn từ đầu... Vì thời điểm đó đã có quá nhiều người làm nội dung này rồi và họ làm rất tốt, tại sao mình phải cố phát minh lại bánh xe? Một điều nữa là tin tưởng độc giả của mình có khả năng tìm hiểu vấn đề. Nếu bạn đọc đủ nhiều các bài viết trên blog thì thấy mình luôn cố gắng chèn thêm các liên kết tham khảo ngoài bài viết, nêu ra vấn đề mở và rất ít khi kết luận chắc chắn một điều gì đó.

    Mình đã cố gắng rèn luyện kỹ năng viết, kỹ năng trình bày và cả cách tương tác với độc giả để mang lại giá trị cho họ. Nhiều lúc ngồi lật lại các con số thống kê thấy lượng đọc bài viết tăng lên lại cảm thấy vui. Nhưng khi nguồn truy cập đến từ Google thì lại thấy buồn, vì điều đó chứng tỏ họ biết đến mình chỉ khi đang cố đi tìm giải pháp, có thể họ chỉ đọc chớp nhoáng, may ra tìm được cách giải quyết và thế là đóng cửa sổ trình duyệt rồi đi như một cơn gió.

    Chừng vài tháng đổ lại đây, một điều khiến mình rất vui đó là lượng người truy cập thẳng vào trang chủ mà không thông qua công cụ tìm kiếm đang tăng dần lên, có nhiều hôm lượng truy cập tự nhiên còn cao hơn cả đến từ Google. Điều đó chứng tỏ độc giả đã có thói quen quay lại trang của mình nhiều hơn và họ tìm thấy được giá trị từ blog mang lại. Vui mừng khôn xiết 🤩

    Bên cạnh đó thì lượng truy cập vào chuyên mục Threads - tức là mục mình đang viết bài này đang cao hơn bao giờ hết. Điều đó chứng tỏ xu hướng đi theo tin nhanh là đúng đắn. Mình có thể ngồi cả ngày để viết tin ngắn cho bạn đọc vì nó rất nhanh mà tiện, không tốn công đi tìm tài liệu để viết, không tốn cả thời gian viết nữa, còn mình thì có rất nhiều thứ để chia sẻ 😅. Nhưng không vì thế mà bỏ bê các bài viết dài, vì dài thì có nhiều thông tin để chia sẻ hơn.

    Vài lời tâm sự thế thôi chứ hơn một tháng nay mình chưa viết bài viết mới nào vì công việc bận quá. Xong lâu dần cứ trì hoãn lại thành lười. À với cả tháng 5 này rất thích hợp để đọc các cuốn sách về cách mạng á. Có hôm đọc đến 2 giờ sáng mới đi ngủ 🥱

    » Xem thêm
  • Mình mới nhìn thấy một trang web khá thú vị nói về các cột mốc đáng nhớ trong lịch sử phát triển Internet toàn cầu: Internet Artifacts

    Chỉ từ 1977 - khi Internet còn nằm trong hộp thí nghiệm thì nhìn xem - giờ đây Internet đã khiến mọi thứ phát triển đến mức nào 🫣

    » Xem thêm

Vấn đề

Trước kia tôi đã viết một số bài nói về các loại modules trong Node.js cũng như trong JavaScript. Đại khái là có nhắc đến CommonJS, AMD rồi cả ESM modules nữa, bạn đọc có thể xem lại tại hai bài viết Tìm hiểu về require trong node.jsTìm hiểu về modules trong Node.js. Tại sao lại có nhiều loại modules như vậy?. Tuy nhiên chưa đi sâu vào chúng.

Nhiều người thắc mắc không biết lúc nào thì dùng require, lúc nào thì dùng import. Hay có thể sử dụng cả hai trong cùng một dự án được hay không? Bài viết ngày hôm nay, chúng ta hãy cùng nhau tìm hiểu về cách hoạt động của hai loại modules này trong Node.js để trả lời những thắc mắc ở bên trên nhé.

Cách ESM và CommonJS hoạt động

Có thể nói rằng, việc ngay từ đầu không có hệ thống modules rõ ràng nào trong JavaScript đã dẫn đến việc phức tạp hóa vấn đề như hiện nay. Cộng đồng sử dụng JavaScript đời đầu đã phải tự tay tạo ra các thể loại modules cho nó, trong đó có thể kể đến như là AMD, UMD... Khi đó, Node.js ra đời và lựa chọn CommonJS làm trình xử lý modules mặc định.

Nhận ra thiếu sót của mình, cuối cùng ECMAScript đã phải giới hiệu hệ thống modules chính thức cho đặc tả này là ESM. Ngay lập tức, JavaScript được cập nhật loại modules này, nhưng có lẽ hơi muộn vì giờ đây đã có quá nhiều package được tạo ra trước đó sử dụng hệ thống modules không chính thức. Nhưng dẫu sao có còn hơn không, vấn đề chỉ còn là thời gian cho đến khi ESM được phổ biến rộng rãi.

Tóm lại, có thể hiểu CommonJS được sinh ra để sử dụng trong Node.js. ESM là hệ thống modules chính thức của JavaScript, nhưng vì sinh sau đẻ muộn cho nên ESM chỉ được hỗ trợ từ Node.js 12+, còn trong trình duyệt hiện đại ngày nay thì ESM hầu như đều có thể chạy được.

Vậy trình duyệt có thể chạy được CommonJS không? Câu trả lời là không. Nhưng chúng ta vẫn có thể tạo ra được package hỗ trợ có thể chạy được trong cả hai môi trường.

Nếu vậy thì trình duyệt có thể chạy được tất cả ESM modules chứ? Câu trả lời vẫn là không. Hiểu đơn giản nếu trong modules có chứa các hàm của Node.js mà trình duyệt không có thì chắc chắc nó sẽ không thể nào chạy được. Ngược lại, ESM cũng chưa chắc đã chạy được trong Node.js vì có thể nó chứa các hàm mà trong Node.js không hỗ trợ. Tóm lại, chạy được hay không còn phụ thuộc vào người phát triển có hỗ trợ nữa hay không.

Ví dụ tạo ra một module bằng CommonJS:

// add.js file
// hoặc add.cjs file

function add(a, b) {
    return a + b;
}

module.exports = add;

Đuôi mở rộng .cjs là hoàn toàn hợp lệ và nó dùng để nhắc cho Node.js biết rằng đây là một module sử dụng CommonJS.

Sau đó chúng ta có thể sử dụng module bằng cú pháp require:

const add = require('./add.js')

add(1, 2);

Tương tự, một module bằng ESM sẽ trông giống như sau:

// add.js file
// hoặc add.mjs file

function add(a, b) {
    return a + b;
}

export default add;

Tương tự, .mjs cũng là một đuôi hợp lệ và nó nhắc cho Node.js biết rằng đây là module sử dụng ESM.

Sử dụng EMS module bằng cú pháp import:

import add from './add.js';

add(1, 2);

Như vậy, chúng ta có thể thấy sự khác biệt lớn nhất giữa CommonJS và ESM nằm ở cú pháp xuất/nhập module, cũng như cú phápimportrequire để sử dụng chúng.

Có thể sử dụng CommonJS và ESM đan xen nhau không?

Câu trả lời là có. Bạn có thể làm theo hướng dẫn dưới đây.

Nhập modules CommonJS vào dự án ESM

Rất đơn giản, sử dụng cú pháp import như bình thường:

// index.js

import add from './add.cjs';

add(1, 2);

Nhập modules ESM vào CommonJS

require là hàm đồng bộ nên không thể sử dụng nó để nhập các modules ESM. Thay vào đó, để nhập ESM vào CommonJS, chúng ta sẽ sử dụng import. import trả về một Promise, trong kết quả chứa một trường default trỏ đến điểm xuất modules mặc định của ESM.

// index.js

(async function () {
  const add = (await import('./index.mjs')).default;

  add(1, 2);
})();

await bắt buộc phải được gọi trong một hàm async nên cách viết trên mới hoạt động được. Hoặc bạn có thể sử dụng một phiên bản Node.js mới hơn có hỗ trợ Top-level await.

Đôi khi có thể thấy trong một số dự án sử dụng được cả cú pháp requireimport song song. Đó nhiều khả năng là do nó có sử dụng một số công cụ hỗ trợ chuyển mã như Typescript, Webpack, Rollup... Về bản chất, chúng ta có thể thoải mái viết cú pháp require hoặc import, nhưng sau khi build, công cụ sẽ chuyển mã về thống nhất một cú pháp của CommonJS hoặc ESM theo thiết lập từ trước.

Universal modules

Universal modules là khái niệm chỉ một module có thể hoạt động trên cả Node.js lẫn trình duyệt. Tức là tùy vào trường hợp sử dụng modules trong môi trường nào thì nó sẽ tự động sử dụng cú pháp của CommonJS hoặc ESM.

Để làm được điều đó, rất đơn giản chỉ cần khai báo đường dẫn đến tệp được sử dụng trong từng môi trường trong file package.json.

{
  ...
  "main": "cjs/index.js",
  "module": "es6/index.js",
  ...
}

Với main là đường dẫn đến index.js sử dụng cú pháp của CommonJS, module thì dẫn đến index.js sử dụng cú pháp của ESM. Sau này cài đặt và sử dụng package đó trong môi trường Node.js hoặc trình duyệt, chúng sẽ tự hiểu và lựa chọn loại module tương thích. Lúc này bạn có thể viết mã tương thích với trình duyệt hoặc Node.js trong cùng một package.

"Khoan đã, như vậy thì tôi phải viết mã hai lần ở hai nơi sao?" Có thể! Nhưng chẳng ai làm vậy, bởi vì build tools sẽ giúp chúng ta tạo ra Universal module, chỉ bằng một lần viết mã và qua một bước build để sinh ra mã cho cả hai môi trường.

Có rất nhiều build tools mà bạn có thể tìm thấy, ví dụ như là webpack, rollup và một cái tên mới nổi gần đây về tốc độ chính là esbuild.

Bạn đọc có thể tham khảo documents của công cụ muốn sử dụng hoặc tìm kiếm ví dụ cũng như các khung để bắt đầu nhanh trên github.

Tổng kết

CommonJS hoạt động trong Node.js nhưng không hoạt động trong trình duyệt. ESM được hỗ trợ bởi tất cả các trình duyệt hiện đại và các phiên bản mới nhất của Node.js 12+.

Rất nhiều công cụ trong hệ sinh thái JavaScript đã được phát triển trong Node.js, và Node chỉ mới bắt đầu hỗ trợ ESM gần đây, do đó, một phần lớn các dự án đều đang sử dụng CommonJS.

Nếu bạn đang bắt đầu với một dự án Node.js mới và phân vân giữa việc có nên hỗ trợ hoàn toàn ESM hay không thì hãy nghĩ đến việc rất nhiều package thông dụng trên npm vẫn đang sử dụng CommonJS. Mặc dù ESM có khả năng hỗ trợ nhập modules bằng CommonJS nhưng vẫn nên cân nhắc khả năng xảy ra sự cố và khắc phục sự cố sau này.

Cuối cùng Universal là một khái niệm chỉ các packages có thể hoạt động trong nhiều môi trường khác nhau như Node và trình duyệt. Bằng cách khai báo đường dẫn nhập tệp trong package.json để qua đó chỉ dẫn cho môi trường biết cách nhập các loại modules tương thích.

Tham khảo:

Cao cấp
Hello

Bí mật ngăn xếp của Blog

Là một lập trình viên, bạn có tò mò về bí mật công nghệ hay những khoản nợ kỹ thuật về trang blog này? Tất cả bí mật sẽ được bật mí ngay bài viết dưới đây. Còn chờ đợi gì nữa, hãy bấm vào ngay!

Là một lập trình viên, bạn có tò mò về bí mật công nghệ hay những khoản nợ kỹ thuật về trang blog này? Tất cả bí mật sẽ được bật mí ngay bài viết dưới đây. Còn chờ đợi gì nữa, hãy bấm vào ngay!

Xem tất cả

Đăng ký nhận thông báo bài viết mới

hoặc
* Bản tin tổng hợp được gửi mỗi 1-2 tuần, huỷ bất cứ lúc nào.

Bình luận (3)

Nội dung bình luận...
Avatar
Phạm Tiến Đạt1 năm trước
- Em cám ơn anh. Bài viết của anh rất hay
Trả lời
Avatar
Xuân Hoài Tống1 năm trước
Cảm ơn e nhé, ghé blog a thường xuyên nha vì a viết hàng tuần á.
Avatar
Phạm Tiến Đạt1 năm trước
- Hiện tại em viết một NPM package và dùng Typescript để compie ra hai file một là cjs và esm, sau đó em dùng cjs trong main và esm trong module ở package.json như vậy thì nếu em install package này dùng trong Node Server thì mặc định sẽ dùng ở cjs phải không anh và ngươc lại ở các dự án Reacts thì sẽ dùng ở ESM ạ. Mong anh trả lời ạ, em cám ơn anh.
Trả lời
Avatar
Xuân Hoài Tống1 năm trước
Ồ nếu thế thì e có thể tham khảo phần Universal modules trong bài viết kia, nó dùng để cấu hình cho Node hoặc Trình duyệt biết nên import tệp nào. Hoặc tham khảo 2 thuộc tính main và browser tại https://docs.npmjs.com/cli/v10/configuring-npm/package-json#main Điều quan trọng là phải có cấu hình 2 thuộc tính này trong package.json thì Node/Trình duyệt mới hiểu được em đang muốn dùng tệp nào ở môi trường nào.
Avatar
Phạm Tiến Đạt1 năm trước
Dạ em chào anh, em có một câu hỏi ạ Theo như anh đề cập thì Commonjs là module trong Nodejs, và Esm là module được sử dụng trong trình duyệt vậy khi import Commonjs vào Esm thì có cần phải qua build tool, webpackage, babel để convert không anh.
Trả lời
Avatar
Xuân Hoài Tống1 năm trước
Chào em, một câu hỏi thú vị. Khi em muốn import commonjs vào esm thì anh hiểu em đang làm việc với dự án Node.js, Node ở các phiên bản hiện tại hỗ trợ cả commonjs và esm nên em có thể làm như thế mà không cần qua build tools nào cả, Node tự làm. Nhưng như thế nhiều vấn đề có thể xuất hiện, ví dụ như em chạy các phiên bản Node thấp hơn hay các phiên bản Node cao hơn có thể ngừng hỗ trợ commonjs cho nên tốt nhất là vẫn nên dùng thêm các build tools để được hỗ trợ lâu dài và đa phiên bản. Còn trong chiều ngược lại, em đang làm dự án của trình duyệt thì muốn import commonjs vào esm thì phải qua build tools rồi em nhé!