패키지 진입점 index.ts 생성하기 위해 배럴 파일 생성 자동화
아래 블로그에서 공유해준 코드로 배럴 파일을 생성하였다.
"scripts": {
...
"barrel": "node generateBarrel.js"
},
안내해주신대로 처리 대상 디렉토리와 예외 파일 목록을 작성했고, script에 추가하여 편리하게 배럴파일을 생성할 수 있었다.
만약 내용을 수정하고 스크립트를 재호출하면 이전의 결과물을 덮어씌우게 된다.
그런데 나는 export default 를 사용해서 컴포넌트를 export 해주었고
+ 컴포넌트 파일명의 경우 파스칼 컨벤션을 사용하므로 아래와 같이 코드를 일부 수정했다.
export * from './components' 이런 식의 코드는 default export 된 컴포넌트를 가져오지 않기 때문이다
function toPascalCase(str) {
return str
.replace(/[-_](.)/g, (_, g1) => g1.toUpperCase())
.replace(/^(.)/, (_, g1) => g1.toUpperCase());
}
//...
targetFolderList.forEach((folder) => {
//...
const exportName = toPascalCase(fileNameWithoutExtension);
//...
exportStatementList.push(
`export {${exportName}} from './${fileNameWithoutExtension}';`
);
생성된 배럴파일
export { Button } from "./button";
export { CheckBox } from "./checkBox";
export { FileUpload } from "./fileUpload";
export { Radio } from "./radio";
export { Select } from "./select";
export { Slider } from "./slider";
export { Switch } from "./switch";
export { Tag } from "./tag";
export { Textarea, TextInput } from "./textfield"; // 이건 수동으로 적어줬음..
- named export vs default export
Default export
const Component = () => {...}
export default Component
import할때 아무 이름으로 import가 가능하다
변수는 export할 수 없다
나는 컴포넌트에 default export 를 사용하고있다.
Named export
export function Component1(){...}
export function Component2(){...}
한 파일 내에서 여러 변수, 클래스 등을 export 할 수 있다
나는 유틸함수나 훅에서 named export를 사용했다.
import할때 동일한 이름으로만 설정 가능하다
import { Component1, Component2 } from './MyComponents'
또는 as 를 사용하여 다른 이름으로 import
* as 를 사용하여 한 파일에 있는 클래스/변수를 한번에 import 할 수 있다
import * as Hello from './MyComponents'
// Hello.Component1 로 호출
위에서 언급했듯, 이 방법은 default export된 컴포넌트를 가져올 수 없다
근데 이렇게 했더니 빌드할때 에러가 발생한다 ㅠㅠ ㅠ
src/components/button/index.ts:1:9 - error TS2614:
Module '"./Button"' has no exported member 'Button'.
Did you mean to use 'import Button from "./Button"' instead?
1 export {Button} from './Button';
이유는
components/하위 폴더 마다 생성할 배럴파일과, components/index.ts 전체 배럴 파일의 형식이 달라야 하기 때문이다
개별 컴포넌트가 존재하는 폴더에서 index.ts 를 만들때 => default export 컴포넌트들에 대한 배럴파일 생성
export {default as MyComponent} from './MyComponent'
전체 배럴파일 생성 시에는 named export 형식으로 작성
export * from './button';
export * from './checkBox';
export * from './fileUpload';
export * from './radio';
export * from './select';
export * from './slider';
export * from './switch';
export * from './tag';
export * from './textfield';
이 부분에 해당하는 내용이
const isDir = fs.statSync(filePath).isDirectory();
const isTargetFile =
fs.statSync(filePath).isFile() &&
(file.endsWith(`.${extension}`) || file.endsWith(`.${extension}x`)) &&
file !== `index.${extension}`;
if (isTargetFile) {
exportStatementList.push(
`export { default as ${exportName} } from './${fileNameWithoutExtension}';`
);
} else if (isDir) {
const nestedIndexPath = path.join(filePath, `index.${extension}`);
if (fs.existsSync(nestedIndexPath)) {
// 디렉토리에 index.ts(x)가 있는 경우 → export * from './folder'
exportStatementList.push(
`export * from './${fileNameWithoutExtension}';`
);
} else {
// index.ts(x) 없는 경우 → default export 라고 가정
exportStatementList.push(
`export { default as ${exportName} } from './${fileNameWithoutExtension}';`
);
}
generateIndexFile(filePath); // 하위 디렉토리도 재귀 처리
}
});
친절하게 주석 달아주신 대로..
내부에 index.ts가 있으므로 named export (isDir : true)
내부에 index.ts가 없는 경우 default export
요렇게 작성하면 되시겠다~~
▼ 수정된 코드 전문
const fs = require("fs");
const path = require("path");
function toPascalCase(str) {
return str
.replace(/[-_](.)/g, (_, g1) => g1.toUpperCase())
.replace(/^(.)/, (_, g1) => g1.toUpperCase());
}
// 소스 코드 디렉토리 경로 설정
const srcFolderPath = path.join(__dirname, "src");
let count = 0;
// (직접 수정) index.js = true, index.ts = false
const isJavaScript = false;
const extension = isJavaScript ? "js" : "ts";
// (직접 수정) 처리 대상 디렉토리 목록
const targetFolderList = ["components"];
// (직접 수정) 예외 파일 목록
const exceptionFileList = [
"style.css.ts",
"type.ts",
"stories",
"ImageUploader",
"ImageInput",
"ImagePreview",
"ButtonSample",
"sample",
"OptionList",
];
targetFolderList.forEach((folder) => {
const folderPath = path.join(srcFolderPath, folder);
if (fs.existsSync(folderPath) && fs.statSync(folderPath).isDirectory()) {
generateIndexFile(folderPath);
}
});
function generateIndexFile(directoryPath) {
const indexFilePath = path.join(directoryPath, `index.${extension}`);
const relativePath = path.relative(__dirname, indexFilePath);
const files = fs.readdirSync(directoryPath);
const exportStatementList = [];
files.forEach((file) => {
const filePath = path.join(directoryPath, file);
const fileNameWithoutExtension = path.basename(file, path.extname(file));
const exportName = toPascalCase(fileNameWithoutExtension);
const isException = exceptionFileList.some((exceptionFileName) =>
file.toLowerCase().includes(exceptionFileName.toLowerCase())
);
if (isException) return;
const isDir = fs.statSync(filePath).isDirectory();
const isTargetFile =
fs.statSync(filePath).isFile() &&
(file.endsWith(`.${extension}`) || file.endsWith(`.${extension}x`)) &&
file !== `index.${extension}`;
if (isTargetFile) {
exportStatementList.push(
`export { default as ${exportName} } from './${fileNameWithoutExtension}';`
);
} else if (isDir) {
const nestedIndexPath = path.join(filePath, `index.${extension}`);
if (fs.existsSync(nestedIndexPath)) {
// 디렉토리에 index.ts(x)가 있는 경우 → export * from './folder'
exportStatementList.push(
`export * from './${fileNameWithoutExtension}';`
);
} else {
// index.ts(x) 없는 경우 → default export 라고 가정
exportStatementList.push(
`export { default as ${exportName} } from './${fileNameWithoutExtension}';`
);
}
generateIndexFile(filePath); // 하위 디렉토리도 재귀 처리
}
});
const content = exportStatementList.join("\n") + "\n";
fs.writeFileSync(indexFilePath, content);
console.log(`${++count} \t ${relativePath}`);
}
'개발개발 > Date-project' 카테고리의 다른 글
조건부 렌더링 - 비정상적인 버튼 동작 발생 원인 / 휴리스틱 알고리즘 / key prop의 중요성 (0) | 2025.04.21 |
---|---|
submit 버튼이 아닌데 submit 되는 버그 (0) | 2025.04.18 |
모노레포 vite build 결과물이 이상해요? (0) | 2025.04.08 |
[react-range-slider] 양방향 input range 라이브러리 적용하기 (0) | 2025.04.07 |
svg 컴포넌트로 사용하기 (0) | 2025.03.31 |