๐ŸŒฑ <groot/>
ํ•œ ๋ฒˆ ์งœ์„œ ๋ฒˆ๋“ค๋Ÿฌ 5๊ฐœ ์ง€์›ํ•˜๊ธฐ โ€” unplugin์œผ๋กœ ๋งŒ๋“  ์œ ๋‹ˆ๋ฒ„์„ค ํ”Œ๋Ÿฌ๊ทธ์ธ | Groot's Tech Blog
๐ŸŒWeb

ํ•œ ๋ฒˆ ์งœ์„œ ๋ฒˆ๋“ค๋Ÿฌ 5๊ฐœ ์ง€์›ํ•˜๊ธฐ โ€” unplugin์œผ๋กœ ๋งŒ๋“  ์œ ๋‹ˆ๋ฒ„์„ค ํ”Œ๋Ÿฌ๊ทธ์ธ

2026.07.02ยท3๋ถ„ ์ฝ๊ธฐ

react-code-locator๋ฅผ ๋งŒ๋“ค ๋•Œ ํ˜„์‹ค์ ์ธ ๊ณ ๋ฏผ์ด ํ•˜๋‚˜ ์žˆ์—ˆ๋‹ค. ์š”์ฆ˜ React ํ”„๋กœ์ ํŠธ์˜ ๋ฒˆ๋“ค๋Ÿฌ๋Š” ์ œ๊ฐ๊ฐ์ด๋‹ค. Vite, Next.js(Webpack), Rollup, esbuild, Rspackโ€ฆ. ์†Œ์Šค์— ์œ„์น˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์‹ฌ์œผ๋ ค๋ฉด ๋ฒˆ๋“ค๋Ÿฌ์˜ ๋นŒ๋“œ ํŒŒ์ดํ”„๋ผ์ธ์— ๋ผ์–ด๋“ค์–ด์•ผ ํ•˜๋Š”๋ฐ, ๋ฒˆ๋“ค๋Ÿฌ๋งˆ๋‹ค ํ”Œ๋Ÿฌ๊ทธ์ธ API๊ฐ€ ๋‹ค๋ฅด๋‹ค.

์ด๊ฑธ ๋‹ค์„ฏ ๋ฒŒ๋กœ ๊ฐ๊ฐ ๊ตฌํ˜„ํ•˜๋ฉด? ์ง€์˜ฅ์ด๋‹ค. ๊ทธ๋ž˜์„œ unplugin ์„ ์ผ๋‹ค. ์ด ๊ธ€์€ "๋ณ€ํ™˜ ๋กœ์ง ํ•˜๋‚˜๋กœ ๋ฒˆ๋“ค๋Ÿฌ ๋‹ค์„ฏ ๊ฐœ๋ฅผ ์ปค๋ฒ„ํ•œ" ์ด์•ผ๊ธฐ์™€, ๊ทธ ๊ณผ์ •์—์„œ ๋งŒ๋‚œ ํ•จ์ •๋“ค์„ ์ •๋ฆฌํ•œ๋‹ค.

๋ฒˆ๋“ค๋Ÿฌ ํ”Œ๋Ÿฌ๊ทธ์ธ์˜ ๊ณตํ†ต์ 

์ด๋ฆ„๊ณผ ํ˜•ํƒœ๋Š” ๋‹ฌ๋ผ๋„, ๋ชจ๋“  ๋ฒˆ๋“ค๋Ÿฌ ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ๊ณตํ†ต์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ํ›…์ด ์žˆ๋‹ค.

"ํŒŒ์ผ ํ•˜๋‚˜์˜ ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ๋ฐ›์•„, ๋ณ€ํ™˜ํ•ด์„œ ๋Œ๋ ค์ค€๋‹ค" (transform)

Vite/Rollup์€ transform(code, id), Webpack/Rspack์€ loader, esbuild๋Š” onLoadโ€ฆ ์ด๋ฆ„๋งŒ ๋‹ค๋ฅผ ๋ฟ ๋ณธ์งˆ์€ ๊ฐ™๋‹ค. unplugin์€ ์ด ๊ณตํ†ต์ ์„ ํ•˜๋‚˜์˜ ํ†ต์ผ๋œ ํ›…์œผ๋กœ ์ถ”์ƒํ™”ํ•˜๊ณ , ๊ฐ ๋ฒˆ๋“ค๋Ÿฌ์šฉ ์–ด๋Œ‘ํ„ฐ๋ฅผ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด ์ค€๋‹ค.

ํ•ต์‹ฌ: transform ์ฝ”์–ด๋Š” ๋”ฑ ํ•˜๋‚˜

