Làm sao để integrate component React vào Solid JS (với Typescript) (phần 1)

0 0 0

Người đăng: TuanNQ

Theo Viblo Asia

Mình rất thích Solid, nhưng...

...mình không thể sống thiếu react-three-fiber và react-flow. Hiện tại không có thư viện nào có khả năng thay thế nó cả.

Solid thật tuyệt vời, nếu bạn đã từng code React rồi thì Solid không những dễ dàng và quen thuộc, mà còn vừa chạy rất nhanh. Tuy nhiên mình không thể rời bỏ React ecosystem (một trong những ecosystem lớn nhất) được. Code lại tất cả những thứ như diffuse light và blur shadow trong react-three-fiber, hay code lại hoàn toàn cả react-flow là không thể chấp nhận được. Mình không muốn viết 10.000 dòng bằng Solid JS chỉ để phải chuyển lại về React!

Vì thế mình viết bài viết này: làm sao để integrate React component vào Solid JS.

Ở phần 1, mình sẽ giải thích làm sao để render một component React ở trong Solid JS:

Bạn có thể xem source code cho phần 1 ở đây.

Ở phần 2, mình sẽ giải thích làm thế nào để "kết nối" (chuyển data) giữa component React và Solid JS:

Bạn có thể xem source code cho phần 2 ở đây.

Ở phần 3 mình sẽ giải thích những config phổ biến bạn nên biết bằng một ứng dụng phức tạp hơn một chút:

Bạn có thể xem ứng dụng này tại đây và check source code tại đây trên Github.

Trong khi có rất nhiều cách để integrate một component React vào trong Solid JS, ở bài viết này mình sẽ hướng dẫn bạn làm điều đó theo cách "monorepo".

Tại sao integrate một component React vào Solid JS không đơn giản chút nào?

Mới đầu mình nghĩ để integrate một component React vào một project dùng Solid JS rất đơn giản. Solid JS và React rất giống nhau. Như vậy thì chắc chỉ cần install Solid và React vào cùng một project, viết component bình thường bằng Solid JS, rồi component con bằng React là được.

Giải pháp đơn giản mới đầu mình nghĩ

Nhưng buồn thay nó không đơn giản như thế.

Khi compile React và Solid ra Javascript thuần, code Javascript được compile này không giống nhau và không pass props được cho nhau một chút nào (kể cả phần JSX cũng khác nhau hoàn toàn!). Hơn nữa, mình cũng phải tìm cách bảo Vite (hay CRA, NextJS,...) compile những file được viết bằng React và Solid một cách khác nhau.

Cơ bản mình có 2 vấn đề chính:

  • Làm sao để bảo Vite / CRA compile những file được viết bằng React hay Solid một cách khác nhau.
  • Làm sao để những component được viết bằng Solid và React giao tiếp được với nhau.

Giải pháp của mình cho vấn đề compile là tạo ra 2 project: một cái cho component React và cái còn lại cho project chính bằng Solid JS.

Giải pháp cho vấn đề compile: 2 project React và Solid riêng biệt

Giải pháp cho vấn đề giao tiếp với nhau là compile component React thành Javascript thuần rồi install nó như một thư viện vào project Solid chính.

React và Solid không thể giao tiếp trực tiếp được với nhau nhưng cả hai đều "hiểu" Javascript thuần. Bằng cách compile component React thành Javascript thuần, Solid sẽ "hiểu" component React này.

Giải pháp cho vấn đề giao tiếp: compile componet React thành Javascript thuần

Bây giờ sau khi project Solid JS chính đã "hiểu" component React này, tiếp theo project Solid JS chính này sẽ tạo ra một cái <div/> rồi đưa cho component React để render trên nó.

Đây là hình vẽ minh họa giải thích cả quá trình nhìn trông "chuyên nghiệp" hơn một chút:

  • Project riêng của component React sẽ export ra function mount nhận vào một cái <div/>
  • Compile project này thành Javascript thuần (hay còn gọi là quá trình "build")
  • Install nó vào project Solid JS
  • Vì mình đã compile project component React này thành Javascript thuần, khi install vào trong project Solid JS, project Solid JS sẽ "hiểu" component React này
  • Truyền một cái <div/> vào function mount

