Bun: Công cụ all-in-one siêu nhanh và đa năng cho ứng dụng JavaScript

Bun là một công cụ all-in-one mới toanh với những tính năng như JavaScript Runtime, Pakage Manager, Bundler, Test Runner và là ứng cử viên sáng giá trong việc thay thế Node.js.

Upload image

Ngày 8 tháng 9, Bun đã chính thức công bố bản release stable 1.0 và thu hút mạnh mẽ sự chú ý từ cộng đồng công nghệ trong những ngày gần đây. Vậy điều gì đã làm cho Bun trở nên nổi bật? Hãy cũng mình đi sâu vào tìm hiểu, khám phá các tính năng của nó nhé!

Điều ấn tượng ban đầu khi biết đến Bun đó là mình đã vô tình nhìn thấy trong một loạt bài tin tức về công nghệ có xen lẫn đâu đó một biểu tượng rất cute, đó là hình chiếc bánh bao và nó là lý do khiến mình nhấn vào để xem nó là cái gì vì nhìn qua thì nó chả liên quan gì đến công nghệ.

Upload image

Nhân vật cute mình nhắc đến đây :D

Bun là gì?

Theo trang chủ, Bun được miêu tả như một bộ công cụ "tất cả trong một" (all-in-one) nhanh và toàn diện cho các ứng dụng JavaScript và TypeScript. Với mục tiêu rõ ràng, Bun được thiết kế ra như một sự thay thế "drop-in" cho Node.js, loại bỏ các hạn chế không cần thiết trước đó, cải thiện hiệu suất, giảm thiểu độ phức tạp và tăng năng suất làm việc cho nhà phát triển.

Tính chất all-in-one được thể hiện ở việc nó không chỉ là một trình JavaScript runtime mà còn là một trình đóng gói (bundler), một trình chạy kiểm thử (test runner) và một trình quản lý gói (package manager).

Như vậy, để đạt được mục tiêu kể trên thì Bun đã giải quyết những vấn đề gì mà Node đang gặp phải? Sau đây, mình sẽ phân tích tính năng cốt lõi của Bun - một JavaScript runtime thông qua việc so sánh với Node.js về những khía cạnh có thể có của nó.

Tính năng cốt lõi của Bun - JavaScript Runtime trong tương quan với Node.js

Có thể nói sự ra đời Node.js (năm 2009) là một bước đột phá trong thời đại công nghệ lúc bấy giờ. Tuy nhiên, cũng giống như những công nghệ khác, càng phát triển thì độ phức tạp cũng tăng theo, phần nào khiến cho công cụ JavaScript dần trở nên chậm chạp và phức tạp đi.

Chính vì thế, Bun được sinh ra với mục tiêu loại bỏ những điều đó bằng cách tinh gọn JavaScript trở nên mượt mà và nhanh hơn nhưng vẫn giữ được bản chất độc đáo, mạnh mẽ vốn có của nó.

Bun mang nhiều ưu điểm hơn về tốc độ so với Node.js bởi chúng sử dụng những JavaScript engine khác nhau. Trong khi Node.js sử dụng V8 Engine của Google, hỗ trợ trình duyệt Chrome thì Bun sử dụng JavaScriptCore được Apple phát triển cho trình duyệt Safari. Cả hai đều có kiến trúc và chiến lược tối ưu hoá khác nhau:

  • Với V8 engine, nó ưu tiên về khả năng thực thi nhanh hơn, tối ưu hoá thời gian chạy nhiều hơn dẫn đến việc sử nhiều bộ nhớ nhiều hơn.
  • Với JavaScriptCore, nó lại ưu tiên thời gian khởi động nhanh hơn, giảm mức sử dụng bộ nhớ và thời gian thực thi lại chậm hơn một chút.

Đó cũng là lý do làm cho Bun có thời gian khởi động nhanh hơn gấp 4 lần so với Node.js. Không chỉ so về khía cạnh tốc độ, Bun còn tối ưu hoá về trải nghiệm phát triển phần mềm cho lập trình viên.

1. Hỗ trợ TypeScript và JSX

Về cơ bản Node.js không hỗ trợ TypeScript. Thông thường để thực thi, Node.js buộc phải tải thêm các dependency từ bên ngoài như typescript, ts-node, v.v.

Ngược lại, Bun giải quyết vấn đề bằng cách đưa bộ chuyển mã JavaScript vào trong thời gian chạy. Điều này cho phép bạn có thể chạy trực tiếp các file .js, .ts, .jsx.tsx mà không cần phải tải thêm các dependencies bên ngoài và thời gian thực thi sẽ nhanh hơn vì không phải trải qua bước biên dịch riêng lẻ nào nữa.

2. Tương thích cả với CommonJS và ESModules

Các hệ thống module nổi bật như CommonJS (CJS) và ESModules (ESM) cho phép các lập trình viên có thể tổ chức code thành các phần có thể sử dụng lại, giúp việc code trở nên dễ dàng hơn.

