├── .github └── workflows │ ├── deploy.yml │ └── docsearch.yml ├── .gitignore ├── Content ├── 404.md ├── blog │ ├── iga-ios-1.md │ ├── index.md │ ├── letswift-2023.md │ ├── publish-introduce.md │ ├── publish-part-1.md │ ├── publish-part-2.md │ ├── publish-part-3.md │ ├── scade-introduce.md │ ├── swiftui-need-mvvm.md │ ├── tuist-xcodebeta.md │ ├── tuistui.md │ ├── universal-framework.md │ ├── what-is-swift.md │ ├── what-is-swiftui.md │ └── yackety-yak-ios-4.md └── index.md ├── LICENSE ├── Makefile ├── Package.resolved ├── Package.swift ├── README.md ├── Resources ├── favicon.ico ├── robots.txt └── static │ ├── fonts │ ├── OpenSans-Bold.ttf │ ├── OpenSans-ExtraBold.ttf │ ├── OpenSans-Light.ttf │ ├── OpenSans-Medium.ttf │ ├── OpenSans-Regular.ttf │ └── OpenSans-SemiBold.ttf │ ├── icons │ ├── arrow-left.svg │ ├── arrow-right.svg │ ├── copy.svg │ ├── envelope-solid.svg │ ├── github.svg │ ├── linkedin.svg │ └── rss-solid.svg │ ├── images │ ├── about.svg │ └── icon.svg │ └── scripts │ └── docsearch.js ├── Sources ├── Components │ ├── Common │ │ └── BasicScripts.swift │ ├── Footer.swift │ ├── Head.swift │ ├── Header.swift │ ├── Pagination.swift │ └── PreviewPost.swift ├── Datas │ ├── Constants.swift │ └── SocialMediaLink.swift ├── Layouts │ ├── ArchiveLayout.swift │ ├── BaseLayout.swift │ ├── PageLayout.swift │ ├── PostLayout.swift │ └── PostsLayout.swift ├── Pages │ ├── 404.swift │ ├── About.swift │ ├── HTMLFactory.swift │ ├── Index.swift │ ├── Page │ │ └── Page.swift │ ├── Post │ │ └── Post.swift │ ├── Sections │ │ └── Section.swift │ └── Tags │ │ ├── TagDetails.swift │ │ └── TagList.swift ├── Styles │ └── global.css ├── Theme.swift ├── Utils │ ├── Foundation │ │ ├── Array.swift │ │ └── DateFormatter.swift │ ├── HTML │ │ ├── HTMLBuilder.swift │ │ └── HTMLConvertable.swift │ ├── Import │ │ └── Import.swift │ ├── Pagination │ │ ├── Blog+paginatedPath.swift │ │ ├── Index+paginatedPath.swift │ │ └── PublishingContext+allPaginatedItems.swift │ ├── Plot │ │ ├── BodyContext.swift │ │ ├── ElementDefinitions.swift │ │ ├── Script.swift │ │ ├── SourceContext.swift │ │ └── Time.swift │ └── Publish │ │ └── PublishingStep │ │ ├── 404Error.swift │ │ ├── CodeBlock.swift │ │ ├── PaginatedPages.swift │ │ └── Tailwind.swift └── main.swift ├── docsearch.config.json ├── package.json └── tailwind.config.js /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: 9 | group: deploy-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | blog-deploy: 14 | runs-on: macos-13 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Select Xcode 15 18 | run: sudo xcode-select -s /Applications/Xcode_15.0.1.app 19 | 20 | - name: Setup Nodejs 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: '20' 24 | 25 | - name: Generate blog 26 | run: make run 27 | 28 | - name: Install dependencies 29 | run: npm install 30 | 31 | - name: Run Tailwind CSS 32 | run: make tailwind 33 | 34 | - name: Deploy 35 | uses: peaceiris/actions-gh-pages@v3 36 | with: 37 | github_token: ${{ secrets.GH_TOKEN }} 38 | publish_dir: ./Output 39 | publish_branch: gh-pages 40 | cname: blog.jihoon.me -------------------------------------------------------------------------------- /.github/workflows/docsearch.yml: -------------------------------------------------------------------------------- 1 | name: docsearch 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: 9 | group: docsearch-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | algolia: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Get the content of config file 19 | id: algolia_config 20 | run: echo "config=$(cat docsearch.config.json | jq -r tostring)" >> $GITHUB_OUTPUT 21 | 22 | - name: Push indices to Algolia 23 | uses: signcl/docsearch-scraper-action@master 24 | env: 25 | APPLICATION_ID: ${{ secrets.ALGOLIA_APPLICATION_ID }} 26 | API_KEY: ${{ secrets.ALGOLIA_API_KEY }} 27 | CONFIG: ${{ steps.algolia_config.outputs.config }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /build 3 | /.build 4 | /.swiftpm 5 | /*.xcodeproj 6 | .publish 7 | Output 8 | .vscode 9 | /node_modules 10 | package-lock.json 11 | yarn.lock 12 | .env -------------------------------------------------------------------------------- /Content/404.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Content/blog/iga-ios-1.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 제 1회 IGA 발표 회고록 3 | date: 2023-07-10 13:22 4 | tags: Recollection, Announcement 5 | description: IGA 발표 회고 6 | postImage: https://github.com/jihoonahn/blog/assets/68891494/083d7040-3ed2-4b8a-9ba3-6af7e66878d9 7 | --- 8 | 9 | 10 | 11 | 이번 제 1회 iOS IGA에서 CLI 구축 방법에 대해서 발표를 했고, 이에 대한 회고 입니다.

12 | 13 | 14 | ## 발표 준비 15 | 16 | 발표 전 원래 주제로 **"생산성으로 위한 Script"**에 대해서 발표를 하려고 생각하고 있었습니다.
17 | 18 | 하지만 Swift로 작성하는 Script는 하나의 작업을 실행하기 위해서 Script에 많은 양의 코드가 들어가고, 중복되는 코드.. 등등 제작하는 것도 리소스가 많이 소비가 되는데, 하나의 작업을 위해서 제작이 되기 때문에 사람들이 사용할만한 이유가 부족하다고 생각이 되었습니다.
19 | 20 | 여러가지 방법을 찾아보다가 Swift Argument Parser라는 라이브러리를 보게 되었고, 오히려 지금 제가 추구하는 생산성은 CLI 쪽이 맞겠다 생각을 해서, 주제를 **"생산성을 위한 CLI 구축"**으로 바꾸게 되었습니다.
21 | 22 | 이번 발표에서는 좋은 예제를 만들고 싶어서, 고민을 많이 했습니다. 23 | 24 | ``` 25 | 1. CLI의 장점을 잘 보여주기 위해서 전용 CLI 제작 26 | 2. ML이라는 주제를 더 잘 녹여 낼 수 있는 GPT CLI 제작 27 | ``` 28 | 29 | 2개의 주제를 가지고 일주일 정도를 고민했습니다.
30 | 1번을 선택하면 발표주제를 더 깊게 설명할 수 있고, 2번을 선택하면 컨퍼런스 주제와 CLI에 대한 흥미를 더 줄 수 있지 않을까? 라고 생각을 했습니다.
31 | 32 | 결과적으로는 컨퍼런스 주제를 더 잘 녹여내자는 생각에 2번을 선택했습니다.

33 | 34 | ## 발표 시작 35 | 36 | 제 발표는 3번째였습니다. 37 | 38 | 39 | 40 | 처음 추영욱님이 먼저 나가셔서 오프닝을 시작했습니다. 오프닝때 부터 반응이 좋았었고, 오프닝이 끝난 후 41 | 저 이전 발표자 분이신 긱코드님이 나가셔서 발표를 진행하셨는데 너무 발표를 재밌게 잘하셔서 더 떨리더라고요 ㅎㅎ..
42 | 43 | 어느정도 떨긴 했지만 최대한 많은 내용을 전달하고자 하는 생각으로 발표를 시작하게 되었습니다. 44 | 45 | 46 | 47 | 48 | 발표는 다음 순서로 진행이 됬습니다. 49 | 50 | ``` 51 | 1. CLI 제작기 52 | 2. CLI vs Script 53 | 3. CLI 제작 방법 54 | 4. 예시 프로젝트 55 | 5. 저는 CLI를 이런 곳에 사용합니다. 56 | 6. CLI가 사용되는 곳 57 | ``` 58 | 59 | 1번, 2번에서는 목차에서 제가 CLI를 제작하게 된 계기를 위에 Script와 고민했던 것, 그리고 그 이후에 CLI를 제작하게 된 배경에 대해서 설명하고
60 | 3번째에는 **Swift Argument Parser** 사용 방법에 대해서 이야기 하고,
61 | 4번째에는 위에서 이야기한 CLI에서 GPT를 사용하는 예시 프로젝트에 대해서,
62 | 그리고 5번째는 CLI를 어디서 이용했는지에 대한 이야기를 하였고,
63 | 마지막은 대부분 iOS 개발자들이 알 수 있는 메이저한 라이브러리/프레임워크에서 어디에 사용됬는지에 대해서 이야기 했습니다.

64 | 65 | 66 | ## 느낀점 67 | 68 | 저번에 와글와글 iOS 발표를 하고, 이번에는 처음으로 오프라인 발표를 해봤지만.. 확실히 온라인 발표와 오프라인 발표는 뭐가 쉬웠다 이런게 아닌 두가지 느낌이 완전히 다르다고 느껴졌습니다.
69 | 70 | 와글와글때는 온라인에서 발표 전에는 진정이 많이 됬는데, 진행하는 도중에 떨리게 되었고, 이번에 진행한 IGA 오프라인 발표는 발표전에 오히려 많이 떨리고, 진행하면서 진정이 많이되는 느낌을 받았습니다.
71 | 72 | 발표를 2번 진행하면서 이번에도 몇번씩 실수를 하는것을 보며, 여전히 부족하다는 것을 깨닳게 되었고, 이번에는 CLI라는 주제가 Swift에서는 자주 발표하는 주제가 아닌 만큼, 걱정이 많이 됬었지만 오거나이저 분들과 스피커 분들도 다들 좋게 말해주셔서 다행이라고 생각이 되었습니다. 73 | -------------------------------------------------------------------------------- /Content/blog/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Blog 3 | description: Jihoonahn's Blog 4 | --- 5 | -------------------------------------------------------------------------------- /Content/blog/letswift-2023.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: LetSwift 2023 회고록 3 | date: 2023-12-13 16:20 4 | tags: Recollection, Announcement 5 | description: LetSwift 2023 발표 회고 6 | postImage: https://github.com/jihoonahn/blog/assets/68891494/b2f52e41-29ec-4a44-a6af-e41f564883f6 7 | --- 8 | 9 | 10 | 11 | 지난 10월 13일 [LetSwift](https://letswift.kr/2023/) 에서 `Swift로 CrossPlatform` 하기 라는 주제로 발표하였고, 이에 대한 회고 입니다. 12 | 13 | ## 발표 신청 14 | 처음 LetSwift에서 발표 신청을 받는 것을 보고 LetSwift 주제인 "Deep Dive into the unknown"에 맞는 주제를 찾으려고 해서 15 | 16 | 제가 많이한 활동으로 총 3가지의 주제를 생각해 봤습니다. 17 | 18 | 1. Tuist Plugin 사용하기 19 | 2. Swift로 Website Deep Dive 20 | 3. Swift로 CrossPlatform 하기 21 | 22 | 23 | 첫번째 꺼는 기존 대부분 사용하는 tuist local plugin에서 더 나아가 Tuist Plugin을 SPM으로 배포 관련한 내용에 대해서 이야기 하려고 했습니다. 24 | 25 | 하지만 너무 간단한 내용이 될거 같아서 Deep Dive라는 취지에는 맞지 않다고 생각해서 생략 하였고, 26 | 27 | Swift 로 Website Deep Dive같은 경우는, Swift WASM 관련된 내용을 다룰 예정이였지만, 비록 WASM을 사용하는 방법이 아니긴 하지만 기존에 Swift로 StaticSite에 관련 내용을 다룬 적이 있어서 제외하였습니다. 28 | 29 | Swift로 CrossPlatform하기 라는 주제는 기존 거의 알려지지 않는 Swift CrossPlatform인 Scade라는 플랫폼을 소개하면서, 기존에 사용하던 경험을 이야기 하는 것이 Deep Dive의 취지에 맞겠다고 생각했습니다. 30 | 31 | 그렇게 해서 "Swift로 CrossPlatform 하기" 라는 주제로 결정하여, LetSwift의 연사 기회를 얻게 되었습니다. 32 | 33 | ## 발표 준비 34 | 35 | 처음에 발표를 진행할 때 고려해야될 부분이 많았습니다. 기존 많이 사용하는 메이저한 플랫폼이 아닌, 많이 마이너한 Swift CrossPlatform이다 보니, 이게 무엇인지, 어떻게 사용하는지에 대한 내용은 필수적으로 들어가야 하며, 36 | 37 | 제가 이번 발표에서 중요하게 여겼던 부분은 직접 Scade를 사용해보면서 아직까지 존재하는 한계점에 대한 내용도 중요하게 생각했으며, 추가적으로 Scade가 보고 있는 목표가 무엇인지도 전달하는 것도 넣으면 괜찮겠다고 생각했습니다. 38 | 39 | 발표를 준비하기 앞서서 Swift CrossPlatform에 대한 추가적인 자료와 더 깊은 공부가 필요했습니다. 40 | 41 | 하지만 Scade같은 경우 문서가 업데이트가 늦게 되는 경우가 있다는점, 제공되는 문서 자체가, 사용방법만 알려주고, 내부 구조 관련되어 있는 내용이 없다는 점...등 42 | 43 | 발표 자료를 준비하면서, 기존에 많이 사용해 봐서 이 주제로 누구보다 잘 말할 수 있다고 생각을 했지만, 기존 여러가지 Flutter와 ReactNative 관련 발표들 만큼의 퀄리티는 힘들겠구나 생각이 되었습니다. 44 | 45 | 처음에 Scade라는 하나의 플랫폼을 전부 설명하는데 40분이상이 걸릴것 같아서 처음 40분 발표를 선택하였고 46 | 47 | 그렇게 10월 6일날 1차 온라인 리허설이 진행을 되었습니다. 48 | 49 | 처음 발표자료를 만들 때, 대부분 Scade에 대해서 처음 들어볼 것으로 추정하였고, 내가 깊게한 Scade라는 플랫폼에 대한 내용을 보여주는것으로 목표를 정했습니다. 50 | 51 | 하지만 처음 리허설이 끝나고 여러가지 피드백을 받으면서 일단 시간이 너무 짧게 끝나버리면서 부터 문제가 발생하였습니다. 52 | 53 | ppt 74 페이지였지만, 40분에는 한참 미치지 못하는 22분으로 끝이 났고, 추가적으로 내용적인 부분도 기술적인 내용에 초점을 맞춰주면 좋겠다는 피드백을 받았습니다. (그외 버벅이는 부분 목소리 키우기 등등.. 사소한 부분도 상세하게 피드백 받았습니다.) 54 | 55 | 피드백을 받았지만, 어느정도의 기술적인 내용인가에 대한 부분과 기존 ppt를 전부 다 갈아야 된다는 생각에 어디서부터 고쳐야 하는거지라고 하면서 고민이 많을때 56 | 57 | 58 | 59 | 리이오 님이 발표 자료에 대한 피드백을 주었습니다. 60 | 61 | ``` 62 | - 발표 내용의 과정이 많이 빠짐 63 | - 타겟과 얼라인이 안맞는 문제 64 | 65 | 1. 어떻게 생겨먹은 녀석인지 (Flutter 비교) 66 | 2. Xcode 기존 프로젝트에서 -> Scade로 마이그레이션 67 | 3. 새로운 프로젝트 Scade 고려 68 | 4. 배포 가능한지 69 | 5. 코드로 UI 짜기 70 | 6. UI Framework인데, Lifecycle 에 대한 내용 -> 안드로이드에 대한 내용이 부족 71 | 72 | 1. 발표를 들으면 -> CrossPlatform을 사용할 수 밖에 없었던 이유 73 | 2. 분기 처리를 해줄 부분 필요 74 | 75 | 76 | 1. 듣는 사람들에게 Combine 지원 안하고 -> Swift Concurrency 77 | ``` 78 | 79 | 기존 내용을 다 설명하기 힘든 부분이 있어서 정리한 내용으로 대체 하였습니다. 80 | 81 | 기존 내용을 듣고 세부적으로 피드백을 해주시고, 추가적인 아이디어도 제시를 해주셨습니다. 82 | 83 | 어느정도 감을 못잡고 있을때, 리이오님 덕분에 많은 부분을 해결하고 밤을 새서 v3를 만들었고 84 | 85 | 10월 8일날 2차 리허설이 시작 되었습니다. 86 | 87 | 2차 리허설 이후에는 내용에 관한 더욱 상세적인 피드백을 주셨습니다. 88 | 89 | 기존 EObject 원리에 대한 피드백과 iOS 개발자 관점에서 설명해주는 것이 어떤지, 90 | 91 | 갑작스럽게 들어가는 Layout에 대한 내용을 천천히 들어가는것이 어떤지, 기존 도입부를 바꿔보는 것이 어떤지 (다른 크로스 플랫폼과의 비교), 발표를 진행하기 위해서 궁금증을 끌어내고 진행하는 것이 좋을것 같다는 의견 92 | 93 | 그리고 발표자료의 비어보이는 과정에 대한 내용에 관련하여도 세부적으로 피드백을 받게 되었습니다. 94 | 95 | 총 94 페이지를 다시 만들고 진행하였으나, 똑같이 시간이 부족하게 측정이 되어서, 내용을 계속 추가하는 것보다는, 차라리 좀 내용을 깊게 정리하고 시간을 줄이는게 맞을거 같다고 판단이 들어서 오거나이저 분들과 상의하여 타임테이블을 20분으로 변경하게 되었습니다. 96 | 97 | 그리고 이제 남은것은? 98 | 99 | 피드백을 바탕으로 계속 수정.. (평균 ppt 1번당 100장) 100 | 101 | 그렇게 총 6번의 수정을 끝으로 발표 자료를 완성하게 되었습니다. (v1은 삭제되서 못찾았습니다.. ㅠㅠ) 102 | 103 | 104 | 105 | ## 발표 시작 106 | 107 | 108 | 109 | 저의 발표는 2시40분에 `대화의실 2`에서 진행되었습니다. 110 | 111 | 바로 전 발표가 당근마켓 팀이다 보니, 더욱 긴장한 채로 발표를 진행하게 되었습니다. 112 | 113 | 114 | 115 | 이전 발표가 끝나고 발표실로 들어갔을때 생각보다 많은 사람들이 발표를 보러 와주셔서 많이 긴장했지만 감사한 마음으로 진행하였습니다. 116 | 117 | 막상 발표를 진행할 예정이 되니, 머리속이 하얗게 변하면서 실수 할까봐 대본을 보면서 발표를 진행하게 되었습니다. 118 | 119 | 발표는 이런식으로 구성이 되었습니다. 120 | 121 | ``` 122 | 1. Swift로 Cross Platform을 하게된 배경 123 | 2. What is Scade 124 | 3. Scade는 무엇을 제공해줄까요? 125 | 4. Scade 설정 방법 126 | 5. Scade 시작해볼까요? 127 | 6. Scade는 어떻게 동작해요? 128 | 7. 마무리 129 | ``` 130 | 131 | 총 99장의 ppt로 진행 되었습니다. 132 | 133 | 첫번째 목차에서는 제가 Scade를 접하게 된 배경을 말하면서, 저와 비슷한 경험이 있는 사람이라면 Scade를 시도해 볼만하다 생각이 들게 하는 것이 목적으로 진행하였고, 134 | 135 | 2,3번은 Scade가 무엇인지이고 무엇을 제공해주는지에 대한 내용을 담았습니다. 136 | 그리고 Scade같은 경우는 전용 IDE를 사용해야해서 4번에 대한 내용은 어쩔 수 없이 포함하게 되었습니다. 137 | 138 | 5번에서는 프로젝트 생성 방법과 코드를 보면서 각각 어떤 역할 처럼 진행 되는지 그리고 간단한 예시 프로젝트를 준비하여 Scade에 대한 어느정도 감을 잡기 위한 토대를 만들었습니다. 139 | 140 | 그리고 Deep Dive에 관한 내용을 6번째인 "Scade는 어떻게 동작해요"라는 부분에 빌드 시스템과 Scade에서 어떻게 JVM과 상호작용 하는지에 대해서 최대한 넣어보았습니다. 141 | 142 | 마무리로는 아직 Scade의 상황과, 추후 Scade가 말하는 목표에 대해서 이야기를 해보면서 발표를 끝냈습니다. 143 | 144 | (TMI) 이전 발표를 연습하면서 24분 정도로 측정이 되서 내용이 초과되겠다 싶었지만, 발표 자리에서 너무 긴장하면서 말이 빨라져 정확하게 20분을 맞춘.. 에피소드도 있습니다. 145 | 146 | ## 발표가 끝나고 147 | 148 | 149 | 150 | 준비해둔 PPT가 끝이나고, Q&A 시간이 시작되었습니다. 151 | 152 | TMI를 하자면, 제가 귀가 좀 안좋아서 다른 사람이 말할때 두번 이상 들어야할때가 있는데, 이게 LetSwift에서 Q&A를 할 때 귀가 안좋은 것 때문에, "혹시 질문을 다시 말씀해 주실수 있나요?" 라고 말을 하게 되었습니다. 153 | 154 | 기존 내용에서 3개 정도 질문을 받고 발표를 마치게 되었습니다. 155 | 156 | 발표를 전부 마치고, 좀 많이 떨어버린 것 때문에 많은 걱정과 함께, 7일 내내 발표 자료만 수정하고 하던 시간이 지나서 이제 발표가 끝났다 라는 안도감이 함께 섞인 기분이였습니다. 157 | 158 | ## 느낀점 159 | 솔직하게 LetSwift라는 큰 자리에 대한 부담감도 있었고, 발표를 진행하면서 떨기도 하고 그런 부분에서 아쉬움이 많이 남았습니다. 160 | 161 | 처음 리허설을 진행해보고, 비록 그래도 실수가 있었지만 꽤 많은 것을 얻을 수 있었으며, 오거나이저 분들이 많이 도움을 주셔서 그래도 성공적으로 발표를 끝마칠 수 있었다고 생각이 들었습니다. 162 | 163 | --- 164 | 165 | **군대 가기전** 마지막 발표로 LetSwift에서 발표하게 되어 크나큰 경험을 하게 되었다고 생각을 합니다. 166 | 167 | 좋은 자리를 마련해주신 오거나이저 분들께 다시 한번 감사의 인사를 전하며 이번 포스트를 마치도록 하겠습니다. 168 | -------------------------------------------------------------------------------- /Content/blog/publish-introduce.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Publish 소개 3 | date: 2023-08-22 16:51 4 | tags: Swift, Publish, Web, Theory 5 | description: Swift로 Static Site 만들기 6 | postImage: https://github.com/jihoonahn/blog/assets/68891494/3404041c-f48c-4471-a9cb-6e3d00d3bfaf 7 | --- 8 | 9 | ## What is Publish 10 | 11 | [Publish](https://github.com/JohnSundell/Publish.git)는 John Sundell님이 만든 정적사이트 생성기 입니다.
12 | Markdown Parser인 Ink와 Swift에서 HTML, XML, RSS를 작성하기 위한 DSL인 Plot을 사용합니다.
13 | 14 | Swift Package로 제공되기 때문에 `Package.swift` 에 Dependency로 추가하여 사용할 수 있습니다.
15 | 16 | ```swift 17 | let package = Package( 18 | ... 19 | dependencies: [ 20 | .package(url: "https://github.com/johnsundell/publish.git", from: "0.9.0") 21 | ] 22 | ... 23 | ) 24 | ``` 25 | 26 | Publish에서 제공하는 CLI를 설치할 때 HomeBrew를 지원하며, 깃허브로 따로 설치할 수도 있습니다. 27 | 28 | ``` 29 | brew install publish 30 | ``` 31 | 32 | 또는 깃허브에서 33 | 34 | ```shell 35 | $ git clone https://github.com/JohnSundell/Publish.git 36 | $ cd Publish 37 | $ make 38 | ``` 39 | 40 | 이렇게 설치하면 됩니다.
41 | 42 | 이제 시작할 때 43 | 44 | ``` 45 | $ mkdir Example 46 | $ cd Example 47 | $ publish new 48 | ``` 49 | 명령어를 이용하면? 50 | 51 | 52 | 53 | 이렇게 파일이 생성이 됩니다. 54 | 55 | ``` 56 | $ publish run 57 | ``` 58 | 59 | 위 명령어를 통해서 웹사이트를 실행시키면 60 | 61 | 62 | 63 | publish에서 제공하는 기본 화면이 보이게 됩니다. 64 | 65 | 실행 시키고 나면, Output 폴더가 생기게 됩니다. 66 | 67 | 68 | 69 | 그리고 내용 같은 경우는 `Content` 폴더 안에서 입력이 가능합니다.
70 | 일단 방금 만든 예시로, 첫번째 post 파일을 수정해 보겠습니다. 71 | 72 | 73 | 74 | 이렇게 수정하고 다시 빌드 해보면? 75 | 76 | 77 | 78 | 이렇게 내용이 잘 적용이 된것을 확인 할 수 있습니다. 79 | 80 | 그리고 글 추가 같은 경우는 markdown File 하나만 만들면 됩니다. (간단하죠?) 81 | 82 | 83 | 84 | 이렇게 글을 추가해 주면 됩니다. 85 | 86 | 87 | 88 | 그러면 추가된 글 까지 보이게 되죠, 89 | 90 | 글을 적는 방법을 알아볼까요? 91 | 92 | ```markdown 93 | --- 94 | date: 2023-08-25 17:09 95 | description: Welcome to publish. 96 | tags: publish, web, static site 97 | --- 98 | ``` 99 | 100 | markdown file 위에 정보를 입력해줍니다.
101 | date는 언제 이 글을 작성했는지를 보여주고, description은 글에 대한 짧은 설명을 적습니다.
102 | 103 | 그리고 publish 에서는 tag기능을 제공합니다. 저기에 tag를 적어두면? 104 | 105 | 106 | 107 | 이렇게 `https://localhost:8000/tags`에 tag들이 추가 되고, tag를 누르면? 108 | 109 | 110 | 111 | tag를 가지고 있는 글들을 조회할 수도 있습니다. 112 | 113 | --- 114 | 115 | 이번 글에서는 간단하게 publish에 대해서 소개하는 글을 적어봤습니다.
116 | 와글와글때 발표했던 내용이랑 중복되는 부분이 많았지만.. 다음 글에서는 publish 커스텀하는 방법, publish로 작성한 글 deploy 하기 등등 publish는 시리즈로 진행할 예정입니다. 117 | -------------------------------------------------------------------------------- /Content/blog/publish-part-1.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Publish 사용하기 part 1 3 | date: 2023-09-12 04:03 4 | tags: Swift, Publish, Web, Theory 5 | description: Swift publish 구조 살펴보기 6 | postImage: https://github.com/jihoonahn/blog/assets/68891494/b671b6fb-65cf-438e-8552-46b19489fdeb 7 | --- 8 | 9 | ## Publish 구조 살펴보기 10 | 11 | 기존 처음 Publish 프로젝트를 생성하고, `main.swift` 코드입니다. 12 | 13 | ```swift 14 | import Foundatio 15 | import Publish 16 | import Plot 17 | 18 | // This type acts as the configuration for your website. 19 | struct Example: Website { 20 | enum SectionID: String, WebsiteSectionID { 21 | // Add the sections that you want your website to contain here: 22 | case posts 23 | } 24 | 25 | struct ItemMetadata: WebsiteItemMetadata { 26 | // Add any site-specific metadata that you want to use here. 27 | } 28 | 29 | // Update these properties to configure your website: 30 | var url = URL(string: "https://your-website-url.com")! 31 | var name = "Example" 32 | var description = "A description of Part" 33 | var language: Language { .english } 34 | var imagePath: Path? { nil } 35 | } 36 | 37 | // This will generate your website using the built-in Foundation theme: 38 | try Example().publish(withTheme: .foundation) 39 | 40 | ``` 41 | 42 | 먼저 위 예시의 Example 구조체 부터 보겠습니다. 43 | 44 | ```swift 45 | struct Example: Website { 46 | enum SectionID: String, WebsiteSectionID { 47 | // Add the sections that you want your website to contain here: 48 | case posts 49 | } 50 | 51 | struct ItemMetadata: WebsiteItemMetadata { 52 | // Add any site-specific metadata that you want to use here. 53 | } 54 | 55 | // Update these properties to configure your website: 56 | var url = URL(string: "https://your-website-url.com")! 57 | var name = "Example" 58 | var description = "A description of Part" 59 | var language: Language { .english } 60 | var imagePath: Path? { nil } 61 | } 62 | ``` 63 | 64 | - `Section ID` : 웹사이트의 Section ID를 나타내는 열거형입니다. 65 | - `itemMetadata` : 사용자가 웹사이트에 대한 Meta data 값을 지정할 수 있는 부분입니다. 66 | - `url` : 웹 사이트를 어떤 URL로 호스팅 될지 지정해주는 부분입니다. 67 | - `name` : 웹 사이트의 이름을 지정해주는 부분입니다. 68 | - `description` : 웹사이트에 대한 설명을 지정해주는 부분입니다. 69 | - `language` : 웹사이트에서 사용하는 주요한 언어를 지정해주는 부분입니다. 70 | - `imagePath` : 웹사이트를 나타내는 이미지의 경로를 지정해주는 부분입니다. 71 | 72 | 위 예시에는 나와있지 않지만, `favicon` 과 `tagHTMLConfig` 값도 있습니다. 73 | 74 | - `favicon` : 웹 브라우저의 주소창에 표시되는 아이콘을 설정 할 수 있습니다. 75 | - `tagHTMLConfig` : 웹 사이트에 대한 Tag HTML을 생성할 때 사용이 됩니다. 76 | 77 | 78 | Publish에는 기본적으로 `Foundation Theme` 가 제공됩니다. 79 | 80 | ```swift 81 | try Example().publish(withTheme: .foundation) 82 | ``` 83 | 84 | 이 부분에서 Publish 자체에서 제공하는 `Foundation Theme` 을 사용한다는 것을 알 수 있습니다. 85 | 86 | 커스텀을 시작하기 위해서는 저부분 부터 수정이 필요합니다.

87 | 88 | ## 테마 커스텀 89 | 90 | 여기서부터 좀 publish 사용 난이도가 많이 올라갑니다. 91 | 92 | `PublishHTMLFactory.swift` 파일을 생성합니다. 93 | 94 | ```swift 95 | struct PublishHTMLFactory: HTMLFactory 96 | ``` 97 | 98 | 그리고 `HTMLFactory` protocol을 상속 받으면 구조가 생성이됩니다. 99 | 100 | 처음에는 위 예시 코드에서 있던 Example 구조체를 Site typealias에 입력해주면 됩니다. 101 | 102 | ```swift 103 | struct PublishHTMLFactory: HTMLFactory { 104 | typealias Site = Example 105 | } 106 | ``` 107 | 108 | 이렇게 해주시면, 109 | 110 | ```swift 111 | struct PublishHTMLFactory: HTMLFactory { 112 | typealias Site = Example 113 | 114 | func makeIndexHTML(for index: Publish.Index, context: Publish.PublishingContext) throws -> Plot.HTML { 115 | <#code#> 116 | } 117 | 118 | func makeSectionHTML(for section: Publish.Section, context: Publish.PublishingContext) throws -> Plot.HTML { 119 | <#code#> 120 | } 121 | 122 | func makeItemHTML(for item: Publish.Item, context: Publish.PublishingContext) throws -> Plot.HTML { 123 | <#code#> 124 | } 125 | 126 | func makePageHTML(for page: Publish.Page, context: Publish.PublishingContext) throws -> Plot.HTML { 127 | <#code#> 128 | } 129 | 130 | func makeTagListHTML(for page: Publish.TagListPage, context: Publish.PublishingContext) throws -> Plot.HTML? { 131 | <#code#> 132 | } 133 | 134 | func makeTagDetailsHTML(for page: Publish.TagDetailsPage, context: Publish.PublishingContext) throws -> Plot.HTML? { 135 | <#code#> 136 | } 137 | } 138 | ``` 139 | 140 | 이런 코드가 생깁니다. 141 | 142 | 각 메서드에 대한 설명을 하겠습니다. 143 | 144 | - `makeIndexHTML` : 웹사이트의 main `index.html` 페이지를 생성합니다. 145 | - `makeSectionHTML` : 웹사이트의 Section 부분에 사용할 `index.html` 페이지를 생성합니다. 146 | - `makeItemHTML` : 웹사이트의 item에 사용할 HTML 파일을 생성합니다. 147 | - `makePageHTML` : 웹사이트의 page에 사용할 HTML 파일을 생성합니다. 148 | - `makeTagListHTML`: Publish에서 제공하는 Tag 부분의 전체 리스트를 제공해주는 화면입니다. 149 | - `makeTagDetailsHTML` : Tag 부분의 상세 내용을 제공해주는 화면입니다. 150 | 151 | `HTMLFactory`에서 `<#code#>` 부분 설명이 길어질것 같아서 다음 Post에 작성법에 대해서 상세하게 설명하도록 하겠습니다. 152 | 153 | HTMLFactory 내용을 다 입력하고, Theme를 등록하면 됩니다. 154 | 155 | ```swift 156 | import Publish 157 | import Plot 158 | 159 | extension Theme where Site == Example { 160 | static var publish: Self { 161 | Theme(htmlFactory: PublishHTMLFactory()) 162 | } 163 | } 164 | ``` 165 | 166 | 이런식으로 Example에서 사용 가능한 `publish` 테마를 등록하였습니다. 167 | 168 | ```swift 169 | try Example().publish(withTheme: .publish) 170 | ``` 171 | `main.swift` 파일 부분에서 172 | 간단하게는 이렇게 사용이 가능합니다. 173 | 174 | 또는 직접 custom pipeline를 구축하는 방법도 있습니다. 175 | 176 | ```swift 177 | try Example().publish(using: [ 178 | .optional(.copyResources()), 179 | .addMarkdownFiles(), 180 | .generateHTML(withTheme: .publish), 181 | .generateRSSFeed(including: [.posts]), 182 | .generateSiteMap() 183 | ]) 184 | ``` 185 | 186 | `publish(using:)` 메서드에서 pipeline을 하나하나 사용자가 원하는대로 지정할 수 있습니다. 187 | 188 | 위에서 진행한 내용은 [예시코드](https://github.com/Jihoonahn/Blog-Document/tree/main/Publish/part1) 를 확인할 수 있습니다. 189 | 190 | --- 191 | 192 | 이번 글에서는 Publish 커스텀 중에서 구조를 이루는 부분에 대한 글을 적어봤습니다.
193 | 다음 글에서는 publish에서 HTML을 어떻게 작성하는지에 대한 내용을 작성할 예정입니다. 194 | -------------------------------------------------------------------------------- /Content/blog/publish-part-2.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Publish 사용하기 part 2 3 | date: 2023-10-26 14:48 4 | tags: Swift, Publish, Web, Theory 5 | description: Swift publish 커스텀하기 6 | postImage: https://github.com/jihoonahn/blog/assets/68891494/0c4b40f7-bde3-4f90-ab09-60a3eca33476 7 | --- 8 | 9 | ## Publish 구조 작성하기 10 | 11 | ```swift 12 | struct PublishHTMLFactory: HTMLFactory { 13 | typealias Site = Example 14 | 15 | func makeIndexHTML(for index: Publish.Index, context: Publish.PublishingContext) throws -> Plot.HTML { 16 | <#code#> 17 | } 18 | 19 | func makeSectionHTML(for section: Publish.Section, context: Publish.PublishingContext) throws -> Plot.HTML { 20 | <#code#> 21 | } 22 | 23 | func makeItemHTML(for item: Publish.Item, context: Publish.PublishingContext) throws -> Plot.HTML { 24 | <#code#> 25 | } 26 | 27 | func makePageHTML(for page: Publish.Page, context: Publish.PublishingContext) throws -> Plot.HTML { 28 | <#code#> 29 | } 30 | 31 | func makeTagListHTML(for page: Publish.TagListPage, context: Publish.PublishingContext) throws -> Plot.HTML? { 32 | <#code#> 33 | } 34 | 35 | func makeTagDetailsHTML(for page: Publish.TagDetailsPage, context: Publish.PublishingContext) throws -> Plot.HTML? { 36 | <#code#> 37 | } 38 | } 39 | ``` 40 | 41 | 이전 포스트에서 만든 `PublishHTMLFactory` 부분 부터 보도록 하겠습니다. 42 | 43 | Publish에서 HTML 로 구조를 작성하기 위해서, [`Plot`](https://github.com/JohnSundell/Plot)이라는 JohnSundell이 만든 라이브러리를 사용합니다. 44 | 45 | 기존에는 46 | ```swift 47 | let html = HTML( 48 | .head( 49 | .title("My website"), 50 | .stylesheet("styles.css") 51 | ), 52 | .body( 53 | .div( 54 | .h1("My website"), 55 | .p("Writing HTML in Swift is pretty great!") 56 | ) 57 | ) 58 | ) 59 | ``` 60 | 61 | 이런 방식으로 작성했지만, Plot이 업데이트 되면서, Component 프로토콜을 사용해서 Component 요소들을 SwiftUI와 유사한 방식으로 작성할 수 있습니다. 62 | 63 | ```swift 64 | struct NewsArticle: Component { 65 | var imagePath: String 66 | var title: String 67 | var description: String 68 | 69 | var body: Component { 70 | Article { 71 | Image(url: imagePath, description: "Header image") 72 | H1(title) 73 | Span(description).class("description") 74 | } 75 | .class("news") 76 | } 77 | } 78 | ``` 79 | 이런 방식으로 말이죠 80 | 81 | 간단하게 알아 봤으니 한번 Publish에서 사용해보겠습니다. 82 | 먼저 가장 먼저 보일 부분인 Index 부분을 커스텀해보겠습니다. 83 | 84 | ```swift 85 | func makeIndexHTML(for index: Index, context: PublishingContext) throws -> HTML { 86 | <#code#> 87 | } 88 | ``` 89 | 90 | 먼저 `HTML`이라는 구조체를 `makeIndexHTML` 메서드에 추가해줍니다. 91 | 92 | ```swift 93 | func makeIndexHTML(for index: Index, context: PublishingContext) throws -> HTML { 94 | /// HTML 구조체 내에 node를 추가할 수 있는 형태 95 | HTML() 96 | 97 | /// HTML 구조체에 들어갈 head 부분이랑 body 부분을 init에서 분리해주는 형태 98 | HTML(head: [], body: {}) 99 | } 100 | ``` 101 | 위 두가지 HTML 에서 하나를 선택해주시면 됩니다. 102 | 저는 위 방식으로 진행하도록 하겠습니다. 103 | 104 | 먼저 사용할 언어를 선택해줍니다. 105 | 106 | ```swift 107 | func makeIndexHTML(for index: Index, context: PublishingContext) throws -> HTML { 108 | HTML( 109 | .lang(context.site.language) /// 110 | ) 111 | } 112 | ``` 113 | 114 | Head에 필요한 정보들을 넣어줍니다. 저는 publish에서 제공해주는 head static 메서드를 통해서 구성해주도록 하겠습니다. 115 | ```swift 116 | func makeIndexHTML(for index: Index, context: PublishingContext) throws -> HTML { 117 | HTML( 118 | .lang(context.site.language), 119 | .head(for: index, on: context.site) 120 | ) 121 | } 122 | ``` 123 | 빌드를 돌리게 되면 124 | 125 | 126 | 127 | 이런식으로 Head 부분이 쉽게 구축이 된것을 확인 할 수 있습니다. 128 | publish에서 제공하는 head 메서드는 저희가 따로 node로 넣어줄 필요없이 가장 자주 사용되는것들로 구성해 줍니다. 129 | 130 | 하지만 웹에서는 /style.css에러가 발생합니다. 이 부분은 131 | 132 | ```swift 133 | static func head( 134 | for location: Location, 135 | on site: T, 136 | titleSeparator: String = " | ", 137 | stylesheetPaths: [Path] = ["/styles.css"], 138 | rssFeedPath: Path? = .defaultForRSSFeed, 139 | rssFeedTitle: String? = nil 140 | ) -> Node {} 141 | ``` 142 | head static 메서드가 선언된 부분을 보면 알 수 있습니다.
143 | `stylesheetPath`에 내용이 들어가지 않기 때문에, 자동으로 style.css 부분을 넣어줘서, 현재는 style.css 파일이 없기 때문에 에러가 발생하는 것입니다. 144 | 145 | ```swift 146 | .head(for: index, on: context.site, stylesheetPaths: []) 147 | ``` 148 | 149 | 그래서 아직 stylesheet 넣지 않을것이라면 이런식으로 빈 배열로 값을 넣어주면 에러가 발생하지 않습니다. 150 | 151 | 그 다음은 Body 입니다.
152 | Body 같은 경우도 node로 추가하는 방법도 있지만, 앞에서 말했던 Component를 사용해봅시다. 153 | 154 | ```swift 155 | func makeIndexHTML(for index: Index, context: PublishingContext) throws -> HTML { 156 | HTML( 157 | .lang(context.site.language), 158 | .head(for: index, on: context.site), 159 | .body { 160 | // Body Code 161 | } 162 | ) 163 | } 164 | ``` 165 | 166 | ## Component 사용법 167 | Component를 사용하기 위해서는 `Component` 라는 프로토콜을 상속 받아줍니다. 168 | 169 | ```swift 170 | struct ComponentName: Component 171 | ``` 172 | 173 | 이렇게 상속을 받아주면 174 | 175 | ```swift 176 | struct ComponentName: Component { 177 | var body: Component {} 178 | } 179 | ``` 180 | 181 | 이런식으로 SwiftUI와 비슷한 스타일로 Component body 부분에서 기존에 사용하던 `Node` 또는 다른 `Component`를 이곳에 넣을 수 있습니다. 182 | 183 | ```swift 184 | struct ComponentName: Component { 185 | var body: Component { 186 | Div { 187 | ... 188 | } 189 | .class("site-div") 190 | } 191 | } 192 | ``` 193 | 194 | 이런식으로 Component를 제작할 수 있습니다. 195 | 196 | 현재 Plot에서 제공하고 있는 Component에서 사용할 수 있는 [`HTMLComponent`](https://github.com/JohnSundell/Plot/blob/master/Sources/Plot/API/HTMLComponents.swift) 입니다. 197 | 198 | ```swift 199 | /// A container component that's rendered using the `
` element. 200 | public typealias Article = ElementComponent 201 | /// A container component that's rendered using the `