๋‚ด๊ฐ€ ์ง  ๊ฑด "์†Œ์Šค ์ฝ”๋“œ๋ฅผ ๋ฐ›์•„ ์œ„์น˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์‚ฝ์ž…ํ•ด ๋Œ๋ ค์ฃผ๋Š”" transformSource ํ•จ์ˆ˜ ํ•˜๋‚˜๋ฟ์ด๋‹ค. unplugin์œผ๋กœ ๊ฐ์‹ธ๋ฉด ๋์ด๋‹ค.

import { createUnplugin } from "unplugin";
import { transformSource } from "./core/transform";

const _unplugin = createUnplugin((options = {}) => ({
  name: "react-code-locator",
  enforce: "pre",

  // ์–ด๋–ค ํŒŒ์ผ์„ ๋ณ€ํ™˜ํ• ์ง€ ํ•„ํ„ฐ (.js/.jsx/.tsx, node_modules ์ œ์™ธ)
  transformInclude(id) {
    return shouldTransform(id, include, exclude);
  },

  // ์‹ค์ œ ๋ณ€ํ™˜: ๋ฒˆ๋“ค๋Ÿฌ๊ฐ€ ๋ญ๋“  ์ด ํ•จ์ˆ˜ ํ•˜๋‚˜๋กœ ์ฒ˜๋ฆฌ๋œ๋‹ค
  transform(code, id) {
    const isEnabled = enabled ?? process.env.NODE_ENV === "development";
    if (!isEnabled) return null;
    return transformSource(code, { filename: id, projectRoot });
  },
}));

๊ทธ๋ฆฌ๊ณ  ๊ฐ ๋ฒˆ๋“ค๋Ÿฌ์šฉ ํ”Œ๋Ÿฌ๊ทธ์ธ์€ unplugin์ด ๋งŒ๋“ค์–ด ์ค€ ๊ฑธ ๊ทธ๋Œ€๋กœ ๋‚ด๋ณด๋‚ด๋ฉด ๋œ๋‹ค.

export const webpackPlugin = _unplugin.webpack;
export const rollupPlugin  = _unplugin.rollup;
export const esbuildPlugin = _unplugin.esbuild;
export const rspackPlugin  = _unplugin.rspack;

transformInclude๋กœ ๋Œ€์ƒ ํŒŒ์ผ์„ ๋จผ์ € ๊ฑฐ๋ฅด๋Š” ๊ฒƒ๋„ ํฌ์ธํŠธ๋‹ค. ๋ณ€ํ™˜ ๋Œ€์ƒ์ด ์•„๋‹ˆ๋ฉด ์•„์˜ˆ transform์„ ๋ถ€๋ฅด์ง€ ์•Š์•„์„œ, node_modules๋‚˜ .css ๊ฐ™์€ ํŒŒ์ผ์— ์“ธ๋ฐ์—†๋Š” ํŒŒ์‹ฑ ๋น„์šฉ์„ ์•ˆ ์“ด๋‹ค.

ํ•จ์ • 1: Vite๋งŒ ์–ด๋Œ‘ํ„ฐ๋ฅผ ์•ˆ ์“ฐ๊ณ  ์ง์ ‘ ์งฐ๋‹ค

์—ฌ๊ธฐ์„œ ์ฒซ ๋ฒˆ์งธ ๋ฒฝ. Vite๋งŒํผ์€ unplugin ์–ด๋Œ‘ํ„ฐ ๋Œ€์‹  ๋„ค์ดํ‹ฐ๋ธŒ Vite ํ”Œ๋Ÿฌ๊ทธ์ธ์œผ๋กœ ๋”ฐ๋กœ ๊ตฌํ˜„ํ–ˆ๋‹ค. ์ด์œ ๋Š” ์‹คํ–‰ ์ˆœ์„œ ๋•Œ๋ฌธ์ด๋‹ค.

