Skip to content

Commit

Permalink
feat: 241124_react_cant_change_jsx_to_string_윤예진 (#112)
Browse files Browse the repository at this point in the history
  • Loading branch information
Yejin0O0 authored Dec 2, 2024
1 parent 965ed7e commit d57826a
Showing 1 changed file with 141 additions and 0 deletions.
141 changes: 141 additions & 0 deletions 기술블로그/_posts/2024-11-24-react_cant_change_jsx_to_string.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
---
layout: post
title: 클라이언트 환경에서 파일을 문자열로 변환할 수 없는 이유
author: 윤예진
categories: 프론트엔드 기술블로그
banner:
image: assets/images/post/2023-11-05.webp
background: "#000"
height: "100vh"
min_height: "38vh"
heading_style: "font-size: 4.25em; font-weight: bold; text-decoration: underline"
tags: [JavaScript, module_bundler, webpack, vite]
---

[오픈소스 프로젝트](https://shadcn-dialog-snippet.vercel.app/)의 첫 배포가 이번 주에 완료되었습니다! 🎉 이 프로젝트는 `shadcn/ui` 라이브러리의 `dialog` 컴포넌트를 활용하여 개발자들이 자주 마주치는 다양한 `dialog` 상황에 대한 코드 스니펫을 모아둔 모음집입니다.

첫 배포를 위해 우리 팀이 구현한 기능은 크게 세 가지입니다.

1. 사용자들이 실제 구현된 모습을 직접 볼 수 있는 dialog 미리보기 화면 구현

2. 원하는 코드를 바로 프로젝트에 적용할 수 있는 코드 복사 기능

3. 코드의 가독성을 높이기 위한 코드 에디터 스타일 구현

이 중에서 저는 두 번째와 세 번째 기능의 개발을 맡았습니다. 코드 복사 기능은 웹 브라우저의 표준 API인 `Clipboard API``writeText` 메서드를 활용했고, 코드 에디터와 유사한 시각적 효과를 위해 `shiki` 라이브러리를 적용했습니다.

이 두 가지 주요 기능을 구현하면서 공통적으로 필요했던 리소스는 상황별로 만들어둔 `dialog` 컴포넌트의 문자열 버전이였습니다.

원했던 상황은 다음과 같습니다.

```
1. 단순히 화면에 렌더링 되는 부분만이 아니라, `import` 구문부터 `export` 구문, 컴포넌트 태그들까지 IDE에서 개발자가 보는 형태 그대로 문자열 값으로 바꾸기
2. 바꾼 문자열을 변수에 저장해 `export` 하기
3. 해당 변수를 컴포넌트에 props로 넘겨줌으로써 중복 코드 줄이기
```

위와 같은 상황을 만들기 위해 파일 자체를 직접 문자열로 변환하고 싶었지만, 이 작업을 수행할 수 없었습니다. 저는 이 작업이 왜 불가능한지 이해가 되지 않았습니다. 프로젝트 내부 파일을 문자열로 바꾸는 일인데 말이죠.

---

### 클라이언트 환경에서 프로젝트 내부 파일을 문자열로 변환하는 작업은 왜 불가능할까?

브라우저와 같은 클라이언트 환경에서는 보안상의 이유로 로컬 파일 시스템에 직접 접근하거나 소스 코드 파일을 읽어 문자열로 변환하는 작업이 제한됩니다.

이러한 제한의 핵심에는 동일 출처 정책(`Same-Origin Policy`)이 있습니다. 이 정책은 한 웹 페이지에서 실행되는 스크립트가 다른 출처(`Origin`)의 리소스에 접근하는 것을 막습니다. 이는 악의적인 스크립트가 사용자의 중요한 정보를 탈취하거나 조작하는 것을 방지하기 위한 중요한 안전장치입니다.

프로젝트 내부 파일을 읽는 것 역시 이 동일 출처 정책의 영향을 받습니다. 브라우저에서 로컬 파일은 일반적으로 `file://` 프로토콜을 통해 접근되는데, 이는 웹 리소스와는 다른 방식으로 취급됩니다. `file://` 프로토콜은 동일 출처 정책 적용 시 하나의 출처로 간주되지만, 보안 위험이 크기 때문에 더욱 엄격하게 관리됩니다.

따라서 개발자가 작성한 파일을 읽으려면 API나 서버를 통한 별도의 승인된 경로가 필요합니다. 이러한 보안 정책들은 사용자의 데이터와 시스템을 보호하는 데 중요한 역할을 합니다.

보안 정책으로 인해 클라이언트 환경에서 파일 시스템에 직접 접근할 수 없는 이유도 알게 되었습니다. 그런데, 여전히 한가지 의문이 남아있습니다.

react 컴포넌트를 만들 때 import 구문을 사용해서 다른 컴포넌트 파일을 사용할 수 있습니다. 브라우저에서 파일 시스템에 접근하지 못한다면, **그럼 왜 `react` 컴포넌트 내부의 `import`는 가능할까요?**

### import 가 동작하는 이유

React 컴포넌트 내부의 `import`가 가능한 이유는 이 작업이 브라우저에서 실행되지 않기 때문입니다. `import`**빌드 시점**에서 처리됩니다. 빌드 도구가 개발자의 파일 시스템에 접근해서 `import`된 파일들을 분석하고, 이를 하나의 번들 파일로 통합합니다. 이러한 과정은 **서버나 로컬 개발 환경**에서 이루어지기 때문에 파일 시스템 접근에 제한이 없습니다.

### 프로젝트에 어떻게 적용할 수 있을까?

빌드 시점에 빌드 도구가 파일 시스템에 접근 할 수 있다는 것을 알았으니, 파일을 빌드 시점에 문자열로 수정하면 제가 원하는 상황이 만들어 질 수 있을 것 같습니다.

우선 파일을 읽어 문자열로 변환하는 함수를 만들어 줍니다.

```tsx
import path from "node:path";
import fs from "fs-extra";

const convertTsxToString = async () => {
const inputFilePath = path.resolve(
"문자열로 바꾸고 싶은 파일 경로",
);
const outputFilePath = path.resolve("문자열로 바꾼 후 저장될 파일 경로");

try {
// 파일을 문자열로 읽기
const code = await fs.readFile(inputFilePath, "utf-8");

// 읽은 문자열을 코드로 내보내기 형태로 변환
const generatedCode = `
export const tsxString = ${JSON.stringify(code)};
`;

// 변환된 코드를 파일에 쓰기
await fs.outputFile(outputFilePath, generatedCode)
} catch (error) {
console.error("에러 발생:", error);
process.exit(1); // 에러 발생 시 프로세스를 종료
}
};

// 변환 함수 실행
convertTsxToString();

```

빌드 시점에 `convertTsxToString` 함수가 실행되어야 하므로, package.json에 스크립트를 따로 만들어야 합니다.

```json
"dev": "vite",
"convert": "NODE_OPTIONS='--loader ts-node/esm' ts-node <convertTsxToString파일 경로>",
"build": "npm run convert-tsx && vite build",
```

런타임 환경은 `Node.js` 환경입니다. `Node.js``TypeScript` 파일을 직접 실행할 수 없으므로 `ts-node` 를 설치해주어야 합니다.

`ts-node`는 기본적으로 Node.js의 CommonJS 방식으로 TypeScript 파일을 처리합니다. ESM 환경에서 `ts-node`를 사용하려면, `--loader ts-node/esm` 옵션을 추가해야 Node.js가 ESM 방식으로 TypeScript를 처리할 수 있습니다.

Node.js 실행 옵션을 `npm run` 스크립트를 통해 전달하려면 `NODE_OPTIONS` 환경 변수를 사용해야 합니다. `NODE_OPTIONS`는 Node.js의 런타임 옵션을 전달할 수 있는 방법이며, `-loader ts-node/esm`를 Node.js 실행 프로세스에 추가하기 위해 사용됩니다.

`convert-tsx-to-string.ts`에서 사용하는 `import`, `export`, 경로 별칭, 타입 검사 등의 동작이 올바르게 작동하려면 `tsconfig.json` 설정도 필요합니다. 특히, **ESM 환경과의 호환성**을 위해 `tsconfig.json`에서 적절한 설정(`module: "ESNext"`, `target: "ESNext"`)을 해야 합니다.

프로젝트에서 적용한 `tsconfig` 설정은 다음과 같습니다.

```tsx
{
"compilerOptions": {
"module": "ESNext", // ESM 형식의 import/export 사용
"moduleResolution": "Node", // Node.js 방식으로 모듈 해석
"target": "ESNext", // 최신 JavaScript 표준으로 컴파일
"strict": true, // 엄격한 타입 검사 활성화
"esModuleInterop": true, // CommonJS와 ES 모듈 간 호환 지원
"jsx": "react-jsx", // React JSX 변환 (React 17+)
"baseUrl": ".", // 프로젝트 루트를 기준 경로로 설정
"paths": {
"@/*": ["src/*"] // @로 별칭 지정
}
},
"include": ["src"], // 컴파일할 파일
"exclude": ["node_modules", "dist"] // 컴파일 제외 경로
}

```

이렇게 3가지 과정을 통해

1. `import` 구문부터 `export` 구문, 컴포넌트 태그들까지 IDE에서 개발자가 보는 형태 그대로 문자열 값으로 바꿀 수 있었고
2. 바꾼 문자열을 변수에 저장해 `export` 하여
3. 해당 변수를 컴포넌트에 props로 넘겨줌으로써 중복 코드 줄이는 것

이 가능해 졌습니다.

0 comments on commit d57826a

Please sign in to comment.