Vậy hãy bắt đầu bằng việc tạo 2 project riêng biệt rồi ghép chúng vào với nhau bằng PNPM Workspace.

Giải quyết vấn đề compile (bằng PNPM Workspace)

Mình sẽ tạo 2 project: một project cho component React và một project chính bằng Solid JS. Tưởng tượng project component React này giống như một "thư viện" mà mình npm install vào project Solid JS chính.

Như vậy thì ngày xưa mình sẽ phải làm thế này:

  • Build project component React như một thư viện
  • Publish nó lên NPM Registry
  • Install nó về từ NPM Registry vào Solid app

Nhìn vào hình trên bạn có thể nghĩ, việc phải publish project component React của mình lên NPM Registry thành một thư viện thật... vô nghĩa. Sau cùng thì cũng chỉ có mỗi mình mình dùng "thư viện" đó của mình chứ có ai dùng nữa đâu. Giá mà có cách nào để không phải publish lên NPM Registry mà vẫn "install" được vào project Solid chính của mình thì tiện nhỉ.

PNPM Workspace được sinh ra để giải quyết vấn đề này.

PNPM workspace

PNPM workspace giúp một project sử dụng một project khác như một "thư viện" mà không cần phải publish lên NPM Registry.

Để giải thích một cách dễ hiểu hơn hãy lấy một ví dụ: project #1 dùng project #2 và project #3:

Ở ví dụ này:

  • Project #1 sẽ "hỏi" PNPM Workspace để install Project #2 và Project #3
  • PNPM Workspace sẽ tìm vị trí của Project #2 và Project #3 (bằng file pnpm-workspace.yaml)
  • PNPM Workspace tiếp theo sẽ tìm những file được build ra của Project #2 và Project #3 (bằng file package.json của mỗi project)
  • PNPM Workspace chuyển những file này cho Project #1