๋‚ด ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ๊ฐ€๋Šฅํ•˜๋ฉด @vitejs/plugin-react๋ณด๋‹ค ๋จผ์ € ๋Œ์•„์•ผ ํ•œ๋‹ค. ํ”Œ๋Ÿฌ๊ทธ์ธ ๋ฆฌ์•กํŠธ๊ฐ€ ์ฝ”๋“œ๋ฅผ ๊ฑด๋“œ๋ฆฌ๊ธฐ ์ „์— ์›๋ณธ ๊ธฐ์ค€์œผ๋กœ ์œ„์น˜๋ฅผ ์‹ฌ์–ด์•ผ ํ•˜๋‹ˆ๊นŒ. ๊ทธ๋ž˜์„œ enforce: "pre"๋กœ ์•ž ์ˆœ์„œ๋ฅผ ์š”์ฒญํ•˜๋Š”๋ฐ, ์ด๊ฒŒ ์ˆœ์„œ๋ฅผ ์™„์ „ํžˆ ๋ณด์žฅํ•ด ์ฃผ์ง€๋Š” ์•Š๋Š”๋‹ค. @vitejs/plugin-react๋„ ๊ฐ™์€ pre ๊ทธ๋ฃน์ด๋ผ, ์ƒํ™ฉ์— ๋”ฐ๋ผ ๊ทธ์ชฝ์ด ๋จผ์ € ๋Œ ์ˆ˜ ์žˆ๋‹ค. unplugin ์–ด๋Œ‘ํ„ฐ๋ฅผ ๊ฑฐ์น˜๋ฉด ์ด๋Ÿฐ ์ˆœ์„œ ์ œ์–ด๊ฐ€ ๋” ์• ๋งคํ•ด์ ธ์„œ, Vite๋Š” ์ˆœ์„œ์™€ ์šฐํšŒ ๋กœ์ง์„ ์ง์ ‘ ์ฅ˜ ์ˆ˜ ์žˆ๋Š” ๋„ค์ดํ‹ฐ๋ธŒ ํ”Œ๋Ÿฌ๊ทธ์ธ์œผ๋กœ ๋”ฐ๋กœ ์งฐ๋‹ค.

export function vitePlugin(options): Plugin[] {
  const transformPlugin: Plugin = {
    name: "react-code-locator",
    enforce: "pre",              // react()๋ณด๋‹ค ์•ž ์ˆœ์„œ๋ฅผ ์š”์ฒญ (๋ณด์žฅ์€ ์•„๋‹˜)
    configResolved(config) {
      resolvedEnabled = enabled ?? config.command === "serve";
    },
    transform(code, id) { /* ... transformSource ... */ },
  };
  return [transformPlugin, ...clientInjector];
}

๊ทธ๋Ÿฐ๋ฐ ์ˆœ์„œ๋ฅผ ์š”์ฒญํ•ด ๋‘๋”๋ผ๋„ @vitejs/plugin-react๊ฐ€ ๋จผ์ € ๋Œ์•„ ํŒŒ์ผ ์•ž์— fast-refresh preamble์„ ๋ถ™์ด๋Š” ๊ฒฝ์šฐ๊ฐ€ ์‹ค์ œ๋กœ ์ƒ๊ธด๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ๋ชจ๋“  ์ค„ ๋ฒˆํ˜ธ๊ฐ€ ๋ฐ€๋ฆฐ๋‹ค. ๊ทธ๋ž˜์„œ ๋„ค์ดํ‹ฐ๋ธŒ ํ”Œ๋Ÿฌ๊ทธ์ธ์—์„œ๋Š” ๋ฐ›์€ ์ฝ”๋“œ๊ฐ€ ๋””์Šคํฌ์˜ ์›๋ณธ๊ณผ ๋‹ค๋ฅด๋ฉด ์›๋ณธ ํŒŒ์ผ์„ ์ง์ ‘ ์ฝ์–ด preamble์„ ๋–ผ์–ด๋‚ด๊ณ  ๋ณ€ํ™˜ํ•œ ๋’ค ๋‹ค์‹œ ๋ถ™์ด๋Š” ์šฐํšŒ๋ฅผ ๋„ฃ์—ˆ๋‹ค. ์ด ์ค„ ๋ฒˆํ˜ธ ๋ฒ„๊ทธ์˜ ์ž์„ธํ•œ ์ด์•ผ๊ธฐ๋Š” ์›Œ๋‚™ ๊ธธ์–ด์„œ ๋”ฐ๋กœ ๋‹ค๋ฃฌ๋‹ค.

ํ•จ์ • 2: Webpack/Rspack loader์™€ ESM/CJS

