CDN 방식으로 react-d3-tree를 가져올 수 있는 방법이 없었다.
esm, cjs 모두 에러가 발생하며 다른 버전을 사용해도 에러가 발생하는 것은 마찬가지였다.
그냥 직접 빌드하자
리액트가 필요하다면 리액트를 직접 설치해서 배포될 빌드 파일에 포함해버리면 어떨까 라는 생각을 하게 되었다.
(그냥 빌드된 js만 있으면 리액트도 실행할 수 있는거 아닌가?)
리액트에 대한 의존성이 추가되지만 devDependencies에 추가 할 수 있을 것으로 판단했다.
devDependencies에만 추가해도 라이브러리를 개발하는데 문제가 없고 결국 빌드된 리액트 파일이 라이브러리에서 직접 사용할 예정이기 때문이다.
(사용자가 직접 빌드하거나 실행할 일은 없을 것이므로 가능)
이를 사용하기 위해서는 결국 빌드를 한 번 이상 수행해야 한다.
컴포넌트 렌더링을 위한 리액트를 프로젝트에 추가했기 때문에 결국 이 리액트를 빌드해야 한다.
자주 사용하는 번들러인 vite를 이번에도 사용을 고려하게 되었다.
Vite에서도 라이브러리 모드를 지원하기 때문에 사용하는데 어려움이 없을 것으로 생각했다.
하지만, vite에서 제공하는 기능은 분명 강력하지만 rollup 기반의 번들러이며 HMR등의 기능을 사용하지 않을 예정이므로 목적에 맞는 rollup으로만 진행해도 충분할 것으로 판단했다.
rollup 기반으로 추가적인 기능을 제공하기 때문에 사용하지 않는 기능들 때문에 발생하는 추가적 의존성을 최대한 피하고자 한 것도 판단의 근거가 되었다.
import babel from '@rollup/plugin-babel';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import terser from '@rollup/plugin-terser';
import replace from '@rollup/plugin-replace';
export default [
{
input: './src/App.jsx', // 번들링 엔트리 포인트
output: {
format: 'iife', // iife': 즉시 실행 함수 표현(IIFE)로 번들링 - 브라우저에서 주로 사용
file: './dist/lib/tree.js', // 출력 파일 경로
name: 'tree', // 전역 변수 이름 (앞으로 번들된 모듈은 tree라는 이름으로 접근 가능)
exports: 'named', // named export로 내보냄
globals: {
react: 'React',
'react-dom/client': 'ReactDOM',
'react-d3-tree': 'Tree',
}, // 외부 종속성의 전역 변수 처리
},
plugins: [
babel({
babelHelpers: 'bundled',
presets: ['@babel/preset-env', '@babel/preset-react'], // react와 최신 문법을 변환
exclude: 'node_modules/**', // node_modules는 제외
}), // 바벨을 사용
resolve(), // Node.js의 모듈을 번들에 포함
commonjs({
include: 'node_modules/**',
}), // CommonJS를 ES6로 변환
terser(), // 번들된 코드를 압축하여 크기를 줄임
replace({
'process.env.NODE_ENV': JSON.stringify('production'), // process.env.NODE_ENV을 production으로 변경 (본인의 경우 없으면 에러가 발생했음)
preventAssignment: true, // 부작용을 방지한다고 함
}),
],
},
];
rollup.config.js의 모습은 위와 같다.
이제 react-d3-tree를 렌더링 하기 위한 파일을 생성한다.
import React from 'react';
import ReactDOM from 'react-dom';
import Tree from 'react-d3-tree';
function App() {
return (
<div style={{ width: '98vw', height: '98vh' }}>
<Tree data={data} pathFunc='step' orientation='vertical' translate={{ x: 800, y: 400 }} />
</div>
)
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
지금까지의 흐름은 아래와 같다.
- App.jsx를 빌드한다
- 빌드 결과는 dist/lib/tree.js에 저장된다
- HTML을 생성하여 dist/lib/tree.js에서 코드를 받아오도록 한다
- data를 HTML에 삽입한다
최종적으로 사용자에게 보여주는 html은 아래와 같은 형태로 구성된다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Import-Visualizer</title>
</head>
<body>
<div id="root"></div>
</body>
<script>
const data = {
name: 'CEO',
children: [
{
name: 'Manager',
attributes: {
department: 'Production',
},
children: [
{
name: 'Foreman',
attributes: {
department: 'Fabrication',
},
children: [
{
name: 'Worker',
},
],
},
{
name: 'Foreman',
attributes: {
department: 'Assembly',
},
children: [
{
name: 'Worker',
},
],
},
],
},
],
};
</script>
<script src="./dist/lib/tree.js"></script>
</html>
여담으로 위와 같은 방식이 작동하는 것이 신기했다.
빌드 당시에 존재하지 않는 변수인 data가 있고 추후 빌드 파일에 직접 그 데이터를 삽입하는 방식인데 제대로 작동하는 것이 신기했다.
물론 조금만 더 생각해본다면 결국 data의 포함 여부에 관계없이 App.jsx는 일반적인 js로 변환될 것이고 data를 나중에 넣는다는 것도 일반적인 js에 변수를 삽입하는 것이기 때문에 제대로 작동하는 것이 맞을 것이다.
최종적으로 다시 한 번 위의 흐름을 표현하면 아래와 같다.
- react-d3-tree를 쓰기 위해서는 react가 필요
- react를 쓰기 위해 react-dom이 필요
- react를 일반 브라우저에서 사용할 수 있도록 변환해야 함
- 3을 위해 컴포넌트를 만들고 rollup으로 빌드함
- 4의 결과로 react + react-dom + react-d3-tree로 조합된 js 파일을 얻을 수 있음
- react 관련 의존성을 제거 → 5에서 얻은 js만 있다면 위 세 라이브러리를 사용할 수 있음 + 버전이 바뀔 때 발생할 수 있는 잠재적 문제점도 모두 피할 수 있게 됨
이제 요구사항 중 시각화와 ast를 활용하여 import 문을 분석하는 기능을 완료했다.
이제 재귀적으로 파일들을 탐색하여 import 계층 데이터(트리)를 만들면 된다.
여담
리액트를 빌드하여 만들어진 js를 삽입하는 방법이 맞는지 의심을 많이 했다.
이 프로젝트에서 자주 참고하는 rollup-plugin-visualizer는 어떻게 하고 있는지 확인해봤다.
이 라이브러리에서도 같은 방법을 사용하고 있는 것을 확인했다.
차이점은 preact를 사용했다는 것이다.
preact를 사용하게 되면 일반적인 react보다 번들의 크기를 줄일 수 있다는 장점이 있는 것 같다.
현재는 리액트를 사용했지만 추후 개선작업을 하며 preact로 마이그레이션을 고려해야 할 것 같다.
'개발 > 개발과정' 카테고리의 다른 글
[import-visualizer] 7. 번들링(빌드) (0) | 2024.06.14 |
---|---|
[import-visualizer] 6. 트리 생성 (0) | 2024.06.14 |
[import-visualizer] 4. 시각화 (0) | 2024.06.14 |
[import-visualizer] 3. 확장자 (0) | 2024.06.14 |
[import-visualizer] 2. 경로별칭 (0) | 2024.06.13 |