- vừa được xem lúc

Mình đã giúp một project enterprise React run dev nhanh hơn 5 lần như thế nào

0 0 6

Người đăng: TuanNQ

Theo Viblo Asia

TLDR: Đừng dùng Creact React App (CRA). Với project nhỏ, hãy dùng Vite, với project lớn, hãy sử dụng Rsbuild.

Đã bao giờ bạn gặp một project React to đến mức mỗi lần viết console.log(data) bạn phải đợi 2-3 phút trước khi thấy data bạn cần debug hiển thị lên màn hình chưa? Đã bao giờ bạn phải mất 5 phút chỉ để ngồi đợi server dev start up? Nếu vậy có lẽ bạn sẽ thích bài viết này: mình đã làm thế nào để giúp một project React enterprise run dev nhanh hơn gấp 5 lần.

Đợi 30 phút mỗi lần save

Mình được tiếp xúc với một dự án React siêu to khổng lồ khoảng 10.000 - 20.000 file ở chỗ làm. Mình nhớ lần đầu tiên đợi server dev start up phải mất tới 30 phút. Project đó to đến mức hot reload trên máy tính mình còn không chạy (vì tràn RAM!). Vì thế mỗi lần save mình phải chạy lại server dev (npm run start), tức là ngồi đợi khoảng 30 phút để thấy những gì mình code hiển thị lên màn hình.

Project React siêu to khổng lồ này là một monorepo bao gồm ít nhất 7 project nhỏ:

  • main_app_1main_app_2: 2 phần chính của dự án
  • 2 phần chính này dùng module nhỏ hơn là libcommon
  • libcommon dùng 2 module nhỏ hơn là componentsui_elements
  • components chứa những component "phức tạp" dựa trên những component "đơn giản" hơn trong module ui_elements
  • 6 module nhỏ này sẽ được import vào trong container dùng Create React App (CRA) để chạy cả dự án

Cách chạy project đó như thế này. Hãy tưởng tượng 6 project main_app_1, main_app_2, lib, common, components, ui_elements giống như một "thư viện", hay là một "package". Đầu tiên mình phải compile 6 "thư viện" đó, tiếp theo container sẽ import (npm install) 6 "thư viện" đó vào, rồi dùng Create React App để chạy.

Project này được viết bằng Typescript, vì thế nó sử dụng tsc, một package đính kèm với Typescript để compile Typescript sang Javascript.

Mỗi lần mình chạy:

npm run build

tức là tương đương với:

tsc

để compile những file viết bằng Typescript sang Javascript, máy tính mình lúc bấy giờ mất khoảng 10-15 phút. Mỗi lần mình chạy:

npm run start

để chạy server dev, mình phải ngồi đợi thêm 5-10 phút nữa.

2 bottleneck chính: tsc và Create React App (CRA)

Nhìn vào quá trình build dev rồi run dev, mình thấy có 2 bottleneck chính: tsc (Typescript compiler) và Create React App (CRA).

tsc có nhiệm vụ compile file Typescript thành Javascript và generate ra những file .d.ts để thông báo cho project khác những function hay component của project đó có type gì.

Ví dụ project main_app_1 import component CustomInput từ project component. Project component sẽ đưa cho project main_app_1 file d.ts để main_app_1 biết CustomInput có những props gì. Tưởng tượng bạn gõ:

<CustomInput on

rồi đợi VSCode gợi ý có những props gì bắt đầu bằng "on", thì đó vai trò của file .d.ts.

Tuy nhiên, trước khi generate ra những file .d.ts này, tsc sẽ phải vào từng file và đọc tất cả những type trong toàn project đó.

Nếu tsc kiểm tra type cả tất cả 10.000 - 20.000 file, thì đúng là không ngạc nhiên nếu nó rất chậm và tốn rất nhiều RAM.

Tiếp đến là Create React App (CRA). Mỗi lần mình thay đổi 1 file là Create React App (CRA) sẽ bundle nguyên cả project 10.000 - 20.000 file thành 1 file main.js lại từ đầu. Hiển nhiên là việc này mất cực kỳ nhiều thời gian và không được... hiệu quả lắm.

Thay tsc bằng swc

Đầu tiên, mình giải quyết vấn đề tsc. Việc generate file .d.ts là cần thiết nhưng nó chỉ cần thiết cho một project khác sử dụng project này.