Để chuyển từ CJS qua ESM, với Node.js bạn cần phải cấu hình một vài chỗ như là thiết lập "type": "module" trong file package.json và sử dụng đuôi file .mjs.

Ngược lại, Bun loại bỏ sự phức tạp này bằng việc hỗ trợ cả hai, đơn giản hoá việc chuyển đổi từ CJS sang ESM mà không phải quan tâm bất kì cấu hình "rườm rà" nào. Có nghĩa là bạn có thể tự do sử dụng importrequire() trong cùng một file.

Ví dụ:

import lodash from "lodash";
const _ = require("underscore");

3. Hỗ trợ Web APIs

Các Web APIs là những thứ không thể thiếu đối với các ứng dụng trên trình duyệt, có thể kể đến như fetch hay WebSocket, v.v. Nhưng khả năng hỗ trợ của chúng trong các môi trường phía máy chủ Node.js chưa được nhất quán. Thông thường để sử dụng fetch, bạn cần phải tải thư viện bên thứ ba như node-fetch (mãi đến Node v18 thì mới chính thức có việc hỗ trợ thử nghiệm với fetch) hay khi sử dụng WebSocket thì bạn cũng cần phải tải thêm ws.

Ngược lại, Bun đơn giản hoá bằng việc hỗ trợ sẵn các Web APIs mà không cần phải tải thêm bất kì thứ gì khác. Chúng hoàn toàn được triển khai bằng mã gốc, nhanh hơn và đáng tin cậy hơn so với lựa chọn bên thứ 3.

Tham khảo các Web APIs được Bun hỗ trợ tại đây.

4. Hot reloading

Hot reloading giúp tăng hiệu suất cho dev bằng cách tự động làm mới và tải lại các phần của ứng dụng trong thời gian thực thi khi mã thay đổi mà không phải yêu cầu khởi động lại hoàn toàn.

Trước đó với Node thì chúng ta phải tải thêm nodemon - một dependency bên ngoài nhưng nó gặp phải một số vấn đề có thể làm ngắt kết nối giữa HTTP và WebSocket và trạng thái lúc đó cũng mất theo. Từ Node v18 thì bạn có thể thay thế bằng cờ --watch nhưng nó vẫn đang là bản thử nghiệm.

Với Bun, người lập trình sẽ làm việc hiệu quả hơn với cờ --hot, nó khắc phục được những vấn đề kể trên cũng như mang đến trải nghiệm mượt mà hơn.

bun --hot server.ts

5. Khả năng tương thích với Node.js

Mối quan tâm hàng đầu của người lập trình viên là khi có sự chuyển đổi sang một trình runtime mới là liệu nó có còn khả năng tương thích với những cái tồn tại trước đó không? Cụ thể ở đây là từ Node.js chuyển sang Bun.

Cái hay của Bun đó là nó hướng đến việc thay thế Node.js một cách không hoàn toàn - "a drop-in replacement". Điều này có nghĩa là bạn không phải thay thế mọi thứ hoàn toàn với Bun, mà là Bun sẽ tương thích mọi thứ có sẵn trước đó, cụ thể đó là đối với những ứng dụng của Node.js và các gói npm hiện có thì ta có thể dễ dàng tích hợp với Bun mà không cần phải có bất kỳ chỉnh sửa nào thêm. Các tính năng chính có thể đảm bảo khả năng tương thích là các module fs, path, net, nhận biết các biến cục bộ như __dirname, process hay thuật toán phân giải module của Node.js bao gồm cấu trúc node_modules quen thuộc.

Dù không hẳn là tương thích hoàn toàn với các API của Node.js nhưng với các tính năng chính thì Bun hoàn toàn đảm bảo việc này, bạn có thể dõi việc cập nhật các trạng thái tương thích tại đây.

6. Bun APIs

Không chỉ dừng lại ở việc có thể tương thích dễ dàng với Node.js, Bun còn cung cấp các thư viện API tiêu chuẩn cho riêng nó, được tối ưu hoá cao và phần nào cũng khẳng định "chất riêng" của mình.

Bun.file()

Sử dụng Bun.file() cho khả năng tải file tối ưu hơn và truy cập nội dung ở nhiều định dạng khác nhau. Ngoài ra, theo cảm nhận của mình thì cú pháp này của Bun có vẻ gọn gàng hơn.

Ví dụ:

// Node
const fs = require("fs/promises");
const fileContents = await fs.readFile("package.json", "utf-8");

// Bun
const file = Bun.file("package.json");
await file.text();

Bun.write()

Tương tự với Bun.file() , Bun.Write() cho khả năng viết file linh hoạt hơn - từ kiểu string, binary data đến Blobs hay thậm chí là kiểu Response.

Ví dụ:

await Bun.write("index.html", "<html/>"); // string
await Bun.write("index.html", Buffer.from("<html/>")); // binary
await Bun.write("index.html", Bun.file("home.html")); //Blobs
await Bun.write("index.html", await fetch("<https://example.com/>")); // Response

Bun.serve()

Bun.serve() cho khả năng thiết lập máy chủ HTTP hay WebSocket dựa trên các Web APIs chuẩn như RequestResponse.

