WYSIWYG (HTML 에디터) : 게시글 작성을 위한 웹 텍스트 에디터 - TEAM-ARK/inflearn-clone-front GitHub Wiki

WYSIWYG

  • What You See Is What You Get : 보는 대로 얻는다.
  • 문서 편집 과정에서 화면에 포맷된 낱말, 문장이 출력물과 동일하게 나오는 방식

1. 인프런에서 사용 중인 에디터

인프런에서 사용하는 웹 텍스트 에디터는?

inflearn에서 tinymce 에디터를 사용 중인 것 같다.

무료 에디터인지 가격정책을 찾아보니 등급별로 제공되는 혜택이 달랐다.

LGPL 라이센스

무료버전 등급을 사용하려 하는데 LGPL 라이센스로 되어 있었고 그것이 무엇인지 찾아보았다.

LGPL은 라이브러리로 사용하여 프로그램을 개발하고 판매/배포할 경우에 소스코드를 공개하지 않아도 되고 LGPL 코드를 사용했음을 명시만 하면 된다.

단, LGPL 코드를 단순히 이용하는 것이 아니라 수정 또는 파생한 라이브러리를 개발하여 배포하는 경우에는 전체 코드를 공개해야 한다.

참고

2. 다른 에디터들은 어떤 것들이 있는가

이 글은 https://blog.hubspot.com/website/best-wysiwyg-html-editor 를 번역해서 정리한 내용으로 오역이나 의역, 개인 의견이 포함되어 있을 수 있습니다.

2-1. CKEditor

  • 브라우저 기반의 서식이 있는 에디터
  • 필요한 콘텐츠 처리 기능을 웹으로 가져올 수 있는 플러그인 기반 아키텍처로 확장 가능합니다.
  • 거의 15년 동안 시장에서 CKEditor는 광범위한 기능과 레거시 소프트웨어 호환성을 갖춘 가장 평판이 좋은 편집자 중 하나로서 이 목록에 자리를 잡았습니다.
  • 사용자에 따르면 설정의 용이성은 최고의 특성 중 하나입니다. CKEditor의 다른 이점으로는 빠른 로딩(개발 시간 절약)과 수동으로 편집하여 서버에 업로드하지 않고도 프로젝트를 즉시 수정할 수 있는 기능이 있습니다.

2-2. Editor.js

  • Editor.js는 오픈 소스 편집기입니다. 이동하고 재정렬할 수 있는 콘텐츠 블록을 편집할 수 있습니다.
  • 블록을 클릭하면 해당 특정 블록에 사용할 수 있는 특정 옵션이 표시됩니다. 마찬가지로 텍스트 내용을 클릭하면 텍스트 서식 및 인라인 스타일에 대한 옵션이 나타납니다.
  • Editor.js는 API(응용 프로그래밍 인터페이스) 덕분에 확장 및 연결 가능하도록 설계되었습니다. 또한 JSON 출력 형식으로 깨끗한 데이터를 반환합니다.

2-3. TinyMCE

  • TinyMCE는 Evernote, Atlassian 및 Medium을 포함한 많은 제품의 뒤에 있는 서식 있는 텍스트 편집기입니다.
  • 개발자에 따르면 TinyMCE의 목표는 다른 개발자가 아름다운 웹 콘텐츠 솔루션을 구축할 수 있도록 돕는 것입니다.
  • 통합하기 쉽고 클라우드 기반, 자체 호스팅 또는 하이브리드 환경에 배포할 수 있습니다. 설정을 통해 Angular, React 및 Vue와 같은 프레임워크를 통합할 수 있습니다.
  • TinyMCE는 표 생성 및 편집, 글꼴 패밀리 설정, 글꼴 검색 및 교체, 글꼴 크기 변경 등의 기능을 사용하여 디자인을 완벽하게 제어할 수 있습니다.

2-4. Quill

  • Quill은 확장 및 사용자 정의를 염두에 두고 구축된 무료 오픈 소스 WYSIWYG 편집기입니다.
  • 모듈식 아키텍처와 표현형 API 덕분에 Quill 코어로 시작한 다음 모듈을 사용자 정의하거나 필요에 따라 이 서식 있는 텍스트 편집기에 고유한 확장을 추가할 수 있습니다.
  • Quill은 모든 사용자 정의 콘텐츠 및 형식을 지원하므로 포함된 슬라이드 데크, 대화형 체크리스트, 3D 모델 등을 추가할 수 있습니다.
  • 이 편집기는 완전히 사용자 정의할 수 있고 더 풍부하고 대화형 콘텐츠를 지원할 수 있기 때문에 개인이 소규모 프로젝트와 Fortune 500대 기업 모두에서 사용합니다.

