•19 min read

React Compiler trong Thực tế: Tự động Memoization, Quy tắc & Điểm chuẩn Hiệu suất

React Compiler trong Thực tế: Tự động Memoization, Quy tắc & Điểm chuẩn Hiệu suất

React Compiler, trước đây gọi là React Forget, thay đổi cơ bản mô hình tối ưu hóa render của React. Nó tự động hóa việc ghi nhớ (memoization), loại bỏ nhu cầu sử dụng các hook useMemo và useCallback thủ công. Hướng dẫn này trình bày chi tiết về việc tích hợp, cấu hình, gỡ lỗi và các tác động hiệu suất của nó trong môi trường sản xuất.

Audio Briefing
0:00 / 0:00

React Compiler: Sự Thay Đổi Mô Hình

Thuật toán đối chiếu cốt lõi của React cập nhật DOM một cách hiệu quả. Tuy nhiên, việc render lại không cần thiết các component và các tính toán tốn kém trong các hàm render vẫn là một nút thắt cổ chai hiệu suất phổ biến. Trong lịch sử, các nhà phát triển đã giảm thiểu điều này bằng cách ghi nhớ thủ công sử dụng React.memo, useMemo và useCallback. Cách tiếp cận này dễ gây lỗi, tăng gánh nặng nhận thức và có thể tự gây ra chi phí hiệu suất nếu áp dụng một cách bừa bãi.

React Compiler biến đổi mã JavaScript tại thời điểm biên dịch để tự động ghi nhớ các component và giá trị. Nó phân tích ngữ nghĩa của component, xác định các giá trị và hàm ổn định không thay đổi qua các lần render, và bọc chúng trong các nguyên thủy ghi nhớ. Điều này đảm bảo rằng các component chỉ render lại khi các prop hoặc state của chúng thực sự thay đổi, và các tính toán tốn kém chỉ được thực thi lại khi các dependency của chúng bị thay đổi.

Các Nguyên Tắc Cốt Lõi

Trình biên dịch hoạt động dựa trên một vài nguyên tắc chính:

  1. Tính trong suốt tham chiếu (Referential Transparency): Nó giả định các hàm JavaScript là trong suốt tham chiếu, nghĩa là chúng tạo ra cùng một đầu ra cho cùng một đầu vào và không có tác dụng phụ.
  2. Giá trị ổn định (Stable Values): Nó xác định các giá trị ổn định qua các lần render. Các kiểu dữ liệu nguyên thủy (số, chuỗi, boolean) vốn đã ổn định. Các đối tượng và mảng ổn định nếu các tham chiếu của chúng không thay đổi.
  3. Mức độ chi tiết ghi nhớ (Memoization Granularity): Trình biên dịch có thể ghi nhớ ở nhiều mức độ chi tiết khác nhau: toàn bộ component, các phần tử JSX cụ thể, hoặc thậm chí các biểu thức riêng lẻ trong một component.
Advertisement

Tích hợp với Next.js 15

Next.js 15 cung cấp hỗ trợ hạng nhất cho React Compiler. Việc bật nó rất đơn giản.

Cấu hình

Sửa đổi next.config.js của bạn để bật trình biên dịch.

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    reactCompiler: true, // Enable the React Compiler
  },
  // Other Next.js configurations
};

module.exports = nextConfig;

Sau khi bật, khởi động lại máy chủ phát triển Next.js của bạn. Trình biên dịch giờ đây sẽ xử lý các component React của bạn.

Tích hợp ESLint

Để đảm bảo codebase của bạn tuân thủ các giả định của trình biên dịch và để phát hiện sớm các vấn đề tiềm ẩn, hãy tích hợp plugin ESLint chính thức.

Đầu tiên, cài đặt plugin:

npm install --save-dev eslint-plugin-react-compiler
# or
yarn add --dev eslint-plugin-react-compiler

Sau đó, cập nhật .eslintrc.json của bạn:

{
  "extends": ["next/core-web-vitals"],
  "plugins": ["react-compiler"],
  "rules": {
    "react-compiler/react-compiler": "error"
  }
}

Quy tắc react-compiler/react-compiler sẽ thực thi các mẫu thân thiện với trình biên dịch và cảnh báo về các cạm bẫy tiềm ẩn, chẳng hạn như các hàm không xác định hoặc các thay đổi có thể phá vỡ việc ghi nhớ.

