[개발일지] 07. Tuist Scaffold로 모듈 생성 자동화하기
Micro-Features Architecture로 모듈화된 환경에서는 새로운 기능을 추가할 때마다 하나의 Feature 모듈을 생성해야 합니다.
Tuist 기반으로 모듈화를 구성해두었기 때문에, 새로운 Feature를 추가하는 과정 자체는 어렵지 않았습니다.
하지만 실제로 하나의 Feature를 만들기 위해서는 다음과 같은 작업이 반복됩니다.
- Feature 모듈 생성
- Interface 모듈 생성
- Tests 모듈 생성
- Test 모듈 생성
- Example 모듈 생성
즉, 하나의 Feature를 추가할 때마다
총 5개의 모듈을 생성해야 하고, 이 과정을 매번 반복해야 합니다.
더해서, 각 모듈마다 기본적으로 필요한 placeholder 파일과 구조도 함께 생성해야 합니다.
이 과정은 시간이 오래 걸리고 구조를 빠뜨리거나 일관성이 깨질 가능성도 존재합니다.
그래서 이 반복 작업을 줄이고, 항상 동일한 구조를 보장할 수 있는 자동화 방법이 필요했습니다.
Tuist Scaffold?
What is Tuist? | Tuist
docs.tuist.dev
scaffold는 템플릿 기반으로 파일과 폴더 구조를 자동 생성해주는 기능입니다.
간단히 이야기 해보면, 정해진 구조를 미리 템플릿으로 만들어두고, 명령어 한 줄로 동일한 구조를 생성할 수 있는 기능입니다.
"딸깍"
왜 Scaffold를 사용할까요?
scaffold를 사용하면
- 반복 작업 제거
- 파일 생성 시간 단축
- 실수 방지
- 아키텍처 강제 적용
과 같은 효과를 얻을 수 있습니다.
특히 Micro-Features Architecture처럼 구조가 중요한 프로젝트에서는 Scaffold의 효과가 크게 나타납니다.
일관된 구조를 생성할 수 있기 때문이죠.
Stencil?
Stencil – Swift Package Index
Stencil by Stencil Project on the Swift Package Index – Stencil is a simple and powerful template language for Swift.
swiftpackageindex.com
scaffold는 단순히 파일을 복사하는 방식이 아니라, Stencil 템플릿을 기반으로 동작합니다.
Stencil은 Swift에서 사용하는 템플릿 엔진으로, 미리 정의된 템플릿에 변수를 주입하여 파일을 생성할 수 있습니다.
예를 들어, 아래와 같은 템플릿 파일이 있다고 가정해보겠습니다.
final class {{ name }}ViewController: UIViewController {
}
여기서 {{ name }}은 변수입니다.
tuist scaffold MicroFeature --name Home
그리고 이런 명령어를 실행하면
final class HomeViewController: UIViewController {
}
이렇게 생성됩니다. 즉, 템플릿에 값을 주입해서 동적으로 코드가 생성되는 방식입니다.
왜 Stencil을 사용할까요?
단순히 파일을 복사하고 붙여넣는 것과 달리, Stencil을 아래와 같은 장점이 있습니다.
- 변수 기반 코드 생성 가능
- 파일 이름과 내부 코드 동기화
- 반복되는 코드 패턴 자동화
- 다양한 케이스 대응 가능
예를 들어, HomeVC, LoginVC, SearchVC 모두 하나의 템플릿으로 생성 가능합니다.
자 그럼 자동화 해봅시다!
Tuist Scaffold와 Stencil이 무엇인지 간단히 알아보았으니, 이제 제가 어떻게 실제로 템플릿을 구성하고 자동화했는지 이야기해보겠습니다.
Scaffold 디렉토리 구조
Micro Feature 생얼을 위해 아래와 같은 Scaffold를 구성했습니다.
Tuist/
└── Scaffold/
└── Templates/
├── Project.stencil
├── MicroFeature.swift
├── InterfacePlaceholder.stencil
├── RootViewController.stencil
├── SceneDelegate.stencil
├── Tests.stencil
└── TestingPlaceholder.stencil
일반적으로 Tuist Scaffold는 `template.stencil` 파일을 진입점으로 사용하지만,
하나의 템플릿 파일이 아닌 여러 stencil 파일을 조합하는 방식으로 설계했습니다.
그 이유는 아래와 같습니다.
역할 분리
Feature를 구성하는 요소들은 각각 역할이 다릅니다.
- 예제 앱
- 피쳐 구현 코드
- 인터페이스 정의
- 테스트 코드
이 모든 것을 하나의 템플릿 파일에 작성하게 되면 파일이 커지고, 각 역할이 뒤섞이게 되어 후에 수정이나 유지보수가 어려울 것이라고 판단되어 역할별로 템플릿 파일을 분할했습닏다.
각 파일은 역할별로 나뉘어 있으며, Tuist는 해당 디렉토리의 모든 템플릿을 읽어 하나의 Feature 구조를 생성합니다.
1. "생성 결과물" 설계
가장 먼저, Scaffold로 최종적으로 어떤 구조를 만들지 결정해야합니다.
예를 들어 Home Feature를 만들면 아래 구조가 먼저 생성되도록 정의합니다.
Features/
└── Home/
├── Feature
├── Interface
├── Tests
├── Testing
└── Example
그리고 각 모듈 안에 어떤 파일이 필요한지도 먼저 정해야합니다.
Flyleaf 프로젝트는 각 모듈 안에
Feature -> VC, ViewModel, Builder
Interface -> Placeholder
Tests -> 기본 테스트 코드
Testing -> Placeholder
Example -> SceneDelegate
위와 같은 파일들이 필요했습니다.
Placeholder가 뭔지 궁금해 하실텐데요!
Interface, Testing과 같은 모듈은 초기에는 실제 코드가 필요 없습니다.
하지만 Xcode는 빈 폴더를 인식하지 않기 때문에, 파일이 하나도 없으면 해당 디렉토리가 프로젝트에 포함되지 않습니다.
이러한 문제를 해결하기 위해, 각 모듈에 최소한의 파일을 유지하기 위한 Placeholder 파일을 추가했습니다.
2. 각 파일에 대한 Stencil 템플릿 작성
구조가 정해졌다면, 이제 실제로 생성할 파일들의 템플릿을 만들면 됩니다.
예를 들어 제가 만든 템플릿은 아래와 같습니다.
1. RootViewController.stencil
Feature모듈의 진입 VC 템플릿입니다.
- 기본 UI 구조
2. InterfacePlaceholder.stencil
Interface 모듈을 위한 Placeholder 템플릿입니다.
3. SceneDelegate.stencil
Example 실행을 위한 SceneDelegate 템플릿입니다.
- Feature를 독립적으로 실행할 수 있도록 구성
4. Tests.stencil / TestingPlaceholder.stencil
테스트 관련 코드 생성 템플릿입니다.
- 기본 테스트 구조
5. Project.stencil
Tuist 프로젝트 정의를 생성하는 템플릿입니다.
- Feature, Interface, Tests 등 각 모듈의 Project.swift 생성
- 타겟, 의존성, 설정 자동 구성
3. Project.swift 자동 생성 템플릿 생성
Tuist를 사용하고 있기 때문에, Project.swift 파일까지 함께 생성되어야 합니다.
예를 들어 아래와 같이 작성할 수 있습니다. (단순한 예제코드일뿐, Tuist 세팅에 맞게 작성해야합니다.)
import ProjectDescription
let project = Project(
name: "{{ name }}Feature",
targets: [
Target(
name: "{{ name }}Feature",
platform: .iOS,
product: .framework,
bundleId: "com.app.{{ name }}Feature",
sources: ["Sources/**"],
dependencies: [
.project(target: "{{ name }}Interface", path: "../{{ name }}Interface")
]
)
]
)
이 템플릿은 단순히 파일 하나를 만드는 것이 아니라, 어떤 모듈을 만들지, 어떤 타겟을 만들지, 어떤 구조를 만들지를 함께 정의합니다.
4. Template 정의
What is Tuist? | Tuist
docs.tuist.dev
Stencil 템플릿을 작성했다면, 이제 어떤 파일을 어디에 생성할지 정의해야 합니다.
Tuist에서는 이를 `Template`로 정의합니다.
let template = Template(
description: "MicroFeature scaffold",
attributes: [
.required("name"),
.required("case")
],
items: [
.file(
path: "Features/{{ name }}/Project.swift",
templatePath: "Project.stencil"
),
...
]
)
이 템플릿 파일은
- 어떤 파일을 생성할지 정의
- 파일이 생성될 경로 지정
- 어떤 stencil 템플릿을 사용할지 연결
- 입력 값을 템플릿에 전달
하는 역할을 합니다.
5. Scaffold 등록
Stencil 템플릿과, Tuist 템플릿 파일을 다 만들었다면, 이제 Tuist가 템플릿을 인식할 수 있도록 연결해야 합니다.
let config = Config(
plugins: [],
templates: [
.string(name: "Feature", path: "Tuist/Scaffold")
]
)
딸깍
tuist scaffold MicroFeature --name Home --case home
명령어를 입력하면...!
복잡하고 번거로웠던 작업들이 한 번에 처리됩니다.