2-5. Summernote

  • Summernote는 Bootstrap 또는 jQuery로 로드할 수 있는 간단한 WYSIWYG 편집기입니다.
  • 플러그인을 사용하여 이 편집기를 사용자 정의하고 확장할 수 있습니다.

참고

3. 에디터 사용해보기

에디터 적용하기

회원 가입을 하면 API가 발급된다.

  • e.g., asdfasdf
<!DOCTYPE html>
<html>
<head>
  <script src="https://cdn.tiny.cloud/1/asdfasdf/tinymce/5/tinymce.min.js" referrerpolicy="origin"></script>
</head>
<body>
  <textarea>
    Welcome to TinyMCE!
  </textarea>
  <script>
    tinymce.init({
      selector: 'textarea',
      plugins: 'a11ychecker advcode casechange export formatpainter linkchecker autolink lists checklist media mediaembed pageembed permanentpen powerpaste table advtable tinycomments tinymcespellchecker',
      toolbar: 'a11ycheck addcomment showcomments casechange checklist code export formatpainter pageembed permanentpen table',
      toolbar_mode: 'floating', 
      tinycomments_mode: 'embedded',
      tinycomments_author: 'Author name', 
    })
  </script>
</body>
</html>
  • 발급된 API를 이용해서 tinyMCE WYSIWIG를 사용할 수 있다.

사용 전에 tiny 웹사이트에서 등록한 도메인만 사용이 가능하다.

  • API를 사용하지 않거나 도메인에 등록하지 않으면 아래와 같이 표시 된다.

react에서 사용하기

    <>
      <Editor
        apiKey={process.env.REACT_APP_TINYMCE_KEY}
        onInit={(evt, editor) => (editorRef.current = editor)}
        initialValue="<p>This is the initial content of the editor.</p>"
        init={{
          height: 500,
          menubar: false,
          plugins: [
            'advlist autolink lists link image charmap print preview anchor',
            'searchreplace visualblocks code fullscreen',
            'insertdatetime media table paste code help wordcount',
          ],
          toolbar:
            'undo redo | formatselect | ' +
            'bold italic backcolor | alignleft aligncenter ' +
            'alignright alignjustify | bullist numlist outdent indent | ' +
            'removeformat | help',
          content_style:
            'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }',
        }}
      />
      <button onClick={log}>Log editor content</button>
    </>

에디터 세부 설정

4. 에디터 적용하기

4-1. 이미지 업로드 분석 1

이미지 파일을 웹 텍스트 에디터에서 어떻게 업로드하는지 알아보자.

1. 업로드 버튼을 이용하여 이미지 추가

이미지 버튼을 누르면 업로드용 모달창 같은 것이 나온다. 업로드 아이콘을 클릭 후 이미지를 업로드를 하면 다음의 순서대로 업로드가 진행된다.

1. 이미지 업로드

1-1. 요청
  • method: POST
  • content-type: multipart/form-data;
  • Request URL: https://www.inflearn.com/api/files/courses/327576
    • [도메인]/api/files/courses/[강의ID]