Nhưng vì phần lớn thời gian mọi người chỉ làm việc trong 1 project, việc generate đi generate lại những file .d.ts cho toàn bộ cả 6 project là không cần thiết. Khi làm việc trong 1 project, VSCode sẽ tự động "hiểu" những function và component trong project đó có type như thế nào (Typescript Language Server built-in). Đó là lý do bạn thường không phải viết phải viết file d.ts cho chính mình và hiếm khi bạn gặp file này trừ khi bạn tự viết một thư viện.

Vậy có cách nào để trực tiếp compile Typescript thành Javascript mà không cần phải check type của toàn bộ 2.000 - 5.000 file không? Sau một chút search Google, mình tìm thấy swc.

SWC cực kỳ nhanh. So với tsc mất khoảng 5 - 10 phút để compile cả project từ Typescript sang Javascript, swc mất khoảng... 10 giây. Trong khi tsc watch máy mình còn không chạy nổi vì thiếu RAM và CPU thì swc mất khoảng 100 - 200 ms để compile!

SWC rất nhanh vì nó không check type cho cả project và cũng không generate ra file .d.ts.

Type checking và đảm bảo code đúng type là điều tốt nhưng trong quá trình code mình không muốn đánh đổi mỗi lần save phải đợi 30 phút mới thấy thay đổi trên màn hình để đảm bảo type đã chính xác. Mỗi lần code xong trước khi tạo Pull Request / Merge Request mình mới chạy lại tsc để đảm bảo tất cả những type mình viết là chính xác.

Tiếp đến là đến bottleneck Creact React App (CRA). So với việc thay thế tsc bằng swc thì việc này phức tạp hơn nhiều.

Thay CRA (Create React App) bằng Vite

Khi nhắc đến những giải pháp thay thế cho CRA (Create React App), có lẽ đầu tiên bạn sẽ nghĩ đến Vite. Mình cũng vậy. Vì thế đầu tiên mình thử thay thế CRA bằng Vite.

Đầu tiên, mình tạo một project React dùng Create React App nhỏ trên máy mình rồi thử thay thế nó bằng Vite. Mình khá bất ngờ khi thực ra nó khá đơn giản. Cơ bản việc này chỉ có 3 bước:

  • npm install Vite và plugin React
  • Tạo file vite.config.ts với plugin React
  • Tạo file index.html (có div với id root) ở trong thư mục ngoài cùng thay vì trong thư mục public

Sau khi thành công với project nhỏ, mình thử làm tương tự như vậy với project siêu to khổng lồ.

Sau khi đọc và hiểu sơ qua những config cần thiết cho project đó, mình config tương tự với Vite và cài những plugin tương tự. Tuy nhiên khi chạy npm run dev, mình gặp lỗi này:

Error: COMMITHASH is undefined

Tại sao lại thế? Vì project này sử dụng git-revision-webpack-plugin nên mình cài plugin tương tự phía Vite là vite-plugin-git-revision. Tuy nhiên 2 plugin này lại có những khác biệt nhỏ. Ở git-revision-webpack-plugin dùng biến global COMMITHASH, thì ở vite-plugin-git-revision lại dùng biến global GITCOMMITHASH.

Mình không muốn thay đổi source code mà chỉ muốn thay đổi config, nên mình tìm đọc thử documentation của vite-plugin-git-revision. Tuy nhiên sau khi nhận ra plugin này thực ra khá đơn giản, mình tự viết một "plugin" nhỏ trong config của mình.

Đây là ý tưởng của mình:

Đây là config demo ở trong vite.config.ts:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc'
import { exec } from "child_process"; const getCommitHash = new Promise<string>((resolve, reject) => { exec("git rev-parse HEAD", (err, stdout) => { if (err) { reject(err); } else { resolve(stdout); } });
}); const getVersion = new Promise<string>((resolve, reject) => { exec("git describe --always", (err, stdout) => { if (err) { reject(err); } else { resolve(stdout); } });
}); const getBranch = new Promise<string>((resolve, reject) => { exec("git rev-parse --abbrev-ref HEAD", (err, stdout) => { if (err) { reject(err); } else { resolve(stdout); } });
}); const getLastCommitDateTime = new Promise<string>((resolve, reject) => { exec("git log -1 --format=%cI", (err, stdout) => { if (err) { reject(err); } else { resolve(stdout); } });
}); export default defineConfig(async () => { const [commitHash, version, branch, lastCommitDateTime] = await Promise.all([ getCommitHash, getVersion, getBranch, getLastCommitDateTime, ]); return { plugins: [react()], source: { define: { VERSION: JSON.stringify(version), LASTCOMMITDATETIME: JSON.stringify(lastCommitDateTime), BRANCH: JSON.stringify(branch), COMMITHASH: JSON.stringify(commitHash), }, }, };
});