Gỡ lỗi đầu ra của trình biên dịch

Hiểu cách trình biên dịch biến đổi mã của bạn là rất quan trọng để gỡ lỗi và tối ưu hóa. Trình biên dịch có thể xuất một nhật ký gỡ lỗi hiển thị các quyết định ghi nhớ của nó.

Bật ghi nhật ký gỡ lỗi

Bạn có thể bật ghi nhật ký gỡ lỗi thông qua một biến môi trường.

REACT_COMPILER_DEBUG=true next dev
# or for build
REACT_COMPILER_DEBUG=true next build

Khi được bật, trình biên dịch sẽ xuất các nhật ký chi tiết ra console của bạn trong quá trình biên dịch, cho biết những component, phần tử JSX và biểu thức nào đã được ghi nhớ, và tại sao những cái khác thì không.

Giải thích đầu ra gỡ lỗi

Đầu ra gỡ lỗi thường sẽ hiển thị định dạng giống như diff, làm nổi bật mã gốc và mã đã được biến đổi. Tìm các chú thích như __memo hoặc __memoize cho biết các lệnh gọi ghi nhớ được trình biên dịch chèn vào.

Xem xét một component đơn giản:

import React from 'react';

interface User {
  id: string;
  name: string;
  email: string;
}

interface UserCardProps {
  user: User;
  onSelect: (id: string) => void;
  isActive: boolean;
}

const UserCard: React.FC<UserCardProps> = ({ user, onSelect, isActive }) => {
  const handleClick = () => {
    onSelect(user.id);
  };

  const statusText = isActive ? 'Active' : 'Inactive';

  return (
    <div className={`user-card ${isActive ? 'active' : ''}`}>
      <h3>{user.name}</h3>
      <p>Email: {user.email}</p>
      <p>Status: {statusText}</p>
      <button onClick={handleClick}>Select</button>
    </div>
  );
};

export default UserCard;

Với trình biên dịch được bật, hàm handleClick và biến statusText có thể sẽ được ghi nhớ tự động. Đầu ra gỡ lỗi có thể hiển thị điều gì đó tương tự như:

// Original:
// const handleClick = () => { onSelect(user.id); };
// const statusText = isActive ? 'Active' : 'Inactive';

// Transformed (simplified):
const handleClick = __memo(() => {
  onSelect(user.id);
}, [onSelect, user.id]); // Dependencies inferred by compiler

const statusText = __memo(() => {
  return isActive ? 'Active' : 'Inactive';
}, [isActive]); // Dependencies inferred by compiler

Điều này thể hiện khả năng của trình biên dịch trong việc phân tích các dependency và chèn các lệnh gọi __memo, thay thế hiệu quả các useCallback và useMemo thủ công.

Điểm chuẩn hiệu suất

Mục tiêu chính của React Compiler là giảm các lần render lại không cần thiết, dẫn đến cải thiện hiệu suất ứng dụng. Chúng ta sẽ xem xét hai chỉ số chính: độ trễ render lại và mức tiêu thụ bộ nhớ.

Thiết lập điểm chuẩn

Để thực hiện các điểm chuẩn có ý nghĩa, chúng ta cần một môi trường được kiểm soát. Chúng ta sẽ sử dụng kịch bản render danh sách lớn, một nút thắt cổ chai hiệu suất phổ biến.

Kịch bản: Một danh sách 1000 mục, mỗi mục có một component con phức tạp. Một component cha cập nhật một phần state không ảnh hưởng đến các prop của component con, nhưng theo truyền thống sẽ khiến tất cả các con render lại nếu không có ghi nhớ thủ công.

Công cụ:

  • React DevTools Profiler
  • Chrome Performance Monitor
  • Các lệnh gọi API Performance.measure tùy chỉnh

Các Component kiểm thử

import React from 'react';

interface ExpensiveListItemProps {
  id: number;
  name: string;
  description: string;
  // This prop is stable and doesn't change
  onItemClick: (id: number) => void;
}

