@user231243/remove-debugger 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/KE-HOACH-HOC-LAI-REACT.md +353 -0
- package/out/extension.js +53 -0
- package/out/extension.js.map +1 -0
- package/package.json +2 -1
- package/.vscodeignore +0 -9
- package/docs/superpowers/plans/2026-06-03-remove-debugger-extension.md +0 -319
- package/docs/superpowers/specs/2026-06-03-remove-debugger-extension-design.md +0 -83
- package/remove-debugger-0.0.1.vsix +0 -0
- package/src/extension.ts +0 -66
- package/tsconfig.json +0 -12
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
# Kế Hoạch Học Lại React Cho Dev Đã Lâu Không Đụng Tới
|
|
2
|
+
|
|
3
|
+
> **Mục tiêu**: Từ một frontend dev từng biết React nhưng đã bỏ lâu, quay lại và bắt kịp đúng chuẩn React hiện đại (React 19, năm 2026) trong khoảng 3–4 tuần.
|
|
4
|
+
>
|
|
5
|
+
> **Đối tượng**: dev có nền lập trình, không phải newbie. Vì vậy plan này đi nhanh, tập trung vào *cái đã đổi*, không dạy lại JS/HTML/CSS cơ bản.
|
|
6
|
+
>
|
|
7
|
+
> **Cách dùng file này**: đọc Mục 1 (tư duy) trước. Sau đó bám theo Mục 6 (lịch theo ngày). Mỗi khái niệm mới đều có code mẫu + bẫy thường gặp ngay tại chỗ.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Mục Lục
|
|
12
|
+
|
|
13
|
+
1. [Tư duy cần thay đổi trước tiên](#1-tư-duy-cần-thay-đổi-trước-tiên)
|
|
14
|
+
2. [Những thói quen cũ cần bỏ](#2-những-thói-quen-cũ-cần-bỏ)
|
|
15
|
+
3. [Lộ trình kiến thức theo tầng](#3-lộ-trình-kiến-thức-theo-tầng)
|
|
16
|
+
4. [Bối cảnh công cụ năm 2026](#4-bối-cảnh-công-cụ-năm-2026)
|
|
17
|
+
5. [Phương pháp "Learn by Source"](#5-phương-pháp-learn-by-source)
|
|
18
|
+
6. [Lịch học chi tiết theo ngày (4 tuần)](#6-lịch-học-chi-tiết-theo-ngày-4-tuần)
|
|
19
|
+
7. [Những bẫy thường gặp khi quay lại](#7-những-bẫy-thường-gặp-khi-quay-lại)
|
|
20
|
+
8. [Bài tự kiểm tra (đánh giá tiến độ)](#8-bài-tự-kiểm-tra)
|
|
21
|
+
9. [Nguồn học đáng tin](#9-nguồn-học-đáng-tin)
|
|
22
|
+
10. [Cách học nhanh nhất với trợ lý](#10-cách-học-nhanh-nhất-với-trợ-lý)
|
|
23
|
+
11. [Checklist tổng khi hoàn thành](#11-checklist-tổng-khi-hoàn-thành)
|
|
24
|
+
12. [Thuật ngữ (Glossary)](#12-thuật-ngữ-glossary)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 1. Tư Duy Cần Thay Đổi Trước Tiên
|
|
29
|
+
|
|
30
|
+
Đây là điều quan trọng nhất, đọc kỹ trước khi code dòng nào.
|
|
31
|
+
|
|
32
|
+
**React ngày xưa** trong đầu bạn: "Một thư viện chạy trong trình duyệt, lấy dữ liệu bằng `useEffect`, render ra UI."
|
|
33
|
+
|
|
34
|
+
**React bây giờ**: tư duy **server-first** (ưu tiên chạy ở server). Một component có thể chạy ngay trên server, lấy dữ liệu thẳng từ database, rồi stream HTML về cho người dùng — *không cần `useEffect`*. Phần chạy ở client chỉ là phần nào thật sự cần tương tác (click, gõ phím, kéo thả).
|
|
35
|
+
|
|
36
|
+
### Ba thay đổi tư duy cụ thể
|
|
37
|
+
|
|
38
|
+
**a) Mặc định là Server, client là ngoại lệ.**
|
|
39
|
+
Trong Next.js App Router, mọi component mặc định là Server Component. Chỉ thêm `'use client'` khi cần state, event, hoặc API trình duyệt. Ngược hẳn với ngày xưa (mọi thứ là client).
|
|
40
|
+
|
|
41
|
+
**b) `useEffect` KHÔNG phải để lấy dữ liệu.**
|
|
42
|
+
Ngày xưa: `useEffect(() => { fetch(...) }, [])`. Bây giờ đó là anti-pattern. Effect chỉ để **đồng bộ với hệ thống bên ngoài React** (websocket, event listener, đo DOM, tích hợp thư viện non-React). Lấy dữ liệu → Server Component hoặc TanStack Query.
|
|
43
|
+
|
|
44
|
+
**c) UI là hàm của state, nhưng state giờ chia 2 loại rõ ràng:**
|
|
45
|
+
- **Server state**: dữ liệu từ backend (danh sách sản phẩm, user...). Quản bằng TanStack Query / RSC. KHÔNG nhét vào Redux.
|
|
46
|
+
- **Client state**: trạng thái UI thuần (modal mở/đóng, tab đang chọn, form đang gõ). Quản bằng `useState` / Zustand.
|
|
47
|
+
|
|
48
|
+
Hầu hết mọi thứ mới mẻ bên dưới đều bắt nguồn từ ba cú chuyển dịch này.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 2. Những Thói Quen Cũ Cần Bỏ
|
|
53
|
+
|
|
54
|
+
| Thói quen cũ | Bây giờ làm gì | Lý do |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| `create-react-app` (CRA) | **Vite** (app thuần client) hoặc **Next.js / React Router 7** (full-stack) | CRA đã bị React team khai tử chính thức, không còn maintain |
|
|
57
|
+
| Lấy dữ liệu bằng `useEffect` | Server Component, `use()`, hoặc TanStack Query | Effect gây race condition, double-fetch, waterfall khó kiểm soát |
|
|
58
|
+
| Class component | Hooks (trừ Error Boundary vẫn là class) | Hooks gọn hơn, tái sử dụng logic dễ hơn |
|
|
59
|
+
| `forwardRef` | `ref` là một prop bình thường | React 19 bỏ bước lọc `ref` khỏi props |
|
|
60
|
+
| Rải `useMemo` / `useCallback` khắp nơi | React Compiler tự lo | Compiler memo hóa chính xác hơn tay người |
|
|
61
|
+
| Redux cho mọi thứ | Server state → TanStack Query / RSC; client state → Zustand / Context | Redux cũ quá nhiều boilerplate cho việc nó làm |
|
|
62
|
+
| `<Context.Provider>` | `<Context>` trực tiếp (React 19) | Cú pháp gọn hơn, `.Provider` bị deprecate |
|
|
63
|
+
| `propTypes` | TypeScript | Type tĩnh, bắt lỗi lúc compile |
|
|
64
|
+
| `react-helmet` cho `<title>`/`<meta>` | Đặt thẳng `<title>` trong JSX (React 19 tự hoist lên `<head>`) | Built-in, không cần thư viện |
|
|
65
|
+
| Higher-Order Component (HOC) chồng chất | Custom hooks | Dễ đọc, không "wrapper hell" |
|
|
66
|
+
| `defaultProps` cho function component | Default value của tham số JS | `defaultProps` bị bỏ cho function component |
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 3. Lộ Trình Kiến Thức Theo Tầng
|
|
71
|
+
|
|
72
|
+
Học theo thứ tự. Đừng nhảy cóc lên tầng 3 khi tầng 1 còn lủng.
|
|
73
|
+
|
|
74
|
+
### Tầng 1 — Nền tảng, học ngay
|
|
75
|
+
|
|
76
|
+
- **Hooks cho thành thạo**: `useState`, `useEffect` cho *đúng*, `useRef`, `useContext`, `useReducer`, custom hooks.
|
|
77
|
+
- Luật vàng `useEffect`: chỉ dùng để đồng bộ với hệ thống ngoài React. Nếu đang tính toán từ props/state → tính thẳng lúc render, đừng nhét vào effect.
|
|
78
|
+
- **TypeScript cho React**: type props, `useState<T>()`, generic component, `ReactNode` vs `ReactElement`, type cho event (`React.ChangeEvent<HTMLInputElement>`).
|
|
79
|
+
- **Vite**: hiểu `vite.config.ts`, env (`import.meta.env`), HMR, alias path.
|
|
80
|
+
|
|
81
|
+
### Tầng 2 — Chuẩn hiện đại
|
|
82
|
+
|
|
83
|
+
- **Server Components (RSC)**: phân biệt Server vs Client Component, ranh giới `'use client'`, cái gì truyền qua ranh giới được (data serialize được) và không (function, class).
|
|
84
|
+
- **Next.js App Router**: file routing (`app/`), `layout.tsx`, `page.tsx`, `loading.tsx`, `error.tsx`, route group, dynamic route, `generateMetadata`.
|
|
85
|
+
- **Data fetching**:
|
|
86
|
+
- Server: component `async` + `await fetch()` / gọi DB thẳng.
|
|
87
|
+
- Client: **TanStack Query** — `useQuery`, `useMutation`, cache, invalidation, optimistic update.
|
|
88
|
+
- **Suspense**: gói component đang chờ, hiện `fallback`. Thay cho mớ cờ `isLoading`.
|
|
89
|
+
|
|
90
|
+
### Tầng 3 — Mới nhất (React 19)
|
|
91
|
+
|
|
92
|
+
- `use()` — đọc promise / context giữa lúc render (kể cả trong `if`, loop).
|
|
93
|
+
- Actions + `useActionState` — xử lý form, tự có pending và lỗi.
|
|
94
|
+
- `useFormStatus` — component con đọc trạng thái form cha mà không cần prop.
|
|
95
|
+
- `useOptimistic` — hiện kết quả ngay cho UI mượt, tự revert nếu lỗi.
|
|
96
|
+
- `ref` là prop — bỏ `forwardRef`.
|
|
97
|
+
- Document metadata — `<title>`, `<meta>` đặt trong JSX, tự hoist.
|
|
98
|
+
- **React Compiler** — tự động memo hóa, bỏ `useMemo`/`useCallback` thủ công.
|
|
99
|
+
|
|
100
|
+
> Năm tính năng đầu đã có app demo + giải thích internals trong `react19-demo/GUIDE.md`.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 4. Bối Cảnh Công Cụ Năm 2026
|
|
105
|
+
|
|
106
|
+
| Hạng mục | Lựa chọn phổ biến | Ghi chú |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| Build tool | **Vite** | Mặc định cho SPA. Nhanh, cấu hình ít |
|
|
109
|
+
| Framework | **Next.js** (nhiều việc nhất) hoặc **React Router 7** (Remix cũ) | Next.js thống trị thị trường tuyển dụng |
|
|
110
|
+
| CSS | **Tailwind CSS** + **shadcn/ui** | Tailwind utility-first; shadcn là bộ component copy thẳng vào repo |
|
|
111
|
+
| Lấy dữ liệu | **TanStack Query** | Chuẩn de-facto cho client data |
|
|
112
|
+
| State client | **Zustand** (đơn giản), **Redux Toolkit** (app lớn) | Dùng RTK chứ không Redux cũ; nhiều app không cần cả hai |
|
|
113
|
+
| Form | **react-hook-form** + **zod** | RHF quản form ít re-render; zod validate + suy ra type |
|
|
114
|
+
| Test | **Vitest** + React Testing Library + **Playwright** | Vitest thay Jest; Playwright cho E2E |
|
|
115
|
+
| Routing (SPA) | **React Router 7** hoặc **TanStack Router** | Nếu không dùng Next.js |
|
|
116
|
+
| Component dev | **Storybook** | Phát triển/ test component cô lập |
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 5. Phương Pháp "Learn by Source"
|
|
121
|
+
|
|
122
|
+
Tên thư mục project là `learning-by-source` — đây là triết lý học của bạn. Nhưng làm cho đúng:
|
|
123
|
+
|
|
124
|
+
**Đọc source React KHÔNG phải món chính để học *dùng* React. Nó là gia vị.** Source là code xây *động cơ* (reconciler, fiber, scheduler), còn bạn đang học *lái xe*.
|
|
125
|
+
|
|
126
|
+
### Quy trình đúng
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
1. DÙNG trước → đọc react.dev, viết code, làm app chạy được
|
|
130
|
+
2. TÒ MÒ → "ủa sao useOptimistic tự revert được hay vậy?"
|
|
131
|
+
3. MỞ ĐÚNG ĐOẠN → tìm chính hàm đó trong source, đọc, hiểu "tại sao"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
KHÔNG đọc source từ đầu tới cuối. Chỉ mở đúng đoạn trả lời tò mò vừa nảy ra.
|
|
135
|
+
|
|
136
|
+
### Khi nào đọc source đáng giá
|
|
137
|
+
|
|
138
|
+
- Đã dùng quen, muốn hiểu *tại sao* (như đã làm với `use()` ném promise).
|
|
139
|
+
- Debug bug sâu, lạ.
|
|
140
|
+
- Đọc/viết thư viện, RFC.
|
|
141
|
+
- Phỏng vấn senior.
|
|
142
|
+
- Thỏa mãn tò mò kỹ thuật.
|
|
143
|
+
|
|
144
|
+
### Map source React (để mở đúng file)
|
|
145
|
+
|
|
146
|
+
| Muốn hiểu | Mở file |
|
|
147
|
+
|---|---|
|
|
148
|
+
| Hooks (`useState`, `useOptimistic`, `useActionState`...) | `packages/react-reconciler/src/ReactFiberHooks.js` |
|
|
149
|
+
| `use()` + Suspense ném promise | `packages/react-reconciler/src/ReactFiberThenable.js` |
|
|
150
|
+
| Cách tạo element, vì sao `ref` là prop | `packages/react/src/jsx/ReactJSXElement.js` |
|
|
151
|
+
| Scheduler, ưu tiên update | `packages/scheduler/src/forks/Scheduler.js` |
|
|
152
|
+
| `useFormStatus` | `packages/react-dom/src/...` (tìm `FormStatus`) |
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 6. Lịch Học Chi Tiết Theo Ngày (4 Tuần)
|
|
157
|
+
|
|
158
|
+
Giả định ~2 giờ/ngày. Điều chỉnh theo thời gian thực tế.
|
|
159
|
+
|
|
160
|
+
### Tuần 1 — Làm mới nền tảng (Vite + Hooks + TS + TanStack Query)
|
|
161
|
+
|
|
162
|
+
| Ngày | Việc | Sản phẩm |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| 1 | Dựng Vite + React + TS. Đọc react.dev mục "Describing the UI". Viết vài component có props + type. | App trống chạy được |
|
|
165
|
+
| 2 | State + event. Đọc "Adding Interactivity". Làm counter, toggle, form controlled. | Component tương tác |
|
|
166
|
+
| 3 | Đọc **"You Might Not Need an Effect"** (cực quan trọng). Liệt kê các chỗ ngày xưa bạn lạm dụng effect. | Ghi chú luật effect |
|
|
167
|
+
| 4 | `useEffect` đúng cách + cleanup. `useRef`, `useContext`. | Component có effect đồng bộ thật |
|
|
168
|
+
| 5 | Cài TanStack Query. `useQuery` lấy danh sách từ API công khai. | Màn hình danh sách |
|
|
169
|
+
| 6 | `useMutation` + invalidation + optimistic. Màn hình chi tiết. | CRUD client hoàn chỉnh |
|
|
170
|
+
| 7 | Ôn + refactor. Tách custom hooks. | **App phim/to-do hoàn chỉnh** |
|
|
171
|
+
|
|
172
|
+
**Dự án tuần 1**: app danh sách phim (dùng API TMDB hoặc OMDb) hoặc to-do. Có loading, error, search. Toàn client.
|
|
173
|
+
|
|
174
|
+
### Tuần 2 — Full-stack với Next.js App Router
|
|
175
|
+
|
|
176
|
+
| Ngày | Việc | Sản phẩm |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| 8 | Dựng Next.js App Router. Hiểu `app/`, `layout`, `page`. | App Next chạy |
|
|
179
|
+
| 9 | Server Component lấy dữ liệu (`async` component + fetch). | Trang list render từ server |
|
|
180
|
+
| 10 | Ranh giới `'use client'`. Khi nào cần. Truyền data qua ranh giới. | Trang có phần interactive |
|
|
181
|
+
| 11 | `loading.tsx` + Suspense + streaming. `error.tsx`. | Loading/error UI |
|
|
182
|
+
| 12 | Server Actions (`'use server'`) — form submit chạy trên server. | Form tạo dữ liệu |
|
|
183
|
+
| 13 | Dynamic route `[id]`, `generateMetadata` cho SEO. | Trang chi tiết + meta |
|
|
184
|
+
| 14 | Ôn + deploy lên Vercel. | **Blog/note app full-stack online** |
|
|
185
|
+
|
|
186
|
+
**Dự án tuần 2**: blog hoặc app ghi chú. CRUD chạy qua Server Actions, dữ liệu lưu thật (SQLite/Postgres hoặc một file JSON cho đơn giản).
|
|
187
|
+
|
|
188
|
+
### Tuần 3 — React 19 + hoàn thiện chất lượng
|
|
189
|
+
|
|
190
|
+
| Ngày | Việc | Sản phẩm |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| 15 | `useActionState` + `useFormStatus` cho form. | Form có pending tự động |
|
|
193
|
+
| 16 | `useOptimistic` — optimistic UI. | Like/comment hiện ngay |
|
|
194
|
+
| 17 | `use()` đọc promise/context. `ref` là prop. | Bỏ hết `forwardRef` cũ |
|
|
195
|
+
| 18 | Tailwind sâu + shadcn/ui. Cài, dùng vài component. | Giao diện đẹp |
|
|
196
|
+
| 19 | react-hook-form + zod — form validate đầy đủ. | Form đăng ký có validate |
|
|
197
|
+
| 20 | Bật React Compiler. So sánh trước/sau (gỡ bớt useMemo). | App đã memo hóa tự động |
|
|
198
|
+
| 21 | Ôn toàn bộ. Gộp mọi thứ vào 1 app. | **App hoàn chỉnh 2026** |
|
|
199
|
+
|
|
200
|
+
### Tuần 4 (tùy chọn) — Đào sâu + chuyên nghiệp hóa
|
|
201
|
+
|
|
202
|
+
| Ngày | Việc |
|
|
203
|
+
|---|---|
|
|
204
|
+
| 22–23 | Testing: Vitest + RTL cho unit, Playwright cho E2E |
|
|
205
|
+
| 24 | Đọc source React đúng đoạn (theo Mục 5) cho 2–3 tính năng đã dùng |
|
|
206
|
+
| 25 | Performance: React DevTools Profiler, code splitting, lazy |
|
|
207
|
+
| 26 | Accessibility (a11y) cơ bản + semantic HTML |
|
|
208
|
+
| 27–28 | Dự án tổng kết tự chọn / chuẩn bị portfolio |
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 7. Những Bẫy Thường Gặp Khi Quay Lại
|
|
213
|
+
|
|
214
|
+
**1. Lấy data trong `useEffect` theo quán tính.**
|
|
215
|
+
```tsx
|
|
216
|
+
// ❌ Quán tính cũ
|
|
217
|
+
useEffect(() => { fetch('/api/x').then(setData) }, []);
|
|
218
|
+
// ✅ Client: TanStack Query
|
|
219
|
+
const { data } = useQuery({ queryKey: ['x'], queryFn: () => fetch('/api/x').then(r => r.json()) });
|
|
220
|
+
// ✅ Server Component: lấy thẳng
|
|
221
|
+
async function Page() { const data = await getX(); return <List data={data} />; }
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**2. Quên `'use client'` khi dùng hook/event trong Next.js.**
|
|
225
|
+
Lỗi kiểu "useState is not a function" hoặc "Event handlers cannot be passed to Client Component props" → thiếu `'use client'` ở đầu file.
|
|
226
|
+
|
|
227
|
+
**3. Truyền function/class qua ranh giới Server→Client.**
|
|
228
|
+
Chỉ data serialize được (string, number, object thường, array) mới qua được. Truyền function từ Server Component xuống Client Component prop → lỗi.
|
|
229
|
+
|
|
230
|
+
**4. Tạo promise trong render khi dùng `use()`.**
|
|
231
|
+
```tsx
|
|
232
|
+
// ❌ Promise mới mỗi render -> loop
|
|
233
|
+
function C() { const x = use(fetch('/api')); }
|
|
234
|
+
// ✅ Tạo ngoài / dùng cache
|
|
235
|
+
const p = fetchData();
|
|
236
|
+
function C() { const x = use(p); }
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**5. Gọi `addOptimistic` ngoài action.**
|
|
240
|
+
`useOptimistic` chỉ có nghĩa trong vùng transition (bên trong action). Gọi ngoài → React cảnh báo, không revert đúng.
|
|
241
|
+
|
|
242
|
+
**6. Vẫn rải `useMemo`/`useCallback` khắp nơi.**
|
|
243
|
+
Khi đã bật React Compiler, phần lớn là thừa. Đừng tối ưu sớm bằng tay.
|
|
244
|
+
|
|
245
|
+
**7. Nhét server state vào Redux/Zustand.**
|
|
246
|
+
Danh sách từ API không nên ở trong store global. Để TanStack Query lo cache + đồng bộ. Store chỉ giữ client state thuần.
|
|
247
|
+
|
|
248
|
+
**8. Dùng `index` làm `key` trong list động.**
|
|
249
|
+
Vẫn là bẫy kinh điển. Dùng id ổn định.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## 8. Bài Tự Kiểm Tra
|
|
254
|
+
|
|
255
|
+
Trả lời được hết = sẵn sàng. Không cần học thuộc, hiểu là đủ.
|
|
256
|
+
|
|
257
|
+
**Cơ bản**
|
|
258
|
+
1. Khi nào *được phép* dùng `useEffect`? Cho 2 ví dụ đúng.
|
|
259
|
+
2. Khác nhau giữa server state và client state? Mỗi loại quản bằng gì?
|
|
260
|
+
3. `useMemo` để làm gì? Sau khi có React Compiler còn cần không?
|
|
261
|
+
|
|
262
|
+
**Trung cấp**
|
|
263
|
+
4. Server Component khác Client Component chỗ nào? Khi nào bắt buộc `'use client'`?
|
|
264
|
+
5. Cái gì truyền qua ranh giới Server→Client được, cái gì không?
|
|
265
|
+
6. `Suspense` hoạt động dựa trên cơ chế gì? (gợi ý: throw)
|
|
266
|
+
7. TanStack Query: `staleTime` vs `gcTime` khác gì?
|
|
267
|
+
|
|
268
|
+
**React 19**
|
|
269
|
+
8. `useActionState` trả về gì? `isPending` từ đâu ra?
|
|
270
|
+
9. Vì sao `useOptimistic` tự revert được khi lỗi mà không cần code rollback?
|
|
271
|
+
10. Vì sao React 19 bỏ được `forwardRef`?
|
|
272
|
+
|
|
273
|
+
> Bí chỗ nào → mở `react19-demo/GUIDE.md` hoặc hỏi trợ lý mở source giải thích.
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## 9. Nguồn Học Đáng Tin (chỉ dùng những nguồn này)
|
|
278
|
+
|
|
279
|
+
| Nguồn | Dùng để | Đọc cụ thể |
|
|
280
|
+
|---|---|---|
|
|
281
|
+
| **react.dev** | Học dùng React đúng chuẩn | "Learn" toàn bộ; đặc biệt "You Might Not Need an Effect", "Reusing Logic with Custom Hooks" |
|
|
282
|
+
| **Next.js docs** | App Router, RSC, Server Actions | Phần "App Router" → "Data Fetching" và "Server Actions" |
|
|
283
|
+
| **TanStack Query docs** | Quản server state | "Important Defaults", "Mutations", "Optimistic Updates" |
|
|
284
|
+
| **TkDodo blog** | TanStack Query nâng cao | Series "Practical React Query" |
|
|
285
|
+
| **Josh Comeau** | Mô hình tư duy React sâu | "Why React Re-Renders", "Understanding useMemo and useCallback" |
|
|
286
|
+
| **Source React (GitHub)** | Hiểu *tại sao* (theo Mục 5) | Chỉ mở đúng file cần, không đọc tràn |
|
|
287
|
+
|
|
288
|
+
> Tránh blog/video cũ trước 2023 — nhiều pattern đã lỗi thời (CRA, lấy data bằng effect, class component).
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## 10. Cách Học Nhanh Nhất Với Trợ Lý
|
|
293
|
+
|
|
294
|
+
Mình (Claude) hỗ trợ theo các cách, chọn cách hợp:
|
|
295
|
+
|
|
296
|
+
1. **Kiểm tra kiến thức** — hỏi nhanh để tìm lỗ hổng (dùng Mục 8).
|
|
297
|
+
2. **Cùng dựng app demo** — từng bước, giải thích mỗi khái niệm ngay lúc làm.
|
|
298
|
+
3. **Xem internals** — khi muốn hiểu "tại sao nó chạy vậy", mở source đúng đoạn.
|
|
299
|
+
4. **Dịch code cũ → mới** — bạn dán component cũ, mình viết lại chuẩn 2026 + giải thích từng thay đổi. **(Nhanh nhất, khuyên dùng đầu tiên.)**
|
|
300
|
+
|
|
301
|
+
Đã làm cùng nhau:
|
|
302
|
+
- `react19-demo/` — app Mini Chat minh họa 5 tính năng React 19.
|
|
303
|
+
- `react19-demo/GUIDE.md` — giải thích từng tính năng + internals.
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## 11. Checklist Tổng Khi Hoàn Thành
|
|
308
|
+
|
|
309
|
+
**Tư duy & nền tảng**
|
|
310
|
+
- [ ] Hiểu rõ tư duy server-first.
|
|
311
|
+
- [ ] Phân biệt server state vs client state, biết dùng công cụ nào.
|
|
312
|
+
- [ ] Viết component bằng TypeScript thành thạo (props, generic, event type).
|
|
313
|
+
- [ ] Dùng `useEffect` đúng chỗ — và biết khi nào KHÔNG dùng.
|
|
314
|
+
|
|
315
|
+
**Chuẩn hiện đại**
|
|
316
|
+
- [ ] Lấy dữ liệu bằng TanStack Query (client) và Server Component (server).
|
|
317
|
+
- [ ] Dựng app Next.js App Router có Server Actions.
|
|
318
|
+
- [ ] Dùng Suspense + streaming cho loading.
|
|
319
|
+
- [ ] Hiểu ranh giới `'use client'` và cái gì truyền qua được.
|
|
320
|
+
|
|
321
|
+
**React 19**
|
|
322
|
+
- [ ] Dùng `use()`, `useActionState`, `useFormStatus`, `useOptimistic`.
|
|
323
|
+
- [ ] `ref` là prop (bỏ `forwardRef`).
|
|
324
|
+
- [ ] Bật React Compiler, gỡ memo thủ công thừa.
|
|
325
|
+
|
|
326
|
+
**Chất lượng**
|
|
327
|
+
- [ ] Style bằng Tailwind + shadcn/ui.
|
|
328
|
+
- [ ] Form bằng react-hook-form + zod.
|
|
329
|
+
- [ ] Viết test cơ bản (Vitest + RTL, một E2E Playwright).
|
|
330
|
+
- [ ] Deploy được lên Vercel.
|
|
331
|
+
|
|
332
|
+
> Xong checklist này = bạn đã bắt kịp React hiện đại.
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## 12. Thuật Ngữ (Glossary)
|
|
337
|
+
|
|
338
|
+
| Thuật ngữ | Nghĩa ngắn |
|
|
339
|
+
|---|---|
|
|
340
|
+
| **RSC** | React Server Components — component chạy trên server, không gửi JS xuống client |
|
|
341
|
+
| **SSR** | Server-Side Rendering — render HTML trên server rồi hydrate ở client |
|
|
342
|
+
| **Hydration** | Gắn JS tương tác vào HTML đã render sẵn từ server |
|
|
343
|
+
| **Streaming** | Gửi HTML từng phần khi sẵn sàng, không chờ toàn bộ |
|
|
344
|
+
| **Suspense** | Cơ chế hiện `fallback` khi một phần cây đang chờ dữ liệu |
|
|
345
|
+
| **Transition** | Update ưu tiên thấp, không chặn UI (nền tảng của `useOptimistic`, `useActionState`) |
|
|
346
|
+
| **Server Action** | Hàm `'use server'` chạy trên server, gọi được thẳng từ form/client |
|
|
347
|
+
| **Fiber** | Đơn vị công việc nội bộ của React cho mỗi component instance |
|
|
348
|
+
| **Reconciler** | Phần lõi React so sánh cây cũ/mới để quyết định cập nhật gì |
|
|
349
|
+
| **Hydration mismatch** | Lỗi khi HTML server và render client khác nhau |
|
|
350
|
+
| **Server state** | Dữ liệu thuộc về backend (cần đồng bộ, cache) |
|
|
351
|
+
| **Client state** | Trạng thái UI thuần (modal, tab, input đang gõ) |
|
|
352
|
+
| **Optimistic update** | Hiện kết quả trước khi server xác nhận, revert nếu lỗi |
|
|
353
|
+
| **Memoization** | Nhớ kết quả tính toán để khỏi tính lại (giờ React Compiler tự lo) |
|
package/out/extension.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.activate = activate;
|
|
4
|
+
exports.cleanText = cleanText;
|
|
5
|
+
exports.deactivate = deactivate;
|
|
6
|
+
const vscode = require("vscode");
|
|
7
|
+
function activate(context) {
|
|
8
|
+
const disposable = vscode.commands.registerCommand('removeDebugger.clean', () => {
|
|
9
|
+
const editor = vscode.window.activeTextEditor;
|
|
10
|
+
if (!editor) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const config = vscode.workspace.getConfiguration('removeDebugger');
|
|
14
|
+
const shouldRemoveDebugger = config.get('removeDebugger', true);
|
|
15
|
+
const shouldRemoveConsole = config.get('removeConsole', true);
|
|
16
|
+
const consoleMethods = config.get('consoleMethodsToRemove', [
|
|
17
|
+
'log', 'warn', 'error', 'info', 'debug', 'trace',
|
|
18
|
+
]);
|
|
19
|
+
const text = editor.document.getText();
|
|
20
|
+
const cleaned = cleanText(text, shouldRemoveDebugger, shouldRemoveConsole, consoleMethods);
|
|
21
|
+
if (cleaned === text) {
|
|
22
|
+
vscode.window.showInformationMessage('No debugger statements or console calls found.');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const edit = new vscode.WorkspaceEdit();
|
|
26
|
+
const fullRange = new vscode.Range(editor.document.positionAt(0), editor.document.positionAt(text.length));
|
|
27
|
+
edit.replace(editor.document.uri, fullRange, cleaned);
|
|
28
|
+
vscode.workspace.applyEdit(edit);
|
|
29
|
+
});
|
|
30
|
+
context.subscriptions.push(disposable);
|
|
31
|
+
}
|
|
32
|
+
function cleanText(text, removeDebugger, removeConsole, consoleMethods) {
|
|
33
|
+
const lines = text.split('\n');
|
|
34
|
+
const filtered = lines.filter(line => {
|
|
35
|
+
if (removeDebugger && /^\s*debugger\s*;?\s*$/.test(line)) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (removeConsole && consoleMethods.length > 0) {
|
|
39
|
+
const methodPattern = consoleMethods.map(m => escapeRegex(m)).join('|');
|
|
40
|
+
const consoleRegex = new RegExp(`^\\s*console\\.(${methodPattern})\\s*\\(.*\\)\\s*;?\\s*$`);
|
|
41
|
+
if (consoleRegex.test(line)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
});
|
|
47
|
+
return filtered.join('\n');
|
|
48
|
+
}
|
|
49
|
+
function escapeRegex(s) {
|
|
50
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
51
|
+
}
|
|
52
|
+
function deactivate() { }
|
|
53
|
+
//# sourceMappingURL=extension.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
},
|
|
7
7
|
"displayName": "Remove Debugger",
|
|
8
8
|
"description": "Remove debugger statements and console calls from JS/TS files",
|
|
9
|
-
"version": "0.0.
|
|
9
|
+
"version": "0.0.5",
|
|
10
10
|
"engines": { "vscode": "^1.75.0" },
|
|
11
11
|
"categories": ["Other"],
|
|
12
12
|
"activationEvents": [
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"onLanguage:typescriptreact"
|
|
17
17
|
],
|
|
18
18
|
"main": "./out/extension.js",
|
|
19
|
+
"files": ["out/", "package.json", "KE-HOACH-HOC-LAI-REACT.md"],
|
|
19
20
|
"contributes": {
|
|
20
21
|
"commands": [
|
|
21
22
|
{
|
package/.vscodeignore
DELETED
|
@@ -1,319 +0,0 @@
|
|
|
1
|
-
# Remove Debugger VS Code Extension Implementation Plan
|
|
2
|
-
|
|
3
|
-
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
-
|
|
5
|
-
**Goal:** Build a VS Code extension that adds an editor title bar button to remove `debugger;` statements and `console.*()` calls from the active JS/TS file.
|
|
6
|
-
|
|
7
|
-
**Architecture:** Single `extension.ts` file registers a command bound to an editor title bar button. When clicked, it reads the active document, applies regex patterns (controlled by user settings) to strip matching lines, then replaces the document via `WorkspaceEdit` to preserve undo history.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** TypeScript, VS Code Extension API (`vscode`), Node.js built-ins only — no external runtime dependencies.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## File Map
|
|
14
|
-
|
|
15
|
-
| File | Action | Responsibility |
|
|
16
|
-
|---|---|---|
|
|
17
|
-
| `package.json` | Create | Extension manifest: name, commands, menus, settings, activationEvents |
|
|
18
|
-
| `tsconfig.json` | Create | TypeScript compiler config |
|
|
19
|
-
| `.vscodeignore` | Create | Exclude dev files from packaged extension |
|
|
20
|
-
| `src/extension.ts` | Create | `activate()`, command handler, removal logic |
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
### Task 1: Scaffold package.json
|
|
25
|
-
|
|
26
|
-
**Files:**
|
|
27
|
-
- Create: `package.json`
|
|
28
|
-
|
|
29
|
-
- [ ] **Step 1: Create package.json**
|
|
30
|
-
|
|
31
|
-
```json
|
|
32
|
-
{
|
|
33
|
-
"name": "remove-debugger",
|
|
34
|
-
"displayName": "Remove Debugger",
|
|
35
|
-
"description": "Remove debugger statements and console calls from JS/TS files",
|
|
36
|
-
"version": "0.0.1",
|
|
37
|
-
"engines": { "vscode": "^1.75.0" },
|
|
38
|
-
"categories": ["Other"],
|
|
39
|
-
"activationEvents": [
|
|
40
|
-
"onLanguage:javascript",
|
|
41
|
-
"onLanguage:typescript",
|
|
42
|
-
"onLanguage:javascriptreact",
|
|
43
|
-
"onLanguage:typescriptreact"
|
|
44
|
-
],
|
|
45
|
-
"main": "./out/extension.js",
|
|
46
|
-
"contributes": {
|
|
47
|
-
"commands": [
|
|
48
|
-
{
|
|
49
|
-
"command": "removeDebugger.clean",
|
|
50
|
-
"title": "Remove Debugger & Console",
|
|
51
|
-
"icon": "$(trash)"
|
|
52
|
-
}
|
|
53
|
-
],
|
|
54
|
-
"menus": {
|
|
55
|
-
"editor/title": [
|
|
56
|
-
{
|
|
57
|
-
"command": "removeDebugger.clean",
|
|
58
|
-
"when": "editorLangId == javascript || editorLangId == typescript || editorLangId == javascriptreact || editorLangId == typescriptreact",
|
|
59
|
-
"group": "navigation"
|
|
60
|
-
}
|
|
61
|
-
]
|
|
62
|
-
},
|
|
63
|
-
"configuration": {
|
|
64
|
-
"title": "Remove Debugger",
|
|
65
|
-
"properties": {
|
|
66
|
-
"removeDebugger.removeDebugger": {
|
|
67
|
-
"type": "boolean",
|
|
68
|
-
"default": true,
|
|
69
|
-
"description": "Remove debugger; statements."
|
|
70
|
-
},
|
|
71
|
-
"removeDebugger.removeConsole": {
|
|
72
|
-
"type": "boolean",
|
|
73
|
-
"default": true,
|
|
74
|
-
"description": "Remove console.* calls."
|
|
75
|
-
},
|
|
76
|
-
"removeDebugger.consoleMethodsToRemove": {
|
|
77
|
-
"type": "array",
|
|
78
|
-
"default": ["log", "warn", "error", "info", "debug", "trace"],
|
|
79
|
-
"description": "Which console methods to remove when removeConsole is enabled."
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
"scripts": {
|
|
85
|
-
"vscode:prepublish": "npm run compile",
|
|
86
|
-
"compile": "tsc -p ./",
|
|
87
|
-
"watch": "tsc -watch -p ./"
|
|
88
|
-
},
|
|
89
|
-
"devDependencies": {
|
|
90
|
-
"@types/vscode": "^1.75.0",
|
|
91
|
-
"@types/node": "^18.0.0",
|
|
92
|
-
"typescript": "^5.0.0"
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
- [ ] **Step 2: Commit**
|
|
98
|
-
|
|
99
|
-
```bash
|
|
100
|
-
git add package.json
|
|
101
|
-
git commit -m "feat: add extension manifest with command, menu, and settings"
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
### Task 2: Scaffold TypeScript config and ignore file
|
|
107
|
-
|
|
108
|
-
**Files:**
|
|
109
|
-
- Create: `tsconfig.json`
|
|
110
|
-
- Create: `.vscodeignore`
|
|
111
|
-
|
|
112
|
-
- [ ] **Step 1: Create tsconfig.json**
|
|
113
|
-
|
|
114
|
-
```json
|
|
115
|
-
{
|
|
116
|
-
"compilerOptions": {
|
|
117
|
-
"module": "commonjs",
|
|
118
|
-
"target": "ES2020",
|
|
119
|
-
"outDir": "out",
|
|
120
|
-
"lib": ["ES2020"],
|
|
121
|
-
"sourceMap": true,
|
|
122
|
-
"rootDir": "src",
|
|
123
|
-
"strict": true
|
|
124
|
-
},
|
|
125
|
-
"exclude": ["node_modules", ".vscode-test"]
|
|
126
|
-
}
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
- [ ] **Step 2: Create .vscodeignore**
|
|
130
|
-
|
|
131
|
-
```
|
|
132
|
-
.vscode/**
|
|
133
|
-
src/**
|
|
134
|
-
.gitignore
|
|
135
|
-
tsconfig.json
|
|
136
|
-
**/*.map
|
|
137
|
-
**/*.ts
|
|
138
|
-
!out/**
|
|
139
|
-
node_modules/**
|
|
140
|
-
docs/**
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
- [ ] **Step 3: Commit**
|
|
144
|
-
|
|
145
|
-
```bash
|
|
146
|
-
git add tsconfig.json .vscodeignore
|
|
147
|
-
git commit -m "feat: add tsconfig and vscodeignore"
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
---
|
|
151
|
-
|
|
152
|
-
### Task 3: Install dev dependencies
|
|
153
|
-
|
|
154
|
-
**Files:**
|
|
155
|
-
- Modify: (node_modules, package-lock.json — generated)
|
|
156
|
-
|
|
157
|
-
- [ ] **Step 1: Install**
|
|
158
|
-
|
|
159
|
-
```bash
|
|
160
|
-
npm install
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
Expected: `node_modules/` created, `package-lock.json` generated. No errors.
|
|
164
|
-
|
|
165
|
-
- [ ] **Step 2: Add .gitignore**
|
|
166
|
-
|
|
167
|
-
```
|
|
168
|
-
node_modules/
|
|
169
|
-
out/
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
- [ ] **Step 3: Commit**
|
|
173
|
-
|
|
174
|
-
```bash
|
|
175
|
-
git add .gitignore package-lock.json
|
|
176
|
-
git commit -m "chore: add gitignore and lock file"
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
---
|
|
180
|
-
|
|
181
|
-
### Task 4: Implement extension.ts
|
|
182
|
-
|
|
183
|
-
**Files:**
|
|
184
|
-
- Create: `src/extension.ts`
|
|
185
|
-
|
|
186
|
-
- [ ] **Step 1: Create src/extension.ts**
|
|
187
|
-
|
|
188
|
-
```typescript
|
|
189
|
-
import * as vscode from 'vscode';
|
|
190
|
-
|
|
191
|
-
export function activate(context: vscode.ExtensionContext): void {
|
|
192
|
-
const disposable = vscode.commands.registerCommand('removeDebugger.clean', () => {
|
|
193
|
-
const editor = vscode.window.activeTextEditor;
|
|
194
|
-
if (!editor) {
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const config = vscode.workspace.getConfiguration('removeDebugger');
|
|
199
|
-
const shouldRemoveDebugger = config.get<boolean>('removeDebugger', true);
|
|
200
|
-
const shouldRemoveConsole = config.get<boolean>('removeConsole', true);
|
|
201
|
-
const consoleMethods = config.get<string[]>('consoleMethodsToRemove', [
|
|
202
|
-
'log', 'warn', 'error', 'info', 'debug', 'trace',
|
|
203
|
-
]);
|
|
204
|
-
|
|
205
|
-
const text = editor.document.getText();
|
|
206
|
-
const cleaned = cleanText(text, shouldRemoveDebugger, shouldRemoveConsole, consoleMethods);
|
|
207
|
-
|
|
208
|
-
if (cleaned === text) {
|
|
209
|
-
vscode.window.showInformationMessage('No debugger statements or console calls found.');
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const edit = new vscode.WorkspaceEdit();
|
|
214
|
-
const fullRange = new vscode.Range(
|
|
215
|
-
editor.document.positionAt(0),
|
|
216
|
-
editor.document.positionAt(text.length)
|
|
217
|
-
);
|
|
218
|
-
edit.replace(editor.document.uri, fullRange, cleaned);
|
|
219
|
-
vscode.workspace.applyEdit(edit);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
context.subscriptions.push(disposable);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
export function cleanText(
|
|
226
|
-
text: string,
|
|
227
|
-
removeDebugger: boolean,
|
|
228
|
-
removeConsole: boolean,
|
|
229
|
-
consoleMethods: string[]
|
|
230
|
-
): string {
|
|
231
|
-
const lines = text.split('\n');
|
|
232
|
-
const filtered = lines.filter(line => {
|
|
233
|
-
if (removeDebugger && /^\s*debugger\s*;?\s*$/.test(line)) {
|
|
234
|
-
return false;
|
|
235
|
-
}
|
|
236
|
-
if (removeConsole && consoleMethods.length > 0) {
|
|
237
|
-
const methodPattern = consoleMethods.map(m => escapeRegex(m)).join('|');
|
|
238
|
-
const consoleRegex = new RegExp(
|
|
239
|
-
`^\\s*console\\.(${methodPattern})\\s*\\(.*\\)\\s*;?\\s*$`
|
|
240
|
-
);
|
|
241
|
-
if (consoleRegex.test(line)) {
|
|
242
|
-
return false;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
return true;
|
|
246
|
-
});
|
|
247
|
-
return filtered.join('\n');
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function escapeRegex(s: string): string {
|
|
251
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
export function deactivate(): void {}
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
- [ ] **Step 2: Compile**
|
|
258
|
-
|
|
259
|
-
```bash
|
|
260
|
-
npm run compile
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
Expected: `out/extension.js` and `out/extension.js.map` created. No TypeScript errors.
|
|
264
|
-
|
|
265
|
-
- [ ] **Step 3: Commit**
|
|
266
|
-
|
|
267
|
-
```bash
|
|
268
|
-
git add src/extension.ts
|
|
269
|
-
git commit -m "feat: implement removal logic and command handler"
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
---
|
|
273
|
-
|
|
274
|
-
### Task 5: Manual verification
|
|
275
|
-
|
|
276
|
-
- [ ] **Step 1: Open VS Code in the project folder**
|
|
277
|
-
|
|
278
|
-
```bash
|
|
279
|
-
code .
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
- [ ] **Step 2: Press F5** to launch the Extension Development Host (a new VS Code window with the extension loaded).
|
|
283
|
-
|
|
284
|
-
- [ ] **Step 3: Create a test file** in the dev host window. Save it as `test.js` with:
|
|
285
|
-
|
|
286
|
-
```javascript
|
|
287
|
-
function example() {
|
|
288
|
-
console.log('hello');
|
|
289
|
-
debugger;
|
|
290
|
-
console.warn('oops');
|
|
291
|
-
const x = 1 + 1;
|
|
292
|
-
console.error('bad');
|
|
293
|
-
return x;
|
|
294
|
-
}
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
- [ ] **Step 4: Verify the trash-can button** appears in the editor title bar (top-right) while `test.js` is active.
|
|
298
|
-
|
|
299
|
-
- [ ] **Step 5: Click the button.** Expected result:
|
|
300
|
-
|
|
301
|
-
```javascript
|
|
302
|
-
function example() {
|
|
303
|
-
const x = 1 + 1;
|
|
304
|
-
return x;
|
|
305
|
-
}
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
- [ ] **Step 6: Press Ctrl+Z.** Expected: original file content restored.
|
|
309
|
-
|
|
310
|
-
- [ ] **Step 7: Open a `.json` file.** Verify the trash-can button does NOT appear.
|
|
311
|
-
|
|
312
|
-
- [ ] **Step 8: Test settings.** Open VS Code settings (Ctrl+,), search "Remove Debugger". Set `removeConsole` to `false`. Open `test.js` again, click the button. Expected: only `debugger;` removed, `console.*` lines kept.
|
|
313
|
-
|
|
314
|
-
- [ ] **Step 9: Commit**
|
|
315
|
-
|
|
316
|
-
```bash
|
|
317
|
-
git add -A
|
|
318
|
-
git commit -m "chore: verified extension works end-to-end"
|
|
319
|
-
```
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
# Remove Debugger VS Code Extension — Design Spec
|
|
2
|
-
|
|
3
|
-
**Date:** 2026-06-03
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
A lightweight VS Code extension that removes `debugger;` statements and `console.*()` calls from the active JavaScript or TypeScript file via a single click on an editor title bar button.
|
|
8
|
-
|
|
9
|
-
## Target Languages
|
|
10
|
-
|
|
11
|
-
JavaScript, TypeScript, JSX, TSX (language IDs: `javascript`, `typescript`, `javascriptreact`, `typescriptreact`).
|
|
12
|
-
|
|
13
|
-
## Activation
|
|
14
|
-
|
|
15
|
-
The extension activates when a JS/TS file becomes the active editor. A button appears in the editor title bar (top-right) only for supported language IDs, registered via the `editor/title` contribution point in `package.json`.
|
|
16
|
-
|
|
17
|
-
## Removal Logic
|
|
18
|
-
|
|
19
|
-
Approach: regex-based line replacement. When the button is clicked, the extension:
|
|
20
|
-
|
|
21
|
-
1. Reads the full text of the active document.
|
|
22
|
-
2. Applies enabled regex patterns in sequence to remove matching lines.
|
|
23
|
-
3. Replaces the document content via a `WorkspaceEdit` — this preserves undo history.
|
|
24
|
-
|
|
25
|
-
**Known limitation:** Multi-line `console.*()` calls (arguments spanning multiple lines) are not removed — only the opening line is matched. This is acceptable for a dev-convenience tool.
|
|
26
|
-
|
|
27
|
-
### Patterns
|
|
28
|
-
|
|
29
|
-
| What | Regex (per line) |
|
|
30
|
-
|---|---|
|
|
31
|
-
| `debugger;` | `/^\s*debugger\s*;?\s*$/` |
|
|
32
|
-
| `console.<method>(...)` | `/^\s*console\.(log\|warn\|error\|...)\s*\(.*\)\s*;?\s*$/` |
|
|
33
|
-
|
|
34
|
-
Lines that are entirely consumed by the match are deleted (including the trailing newline). Lines where a `console.*` call is embedded in a larger expression (e.g., `return console.log(x)`) are left untouched.
|
|
35
|
-
|
|
36
|
-
## Settings
|
|
37
|
-
|
|
38
|
-
```json
|
|
39
|
-
"removeDebugger.removeDebugger": {
|
|
40
|
-
"type": "boolean",
|
|
41
|
-
"default": true,
|
|
42
|
-
"description": "Remove debugger; statements."
|
|
43
|
-
},
|
|
44
|
-
"removeDebugger.removeConsole": {
|
|
45
|
-
"type": "boolean",
|
|
46
|
-
"default": true,
|
|
47
|
-
"description": "Remove console.* calls."
|
|
48
|
-
},
|
|
49
|
-
"removeDebugger.consoleMethodsToRemove": {
|
|
50
|
-
"type": "array",
|
|
51
|
-
"default": ["log", "warn", "error", "info", "debug", "trace"],
|
|
52
|
-
"description": "Which console methods to remove when removeConsole is enabled."
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Users can keep `console.error` while removing `console.log` by editing `consoleMethodsToRemove`.
|
|
57
|
-
|
|
58
|
-
## Project Structure
|
|
59
|
-
|
|
60
|
-
```
|
|
61
|
-
remove-debugger-vs-extension/
|
|
62
|
-
├── src/
|
|
63
|
-
│ └── extension.ts # activate(), command handler, removal logic
|
|
64
|
-
├── package.json # manifest: commands, menus, settings, activationEvents
|
|
65
|
-
├── tsconfig.json
|
|
66
|
-
└── .vscodeignore
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
All logic lives in `extension.ts`. No external runtime dependencies — only the VS Code API and Node built-ins.
|
|
70
|
-
|
|
71
|
-
## Error Handling
|
|
72
|
-
|
|
73
|
-
- If no active editor is open, the command is a no-op (button is hidden by `when` clause).
|
|
74
|
-
- If the document is not a supported language, the button does not render.
|
|
75
|
-
- If no matches are found, the document is unchanged (no empty edit applied).
|
|
76
|
-
|
|
77
|
-
## Testing
|
|
78
|
-
|
|
79
|
-
Manual testing only for initial version:
|
|
80
|
-
- Open a JS/TS file with `debugger;` and `console.log()` lines, click the button, verify removal.
|
|
81
|
-
- Verify undo (`Ctrl+Z`) restores the original content.
|
|
82
|
-
- Verify settings (`removeConsole: false`) correctly skips console removal.
|
|
83
|
-
- Verify button does not appear on non-JS/TS files (e.g., `.json`, `.md`).
|
|
Binary file
|
package/src/extension.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import * as vscode from 'vscode';
|
|
2
|
-
|
|
3
|
-
export function activate(context: vscode.ExtensionContext): void {
|
|
4
|
-
const disposable = vscode.commands.registerCommand('removeDebugger.clean', () => {
|
|
5
|
-
const editor = vscode.window.activeTextEditor;
|
|
6
|
-
if (!editor) {
|
|
7
|
-
return;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const config = vscode.workspace.getConfiguration('removeDebugger');
|
|
11
|
-
const shouldRemoveDebugger = config.get<boolean>('removeDebugger', true);
|
|
12
|
-
const shouldRemoveConsole = config.get<boolean>('removeConsole', true);
|
|
13
|
-
const consoleMethods = config.get<string[]>('consoleMethodsToRemove', [
|
|
14
|
-
'log', 'warn', 'error', 'info', 'debug', 'trace',
|
|
15
|
-
]);
|
|
16
|
-
|
|
17
|
-
const text = editor.document.getText();
|
|
18
|
-
const cleaned = cleanText(text, shouldRemoveDebugger, shouldRemoveConsole, consoleMethods);
|
|
19
|
-
|
|
20
|
-
if (cleaned === text) {
|
|
21
|
-
vscode.window.showInformationMessage('No debugger statements or console calls found.');
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const edit = new vscode.WorkspaceEdit();
|
|
26
|
-
const fullRange = new vscode.Range(
|
|
27
|
-
editor.document.positionAt(0),
|
|
28
|
-
editor.document.positionAt(text.length)
|
|
29
|
-
);
|
|
30
|
-
edit.replace(editor.document.uri, fullRange, cleaned);
|
|
31
|
-
vscode.workspace.applyEdit(edit);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
context.subscriptions.push(disposable);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function cleanText(
|
|
38
|
-
text: string,
|
|
39
|
-
removeDebugger: boolean,
|
|
40
|
-
removeConsole: boolean,
|
|
41
|
-
consoleMethods: string[]
|
|
42
|
-
): string {
|
|
43
|
-
const lines = text.split('\n');
|
|
44
|
-
const filtered = lines.filter(line => {
|
|
45
|
-
if (removeDebugger && /^\s*debugger\s*;?\s*$/.test(line)) {
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
if (removeConsole && consoleMethods.length > 0) {
|
|
49
|
-
const methodPattern = consoleMethods.map(m => escapeRegex(m)).join('|');
|
|
50
|
-
const consoleRegex = new RegExp(
|
|
51
|
-
`^\\s*console\\.(${methodPattern})\\s*\\(.*\\)\\s*;?\\s*$`
|
|
52
|
-
);
|
|
53
|
-
if (consoleRegex.test(line)) {
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return true;
|
|
58
|
-
});
|
|
59
|
-
return filtered.join('\n');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function escapeRegex(s: string): string {
|
|
63
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function deactivate(): void {}
|