Nhưng lần này mình vẫn tiếp tục gặp:

Error: process.env is undefined

Tại sao lại thế? Hóa là với Vite, mọi biến environment phải bắt đầu VITE_. Ví dụ, nếu Create React App bạn dùng biến environment với tên DEFAULT_LANGUAGE, thì trong Vite nó phải là VITE_DEFAULT_LANGUAGE. Nhưng còn nữa, trong khi CRA dùng process.env, thì Vite lại dùng import.meta. Tức là thay vì dùng process.env.DEFAULT_LANGUAGE thì mình phải dùng import.meta.VITE_DEFAULT_LANGUAGE.

Tất nhiên mình không muốn vào từng file trong cả project tìm process.env rồi thay đổi bằng import.meta. Vì thế sau một hồi search Google, mình tìm thấy giải pháp ở bài viết này:

import { defineConfig, loadEnv } from 'vite'; export default defineConfig(({ command, mode }) => { const env = loadEnv(mode, process.cwd(), ''); return { define: { 'process.env.YOUR_STRING_VARIABLE': JSON.stringify(env.YOUR_STRING_VARIABLE), 'process.env.YOUR_BOOLEAN_VARIABLE': env.YOUR_BOOLEAN_VARIABLE, // If you want to exposes all env variables, which is not recommended // 'process.env': env }, };
});

Thay vì dùng process.env, thì với config này, Vite sẽ tìm và thay thế tất cả những string là process.env.YOUR_STRING_VARIABLE bằng giá trị mà bạn chọn. Ví dụ, nếu trong code mình có process.env.DEFAULT_LANGUAGE, Vite sẽ tìm và thay thế tất cả những string đó bằng "en" chẳng hạn.

Sau khi cẩn thận tìm kiếm và sử dụng cách trên cho tất cả những global variable có trong project, khi mình chạy npm run dev cuối cùng server dev cũng lên!

So với CRA server dev mất khoảng 10 phút chỉ để khởi động lên, server dev của Vite chạy sẵn sàng ở http://localhost:5173 gần như ngay lập tức. Mặc dù vậy mỗi lần mình gõ URL vào trình duyệt, mình vẫn phải đợi gần 5 phút để trang web bắt đầu hiển thị lên. Dù như thế là khá lâu, nhưng quả thực với việc đó cũng không... nằm ngoài dự đoán với một project có khoảng 10.000 - 20.000 file.

Tuy nhiên khi mình chỉnh sửa file rồi save, những thay đổi mình viết không hiển thị lên màn hình! Vite HMR không hoạt động. Mình lại phải stop, restart lại server, rồi ngồi đợi 5 phút.

Mình thử cấu hình tương tự với project nhỏ của mình nhưng Vite vẫn hoạt động bình thường. Có vẻ như Vite HMR chỉ không hoạt động khi gặp project React siêu to khổng lồ đến mức này.

Thay vì phải đợi 30 phút thì giờ xuống 5 phút đã là một bước tiến đáng kể, nhưng mình vẫn muốn tìm cách để làm quá trình dev còn nhanh hơn nữa.

Sau khi search Google "Vite alternative", mình tìm thấy Rsbuild.

Thay Vite bằng Rsbuild

Ngay từ đầu Rsbuild đã được thiết kế có config giống và có thể dễ dàng thay thế cho những giải pháp dựa trên Webpack như Create React App (CRA),... Đúng như vậy, có nhiều Webpack plugin có thể dùng được trực tiếp trong Rsbuild. Ví dụ như plugin git-revision-webpack-plugin mà mình tốn mấy tiếng đồng hồ để tìm documentation, đọc hiểu sourcecode rồi tạo lại mà có thể dùng được luôn trong rsbuild.config.ts.

Khác với CRA và giống với Vite, Rsbuild cũng sử dụng import.meta thay vì process.env. Nhưng giải pháp cũng tương tự như vậy: thêm vào trong source.definersbuild.config.ts và Rsbuild sẽ thay thế tất cả những string process.env.... bằng giá trị mình lựa chọn.

Sau khi config xong, mình chạy npm run dev và đợi khoảng 30 giây, server dev bắt đầu bật ở http://localhost:3000. Mình gõ URL và trang web gần như hiển thị ngay lập tức. Thật là khả quan.

Tiếp đó mình thêm console.log('hi there') và hồi hộp đợi. Liệu lần này HMR (Hot module reload) có chạy không?

Khoảng 4 giây sau, hi there hiển thị console. Mình thêm <div>Hi there</div>, và chỉ khoảng 5 giây sau, "Hi there" hiển thị trên màn hình.

Thật kỳ diệu! So với Creact React App (CRA) phải mất 30 phút mới lên và thường xuyên chết vì tràn RAM, Vite chỉ mất 5 phút nhưng HMR (Hot module reload) không chạy, Rsbuild chỉ mất 20 - 30 giây để chạy server và HMR chỉ mất có 5 giây cảm giác như tà thuật ma giáo được bí truyền từ 20 đời nay lại vậy.

Theo như benchmark ở trang chủ, Rsbuild nhanh hơn Vite + SWC khoảng 3 lần. Mới đầu thì mình cũng có hơi... không tin lắm. Nhưng trong project này Rsbuild không chỉ nhanh hơn Vite gấp 3 lần, nếu tính đến việc HRM của Vite không chạy, thì với một lập trình viên như mình nó phải nhanh hơn gấp từ 10 - 20 lần khi phải stop rồi restart lại dev server của Vite!

Kết luận

Và đó là quá trình mình giúp những thành viên trong dự án tiết kiệm nhiều thời gian và effort bằng cách giúp run dev nhanh hơn gấp 5 - 10 lần.

Đừng sử dụng Create React App, nó thậm chí còn không được recommend ở documentation chính thức của React nữa. Nếu bạn không có nhu cầu sử dụng Server Side Rendering, với project nhỏ, hãy sử dụng Vite, với những project React lớn, hãy sử dụng Rsbuild.

Credits

Nếu bạn thích con cá mà mình sử dụng, hãy xem: https://thenounproject.com/browse/collection-icon/stripe-emotions-106667/.

image.png

Bình luận

Bài viết tương tự

- vừa được xem lúc

Tìm hiểu về Virtual DOM trong React

Nếu bạn đang dùng React hoặc đang học ReactJS, chắc hẳn bạn đã nghe qua thuật ngữ Virtual DOM . Vậy Virtual DOM là gì và tại sao React lại sử dụng nó. Chúng ta hãy cùng tìm hiểu nhé. Let's go.

0 0 496

- vừa được xem lúc

10 câu hỏi phỏng vấn React cơ bản dành cho các developer

1. Ưu nhược điểm của React. Ưu điểm:. .

0 0 119

- vừa được xem lúc

Tìm hiểu về ESlint và cách cấu hình trong React

Eslint là gì . vậy Eslint được tạo ra để làm gì .

0 0 335

- vừa được xem lúc

Hướng dẫn cấu hình ReactJS với Webpack và Babel

Ok trong bài viết này, mình sẽ hướng dẫn các bạn cấu hình dự án ReactJS kết hợp Webpack và Babel. Bài viết này được thực hiện năm 2021 được cấu hình trên Webpack 5, như các bạn biết thì các bài viết c

0 0 147

- vừa được xem lúc

Tips and tricks trong ReactJS

Dưới đây sẽ là một số thủ thuật tuyệt vời mà bạn có thể áp dụng để cải thiện chất lương project React của mình. Hãy áp dụng những thủ thuật này trong project React của bạn ngay hôm nay thôi nào.

0 0 153

- vừa được xem lúc

Tìm hiểu về Reactjs căn bản

Chắc hẳn "React" đã không còn là từ mới lạ đối với các bạn nữa vì sự phổ biến của nó, đã có nhiều "đàn anh" đi trước như : Angular, Backbone,... Thế nhưng sự cạnh tranh của React là không hề kém cạnh,

0 0 65