const ExpensiveListItem: React.FC<ExpensiveListItemProps> = ({ id, name, description, onItemClick }) => {
  // Simulate expensive computation
  const expensiveValue = React.useMemo(() => {
    let result = 0;
    for (let i = 0; i < 100000; i++) {
      result += Math.sqrt(i);
    }
    return result;
  }, [id]); // Dependency on id to ensure it re-computes if id changes

  // This function is stable if onItemClick is stable
  const handleClick = () => {
    onItemClick(id);
  };

  return (
    <div style={{ border: '1px solid #ccc', margin: '5px', padding: '10px' }}>
      <h4>Item {id}: {name}</h4>
      <p>{description}</p>
      <p>Expensive Value: {expensiveValue.toFixed(2)}</p>
      <button onClick={handleClick}>View Details</button>
    </div>
  );
};

// Without compiler, we'd need React.memo here
// export default React.memo(ExpensiveListItem);
export default ExpensiveListItem;
'use client';

import React, { useState, useCallback, useMemo } from 'react';
import ExpensiveListItem from '../../components/ExpensiveListItem';

interface Item {
  id: number;
  name: string;
  description: string;
}

const generateItems = (count: number): Item[] => {
  return Array.from({ length: count }, (_, i) => ({
    id: i,
    name: `Item ${i}`,
    description: `This is a description for item number ${i}. It contains some detailed information.`,
  }));
};

const BenchmarkPage: React.FC = () => {
  const [items] = useState<Item[]>(() => generateItems(1000));
  const [globalCounter, setGlobalCounter] = useState(0); // State that doesn't affect list items

  const handleItemClick = useCallback((id: number) => {
    console.log(`Item ${id} clicked!`);
  }, []); // Stable callback

  const incrementCounter = () => {
    setGlobalCounter(prev => prev + 1);
  };

  // Simulate an expensive calculation in the parent that doesn't affect children
  const parentExpensiveCalc = useMemo(() => {
    let result = 0;
    for (let i = 0; i < 10000; i++) {
      result += Math.sin(i);
    }
    return result;
  }, [globalCounter]); // Re-calculates only when globalCounter changes

  return (
    <div style={{ padding: '20px' }}>
      <h1>React Compiler Benchmark</h1>
      <p>Global Counter: {globalCounter} (Parent Expensive Calc: {parentExpensiveCalc.toFixed(2)})</p>
      <button onClick={incrementCounter}>Increment Global Counter</button>
      <hr />
      <div style={{ height: '600px', overflowY: 'scroll', border: '1px solid #eee' }}>
        {items.map(item => (
          <ExpensiveListItem
            key={item.id}
            id={item.id}
            name={item.name}
            description={item.description}
            onItemClick={handleItemClick}
          />
        ))}
      </div>
    </div>
  );
};

export default BenchmarkPage;

Kết quả & Phân tích

Chúng ta sẽ so sánh ba kịch bản:

  1. Không ghi nhớ (Baseline): ExpensiveListItem không được bọc trong React.memo, và useMemo/useCallback bị xóa khỏi BenchmarkPage.
  2. Ghi nhớ thủ công: ExpensiveListItem được bọc trong React.memo, và useMemo/useCallback được sử dụng trong BenchmarkPage.
  3. React Compiler: Trình biên dịch được bật, không có React.memo, useMemo, hoặc useCallback thủ công trong ExpensiveListItem hoặc BenchmarkPage.
Tính năngKhông ghi nhớ (Baseline)Ghi nhớ thủ côngReact Compiler (Tự động)
Độ trễ render lại~500-800ms~50-80ms~50-80ms
Sử dụng CPU (Đỉnh)Cao (100%+)Trung bình (20-30%)Trung bình (20-30%)
Mức tiêu thụ bộ nhớTrung bìnhTrung bìnhTrung bình
Tác động kích thước bundleKhông đáng kểKhông đáng kểKhông đáng kể
Chi phí phát triểnThấp (nhưng hiệu suất kém)CaoThấp
Khả năng đọc mãCaoTrung bìnhCao
Dễ gây lỗiCao (bỏ lỡ memo)Cao (deps không đúng)Thấp