unplugin์˜ WebpackยทRspack ์–ด๋Œ‘ํ„ฐ๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ loader ํŒŒ์ผ์„ ๋”ฐ๋กœ ์ฐธ์กฐํ•œ๋‹ค. ์ด loader๋“ค์ด ๋ฐฐํฌ ํŒจํ‚ค์ง€ ์•ˆ์— ์ œ๋Œ€๋กœ ๋“ค์–ด๊ฐ€๊ณ , CommonJS๋กœ๋„ ๋กœ๋“œ๋˜๊ฒŒ ํ•˜๋ ค๋ฉด ๋นŒ๋“œ ํ›„์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ–ˆ๋‹ค.

  • ๋นŒ๋“œ ์‹œ unplugin์˜ loader๋“ค์„ dist๋กœ ๋ณต์‚ฌํ•˜๊ณ ,
  • .js๋ฅผ .cjs๋กœ๋„ ๋ณต์‚ฌํ•œ ๋’ค,
  • ๋ฒˆ๋“ค ์•ˆ์˜ loader ๊ฒฝ๋กœ ๋ฌธ์ž์—ด์„ .cjs๋กœ ์น˜ํ™˜ํ•˜๋Š” ํŒจ์น˜ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋Œ๋ฆฐ๋‹ค.
// scripts/patch-loaders.cjs โ€” ๋ฒˆ๋“ค ์•ˆ loader ๊ฒฝ๋กœ๋ฅผ .cjs๋กœ ๊ต์ฒด
const patterns = [
  ["webpack/loaders/transform", "webpack/loaders/transform.cjs"],
  ["rspack/loaders/transform", "rspack/loaders/transform.cjs"],
  // ...
];
for (const [from, to] of patterns) {
  content = content.replaceAll(JSON.stringify(from), JSON.stringify(to));
}

package.json์˜ exports ๋งต์œผ๋กœ import/require/browser/types ์ง„์ž…์ ์„ ๋‚˜๋ˆ ์ฃผ๋Š” ๊ฒƒ๊นŒ์ง€ ํ•ด์•ผ "์–ด๋–ค ๋ฒˆ๋“ค๋Ÿฌ์—์„œ ์–ด๋–ป๊ฒŒ ๋ถˆ๋Ÿฌ๋„ ๊นจ์ง€์ง€ ์•Š๋Š”" ํŒจํ‚ค์ง€๊ฐ€ ๋๋‹ค.

"exports": {
  ".": {
    "browser": "./dist/runtime.js",
    "types": "./dist/index.d.ts",
    "import": "./dist/index.js",
    "require": "./dist/index.cjs"
  }
}

๋ฐฐ์šด ๊ฒƒ

  • ๋ฒˆ๋“ค๋Ÿฌ๋งˆ๋‹ค API๋Š” ๋‹ฌ๋ผ๋„ "ํŒŒ์ผ์„ ๋ฐ›์•„ ๋ณ€ํ™˜ํ•œ๋‹ค"๋Š” ๊ณตํ†ต์ ์ด ์žˆ๊ณ , unplugin์€ ๊ทธ ๊ณตํ†ต์ ์„ ์ถ”์ƒํ™”ํ•ด ์ค€๋‹ค. ํ•ต์‹ฌ ๋กœ์ง ํ•˜๋‚˜๋งŒ ์ž˜ ๋งŒ๋“ค๋ฉด ๋œ๋‹ค.

  • ๋‹ค๋งŒ ์ถ”์ƒํ™”๊ฐ€ ๋ชจ๋“  ๊ฑธ ๊ฐ์ถฐ์ฃผ์ง„ ์•Š๋Š”๋‹ค. ์‹คํ–‰ ์ˆœ์„œ(enforce)๋‚˜ loader ๊ฒฝ๋กœ, ESM/CJS ๊ฐ™์€ ๋ฒˆ๋“ค๋Ÿฌ๋ณ„ ํ˜„์‹ค์€ ๊ฒฐ๊ตญ ๋งˆ์ฃผํ•ด์•ผ ํ–ˆ๋‹ค.

  • ์ถ”์ƒํ™”๋Š” 90%๋ฅผ ์—†์• ์ฃผ๊ณ , ๋‚˜๋จธ์ง€ 10%(๊นŒ๋‹ค๋กœ์šด ๊ฒƒ)๋ฅผ ๋‚จ๊ธด๋‹ค. ๊ทธ 10%๋ฅผ ์ดํ•ดํ•˜๋Š” ๊ฒŒ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ €์ž์˜ ๋ชซ์ด๋ผ๋Š” ๊ฑธ ๋ฐฐ์› ๋‹ค.

  • ์‹ค์ œ ๊ตฌํ˜„: react-code-locator (GitHub)

  • ๊ด€๋ จ ๊ธ€: ์ฝ”๋“œ๋Š” ์–ด๋–ป๊ฒŒ ํŠธ๋ฆฌ๊ฐ€ ๋˜๋Š”๊ฐ€ โ€” AST์™€ ํŒŒ์‹ฑ

โ† ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ
ยฉ 2026 ยท Groot's Tech Blog. Built with Gatsby ๐ŸŒฑ