Đây là hình vẽ minh họa giải thích về cấu trúc thư mục cơ bản của một project dùng PNPM Workspace:

  • pnpm-workspace.yaml ở thư mục ngoài cùng của project tổng: để pnpm workspace biết tất cả vị trí của "project con"
  • packages là thư mục chứa tất cả project con: nếu bạn đổi tên thư mục này bạn sẽ phải sửa tương ứng trong pnpm-workspace.yaml
  • package.json của các project đóng vai trò là "thư viện" (như trong ví dụ của mình sẽ là project #2 và #3): chỉ vị trí những file được "build" của project đó
  • package.json ở project "chính": chữ "workspace:*" nói cho pnpm workspace biết rằng project này muốn sử dụng project #2 và project #3 đã có ở trên máy mình (chứ không phải download về từ NPM Registry)

Giống như PNPM Workspace, Yarn workspace và NPM workspace cũng được sinh ra để giải quyết những vấn đề này và thực sự cũng rất giống nhau. Ở bài viết này mình sẽ sử dụng PNPM Workspace.

Vậy sau khi hiểu "hòm hòm" về PNPM Workspace làm gì và như thế nào, ở phần tiếp theo hãy cùng tạo ra một project tổng dùng PNPM Workspace có 2 project con:

  • Project component React.
  • Project Solid JS chính dùng project component React trên

Tạo project tổng dùng PNPM Workspace và thêm project Solid JS vào

PNPM Workspace được "đính kèm" trong pnpm. Nếu máy bạn đã có pnpm thì bạn có thể dùng pnpm workspace. Nếu bạn chưa cài đặt pnpm trong máy thì bạn chỉ cần:

npm i -g pnpm

...là được.

Tiếp theo, hãy tạo một thư mục mới. Mình sẽ đặt tên thư mục của mình là react-in-solid.

Tiếp theo, tạo file pnpm-workspace.yaml để nói cho PNPM biết vị trí của project Solid của mình (mình sẽ đặt tên project này là "solid-project"):

packages: - "packages/solid-project"

Tiếp theo, mình sẽ tạo thư mục packages để chứa project Solid. Ở trong thư mục, mình sẽ sử dụng Vite để tạo ra một project Solid mới có tên là "solid-project".

cd packages npx create-vite@latest √ Project name: ... solid-project
√ Select a framework: » Solid
√ Select a variant: » TypeScript

Đây là hình vẽ minh họa giải thích những gì mình vừa làm:

Tiếp đến hãy install dependencies bằng pnpm install. Nhớ rằng hãy chạy lệnh này ở thư mục gốc. Vì file pnpm-workspace.yaml nằm ở thư mục gốc, chỉ có như vậy thì pnpm mới biết về file này.

pnpm install

Tiếp theo, hãy chạy server dev bằng:

pnpm run dev

Hãy nhớ chạy lệnh này ở trong thư mục packages/solid-project!

Optional: flag --filter

Phải nhớ chuyển folder khi chạy mỗi lệnh khác nhau khá phiền phức. Vì vậy, PNPM workspace có flag --filter để giải quyết vấn đề này.

Bạn có thể chạy lệnh start server dev ở trên ở thư mục gốc với flag --filter như thế này:

pnpm --filter \"solid-project\" run dev

Flag --filter này chỉ ra lệnh dev này chạy ở project nào. Nó giống như bạn chạy lệnh pnpm run dev ở thư mục "solid-project" vậy.

Để cho tiện, hãy tạo file package.json ở thư mục gốc và thêm script dev để chạy server dev ở project "solid-project" . Đầu tiên, hãy chạy:

pnpm init

Tiếp theo, ở file package.json hãy thêm script `dev:

"scripts": { "dev": "pnpm --filter \"solid-project\" run dev"
}

Bây giờ bạn có thể chạy pnpm run dev ở thư mục gốc rồi!

Tạo project component React và thêm nó vào PNPM workspace

Đầu tiên, hãy thông báo cho pnpm workspace biết mình có một project mới tên là react-component bằng cách thêm vào file pnpm-workspace.yaml như sau:

packages: - "packages/solid-project" - "packages/react-component"

Tiếp theo, mình sẽ dùng Vite để tạo ra project component React ở thư mục packages.

cd packages npx create-vite@latest √ Project name: ... react-component
√ Select a framework: » React
√ Select a variant: » TypeScript + SWC

Mình sử dụng SWC ở đây vì SWC rất nhanh nhưng bạn có thể chỉ sử dụng Typescript mà không dùng SWC cũng được

Tiếp theo, hãy install dependencies ở thư mục gốc bằng:

pnpm install

Đây là hình vẽ minh họa giải thích những gì mình vừa làm:

image.png

Nối 2 project React and Solid vào với nhau

Trước khi tiếp tục làm sao để render component React ở trong Solid, mình sẽ giải thích một cách sơ qua ngắn gọn Solid, React, Vite làm gì.

Optional: Vite và React (và Solid) làm gì?

Một cách đơn giản, React (hay Solid) cho phép mình "vẽ" giao diện mình muốn bằng Javascript trên một cái div.

  • Developer viết code để "dạy" React (hay Solid JS) làm sao để tạo ra giao diện mà mình muốn
  • Sau đó mình có file index.html với một thẻ div đưa cho React
  • React sẽ "vẽ" giao diện mình muốn trên thẻ div đó

Vậy với cách hiểu đơn giản như thế, đoạn code setup ở trong file main.tsx mà bạn nhìn thấy nghĩa là thế này:

Mình truyền cho function createRoot một cái div có id là "root" (div này phải tồn tại trong file index.html), và bảo nó render trên cái div đó giao diện mà mình muốn.

Vì trình duyệt chỉ hiểu Javascript thuần (chứ không hiểu Typescript với JSX), Vite sẽ compile và bundle code của thư viện React với code mình viết thành Javascript thuần, rồi cùng với đó đưa cho server dev file index.html với div có id là "root" để mình có thể truy cập vào ở http://localhost:5173.

Kiến trúc tổng thể của cả project

Nhắc lại một chút về ý tưởng làm sao để render một component React vào Solid mà mình đã nói ở trên:

...cùng với những gì mà Vite đã làm cho mình, đây là kiến trúc tổng quan của cả project:

  • Project component React của mình sẽ export function mount nhận vào thẻ div để render component React trên đó
  • Compile project này về Javascript thuần
  • Import nó vào project Solid
  • Project Solid sẽ đưa function mount một cái div để nó render trên đấy
  • Vite sẽ hoàn thành nốt phần việc còn lại: compiling, bundling,... và mình có thể vào http://localhost:5173 để xem

Bây giờ sau khi đã hiểu về kiến trúc tổng quan dự án, hãy qua phần tiếp theo bắt tay vào render component React ở trong Solid.

Compile project component React về Javascript thuần (hay còn gọi là build)

Đầu tiên, ở project component React, hãy xóa tất cả những file trong thư mục ./src. Tiếp theo, tạo file App.tsx ở trong như mục đó như thế này:

// src/App.tsx
export const App = () => { return <div>Hi from React</div>;
};

Ở đây mình tạo một component mà sẽ render một div với text là "Hi from React".

Tiếp theo tạo file index.tsx như thế này:

// src/index.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { App } from "./App"; // For the main Solid app
export const mount = (root: HTMLElement) => { createRoot(root).render( <StrictMode> <App /> </StrictMode> );
};

Function mount nhận vào một div và sẽ render app của mình trên đó. File index.tsx đóng vai trò là "entry file" cho project Solid.

Bây giờ, mình sẽ compile code mà mình viết thành Javascript thuần để import vào project Solid JS.

Một trong những cách đơn giản nhất để compile code mà mình viết (React TSX) thành Javascript thuần là dùng tsc. tsc (thư viện đính kèm với Typescript khi bạn install Typescript) sẽ compile những file .ts, .tsx thành file .js Javascript thuần. Bạn có thể dùng từ những công cụ như Webpack (cái mà CRA dùng), ESBuild (cái mà Vite dùng), Rspack (cái mà Rsbuild dùng), cho đến những công cụ được tạo ra dành riêng cho việc tự build một thư viện như Tsup, Rslib, cho đến những công cụ đơn giản nhất như tsc, swc. Ở trong bài viết này để giữ mọi thứ tối giản nhất có thể mình sẽ sử dụng tsc.

Bây giờ mình sẽ tạo ra file tsconfig.build.json:config dành cho tsc chỉ dùng cho lúc compile project React component. Mình sẽ bảo tsc bắt đầu compile với entry file là ./src/index.tsx rồi đưa ra kết quả trả về vào thư mục dist như thế này:

{ "compilerOptions": { "target": "ES2016", "jsx": "react-jsx", "module": "Preserve", "declaration": true, "inlineSourceMap": true, "outDir": "./dist", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true }, "files": ["./src/index.tsx"]
}

Tiếp theo, mình sẽ bảo tsc dùng file tsconfig.build.json này để build app của mình (hay còn gọi là compile code của mình thành Javascript thuần). Ở file package.json ở project component React, mình sẽ đổi script build thành:

"scripts": { ... "build": "tsc -p tsconfig.build.json", ... },

Bây giờ, hãy build project component React của mình bằng cách chạy lệnh build:

pnpm run build

Bước cuối cùng mình cần làm là thông báo cho pnpm biết làm sao để tìm những file được build của mình. Ở trong file package.json, thêm trường main vào như sau:

{ "name": "react-component", "main": "./dist/index.js", ...
}

Đây là sơ đồ giải thích những gì mình làm từ trước đến giờ:

Optional: chạy project react-component một cách độc lập

Trong tất cả những gì Vite làm cho mình, mình chỉ cần phần compile code của mình thành Javascript thuần (Vanilla Javascript) như mình đã khoanh ở đây.

Điều này nghĩa là mình có thể uninstall Vite và chỉ giữ cái phần mà compile code của mình về Javascript thuần (ESBuild / SWC). Mặc dù hoàn toàn có thể làm như thế, mình muốn giữ Vite lại để mình vẫn có thể chạy project component React này một cách độc lập.

Tuy nhiên cái khó ở đây là trong khi project Solid chính chỉ muốn function mount, Vite lại muốn mình chạy function mount đó ngay lập tức với #root div. Bạn có thể thấy bình thường ở file main.tsx lập tức render ứng dụng của mình trên #root div như thế này:

createRoot(document.getElementById('root')!).render( <App/>,
)

Để giải quyết vấn đề này, mình sẽ tạo ra 2 "entry file" khác nhau. Entry file đầu tiên là index.tsx sẽ export ra function mount để project Solid sử dụng. Entry file thứ hai là main.tsx sẽ chạy function mount ngay lập tức với #root div.

Đây là hình vẽ minh họa "kỹ thuật" hơn một chút:

Bây giờ hãy tạo file main.tsx như thế này:

// src/main.tsx
import { mount } from "."; // For stand-alone development
mount(document.getElementById("root")!);

Ở file index.html của project component React, hãy đảm bảo rằng tag <script/> import file main.tsx như thế này:

... <script type="module" src="/src/main.tsx"></script>
...

File index.html file sẽ import file main.tsx (hãy chắc chắn rằng nó không phải file index.tsx) là entry file để render component React của mình.

Nếu bạn chạy server dev của project react-component rồi truy cập vào http://localhost:5173, bạn sẽ thấy thế này:

Import vào project Solid chính

Đầu tiên, hãy "install" thư việc react-component của mình giống như một "package" hay "dependency" bằng cách thêm vào file package.json của project Solid của mình như sau:

"dependencies": { ... "react-component": "workspace:*" },

Đây là sơ đồ giải thích những gì mình vừa làm:

Bây giờ hãy chạy pnpm install để install react-component vào solid-project. Hãy nhớ chạy lệnh này ở thư mục gốc:

pnpm install

Bây giờ, ở project chính Solid JS, hãy xóa tất cả những file trong thư mục src. Tiếp đến, hãy tạo ra file App.tsx với nội dung như sau:

export const App = () => { return <div>Hi from Solid</div>;
};

Component App render một cái div với text bên trong là "Hi from Solid".

Tiếp theo, tạo file index.tsx với nội dung như sau:

import { render } from 'solid-js/web'
import App from './App.tsx' const root = document.getElementById('root') render(() => <App />, root!)

File index.tsx sẽ render app của mình trên một cái div với id là "root".

Nếu bạn chạy server pnpm run dev và vào http://localhost:5173, bạn sẽ thấy thế này:

Tiếp theo, trong file App.tsx, hãy render component React của mình:

import { mount } from "react-component";
import { onMount } from "solid-js"; export const App = () => { let container!: HTMLDivElement; onMount(() => { mount(container); }); return ( <> <div ref={container} /> <div>Hi from Solid</div> </> );
};

Ở trong file App.tsx sẽ import function mount của project component React, và khi component Solid của mình khởi chạy (onMount), mình sẽ chạy function mount với div container.

Bây giờ nếu bạn truy cập http://localhost:5173, bạn sẽ thấy thế này trên màn hình:

Thật tuyệt vời! Và đó là cách mình render một component React trong một project dùng Solid JS.

Ở phần 2 mình sẽ tiếp tục giải quyết vấn đề làm sao để "giao tiếp" (chuyển data) giữa component React và app dùng Solid chính của mình.

Bình luận

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

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

Những điều cần lưu ý và sử dụng Hook trong React (Phần 5)

V. Sử dụng useRef như thế nào cho đúng.

0 0 147

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

7 Cách viết code React "clean" hơn

Mở đầu. Là nhà phát triển React, tất cả chúng ta đều muốn viết code sạch hơn, đơn giản hơn và dễ đọc hơn. 1. Sử dụng JSX shorthands.

0 0 204

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

Create app covid-19 use Reactjs

Chào các bạn hôm nay mình sẽ chia sẻ với các bạn một app covid-19, để mọi người cùng tham khảo, tính năng của App này chỉ đơn giản là show số liệu về dịch Covid của các nước trên thế giới như: Số ngườ

0 0 59

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

ReactJS Custom Hooks

ReactJS cung cấp rất nhiều cho bạn các hook mà bạn có thể sử dụng hằng ngày vào project của mình. Nhưng bên cạnh đó bạn có thể tự tạo ra các hook của riêng bạn để sử dụng, làm cho việc tối ưu hóa code

0 0 83

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

3 cách để tránh re-render khi dùng React context

3 cách để tránh re-render khi dùng React context. Nếu đã từng sử dụng React context cho dự án của bạn, và gặp phải tình trạng các component con - Consumer re-render rất nhiều lần, thậm chí bị sai logi

0 0 42

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

Tìm hiểu về React Hook: Sử dụng useDebugValue

Trong bài viết hôm này, tôi sẽ giới thiệu các bạn một React Hook tiếp theo, đó là useDebugValue. useDebugValue là gì .

0 0 61