1-2. 응답
  • Status code : 200
  • content-type: application/json;
  • data
{
  "ok": true,
  "id": 170219,
  "user_id": 167086,
  "course_id": 327576,
  "use": "editor",
  "name": "렌더링수정전2.PNG",
  "type": "image/png",
  "size": "79649",
  "url": "https://cdn.inflearn.com/public/files/courses/327576/7eb203ac-647b-46e4-afc7-60400c95e150/렌더링수정전2.PNG",
  "storage": "s3",
  "s3_info": {
    "Key": "public/files/courses/327576/7eb203ac-647b-46e4-afc7-60400c95e150/렌더링수정전2.PNG",
    "key": "public/files/courses/327576/7eb203ac-647b-46e4-afc7-60400c95e150/렌더링수정전2.PNG",
    "ETag": "\"38002127c1060fc6edf794d0b399db62\"",
    "Bucket": "ant-man-live",
    "Location": "https://ant-man-live.s3.ap-northeast-2.amazonaws.com/public/files/courses/327576/7eb203ac-647b-46e4-afc7-60400c95e150/%EB%A0%8C%EB%8D%94%EB%A7%81%EC%88%98%EC%A0%95%EC%A0%842.PNG",
    "VersionId": "g7f9DQ_rt8ey_sUrI9KHhlO65PqxUONs"
  },
  "vimeo_info": null,
  "vimeo_uploaded": false,
  "deleted_file": false,
  "created_at": "2021-10-13T14:37:06.857Z",
  "updated_at": "2021-10-13T14:37:06.857Z",
  "deleted_at": null,
  "vod_id": null,
  "vod_status": null,
  "resource_info": null,
  "vod_info": null,
  "is_uploaded": true,
  "duration": null,
  "is_drm": false,
  "drm_status": "NOT_CONVERTED"
}
2. 업로드한 이미지 가져오기

  • Request URL을 들어가보니 저장된 이미지가 있는 URL 이다.
2-1. 요청
  • method : GET
  • Reqeust URL : 업로드한 이미지가 저장되어 있는 스토리지
2-2. 응답
  • content-type: image/png

3. 뭔지 모르겠지만 resource 요청

3-1. 요청
  • method: POST
  • content-type: text/plain;
  • Request Payload
[
  "38fc19d0-a391-5948-8881-7c2a531963ce",
  [
    [
      "https://cdn.inflearn.com/public/files/courses/327576/7eb203ac-647b-46e4-afc7-60400c95e150/%EB%A0%8C%EB%8D%94%EB%A7%81%EC%88%98%EC%A0%95%EC%A0%842.PNG",
      35,
      null
    ]
  ]
]
3-2. 응답
  • status code: 200

4. 뭔진 모르겠지만 ajax-v2 요청

4-1. 요청
  • Reqeust payload
[
  "38fc19d0-a391-5948-8881-7c2a531963ce",
  [["/api/files/courses/327576",[349,22],[0,0],200,"POST"]]
]

업로드 후 모달창

에디터에서 이미지 확인

  • save를 누르면 에디터에 이미지가 추가된다
  • 추가된 이미지를 눌러보면 img 태그가 에디터 안에 추가되었고 src 속성에서 가리키는 URL은 업로드 후 응답으로 받은 URL인 것을 확인 할 수 있었다.

2. 에디터에 직접 복사하는 방식으로 이미지 추가

  • 에디터에 사진을 바로 붙여넣기 하는 방식으로 추가했을 땐 서버로 전송되지 않고 브라우저에서 가지고 있다가 base64 방식으로 전달하는 것 같다.
  • 이 경우 사진이 많아져도 전송에 문제가 없는지 궁금하다.

  • 10개의 사진을 붙여넣기 방식으로 추가후 전송해서 문제가 없는지 테스트 해보았다.

  • 문제 없이 전송이 잘 되었다.

  • 저장된 페이지로 이동해서 서버에서 받을 땐 어떻게 받아오는지 확인해 보았다.

업로드 방식으로 추가한 이미지

붙여 넣기 방식으로 추가한 이미지

4-2. 공식문서 읽기

Prerequisites(전제 조건)

  • Node.js(and npm)

Procedure

install

npm install --save @tinymce/tinymce-react

example

 import React, { useRef } from 'react';
 import { Editor } from '@tinymce/tinymce-react';

 export default function App() {
   const editorRef = useRef(null);
   const log = () => {
     if (editorRef.current) {
       console.log(editorRef.current.getContent());
     }
   };
   return (
     <>
       <Editor
         onInit={(evt, editor) => editorRef.current = editor}
         initialValue="<p>This is the initial content of the editor.</p>"
         init={{
           height: 500,
           menubar: false,
           plugins: [
             'advlist autolink lists link image charmap print preview anchor',
             'searchreplace visualblocks code fullscreen',
             'insertdatetime media table paste code help wordcount'
           ],
           toolbar: 'undo redo | formatselect | ' +
           'bold italic backcolor | alignleft aligncenter ' +
           'alignright alignjustify | bullist numlist outdent indent | ' +
           'removeformat | help',
           content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }'
         }}
       />
       <button onClick={log}>Log editor content</button>
     </>
   );
 }

TinyMCE에 대한 접근 방법 : Tiny Cloud, self-hostng

