블로그 자동화 프로세스 및 경험 최적화 - 두 번째 이야기

✍🏼 작성일 2022년 08월 14일    💡 수정일 2022년 08월 25일
❗️ 참고: 이 글이 작성된 지 이미 일이 지났습니다. 시의성에 유의하세요
🖥  설명:블로그 경험이 한층 더 업그레이드되었습니다!
📚  Craft에도 게시: https://www.craft.do/s/BJ8gIm5fmtdOCB

서문

이전에 블로그 자동화 프로세스](링크)와 이후의 2022 블로그 자동화 프로세스](링크)를 구축했지만, 전반적으로 다음과 같은 문제점들이 있었습니다:

  1. Github CI를 통해 Craft에서 가져온 이미지를 텐센트 클라우드에 업로드할 때 네트워크 오류가 자주 발생하여 프로세스가 불안정했습니다.

  2. 블로그 이미지를 텐센트 클라우드에 업로드할 때 png 형식으로 강제 변환되는데, 실제로는 gif 같은 더 많은 형식을 지원해야 합니다.

  3. Craft에서 Markdown으로 변환한 결과가 표준 Markdown과 다른 부분이 많았습니다:

    1. 중첩 리스트를 지원하지 않습니다.

    2. 인용 블록 내에 중첩 리스트가 있을 때, 렌더링 결과가 리스트가 바깥층에 나타납니다.

    3. Craft의 “포커스”(block)와 “블록”(focus) 중 후자만 blockquote(인용 블록)으로 인식되는데, 둘 다 인용 블록으로 처리되어야 합니다.

    4. Craft의 북마크에는 페이지 설명, 제목 등 많은 정보가 포함되어 있지만, Markdown으로 변환하면 링크만 포함된 단락으로 축소되어 정보가 손실됩니다.

    5. Craft 자체에서 이미지 설명을 지원하지 않습니다.

    6. Craft에서 생성된 코드 블록은 백틱 뒤에 언어를 자동으로 추가하지 않아 일부 언어가 plaintext로 인식되고 하이라이트가 적용되지 않습니다.

이러한 문제점들을 해결하기 위해 이번에 다시 한 번 개선 작업을 진행했습니다.

최적화 1: 직접 만든 CraftBlockToMarkdown 함수 사용

Craft 측에 craft.markdown.craftBlockToMarkdown가 제대로 변환되지 않는 문제를 보고했습니다:

Wrong render of markdown with Decoration focus and image question Decoration block 'focus'는 'blockquote'로 렌더링되어야 하지만, 현재 markdow API craftBlockToMarkdown(flavor는 'common')은 이 블록 타입을 무시하고 일반 텍스트로 렌더링합니다. markdow API craftBlockToMarkdown이 반환하는 이미지 URI는 모든 곳에서 사용 가능하여 문제를 일으킬 수 있습니다. 사이트 화이트리스트 설정이 있었으면 좋겠습니다. 내 문서의 이미지를 화이트리스트에 있는 사이트에서만 사용할 수 있도록 허용하는 것입니다. 이 전략은 AWS 데이터 트래픽 비용도 줄일 수 있습니다. https://forum.developer.craft.do/t/wrong-render-of-markdown-with-decoration-focus-and-image-question/235

하지만 공식적으로 해결할 의지가 없는 것 같아, 직접 간단한 변환 함수를 작성했습니다:

craftBlockToMarkdown GitHub Gist: 코드, 노트, 스니펫을 즉시 공유하세요. https://gist.github.com/Xheldon/036d9b187bd83303205001e8af97eda7

이 함수는 Jekyll에 맞춰 다음 단계 처리를 위해(아래 최적화 3 참조) imageBlockurlBlock 타입의 블록을 특별히 처리합니다:

Image

이 함수는 이미 공식 플러그인 개발자 포럼에 올려두었습니다. 관심 있으시면 의견 나눠주세요:

A better implementation of craft blocks to Markdown transformation methods Craft의 markdown API에는 다음과 같은 문제가 있습니다: 1. 중첩 리스트를 지원하지 않음. 2. 중첩 인용 블록 순서가 잘못됨. 3. 후자만 참조 블록으로 인식됨. 4. urlBlock(북마크라고 할 수 있음?)이 링크로 인식됨(이 함수에서는 jekyll의 렌더링 태그를 커스터마이즈할 수 있으니 원하는 대로 수정 가능). 참고: 1. 토글/수식은 지원하지 않음. 마크다운도 이를 지원하지 않기 때문이지만, 직접 구현할 수 있음. 2. 이 함수는 제 jekyll 블로그에 사용 중입니다... https://forum.developer.craft.do/t/a-better-implementation-of-craft-blocks-to-markdown-transformation-methods/554

최적화 2: Github CI 프로세스를 로컬로 이동

이전 프로세스는 플러그인의 발행 버튼을 클릭하면(GitHub Token, COS 정보 등을 먼저 입력해야 함) 그냥 기다리기만 하면 되었습니다.

이번 최적화에서는 이전 프로세스와 일관성을 유지하기 위해 Craft 플러그인을 하나 더 작성했습니다. 이 플러그인을 클릭하면 문서 내용을 가져와 간단한 처리(예: headerImg의 이미지 저작권 정보를 meta로 가져오기)를 한 후, 문서 정보를 매개변수로 포함한 특정 링크를 호출합니다:

1
2
3
craft.editorApi.openURL(
`xhelper://${btoa(unescape(encodeURIComponent(content)))}`
);

이 특정 링크의 역할은 Apple Script로 작성된 Application을 호출하는 것입니다. Application을 작성하는 방법과 링크 호출에 응답하는 방법은 제가 작성한 이 문서]를 참조하시면 됩니다. 여기서는 node를 호출하며, 실제로는 이전에 Github CI에서 실행하던 코드를 실행합니다. 간단한 스크린샷을 첨부합니다:

Xhelper截图👆🏻

효과:

Image

이전 프로세스에서는 제가 편의를 위해 모든 이미지를 png 형식으로 변환했습니다(Craft에 업로드된 이미지가 항상 확장자를 가지는 것은 아니기 때문입니다. 드래그 앤 드롭이나 이미지 업로드 방식으로 추가된 이미지는 확장자가 있지만, 복사 붙여넣기나 웹 업로드로 추가된 이미지는 확장자가 없습니다). 이번에는 이 로직을 제거하고, 먼저 이미지에 확장자가 있는 경우 그 확장자를 사용하도록 했습니다. 확장자가 없는 경우에만 png 형식으로 강제 변환합니다. Sharp 라이브러리를 사용하는 것을 추천드립니다. 정~말~ 유용합니다:

sharp - High performance Node.js image processing "Resize large images in common formats to smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions" https://sharp.pixelplumbing.com/

그래서… GIF를 보여드리겠습니다!

支持gif啦

참고: 이전에는 편의를 위해(인증 없이, 고정 IP 서버 없이) WeChat Cloud Hosting]을 사용했고, 요약을 WeChat 공식 계정으로 전송하는 로직도 구현했습니다. 하지만 문제는 이 서버가 단지 공식 계정 발행을 편하게 하려고 만든 것인데, 제 성격상 보통 3개월에 한 번씩만 사용하다 보니, 사용할 때마다 콜드 스타트(서버는 기본적으로 30분 동안 요청이 없으면 인스턴스를 자동으로 종료합니다. 실행 시간에 따라 요금이 부과되기 때문입니다)가 발생해 자주 실패했습니다. 이 문제를 해결하는 시간이면 차라리 수동으로 WeChat 공식 계정 편집기에 복사 붙여넣기를 하는 게 나았습니다. 그래서 WeChat 공식 계정은 포기하고, 수동으로 붙여넣기… 이것도 괜찮네요!