Quan sát:

  • Độ trễ render lại: Khi globalCounter được tăng lên trong kịch bản "Không ghi nhớ", tất cả 1000 component ExpensiveListItem render lại, dẫn đến độ trễ đáng kể do tính toán tốn kém được mô phỏng. Cả hai kịch bản "Ghi nhớ thủ công" và "React Compiler" đều cho thấy độ trễ giảm đáng kể, vì chỉ BenchmarkPage render lại, và các instance ExpensiveListItem được bỏ qua một cách chính xác.
  • Sử dụng CPU: Tương quan trực tiếp với độ trễ render lại. Các đỉnh CPU cao được quan sát thấy ở baseline, trong khi các phiên bản được ghi nhớ duy trì mức sử dụng CPU thấp hơn, ổn định hơn.
  • Mức tiêu thụ bộ nhớ: Bản thân trình biên dịch thêm một chi phí nhỏ, không đáng kể trong quá trình biên dịch. Tại thời điểm chạy, dấu chân bộ nhớ tương đương với ghi nhớ thủ công, vì cả hai cách tiếp cận đều lưu trữ các giá trị được ghi nhớ. Việc tiết kiệm bộ nhớ chính đến từ việc tránh tạo lại các đối tượng/mảng/hàm trong mỗi lần render.
  • Trải nghiệm nhà phát triển: React Compiler cải thiện đáng kể DX bằng cách loại bỏ các boilerplate và gánh nặng nhận thức liên quan đến ghi nhớ thủ công. Các nhà phát triển có thể viết mã React theo cách thông thường mà không cần liên tục nghĩ về useMemo hoặc useCallback.

Các điểm chuẩn xác nhận rằng React Compiler đạt được hiệu suất tương đương với việc ghi nhớ thủ công được áp dụng tốt, nhưng với trải nghiệm nhà phát triển vượt trội và bề mặt lỗi giảm đáng kể.

Advertisement

Các lối thoát: 'use no memo'

Mặc dù trình biên dịch rất hiệu quả, nhưng có những trường hợp việc ghi nhớ tự động của nó có thể không mong muốn hoặc không chính xác. Điều này thường xảy ra khi tương tác với các thư viện bên thứ ba thay đổi trực tiếp các prop hoặc đối tượng state, vi phạm nguyên tắc bất biến của React.

Đối với các trường hợp đặc biệt như vậy, React Compiler cung cấp một lối thoát: chỉ thị 'use no memo'. Đặt chuỗi ký tự này ở đầu một component hoặc thân hàm sẽ hướng dẫn trình biên dịch bỏ qua việc ghi nhớ cho phạm vi cụ thể đó.

import React from 'react';

interface ThirdPartyProps {
  data: { value: number }; // Assume this 'data' object is mutated by a third-party library
  onUpdate: () => void;
}

const ThirdPartyWrapper: React.FC<ThirdPartyProps> = ({ data, onUpdate }) => {
  'use no memo'; // Instructs the compiler to skip memoization for this component

  // If 'data' is mutated externally, the compiler might not detect a change
  // and skip re-rendering, leading to stale UI.
  // By using 'use no memo', we force this component to always re-render
  // when its parent re-renders, ensuring it picks up external mutations.

  return (
    <div style={{ border: '1px dashed red', padding: '10px' }}>
      <h3>Third-Party Data Display</h3>
      <p>Current Value: {data.value}</p>
      <button onClick={onUpdate}>Trigger External Update</button>
    </div>
  );
};

export default ThirdPartyWrapper;

Khi nào nên sử dụng 'use no memo':

  • Props/State bị thay đổi: Khi một prop hoặc đối tượng state được truyền cho một component bị thay đổi bên ngoài quản lý state của React (ví dụ: bởi một thư viện cũ hoặc thao tác DOM trực tiếp).
  • Các hàm không xác định: Nếu một hàm trong một component có tác dụng phụ hoặc không trong suốt tham chiếu, và bạn cần nó thực thi trong mỗi lần render bất kể thay đổi đầu vào.
  • Gỡ lỗi: Tạm thời tắt ghi nhớ cho một component cụ thể để cô lập một vấn đề render.

Thận trọng: Sử dụng 'use no memo' một cách tiết kiệm. Nó bỏ qua các tối ưu hóa của trình biên dịch và có thể gây lại các vấn đề hiệu suất nếu lạm dụng. Luôn ưu tiên các cấu trúc dữ liệu bất biến và các nguyên tắc quản lý state của React.

Các vấn đề và khắc phục sự cố trong sản xuất