Tiny cloud

  • apiKey prop에 키를 전달
  <Editor apiKey='your-api-key' init={{ /* your other settings */ }} />

TinyMCE Self-hosted

  • tinymceScriptSrc를 사용하여 React 앱과 독립적으로 TinyMCE를 배포
    • tinymceScriptSrc prop에 구체적인 TinyMCE script 경로를 지정
<Editor tinymceScriptSrc="/path/to/tinymce.min.js" />
  • 이 외에도 여러가지 self-hosted 방법이 있지만 Tiny Cloud를 이용하는 방식을 사용할 것이기 때문에 이 포스트엔 더 이상 다루지 않음

TinyMCE React technical reference

Installing the TinyMCE React integration using NPM or Yarn

// npm
$ npm install --save @tinymce/tinymce-react
// yarn
$ yarn add @tinymce/tinymce-react

Configuring the editor

Configuring editor source

tinymce-react integration은 다음과 같이 이루어집니다.

  1. 전역(global) tinymce가 페이지에 있는 경우 그것이 사용됩니다.
  2. tinymceScriptSrc prop이 있으면 script 태그가 페이지에 추가되어 주어진 URL에서 TinyMCE를 불러옵니다.
  3. 만약 위 1, 2번 조건들이 적용되지 않았다면, 페이지에 Tiny Cloud로 부터 TinyMCE를 불러오기 위한 스크립트 태그가 추가 됩니다.

다음 props들은 에디터를 구성하는 데 사용됩니다.

  • apiKey : Tiny Cloud API key.

    • Tiny Cloud로 부터 불러올 때, 이 prop은 "This domain is not registered..." 경고 메세지를 없애는 데 사용됩니다.
  • cloudChannel : TinyMCE가 Tiny Cloud로 부터 불러올 때 사용되는 채널

  • scriptLoading : 스크립트 로딩 동작 prop

    • asnyc와 defer 속성의 세팅을 허용합니다.
    • 추가적인 지연 시간 milliseconds 단위로 추가할 수 있습니다.
  • tinyScriptSrc : The URL to use for sourcing TinyMCE, when loading a self-hosted version of TinyMCE

Configuring page elements

아래 props 들은 react integration이 생성하는 페이지 요소에 대한 일부 제어를 제공합니다.

id : The id attribute of the element that the editor is initialized on.

  • 편집기가 초기화되는 요소의 id 속성입니다.
  • textarea 태그의 id 매칭되는 부분이고 React에서 사용하는 경우 따로 설정하지 않아도 된다.

inline : 에디터를 페이지의 일부분으로 로드합니다.(페이지 스타일 공유)

  • boolean
  • inline 속성을 사용한 경우(그냥 보면 일반적인 웹페이지 처럼 보인다.)
  • editor로 작성된 page영역을 클릭하면 에디터가 나온다.
  • inline 속성을 사용하지 않은 경우, 일반적으로 우리가 생각하는 에디터화면이 나온다.

tagName : 인라인 에디터를 만드는 데 사용되는 태그

  • 클래식한 iframe 방식의 에디터에서는 무시됩니다.

  • 일반적으로 우리가 사용하는 에디터 모양은 tinyMCE에선 iframe방식으로 제공되는 것 같다.

// inline과 tagName을 사용한 경우
<Editor
	inline
    tagName="aTagNameOfEditor"
	// ...
/>

  • iframe으로 에디터가 들어가지 않고 지정한 태그로 에디터가 들어갔다.
  • default 태그는 div 이다.

textareaName

  • textarea 태그(HTML 요소)의 name 속성입니다. 클래식(iframe) 편집기를 만드는 데 사용됩니다. 인라인 편집기에서는 무시됩니다.
    • form태그로 에디터를 감싸고 submit 버튼으로 전송하려는 경우에 사용할 수 있을 것 같다.

Configuring editor settings

아래 props들은 에디터가 초기화 될 때 사용됩니다. 에디터가 시작된 후 변경사항들은 무시됩니다.

init

  • TinyMCE가 초기화 될 때 전달되는 추가 옵션

plugins

  • 에디터 플러그인을 지정
  • init prop의 플러그인과 결합됨

toolbar

  • 에디터 도구 모음을 지정
  • init prop의 toolbar를 override 함 (여기에서 각종 에디터에 필요한 아이콘을 추가할 수 있는 것 같다.)