최적화 3: Craft의 Bookmark와 이미지 Caption 구현

Jekyll 기반 블로그 시스템에서 이 효과를 구현할 수 있었던 이유는 Github의 Jekyll 서비스를 직접 사용하지 않고, Jekyll로 HTML을 빌드한 후 저장소에 푸시하는 방식으로 구현했기 때문입니다. 이유와 과정은 여기]를 참조하세요.

Bookmark와 이미지 Caption은 이전 단계에서 생성된 Markdown에 포함된 특수 Jekyll 태그를 활용하고, 사용자 정의 Jekyll 플러그인으로 구현했습니다. Bookmark 렌더링에는 render_bookmark을, 이미지 caption 렌더링에는 render_caption을 사용했습니다(방금 배운 2시간짜리 ruby 스크립트 언어로 작성했습니다):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
module Jekyll
class RenderBookMarkBlock < Liquid::Block
def initialize(tag_name, attr, tokens)
super
attrs = attr.scan(/url\=\"(.*)\"\stitle\=\"(.*)\"\simg\=\"(.*)\"/)
@url = attrs[0][0]
@title = attrs[0][1]
@img = attrs[0][2]
@error = ""
end
def render(context)
@desc = super
"<p><a class='link-bookmark' href='#{@url}'><span><img src='#{@img}'/></span><span><span>#{@title}</span><span>#{@desc}</span><span>#{@url}</span></span></a></p>"
end

end
end

module Jekyll
class RenderImageCaptionBlock < Liquid::Block
def initialize(tag_name, attr, tokens)
super
attrs = attr.scan(/caption\=\"(.*)\"\simg\=\"(.*)\"/)
@caption = attrs[0][0]
@img = attrs[0][1]
end
def render(context)
text = super
"<p caption='#{@caption}'><img src='#{@img}' alt='#{@caption}' title='#{@caption}' /></p>"
end

end
end

Liquid::Template.register_tag('render_caption', Jekyll::RenderImageCaptionBlock)
Liquid::Template.register_tag('render_bookmark', Jekyll::RenderBookMarkBlock)

Jekyll에서 플러그인을 사용하는 것은 매우 간단합니다. 루트 디렉토리에 _plugins 디렉토리를 생성하고, ruby로 작성된 Jekyll 플러그인을 넣으면 Jekyll Build 시 해당 플러그인이 로드됩니다.

기타 세부 사항

이전 내용은 표준 Markdown으로 생성된 HTML이었기 때문에 일부 RSS 리더가 내용을 가져온 후 형식이 잘 유지되었습니다. 하지만 이번 최적화에서 Bookmark를 추가하면서 RSS 형식이 깨졌습니다(Reeder 5의 스크린샷):

Reeder5中Bookmark格式错乱

그래서 ruby 플러그인을 하나 더 작성해 이 태그를 필터링하고 일반 HTML 링크로 변환했습니다:

1
2
3
4
5
6
7
8
9
module Jekyll
module BookmarkFilter
def bookmark_filter(input)
input.gsub(/^\<p\>\<a\s+class=\"link-bookmark\"\shref=(.*)\starget=\"_blank\"\>\<span\>(.*)\<\/span\>\<span\>\<span\>(.*)\<\/span\>\<span\>\n(.*)\n\<\/span\>\<span\>(.*)\<\/span\>\<\/span\>\<\/a\>\<\/p\>$/, '<p><a href=\1 target="_blank">\4</a></p>');
end
end
end

Liquid::Template.register_filter(Jekyll::BookmarkFilter)

그런 다음 feed.xml에서 사용하면 됩니다: post.content | bookmark_filter.

궁금한 점이 있으면 언제든지 문의해주세요!

- EOF -
이 글의 최초 게시: 블로그 자동화 프로세스 및 경험 최적화 - 두 번째 이야기 - Xheldon Blog