Section 7/171 menit

7. Scaffolding Templates

7. Scaffolding Templates

Membuat Template Module

Scaffolding Tuist memungkinkan generate kode boilerplate secara konsisten dari template:

swift
Tuist/
└── Templates/
    ├── feature/
    │   ├── template.swift      ← definisi template
    │   └── Sources/
    │       └── ___NAME___View.swift.stencil   ← file template
    └── core/
        ├── template.swift
        └── Sources/
            └── ___NAME___Service.swift.stencil

template.swift untuk Feature Module

swift
// Tuist/Templates/feature/template.swift
import ProjectDescription

let nameAttribute: Template.Attribute = .required("name")
let authorAttribute: Template.Attribute = .optional("author", default: "Developer")

let template = Template(
    description: "Feature module template dengan View + ViewModel + Tests",
    attributes: [nameAttribute, authorAttribute],
    items: [
        // Project.swift
        .file(
            path: "Projects/Features/Feature\(nameAttribute)/Project.swift",
            templatePath: "Project.swift.stencil"
        ),
        // Main source file
        .file(
            path: "Projects/Features/Feature\(nameAttribute)/Sources/Feature\(nameAttribute)/Feature\(nameAttribute)View.swift",
            templatePath: "Sources/FeatureView.swift.stencil"
        ),
        // ViewModel
        .file(
            path: "Projects/Features/Feature\(nameAttribute)/Sources/Feature\(nameAttribute)/Feature\(nameAttribute)ViewModel.swift",
            templatePath: "Sources/FeatureViewModel.swift.stencil"
        ),
        // Test file
        .file(
            path: "Projects/Features/Feature\(nameAttribute)/Tests/Feature\(nameAttribute)Tests/Feature\(nameAttribute)ViewModelTests.swift",
            templatePath: "Tests/FeatureViewModelTests.swift.stencil"
        ),
        // Workspace update (directory creation)
        .directory(
            path: "Projects/Features/Feature\(nameAttribute)/Resources",
            sourcePath: "Resources"
        ),
    ]
)

Stencil Template Files

swift
// Tuist/Templates/feature/Sources/FeatureView.swift.stencil
// Template menggunakan Stencil syntax: {{ name }}, {% if ... %}

import SwiftUI

// Feature: {{ name }}
// Author: {{ author }}
public struct Feature{{ name }}View: View {

    @StateObject private var viewModel = Feature{{ name }}ViewModel()

    public init() {}

    public var body: some View {
        NavigationView {
            VStack {
                if viewModel.isLoading {
                    ProgressView()
                } else {
                    content
                }
            }
            .navigationTitle("{{ name }}")
            .task {
                await viewModel.onAppear()
            }
        }
    }

    @ViewBuilder
    private var content: some View {
        Text("Feature {{ name }}")
    }
}

#Preview {
    Feature{{ name }}View()
}
swift
// Tuist/Templates/feature/Sources/FeatureViewModel.swift.stencil

import Foundation
import Observation

@MainActor
@Observable
public final class Feature{{ name }}ViewModel {

    public private(set) var isLoading = false
    public private(set) var errorMessage: String?

    public init() {}

    public func onAppear() async {
        isLoading = true
        defer { isLoading = false }

        do {
            try await loadData()
        } catch {
            errorMessage = error.localizedDescription
        }
    }

    private func loadData() async throws {
        // TODO: implement
        try await Task.sleep(for: .milliseconds(500))
    }
}
swift
// Tuist/Templates/feature/Project.swift.stencil

import ProjectDescription
import ProjectDescriptionHelpers

let project = Project.module(
    name: "Feature{{ name }}",
    layer: .feature,
    dependencies: [
        .core("NetworkKit"),
        .shared("SharedUI"),
    ],
    hasExampleApp: false
)

Menggunakan Template

swift
# Generate feature module baru
tuist scaffold feature --name Payment --author "Alice"

# Output:
# ✅ Projects/Features/FeaturePayment/Project.swift
# ✅ Projects/Features/FeaturePayment/Sources/FeaturePayment/FeaturePaymentView.swift
# ✅ Projects/Features/FeaturePayment/Sources/FeaturePayment/FeaturePaymentViewModel.swift
# ✅ Projects/Features/FeaturePayment/Tests/FeaturePaymentTests/FeaturePaymentViewModelTests.swift
# ✅ Projects/Features/FeaturePayment/Resources/

# Tambahkan ke Workspace.swift
# Jalankan tuist generate
# Langsung coding

Template untuk Core Module

swift
// Tuist/Templates/core/template.swift
import ProjectDescription

let nameAttribute: Template.Attribute = .required("name")

let template = Template(
    description: "Core module template (service/repository pattern)",
    attributes: [nameAttribute],
    items: [
        .file(
            path: "Projects/Core/\(nameAttribute)/Project.swift",
            templatePath: "Project.swift.stencil"
        ),
        .file(
            path: "Projects/Core/\(nameAttribute)/Sources/\(nameAttribute)/\(nameAttribute)Service.swift",
            templatePath: "Sources/Service.swift.stencil"
        ),
        .file(
            path: "Projects/Core/\(nameAttribute)/Sources/\(nameAttribute)/\(nameAttribute)ServiceProtocol.swift",
            templatePath: "Sources/ServiceProtocol.swift.stencil"
        ),
        .file(
            path: "Projects/Core/\(nameAttribute)/Tests/\(nameAttribute)Tests/\(nameAttribute)ServiceTests.swift",
            templatePath: "Tests/ServiceTests.swift.stencil"
        ),
    ]
)