Managing the editor

아래 props들은 에디터가 초기화된 후 업데이트 될 수 있다.

disabled

  • 읽기 전용 모드

initialValue

  • 에디터의 시작 값
  • 에디터가 로드된 후 이 값을 변경하면 에디터가 재설정 됨(에디터 내용 포함)

onBeforeAddUndo

  • 에디터가 실행취소 단계를 생성하려 할 때 작동하는 이벤트 핸들러
  • 필요한 경우 실행취소를 방지하기도 한다.

onEditorChange

  • 에디터 변화를 감지하는 이벤트핸들러
  • TinyMCE가 controlled component로써 구현될 때 유용하다.

onInit

  • 에디터가 초기화되었을 때 알려주는 이벤트 핸들러
  • 에디터의 초기값을 가져올 때 유용
  • 제어되지 않는 구성요소에서 사용되는 에디터에 대한 참조를 얻는데 유용

value

  • 에디터의 값을 설정하는데 사용
  • 오직 controlled component에서만 사용됨

Available props

TinyReact component에서 필수 prop은 없지만 apiKey prop을 구성하지 않은 경우 Tiny Cloud에서 로드할 때 에디터에 대한 경고 메세지가 표시 됨

apiKey

  • 이 도메인은 동록되지 않았습니다... 라는 메세지를 없애는 데 사용(Tiny Cloud를 사용할 때)

cloudChannel

  • 에디터에 사용되는 TinyMCE빌드를 '특정 버전 또는 안정성 수준을 나타내는 채널'로 변경
  • default로 설정된 값 : 5-stable
  • 적용 가능한 값들 : 5-stable, 5-testing, 5-dev, 5.10
    • 5-stable : TinyMCE의 현재 enterprise release
    • 5-testing : TinyMCE의 다음 enterprise release에 대한 현재 후보
    • 5-dev : The nightly-build version of TinyMCE.
    • A version number such as 5.10: The specific enterprise release version of TinyMCE.

disabled

read-only mode(true)와 standard editable mode(false) 중 선택 가능하게 함

  • default : false

id

  • tinymce.get('ID') 메소드로 에디터 인스턴스를 검색하는데 사용
  • default으로 UUID가 생성 됨

init

  • 에디터를 초기화하는데 사용
  • tinymce.init({...}) 메서드로 설정
    • basic settup
    • tinymce-react의 init은 selector, target, readonly 옵션들이 필요가 없습니다.
    • 만약 init에 selector, target, readonly 옵션들이 전달이 되더라도 integration에 의해 재정의 됩니다.
<Editor
  init={{
    plugins: [
     'lists link image paste help wordcount'
    ],
    toolbar: 'undo redo | formatselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | help'
  }}
/>

initialValue

  • 에디터의 초기 HTML
  • initialValue는 에디터의 undo state와 cursor pointer를 재설정합니다.
  • 에디터가 로드되기 전 또는 로드 후 비동기 프로세스에 의해 설정될 수 있음
const [initialValue, setInitialValue] = useState(undefined);
useEffect(() => {
  // a real application might do a fetch request here to get the content
  setTimeout(() => setInitialValue('<p>Once upon a time...</p>'), 500);
}, []);

return (
  <Editor
    initialValue={initialValue}
  />
);

inline

  • 편집기를 인라인 모드로 설정하는 데 사용됩니다. <Editor inline={true} />를 사용하는 것은 TinyMCE tinymce.init({...}) 메서드에서 {inline: true}를 설정하는 것과 같습니다.

onEditorChange

  • TinyMCE의 컴포넌트 밖에서 에디터의 state를 저장하는데 사용 됨
  • 이 prop은 TinyMCE React 컴포넌트를 제어 컴포넌트로 사용할 때 일반적으로 사용됩니다
    • controlled component vs uncontrolled component 차이?
  • value : 현재 에디터의 값(HTML)
  • editor : 에디터에 대한 참조(reference)
  • Using the TinyMCE React component as a controlled component.

plugins

<Editor
  plugins='lists code'
/>

scriptLoading

async

  • TinyMCE를 로드하기 위해 생성된 스크립트 태그에 async 속성을 설정합니다.
  • default value : false

defer

  • TinyMCE를 로드하기 위해 생성된 스크립트 태그에 defer 속성을 설정합니다.
  • default value : false
