'์ด ๋ฒํผ ๋์ฒด ์ด๋ ํ์ผ์ด์ผ?' โ ์ฐํด๋ฆญ์ผ๋ก ์ปดํฌ๋ํธ ์์ค๋ก ์ ํํ๋ ๋๊ตฌ๋ฅผ ๋ง๋ค์๋ค
์์์ ์ฌ์ํ ๋ถํธ์ด์๋ค
ํ๋ฉด์ ๋ณด์ด๋ ๋ฒํผ ํ๋๋ฅผ ๊ณ ์น๋ ค๊ณ ๊ทธ ๋ฒํผ์ด ์ด๋ ํ์ผ, ๋ช ๋ฒ์งธ ์ค์์ ๊ทธ๋ ค์ง๋์ง ์ฐพ๋ ์ผ. ๋ณ๊ฒ ์๋ ๊ฒ ๊ฐ์ง๋ง, ์ฝ๋๋ฒ ์ด์ค๊ฐ ์ปค์ง์๋ก ์ด๊ฒ ์๊ทผํ ์๊ฐ์ ์ก์๋จน๋๋ค. ์ปดํฌ๋ํธ ์ด๋ฆ์ผ๋ก ๊ฒ์ํด๋ ๊ฐ์ ์ด๋ฆ์ด ์ฌ๋ฌ ๊ฐ๊ณ , ์ฌ๋ฌ ๋ฒ ๊ฐ์ธ์ง(HOC๋ก ๋ํ๋) ์ปดํฌ๋ํธ๋ ์ ์ ์์น์ ์ค์ ๋ ๋ ์์น๊ฐ ๋ค๋ฅด๋ค.
React DevTools๊ฐ ์ด ๋ฌธ์ ๋ฅผ ์ด๋ ์ ๋ ํ์ด์คฌ๋ค. ์์ ์ Fiber์
_debugSource๊ฐ ์์ด์ "์ด ์์๊ฐ ์ด๋์ ์๋์ง"๋ฅผ ์ ์ ์์์ผ๋๊น.
(์ ์ด์ ๊ฐ๋ฐ ๋น๋ + jsxDEV ๋ณํ์์๋ง ์ฑ์์ง๋ ๊ฐ๋ฐ์ฉ ์ ๋ณด์๋ค.)
๊ทธ๋ฐ๋ฐ React 19์์ _debugSource๊ฐ ์ ๊ฑฐ๋๋ค. ๋ด๊ฐ ๊ธฐ๋๋ ๋ฐฉ๋ฒ์ด
์ฌ๋ผ์ง ๊ฒ์ด๋ค.
๊ทธ๋์ ์ง์ ๋ง๋ค๊ธฐ๋ก ํ๋ค. ๋ชฉํ๋ ํ๋์๋ค.
ํ๋ฉด์์ ์์๋ฅผ ์ฐํด๋ฆญํ๋ฉด ๊ทธ ์์ค ์ฝ๋ ์์น๋ก ๋ฐ๋ก ์ ํํ๋ค.
๋ง๋ค๊ณ ๋ณด๋ react-code-locator๋ผ๋ ์ด๋ฆ์ผ๋ก npm์ ์ฌ๋ฆด ๋งํผ ์ธ๋ชจ๊ฐ
์์๊ณ , ๋ง๋๋ ๊ณผ์ ์์ ๋ฐฐ์ด ๊ฒ ํจ์ฌ ๋ง์๋ค. ๊ทธ ์ด์ผ๊ธฐ๋ฅผ ์ ๋ฆฌํ๋ค.
ํฐ ๊ทธ๋ฆผ: ๋น๋ ํ์ + ๋ฐํ์ ๋ ๋จ๊ณ
ํต์ฌ ์์ด๋์ด๋ ๋ ๋จ๊ณ๋ก ๋๋๋ค.
- ๋น๋ ํ์ โ ๋ฒ๋ค๋ฌ ํ๋ฌ๊ทธ์ธ์ด ์์ค ์ฝ๋๋ฅผ ํ์ผ๋ฉด์, ๊ฐ JSX ์์์
์ปดํฌ๋ํธ ์ ์ธ์ "์ด๊ฑด
ํ์ผ:์ค:์นธ์์ ์๋ค"๋ ์์น ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์ฃผ์ ํ๋ค. - ๋ฐํ์ โ ๋ธ๋ผ์ฐ์ ์์ ์์๋ฅผ ํด๋ฆญํ๋ฉด, React Fiber ํธ๋ฆฌ๋ฅผ ๊ฑฐ์ฌ๋ฌ ์ฌ๋ผ๊ฐ๋ฉฐ ์ฃผ์ ํด๋ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์ฐพ์ ์์ค ์์น๋ฅผ ์์๋ด๊ณ , ํ์ด๋ผ์ดํธํ๊ฑฐ๋ ์๋ํฐ๋ฅผ ์ฐ๋ค.
_debugSource์ฒ๋ผ React ๋ด๋ถ ๊ตฌํ์ ๊ธฐ๋์ง ์๊ณ ๋ด๊ฐ ์ง์ ๋ฉํ๋ฐ์ดํฐ๋ฅผ
์ฌ๊ธฐ ๋๋ฌธ์, React ๋ฒ์ ์ด ๋ฐ๋์ด๋ ๋์ํ๋ค. ์ด๊ฒ ์ด ์ ๊ทผ์ ๊ฐ์ฅ ํฐ ์ฅ์ ์ด๋ค.
1๋จ๊ณ โ ์์ค ์์น๋ฅผ ์ฝ๋์ ์ฌ๊ธฐ
๋น๋ ํ์ ๋ณํ์ ์์น์ ๋ฑ ํ๋์๋ค.
์๋ณธ ์ฝ๋๋ฅผ ์ ๋ ์ฌ์์ฑํ์ง ์๋๋ค.
AST๋ฅผ ํ์ฑํด์ ๋ค์ ์ฝ๋๋ก ๋ฝ์๋ด๋ฉด(์ฝ๋ ์ ๋๋ ์ด์ ), ์๋ณธ์ ํฌ๋งคํ ์ด๋ TypeScript/JSX ๋ฌธ๋ฒ์ด ๋ฏธ๋ฌํ๊ฒ ํ์ด์ง ์ํ์ด ์๋ค. ๊ทธ๋์ ํ์ฑ์ ์์น๋ฅผ ์ฐพ๋ ์ฉ๋๋ก๋ง ์ฐ๊ณ , ์ค์ ์ฝ์ ์ ์๋ณธ ๋ฌธ์์ด์ ๊ทธ๋๋ก ๋ ์ฑ ํ์ํ ์ง์ ์ ๋ฌธ์์ด๋ง ๋ผ์ ๋ฃ๋ ๋ฐฉ์์ผ๋ก ๊ฐ๋ค.
import * as acorn from "acorn";
import acornJsx from "acorn-jsx";
import acornTs from "acorn-typescript";
import MagicString from "magic-string";
// .js/.jsx/.tsx: TypeScript ํ๋ฌ๊ทธ์ธ์ด JSX ํ๋ฌ๊ทธ์ธ๋ณด๋ค ๋จผ์ ์์ผ ํ๋ค
const ParserTSX = acorn.Parser.extend(acornTs(), acornJsx());
// .ts ์ ์ฉ: JSX ํ๋ฌ๊ทธ์ธ ์์
const ParserTS = acorn.Parser.extend(acornTs());
์ฐพ์ ์์น๋ง๋ค ์ฝ์
ํ ํ
์คํธ๋ฅผ ๋ชจ์๋๋ค๊ฐ, ๋ง์ง๋ง์ MagicString์ผ๋ก ํ ๋ฒ์
๊ฝ๋๋ค.
const s = new MagicString(code);
for (const { at, text } of insertions) {
s.appendLeft(at, text);
}
return { code: s.toString(), map: s.generateMap({ hires: true }) };
๋ฌด์์ ์ฌ๋๋๋ฉด:
- JSX ์์์๋ ์์ค ์์น๋ฅผ ๋ด์ prop์ ๋ถ์ธ๋ค. โ
<div ... >๊ฐ "์ดdiv๋src/App.tsx:12:5์์ ์๋ค"๋ฅผ ๋ค๊ณ ๋ค๋๊ฒ ๋๋ค. - ์ปดํฌ๋ํธ ์ ์ธ(ํจ์/ํด๋์ค/ํ์ดํ ํจ์)์๋ ์ ์ธ ์งํ์
Comp.__componentSourceLoc = "src/App.tsx:8:1"๊ฐ์ ํ ๋น๋ฌธ์ ๋ถ์ธ๋ค.
์ฝ์ง 1: <T,> ์ ๋ค๋ฆญ ํ์ดํ ํจ์์ ํจ์
.tsx์์ JSX์ TypeScript ์ ๋ค๋ฆญ์ ๋ฌธ๋ฒ์ด ์ถฉ๋ํ๋ค. ์๋ฅผ ๋ค์ด
const f = <T,>(x: T) => x ์ <T,>๋ฅผ JSX ํ์๊ฐ JSX ํ๊ทธ๋ก ์คํดํ๋ค.
์ด๊ฑธ ๋ฌด๋ฆฌํ๊ฒ ํ์ฑํ๋ ค๋ค ํ์ผ ์ ์ฒด๊ฐ ๊นจ์ง๋๋, .ts ํ์ผ์ ์์ JSX
ํ์๋ฅผ ํ์ฐ์ง ์๋๋ก ํ์๋ฅผ ๋ถ๋ฆฌํ๋ค. (.ts์ JSX๊ฐ ์ฌ ์ ์์ผ๋ ์์ ํ๋ค.)
์ฝ์ง 2: HOC๋ก ๊ฐ์ผ ์ปดํฌ๋ํธ๋ "์กฐ์ฌ์ค๋ฝ๊ฒ" ์ฌ์ด์ผ ํ๋ค
export default memo(MyComp), styled.div\...`` ์ฒ๋ผ ํจ์ ํธ์ถ์ด๋ ํ๊ทธ๋
ํ
ํ๋ฆฟ์ผ๋ก ๊ฐ์ผ ๊ฒฝ์ฐ, ๋ฐํ๊ฐ์ด ์ปดํฌ๋ํธ๊ฐ ์๋ ์๋ ์๋ค. ๊ทธ๋์ ์ด๋ฐ
"wrapped" ์ผ์ด์ค๋ ํ์
๊ฐ๋๋ฅผ ์์์, ๊ฐ์ด ๊ฐ์ฒด/ํจ์์ผ ๋๋ง ํ๋กํผํฐ๋ฅผ
ํ ๋นํ๋๋ก ํ๋ค.
// ์์๊ฐ์ด๋ฉด ์กฐ์ฉํ ๊ฑด๋๋ด๋ค (throw ์์)
Name && (typeof Name === "object" || typeof Name === "function")
&& (Name.__componentSourceLoc = "src/App.tsx:8:1");
๋ฐ๋๋ก ํ์ดํ/ํจ์/ํด๋์ค ํํ์์ฒ๋ผ ๊ฐ์ด ํ์คํ "direct" ์ผ์ด์ค๋ ๊ฐ๋ ์์ด ๋ฐ๋ก ํ ๋นํ๋ค. ์ด๋ฐ ์ธ๋ฐํ ๋ถ๊ธฐ๊ฐ ์ค์ ํ๋ก์ ํธ์ ์จ๊ฐ ์ปดํฌ๋ํธ ํจํด์ ๊ฒฌ๋๊ฒ ํด์คฌ๋ค.
2๋จ๊ณ โ ๋ฐํ์์์ Fiber๋ฅผ ๊ฑฐ์ฌ๋ฌ ์ฌ๋ผ๊ฐ๊ธฐ
๋ธ๋ผ์ฐ์ ์์ ์์๋ฅผ ํด๋ฆญํ๋ฉด, ๊ทธ DOM์ ๋ถ์ React Fiber๋ฅผ ์ฐพ๋๋ค. React๋
DOM ๋
ธ๋์ __reactFiber$... ๊ฐ์ ํค๋ก Fiber๋ฅผ ๋งค๋ฌ์ ๋๋ค.
function getReactFiberKey(element: Element) {
return Object.keys(element).find(
(k) => k.startsWith("__reactFiber$") ||
k.startsWith("__reactInternalInstance$"),
);
}
์ฌ๊ธฐ์๋ถํฐ Fiber์ return(๋ถ๋ชจ)์ ํ๊ณ ์๋ก ์ฌ๋ผ๊ฐ๋ฉฐ, ๋น๋ ํ์์ ์ฌ์ด๋
์์ค ์ ๋ณด๋ฅผ ๋ชจ์๋ค. ์ฌ๋ฏธ์๋ ๊ฑด "ํ๋ฉด์์ ๋ณด์ด๋ ์์น"์ "์ค์ ๊ตฌํ
์์น"๊ฐ ๋ค๋ฅผ ์ ์๋ค๋ ์ ์ด๋ค. ๊ทธ๋์ ๋ ๊ฐ์ง ๋ชจ๋๋ฅผ ๋๋ค.
- Screen ๋ชจ๋(
Alt+1) โ ๋ด ํ๋ก์ ํธ์์ ์ด ์์๋ฅผ ์ค์ ๋ก ์ด ์์น. ๋์์ธ ๊ณ ์น ๋ ์ํ๋ ๊ทธ ์์น. - Implementation ๋ชจ๋(
Alt+2) โ ์ปดํฌ๋ํธ๊ฐ ์ ์๋ ๊ตฌํ ์์น. (๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ปดํฌ๋ํธ ๋ด๋ถ๊น์ง)
์ ๋ณด๋ฅผ ์ฐพ์ผ๋ฉด DevTools ์ธ์คํํฐ์ฒ๋ผ ํ๋ ์ค๋ฒ๋ ์ด๋ก ์์๋ฅผ ํ์ด๋ผ์ดํธํ๊ณ ,
ํ์ผ๋ช
๊ณผ ์ค ๋ฒํธ๋ฅผ ๋ผ๋ฒจ๋ก ๋์ด๋ค. ์ฐํด๋ฆญํ๋ฉด ์ปจํ
์คํธ ๋ฉ๋ด๊ฐ ๋ ์ ๊ธฐ๋ณธ์ผ๋ก๋
๊ฒฝ๋ก ๋ณต์ฌ๋ฅผ ํ ์ ์๊ณ , openInEditor ์ต์
(๊ธฐ๋ณธ๊ฐ false)์ ์ผ๋ฉด
์๋ํฐ์์ ์ด๊ธฐ ํญ๋ชฉ์ด ์ถ๊ฐ๋๋ค.
"์๋ํฐ์์ ์ด๊ธฐ"๋ ๊ฐ๋ฐ ์๋ฒ์ /__open-in-editor ์๋ํฌ์ธํธ๋ฅผ ํ๋ ๋๊ณ ,
ํด๋ฆญ ์ ๊ทธ ์ฃผ์๋ก ์์ฒญ์ ๋ณด๋ด ๋ก์ปฌ ์๋ํฐ๋ฅผ ์คํํ๋ ๋ฐฉ์์ด๋ค.
const onOpen = () =>
fetch(`/__open-in-editor?file=${encodeURIComponent(result.source)}`);
VS Code, Cursor, WebStorm, Vim ๋ฑ ์ฌ๋งํ ์๋ํฐ์ CLI ๋ช ๋ น์ ๋งคํํด๋๋ค.
ํ ๋ฒ ๋ง๋ค๊ณ ๋ค์ฏ ๊ฐ ๋ฒ๋ค๋ฌ ์ง์ํ๊ธฐ
์ฒ์์ Vite๋ง ์๊ฐํ๋๋ฐ, Webpack(Next.js), Rollup, esbuild, Rspack๊น์ง ์ฐ๊ณ ์ถ์ด์ก๋ค. ์ด๊ฑธ ๊ฐ๊ฐ ๊ตฌํํ๋ฉด ์ง์ฅ์ด๋ค. ๊ทธ๋์ unplugin ์ผ๋ก ํตํฉํ๋ค. ๋ณํ ๋ก์ง ํ๋๋ง ์ ๋ง๋ค์ด๋๋ฉด ์ฌ๋ฌ ๋ฒ๋ค๋ฌ์ฉ ํ๋ฌ๊ทธ์ธ์ด ํ ๋ฒ์ ๋์จ๋ค. ๋ค๋ง Vite๋งํผ์ ํ๋ฌ๊ทธ์ธ ์คํ ์์ ๋ฌธ์ ๋๋ฌธ์ unplugin ์ด๋ํฐ ๋์ ๋ค์ดํฐ๋ธ Vite ํ๋ฌ๊ทธ์ธ์ผ๋ก ๋ฐ๋ก ๊ตฌํํ๋ค.
export { vitePlugin, webpackPlugin, rollupPlugin, esbuildPlugin, rspackPlugin }
from "./unplugin";
๋๋์๋ณด๋ฉฐ โ ๋ง๋ค๋ฉด์ ๋ฐฐ์ด ๊ฒ๋ค
์ด ํ๋ก์ ํธ์์ ํฌ๊ฒ ์ธ ๋ฒ ๊ฐ์์์๋ค.
- ํ์ ๊ต์ฒด (oxc-parser โ acorn): ์ฒ์์ ๋น ๋ฅธ oxc-parser๋ฅผ ์ผ๋๋ฐ,
๋ค์ดํฐ๋ธ ๋ฐ์ธ๋ฉ ๋๋ฌธ์ ๋ฒ๋ค๋งยท๋ฐฐํฌ๊ฐ ๊น๋ค๋ก์ ๋ค.
acorn๊ณ์ด(+ TS/JSX ํ๋ฌ๊ทธ์ธ)๋ก ๋ฐ๊พธ๋ ์์ JS๋ก ์์ ํ ๋ฒ๋ค ๊ฐ๋ฅํด์ก๊ณ , ์ค์น๊ฐ ํจ์ฌ ๊ฐ๋ฒผ์์ก๋ค. - Babel ์์กด์ฑ ์ ๊ฑฐ: ์ด๊ธฐ์ Babel์ ๊ธฐ๋์ง๋ง, ๊ฒฐ๊ตญ "๋น๋ ํ๋ฌ๊ทธ์ธ ํ๋" ๋ก ๋๋๊ฒ ๋ง๋๋ ๊ฒ ๋ชฉํ์๋ค. Babel๋ React DevTools๋ ๋ธ๋ผ์ฐ์ ํ์ฅ๋ ํ์ ์์ด, ํ๋ฌ๊ทธ์ธ ํ ์ค์ด๋ฉด ๋๋๋ก ์ ๋ฆฌํ๋ค.
- ๋ฌธ์์ด ์กฐ์ ์ต์ ํ (slice โ MagicString): ๋งค ์ฝ์
๋ง๋ค
slice + ์ฐ๊ฒฐ์ ํ๋ ํ์ผ์ด ํฌ๊ณ ์ฝ์ ์ด ๋ง์์๋ก O(nยทm)๋ก ๋๋ ค์ก๋ค. MagicString์ผ๋ก ๋ฐ๊ฟ ์ฝ์ ์ ์ ๋ ฌ๋ ์์น์ ๊ธฐ๋ก๋ง ํด๋๋ ์ฌ์ค์ ์์ ๋น์ฉ์ด ๋๊ณ (์ต์ข ๋ฌธ์์ด ์์ฑ์ ๋ง์ง๋ง 1ํ), ์์ค๋งต ์์ฑ๊น์ง ๋ค์ผ๋ก ์ป์๋ค.
๊ฐ์ฅ ํฌ๊ฒ ๋จ์ ๊ตํ์, "React ๋ด๋ถ ๊ตฌํ์ ๊ธฐ๋์ง ๋ง๊ณ ๋ด๊ฐ ํ์ํ ๋ฐ์ดํฐ๋ฅผ
์ง์ ๋ง๋ค์" ์๋ค. _debugSource๊ฐ ์ฌ๋ผ์ก์ ๋ ๋ง๋งํ์ง๋ง, ์คํ๋ ค ๋ฒ์ ์
๋
๋ฆฝ์ ์ธ ๋ ํผํผํ ๋ฐฉ๋ฒ์ผ๋ก ์ด์ด์ก๋ค.
์จ๋ณด๊ธฐ
npm i -D react-code-locator
// vite.config.ts
import react from "@vitejs/plugin-react";
import { vitePlugin } from "react-code-locator";
export default defineConfig({
plugins: [react(), vitePlugin()],
});
์ด์ ๊ฐ๋ฐ ์ค์ Shift๋ฅผ ๋๋ฅธ ์ฑ ์์์ ๋ง์ฐ์ค๋ฅผ ์ฌ๋ฆฌ๋ฉด ํ์ด๋ผ์ดํธ๋๊ณ ,
์ฐํด๋ฆญํ๋ฉด ๊ฒฝ๋ก๋ฅผ ๋ณต์ฌํ ์ ์๋ค(openInEditor ์ต์
์ ์ผ๋ฉด ์๋ํฐ๋ก ๋ฐ๋ก
์ ํ). ์ฌ์ํ ๋ถํธ ํ๋๊ฐ, ๋งค์ผ ์ฐ๋ ๋๊ตฌ๊ฐ ๋๋ค.
- npm: react-code-locator
- GitHub: web-element-code-locator