이전 모듈에서는 웹 작업자 개요를 살펴보았습니다. 웹 작업자는 자바스크립트를 기본 스레드에서 별도의 웹 작업자 스레드로 이동하여 입력 응답성을 개선할 수 있습니다. 이렇게 하면 기본 스레드에 직접 액세스할 필요가 없는 작업이 있을 때 웹사이트의 다음 페인트와의 상호작용 (INP)을 개선할 수 있습니다. 그러나 개요만으로는 충분하지 않으며 이 모듈에서는 웹 작업자에 대한 구체적인 사용 사례를 제공합니다.
이미지에서 Exif 메타데이터를 제거해야 하는 웹사이트가 이러한 사용 사례 중 하나일 수 있습니다. 실제로 Flickr와 같은 웹사이트에서는 사용자가 호스팅하는 이미지에 관한 기술 세부정보(예: 색심도, 카메라 제조업체 및 모델, 기타 데이터)를 알아보기 위해 Exif 메타데이터를 확인할 수 있는 방법을 제공합니다.
그러나 이미지를 가져오고, 이미지를 ArrayBuffer
로 변환하고, Exif 메타데이터를 추출하는 로직은 전적으로 기본 스레드에서 실행되면 비용이 많이 들 수 있습니다. 다행히 웹 작업자 범위를 사용하면 이 작업을 기본 스레드에서 실행할 수 있습니다. 그런 다음 웹 작업자의 메시징 파이프라인을 사용하여 Exif 메타데이터가 기본 스레드에 HTML 문자열로 다시 전송되어 사용자에게 표시됩니다.
웹 워커가 없는 기본 스레드의 모습
먼저 웹 작업자 없이 이 작업을 할 때 기본 스레드가 어떻게 표시되는지 관찰합니다. 이렇게 하려면 다음 단계를 따르세요.
- Chrome에서 새 탭을 열고 DevTools를 엽니다.
- 성능 패널을 엽니다.
- https://exif-worker.glitch.me/without-worker.html으로 이동합니다.
- 성능 패널에서 DevTools 창 오른쪽 상단에 있는 Record를 클릭합니다.
- 필드에 이 이미지 링크 또는 Exif 메타데이터가 포함된 다른 링크 중 하나를 붙여넣고 Get that JPEG! 버튼을 클릭합니다.
- 인터페이스에 Exif 메타데이터가 채워지면 Record를 다시 클릭하여 기록을 중지합니다.
래스터라이저 스레드 등 있을 수 있는 다른 스레드를 제외하고 앱의 모든 것은 기본 스레드에서 발생합니다. 기본 스레드에서는 다음과 같은 결과가 발생합니다.
- 이 양식은 입력을 받고
fetch
요청을 전달하여 Exif 메타데이터가 포함된 이미지의 초기 부분을 가져옵니다. - 이미지 데이터가
ArrayBuffer
로 변환됩니다. exif-reader
스크립트는 이미지에서 Exif 메타데이터를 추출하는 데 사용됩니다.- 메타데이터를 스크래핑하여 HTML 문자열을 생성한 후 메타데이터 뷰어를 채웁니다.
이제 이 점을 웹 worker를 사용하여 동일한 동작을 구현하는 것과 대조해 보세요.
웹 작업자가 있는 기본 스레드의 모습
지금까지 기본 스레드의 JPEG 파일에서 Exif 메타데이터를 추출하는 방법을 살펴봤습니다. 이제 웹 작업자가 함께 사용될 때 어떻게 표시되는지 살펴보겠습니다.
- Chrome에서 다른 탭을 열고 DevTools를 엽니다.
- 성능 패널을 엽니다.
- https://exif-worker.glitch.me/with-worker.html로 이동합니다.
- 성능 패널에서 DevTools 창 오른쪽 상단에 있는 기록 버튼을 클릭합니다.
- 입력란에 이 이미지 링크를 붙여넣고 JPEG 가져오기 버튼을 클릭합니다.
- 인터페이스가 Exif 메타데이터로 채워지면 기록 버튼을 다시 클릭하여 기록을 중지합니다.
이것이 바로 웹 작업자의 힘입니다. 기본 스레드에서 모든 작업을 실행하는 대신 메타데이터 뷰어를 HTML로 채우는 작업을 제외한 모든 작업은 별도의 스레드에서 이루어집니다. 즉, 기본 스레드가 다른 작업을 할 수 있도록 해제됩니다.
여기서 가장 큰 장점은 웹 워커를 사용하지 않는 이 앱의 버전과 달리 exif-reader
스크립트가 기본 스레드가 아닌 웹 워커 스레드에 로드된다는 것입니다. 즉, exif-reader
스크립트를 다운로드, 파싱, 컴파일하는 비용은 기본 스레드에서 발생합니다.
이제 이 모든 것을 가능하게 하는 웹 작업자 코드를 자세히 살펴보겠습니다.
웹 작업자 코드 살펴보기
웹 작업자가 만들어내는 차이를 보는 것만으로는 충분하지 않으며, 적어도 이 경우에는 코드가 어떻게 보이는지 이해하여 웹 작업자 범위에서 가능한 작업을 파악할 수 있습니다.
웹 작업자가 그림을 시작하기 전에 발생해야 하는 기본 스레드 코드로 시작합니다.
// scripts.js
// Register the Exif reader web worker:
const exifWorker = new Worker('/js/with-worker/exif-worker.js');
// We have to send image requests through this proxy due to CORS limitations:
const imageFetchPrefix = 'https://res.cloudinary.com/demo/image/fetch/';
// Necessary elements we need to select:
const imageFetchPanel = document.getElementById('image-fetch');
const imageExifDataPanel = document.getElementById('image-exif-data');
const exifDataPanel = document.getElementById('exif-data');
const imageInput = document.getElementById('image-url');
// What to do when the form is submitted.
document.getElementById('image-form').addEventListener('submit', event => {
// Don't let the form submit by default:
event.preventDefault();
// Send the image URL to the web worker on submit:
exifWorker.postMessage(`${imageFetchPrefix}${imageInput.value}`);
});
// This listens for the Exif metadata to come back from the web worker:
exifWorker.addEventListener('message', ({ data }) => {
// This populates the Exif metadata viewer:
exifDataPanel.innerHTML = data.message;
imageFetchPanel.style.display = 'none';
imageExifDataPanel.style.display = 'block';
});
이 코드는 기본 스레드에서 실행되며 웹 작업자에게 이미지 URL을 전송하는 양식을 설정합니다. 여기에서 웹 작업자 코드는 외부 exif-reader
스크립트를 로드하는 importScripts
문으로 시작된 후 기본 스레드에 대한 메시지 파이프라인을 설정합니다.
// exif-worker.js
// Import the exif-reader script:
importScripts('/js/with-worker/exifreader.js');
// Set up a messaging pipeline to send the Exif data to the `window`:
self.addEventListener('message', ({ data }) => {
getExifDataFromImage(data).then(status => {
self.postMessage(status);
});
});
이 비트 JavaScript는 사용자가 JPEG 파일 URL이 포함된 양식을 제출할 때 URL이 웹 워커에 도착하도록 메시지 파이프라인을 설정합니다.
거기에서 다음 비트 코드는 JPEG 파일에서 Exif 메타데이터를 추출하고 HTML 문자열을 빌드하고 이 HTML을 다시 window
로 전송하여 최종적으로 사용자에게 표시합니다.
// Takes a blob to transform the image data into an `ArrayBuffer`:
// NOTE: these promises are simplified for readability, and don't include
// rejections on failures. Check out the complete web worker code:
// https://glitch.com/edit/#!/exif-worker?path=js%2Fwith-worker%2Fexif-worker.js%3A10%3A5
const readBlobAsArrayBuffer = blob => new Promise(resolve => {
const reader = new FileReader();
reader.onload = () => {
resolve(reader.result);
};
reader.readAsArrayBuffer(blob);
});
// Takes the Exif metadata and converts it to a markup string to
// display in the Exif metadata viewer in the DOM:
const exifToMarkup = exif => Object.entries(exif).map(([exifNode, exifData]) => {
return `
<details>
<summary>
<h2>${exifNode}</h2>
</summary>
<p>${exifNode === 'base64' ? `<img src="data:image/jpeg;base64,${exifData}">` : typeof exifData.value === 'undefined' ? exifData : exifData.description || exifData.value}</p>
</details>
`;
}).join('');
// Fetches a partial image and gets its Exif data
const getExifDataFromImage = imageUrl => new Promise(resolve => {
fetch(imageUrl, {
headers: {
// Use a range request to only download the first 64 KiB of an image.
// This ensures bandwidth isn't wasted by downloading what may be a huge
// JPEG file when all that's needed is the metadata.
'Range': `bytes=0-${2 ** 10 * 64}`
}
}).then(response => {
if (response.ok) {
return response.clone().blob();
}
}).then(responseBlob => {
readBlobAsArrayBuffer(responseBlob).then(arrayBuffer => {
const tags = ExifReader.load(arrayBuffer, {
expanded: true
});
resolve({
status: true,
message: Object.values(tags).map(tag => exifToMarkup(tag)).join('')
});
});
});
});
약간 읽기 쉬울 수도 있지만 이는 웹 워커의 경우 상당히 복잡한 사용 사례이기도 합니다.
그러나 그 결과는 이 사용 사례뿐만 아니라 노력할 가치가 있습니다.
fetch
호출 격리 및 응답 처리, 기본 스레드를 차단하지 않고 대량의 데이터 처리와 같은 모든 종류의 작업에 웹 작업자를 사용할 수 있습니다. 시작에 불과합니다.
웹 애플리케이션의 성능을 개선할 때는 웹 작업자 환경에서 합리적으로 실행할 수 있는 모든 방법을 생각해 보세요. 상당한 이점을 얻을 수 있고 웹사이트의 사용자 환경을 전반적으로 개선할 수 있습니다.