<Editor scriptLoading={{ async: true }}>

tagName

  • <Editor inline={true} />인 경우에만 유효합니다. 인라인 모드에서 편집기의 HTML 요소를 정의하는 데 사용됩니다.
<Editor
  inline={true}
  tagName='section'
/>

textareaName

  • 편집기가 클래식(iframe) 모드일 때만 유효합니다. 양식의 편집기에 사용되는 텍스트 영역 요소의 이름 속성을 설정합니다.
  • form 을 사용할거 아니면 안쓸 것 같다.
<form method="post">
  <Editor
    textareaName='description'
  />
  <button type="submit">Submit</button>
</form>

toolbar

  • 에디터의 아이콘들
<Editor
  plugins='code'
  toolbar='bold italic underline code'
/>

value

  • controlled component로 작동할 때 편집기의 HTML 콘텐츠를 설정합니다.
  • 이 prop(:value)이 현재 에디터의 내용과 다르면 에디터 컨텐트(내용)은 200ms안에 맞춰지고 undo level이 생성된다.
    • 에디터 값 변경 불가 - 값 변경 안되는데 쓸일이 있을까? inline으로 사용할 경우엔 쓸 수도 있을 것 같다.

Using the TinyMCE React component as a uncontrolled component

  • TinyMCE React component는 uncontrolled component로 사용되도록 설계되었다. 더 큰 문서들에서 잘 동작하도록
  • onInit 이벤트 핸들러는 콘텐츠 검색을 지원하기 위해 편집기가 로드될 때 편집기 참조를 저장하는 데 사용할 수 있습니다.

Using the TinyMCE React component as a controlled component

  • controlled component는 각 키 입력 또는 수정 시 전체 문서를 문자열로 변환해야 하므로 대용량 문서에서 성능 문제가 발생할 수 있습니다. -에디터를 controlled component로 사용하려면 value 및 onEditorChange 소품이 모두 필요합니다.
  • value prop은 편집기 내용을 설정하고 재설정하는 데 사용됩니다. 최신 버전의 편집기 콘텐츠로 업데이트되지 않은 경우 편집기는 모든 변경 사항을 롤백합니다.
  • onEditorChange prop은 에디터 내용이 변경될 때 실행될 이벤트 핸들러를 제공하는 데 사용됩니다. 변경 사항이 롤백되는 것을 방지하려면 편집기에 대한 변경 사항을 200밀리초 이내에 value prop에 적용해야 합니다.

고찰

  • docs에 각 props에 대해서 설명을 해주는 부분에 예제 코드가 없어서 이해하는데 너무 불편하다.
  • 문서를 처음부터 보다가 중반정도에서 다시 하나씩 자세히 다루는데 이때 예제 코드가 나온다.
    • 모든 prop마다 예제가 다 나오는 것은 아니다.

참고 문헌

에 대해 알아봐야겠다.

4-3. 이미지 전송

tinyMCE로 이미지를 전송하면 서버에 base64로 전송이 되는 것은 알고 있었는데 텍스트 에디터엔 blob형태로 추가되어서 실제로 base64로 인코딩되어서 전송이 되는지 눈으로 확인해보았다.

  • 아이콘을 통해 이미지를 추가 하거나 이미지를 에디터에 직접 붙여넣기 해서 추가하거나 모두 img 태그의 src에 blob형태로 추가가 되는 것 처럼 보였다. 그런데 서버로 직접 전송을 해보니 base64로 변환되어 전송이 되는 것을 확인했다.

  • 프론트에선 base64로 전송을 하고 서버에서 따로 저장하는 로직을 추가해야 될 것 같다.
  • 업로드 마다 서버에 전송을 해서 추가할 수도 있겠지만 현재 서버가 구축된 상황이 아니고 인프런에서도 이렇게 전송해서 처리하는 것을 확인했기에 이렇게 처리하고 서버 개발자들과 협업할 때 다시 상의를 해봐야될 것 같다.
  • 이렇게 이미지를 추가하는 방식의 단점은 인코딩에서 발생한 패딩으로 인해 전송 사이즈가 커질 수 있다.
  • 장점은 쓰레기 파일(업로드 했는데 막상 사용되지 않는 이미지 파일)이 생기지 않고 프론트에서 에디터 설정을 추가로 해줄 필요가 없다.

참고

원문

⚠️ **GitHub.com Fallback** ⚠️