이 글은 CKEditor의 Image Upload Plugin을 커스텀하는 과정에 대해서 설명하고 있습니다.
참고 : https://ckeditor.com/docs/ckeditor5/latest/framework/deep-dive/upload-adapter.html
커스텀을 시작하기 전에 먼저 용어에 대해서 알고 가야한다.
File Loader를 intercept 하여 처리하는 역할.
FileRepository API를 사용하여 Upload Adpter 인스턴스를 생성한다.
그리고 Upload Adapter의 upload() 메서드를 호출하고, 반환받은 Promise를 사용해 editor content의 이미지를 업데이트 한다.
각 이미지 업로드 시마다 File loader 인스턴스가 생성되며, 업로드 과정에서 사용된다.
사용자로부터 업로드를 요청받은 이미지를 처리하는 핸들러이다.
서버로 안전하게 이미지를 전송하고, 서버로부터 받은 응답을 다시 file loader에게 전달한다.
커스텀 Upload Plugin 구현
file loader를 전달받고, 이를 통해서 어댑터가 처리할 수 있도록 한다.
FileRepository API를 사용해서 Upload Adapter 인스턴스 생성 및 반환
커스텀 Upload Adapter 구현
Upload Plugin으로부터 전달받은 file loader를 사용하여 Upload Plugin이 호출할 upload() 메서드를 구현한다.
upload() 메서드는 서버로 이미지 업로드를 요청하고, 응답을 받아서 Promise를 반환하도록 구현해야 한다.
config에 plugin 추가
구현한 커스텀 플러그인을 추가한다.
function uploadPlugin(editor) {
editor.plugins.get("FileRepository").createUploadAdapter = (loader) => {
return uploadAdapter(loader);
};
}
FileRepository.createUploadAdapter() 팩토리 메서드를 사용해 uploadAdapter 인스턴스를 생성하고 Upload Plugin에 부착할 수 있다.
이를 통해 Upload Adapter를 Editor에 적용시킬 수 있다.
loader는 앞서 설명한 file loader 인스턴스로, 이를 사용하여 upload adapter를 구현하기 위해 파라미터로 전달한다.
function uploadAdapter(loader) {
return {
upload: () => {
return new Promise((resolve, reject) => {
loader.file.then(file => {
// 1. FileReader 객체 생성
const reader = new FileReader();
// 3. 읽기 작업 완료 시 onload 핸들러 호출 -> onload 콜백 함수를 실행
reader.onload = () => {
const imageData = reader.result.split(',')[1]; // base64 데이터 추출
fetch('yourServerURL', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ image: imageData })
})
.then((res) => res.json())
.then(res => {
resolve({
default: res.url
});
})
.catch(error => {
console.error('Error uploading image:', error);
reject(error.message || 'Failed to upload image');
});
};
// 2. 읽기 작업 실행
reader.readAsDataURL(file);
})
.catch(error => {
console.error('Error getting file from loader:', error);
reject(error);
});
});
}
};
}
Upload Adapter의 upload() 메서드는 Upload Plugin이 사용할 Promise를 반환해야 한다. Promise 객체는 resolve(성공 콜백 함수), reject(실패 콜백 함수)를 인자로 받으며, 어떤 콜백 함수를 호출하는 지에 따라서 반환되는 Promise가 결정된다.
resolve(성공) 호출 시에는 파라미터로 이미지의 default URL을 전달해야 한다. (json 형식)
reject(실패) 호출 시에는 에러 메세지를 전달하도록 구현했다.
reader.readAsDataURL()를 호출하여 읽기 작업을 수행한다. onload 이벤트 핸들러가 호출됨콜백 함수 수행FileLoader의 file 프로퍼티는 Promise를 반환한다. file이 없다면 null을 반환하고, 있다면 담겨진 파일을 반환한다. null일 경우 catch를 통하여 reject(실패 콜백 함수) 를 호출한다.
file이 정상적으로 들어있다면, FileReader 객체를 생성하여 전달받은 파일을 비동기적으로 읽는다. 읽기 작업이 완료되면 reader.onload 이벤트 핸들러가 호출되며, 핸들러가 수행할 콜백함수를 작성해야 한다.
reader.onload의 콜백 함수를 작성하여 읽어온 이미지 result를 처리한다.서버에 이미지를 전송하고, 핸들링 하는 과정이 포함된다.

파일을 읽은 result에는 string 형식으로 데이터가 담겨있고, base64로 인코딩된 이미지만이 필요하기 때문에 ,를 기준으로 split하여 뒤의 값을 imageData 변수에 저장한다.
fetch()를 통해서 서버에 이미지를 저장할 수 있도록 요청한다. 서버 내부에서 S3 버킷에 이미지를 저장하도록 처리했다. 서버 코드에서 Json 형식으로 데이터를 읽기 때문에, 헤더에 Content-Type을 application/json으로 명시했다. body의 데이터를 JSON.stringify() 메서드를 사용해 json 데이터를 직렬화 해주었다.
fetch() 요청이 성공적으로 완료되면, 받아온 http response를 json() 메서드를 통해 역직렬화 한다.
성공, 실패 여부에 따라서 resolve, reject 콜백함수를 호출하여 최종적으로 반환하게 되는 Promise를 결정한다. 성공 시 서버로부터 반환되는 url을 default URL로 전달했다.
CKEditor의 Configuration에서 plugin을 추가한다.
<CKEditor
editor={DecoupledEditor}
config={plugins: [uploadPlugin, FileRepository]}
/>
위는 예시로 config에 위와 같이 plugin을 추가해서 사용하면 된다.