Bun.serve({
  port: 3000,
  fetch(request) {
    return new Response("Hello from Bun!");
  },
  // dễ dàng cấu hình thêm với WebSocket
    websocket: {
    open(ws) { ... },
    message(ws, data) { ... },
    close(ws, code, reason) { ... },
  },
});

Ngoài ra, Bun còn cung cấp các APIs khác như là bun:sqlite, Bun.password và bạn có thể theo dõi thêm tại đây.

Bun là một trình JavaScript runtime nhưng nó không phải là tính năng duy nhất, sau đây mình sẽ tiếp tục chia sẻ thêm về những tính năng còn lại trong bộ “all-in-one” của Bun

Các tính năng khác của Bun

1. Bun là một trình quản lý gói (package manager)

Nếu như không coi Bun là một runtime thì Bun cũng đảm nhận tốt vai trò của một trình quản lý gói giống như pnpm, npm, yarn nhưng với tốc độ tải gói nhanh hơn rất nhiều.

Cú pháp khá thân quen giống như yarn

bun install
bun add <package> [--dev|--production|--peer]
bun remove <package>
bun update <package>

Để đạt được tốc độ tải vượt trội, Bun tận dụng đến bộ nhớ đệm toàn cục (global store) để loại bỏ việc tải xuống lại các gói cùng phiên bản từ npm registry giúp lần cài đặt tiếp theo được nhanh hơn thậm chí là có thể thực hiện việc này offline (giống như cách tiếp cận của pnpm). Hơn thế nữa, Bun sử dụng các lệnh gọi hệ thống được cho là nhanh nhất hiện có ở mỗi hệ điều hành, giúp tối ưu hiệu suất hơn (cụ thể đó là ở MacOS, nó sử dụng clonefileat()).

Upload image

Minh hoạ về tốc độ tải của Bun

2. Bun là một trình đóng gói (bundler)

Với hệ sinh thái của Node.js, việc đóng gói thường được xử lý bởi các bên thứ ba có thể kể đến như Webpack, Vite hay Parcel. Nhưng với Bun, nó là một trình đóng gói đúng nghĩa. Bun được thiết kế để gộp mã JavaScript và TypeScript ngay từ đầu cho nhiều nền tảng khác nhau. Việc đóng gói lúc này không cần phải sử dụng Webpack hay là Vite nữa mà chỉ cần câu lệnh của Bun:

bun build ./index.ts --outdir ./build

Bun có khả năng cung cấp các API plugin cho việc tương thích với cái cấu hình của bundler

import mdx from "@mdx-js/esbuild";

Bun.build({
  entrypoints: ["index.tsx"],
  outdir: "build",
  plugins: [mdx()],
});

3. Bun là một trình chạy kiểm thử (test runner)

Bun giới thiệu trình chạy kiểm thử được tích hợp sẵn, hứa hẹn mang lại tốc độ, khả năng tương thích cao và cung cấp các tính năng phục vụ cho quy trình phát triển như hiện tại.

Cụ thể với bun:test, Bun nó có thể tương thích hoàn toàn với Jest - một khung thử nghiệm không còn xa lạ.

import { test, expect } from "bun:test";

test("2 + 2", () => {
  expect(2 + 2).toBe(4);
});

Sử dụng bun test để chạy câu lệnh.

Kết

Có thể thấy Bun sinh ra từ nhu cầu mong muốn việc phát triển phần mềm trở nên hiệu quả hơn, nó mang lại tốc độ siêu nhanh trong lúc code, cung cấp công cụ all-in-one cho khả năng trải nghiệm lập trình được mượt mà hơn. Mọi thứ mình cần code đều nằm trong một công cụ nhất định, hạn chế việc cài thêm các thư viện bên ngoài. Web APIs cũng được hỗ trợ một cách "chính gốc", mang đến trải nghiệm sử dụng các APIs được nhất quán, nhanh hơn và đáng tin cậy hơn so với lựa chọn bên thứ 3.

Dù là vậy, so với Node.js thì đây chỉ là bước đầu của Bun (bản v1.0), Bun vẫn còn một vài hạn chế có thể kể đến như chưa hỗ trợ hoàn toàn việc tương thích với Node.js, đặc biệt là việc hỗ trợ trên Windows chưa được hoàn chỉnhh, v.v. Tuy nhiên, với tất cả những gì nó mang lại, Bun chắc chắn là một công cụ mạnh mẽ và đáng để bạn trải nghiệm.

Như vậy, mình vừa trải qua bài viết khá dài về Bun, bởi là công cụ all-in-one cho nên có khá nhiều thứ để chia sẻ trong bài viết này. Hy vọng những thông tin trên đã giúp bạn có thêm nhiều góc nhìn thú vị về Bun. Hẹn gặp các bạn ở bài viết tiếp theo!

Tham khảo:

Trang chủ của Bun

Bun 1.0

Bun vs Node.js: Everything you need to know

Atekco - Home for Authentic Technical Consultants