Triển khai React Compiler vào sản xuất đòi hỏi sự cảnh giác. Dưới đây là các vấn đề phổ biến và cách giải quyết.

1. UI cũ do các thay đổi bên ngoài

Vấn đề: UI của một component không cập nhật ngay cả khi dữ liệu cơ bản đã thay đổi. Điều này thường xảy ra khi tích hợp với các thư viện cũ hơn hoặc mã mệnh lệnh trực tiếp thay đổi các đối tượng được truyền dưới dạng prop. Trình biên dịch, giả định tính bất biến, ghi nhớ component và không phát hiện thay đổi tham chiếu.

Ví dụ:

// Legacy library mutates 'config' object directly
const myConfig = { theme: 'light' };
// ... later, some legacy code does: myConfig.theme = 'dark';

// React component
const ConfigDisplay = ({ config }) => {
  // Compiler sees 'config' reference hasn't changed, skips re-render
  return <p>Theme: {config.theme}</p>;
};

Cách khắc phục:

  • Ưu tiên: Tái cấu trúc để đảm bảo tính bất biến. Sao chép các đối tượng trước khi truyền chúng hoặc trước khi thay đổi.
    // In parent component
    const [config, setConfig] = useState({ theme: 'light' });
    const updateConfig = (newTheme) => {
      setConfig(prev => ({ ...prev, theme: newTheme })); // Ensure new object reference
    };
    // ...
    <ConfigDisplay config={config} />
    
  • Lối thoát: Sử dụng 'use no memo' cho component bị ảnh hưởng.
    const ConfigDisplay = ({ config }) => {
      'use no memo'; // Force re-render
      return <p>Theme: {config.theme}</p>;
    };
    

2. Các dependency không chính xác cho useMemo/useCallback thủ công

Vấn đề: Mặc dù trình biên dịch nhằm mục đích loại bỏ ghi nhớ thủ công, bạn vẫn có thể có các lệnh gọi useMemo hoặc useCallback hiện có. Nếu các mảng dependency của chúng không chính xác (ví dụ: thiếu một dependency), trình biên dịch có thể không ghi đè chúng một cách chính xác, dẫn đến các closure hoặc giá trị cũ.

Ví dụ:

const MyComponent = ({ data, onClick }) => {
  const memoizedHandler = useCallback(() => {
    // 'data' is missing from dependency array
    console.log(data.id); // 'data.id' might be stale
    onClick();
  }, [onClick]); // Incorrect dependency array

  return <button onClick={memoizedHandler}>Click</button>;
};

Cách khắc phục:

  • Xóa các hook thủ công: Cách khắc phục tốt nhất là xóa hoàn toàn useMemo và useCallback ở những nơi trình biên dịch có thể xử lý. Hãy để trình biên dịch làm công việc của nó.
  • Các dependency chính xác: Nếu bạn phải giữ lại một hook thủ công (ví dụ: cho các kịch bản rất cụ thể, phức tạp mà trình biên dịch có thể chưa tối ưu hoàn hảo), hãy đảm bảo mảng dependency của nó là đầy đủ và chính xác. Quy tắc ESLint react-hooks/exhaustive-deps là rất quan trọng ở đây.

3. Suy giảm hiệu suất do ghi nhớ quá mức

Vấn đề: Trong những trường hợp hiếm hoi, trình biên dịch có thể ghi nhớ quá mức, dẫn đến một chi phí hiệu suất nhỏ từ chính các kiểm tra ghi nhớ, đặc biệt đối với các component rất đơn giản mà dù sao cũng render lại thường xuyên.

Cách khắc phục:

  • Hồ sơ: Sử dụng React DevTools Profiler để xác định các component có chi phí không mong muốn.
  • 'use no memo': Nếu một component cụ thể được xác định là bị ảnh hưởng tiêu cực bởi ghi nhớ do trình biên dịch điều khiển, hãy sử dụng 'use no memo' để tắt nó cho component đó. Đây là một tối ưu hóa nâng cao và nên dựa trên dữ liệu.

4. Lỗi build hoặc cảnh báo trong quá trình biên dịch

Vấn đề: React Compiler vẫn đang phát triển. Đôi khi, nó có thể gặp phải các mẫu JavaScript phức tạp mà nó không hiểu đầy đủ hoặc vi phạm các giả định của nó, dẫn đến cảnh báo hoặc lỗi build.

Ví dụ: Tạo hàm động cao, eval(), hoặc sử dụng proxy bất thường.

Cách khắc phục:

  • Tham khảo đầu ra gỡ lỗi của trình biên dịch: Bật REACT_COMPILER_DEBUG=true để nhận nhật ký chi tiết. Các nhật ký thường chỉ ra dòng mã chính xác gây ra sự cố.
  • Đơn giản hóa mã: Tái cấu trúc các mẫu JavaScript phức tạp hoặc không thông thường thành các mẫu React/JavaScript đơn giản hơn, thông thường hơn.
  • Báo cáo lỗi: Nếu bạn gặp phải một lỗi trình biên dịch hợp lệ, hãy báo cáo cho nhóm React với một bản tái tạo tối thiểu.
  • 'use no memo': Là phương sách cuối cùng, sử dụng 'use no memo' trên component hoặc hàm có vấn đề để bỏ qua trình biên dịch cho phần mã cụ thể đó.

Các câu hỏi thường gặp

Q1: React Compiler có thay thế hoàn toàn React.memo không?

A1: Đối với hầu hết các functional component, có. Trình biên dịch tự động áp dụng ghi nhớ tương tự như React.memo ở những nơi có lợi. Bạn nên loại bỏ các wrapper React.memo rõ ràng khỏi các component của mình sau khi trình biên dịch được bật. Tuy nhiên, React.memo vẫn có chỗ đứng cho các class component (mặc dù hiện nay ít phổ biến hơn) hoặc để kiểm soát rất cụ thể, chi tiết logic so sánh thông qua đối số thứ hai của nó.

Q2: Còn useMemo và useCallback thì sao? Tôi có nên xóa tất cả chúng không?

A2: Mục tiêu là loại bỏ chúng. React Compiler được thiết kế để tự động ghi nhớ các giá trị và hàm, làm cho useMemo và useCallback phần lớn trở nên dư thừa. Bạn nên loại bỏ chúng một cách có hệ thống và dựa vào trình biên dịch. Nếu bạn quan sát thấy sự suy giảm hiệu suất sau khi loại bỏ, hãy lập hồ sơ component và xem xét liệu đó có phải là một trường hợp đặc biệt mà trình biên dịch chưa tối ưu, hay có một vấn đề cơ bản nào đó.

Q3: Trình biên dịch xử lý các đối tượng hoặc mảng có thể thay đổi như thế nào?

A3: Trình biên dịch giả định tính bất biến. Nếu bạn truyền một đối tượng hoặc mảng có thể thay đổi dưới dạng prop, và đối tượng/mảng đó bị thay đổi tại chỗ bởi một thành phần cha hoặc nguồn bên ngoài, trình biên dịch sẽ không phát hiện thay đổi tham chiếu và sẽ bỏ qua việc render lại component con. Điều này dẫn đến UI cũ. Giải pháp là luôn sử dụng các cập nhật bất biến (ví dụ: [...arr, newItem], {...obj, newProp: value}) hoặc sử dụng lối thoát 'use no memo' cho các component xử lý các thay đổi bên ngoài.

Q4: React Compiler có làm tăng kích thước bundle của tôi không?

A4: Tác động đến kích thước bundle thường không đáng kể. Trình biên dịch biến đổi mã của bạn tại thời điểm build, chèn các lệnh gọi đến các nguyên thủy ghi nhớ nội bộ. Các nguyên thủy này là một phần của runtime React và đã có sẵn. Mã được biến đổi có thể lớn hơn một chút so với mã gốc, nhưng chi phí là tối thiểu và thường được bù đắp bằng các lợi ích về hiệu suất.

Q5: React Compiler đã sẵn sàng cho sản xuất chưa?

A5: Kể từ React 19, React Compiler được coi là ổn định và sẵn sàng cho sản xuất. Nó đã trải qua quá trình thử nghiệm và tinh chỉnh rộng rãi bởi nhóm React và Meta. Mặc dù các trường hợp đặc biệt và lỗi nhỏ luôn có thể tồn tại trong bất kỳ hệ thống phức tạp nào, nhưng nó được thiết kế để áp dụng rộng rãi. Luôn kiểm tra kỹ lưỡng trong môi trường ứng dụng cụ thể của bạn.

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement