Webpack을 사용하는 코드에서 Web Worker의 스크립트 파일의 경로 설정 문제

박기완·2023년 3월 22일
0

TL; DR

Webpack에서 Worker API를 사용할 때는 Worker 생성과 URL 생성을 모두 인라인으로 작성해야 한다.

const worker = new Worker(new URL("./script.ts", import.meta.url));

배경

사이드 프로젝트에서 많은 계산을 요하는 작업이 있어서 Web Workers API를 사용했다. 시키는 대로 막 구현한 구조는 다음과 같다.

// my-module/index.ts
export function runHeavyTaskUsingWorker(input: Input): Promise<Output> {
  return new Promise((resolve, reject) => {
    const worker = new Worker(new URL("./script.ts", import.meta.url));

    worker.onmessage = (event: MessageEvent<Output>) => {
      resolve(event.data.palette);
      worker.terminate();
    };

    worker.postMessage(input);
  });
}

// my-module/script.ts
addEventListener(
  "message",
  (event: MessageEvent<Input>) => {
    postMessage(heavyTask(event.data));
  },
);

function heavyTask(input: Input): Output {
  // 구현 생략
}

코드를 보다 문득 Web Worker와 관련된 부분을 추상화 하고 싶은 생각이 들었다. CPU를 많이 사용하는 함수를 넣어서 Worker로 실행하도록 해주는 함수를 만들고 싶었다. 상상한 인터페이스는 다음과 같다.

const runWorker = generateWorkerRunner(heavyTask)
// runWorker의 타입: function runWorker(input: Input): Promise<Output>

이리저리 바꾸면서 시간을 보내다가 결국 완성은 못했지만, Webpack에 대해 재미있는 사실을 배웠다.

Web Worker에 전달하는 스크립트 주소

worker를 생성할 때 원래는 HTML의 script 태그처럼 worker가 실행할 스크립트의 주소를 넣어줘야 한다. const worker = new Worker("/my-script.js"); 형태로 말이다. 그런데 내 사이드프로젝트를 포함한 Webpack 기반의 번들링 환경에선 Webpack이 최종 파일의 URL을 만드니까 스크립트의 정확한 주소를 하드코딩할 수 없다. 현재 모듈 기반의 상대 경로를 지정해주면 Webpack이 실제 파일 경로로 바꿔줘야 한다. 이것이 다음 코드의 존재 이유다.

new URL("./script.ts", import.meta.url)

URL에 들어가는 첫 번째 파라미터는 현재 모듈에서 스크립트 파일로 가는 상대 경로, 두 번째 파라미터는 base로 이용할 현재 모듈의 경로이고, URL 함수가 그걸 조합해서 스크립트 모듈 주소를 반환하는 줄 알았다. 그래서 다음 코드가 당연히 가능할 거라고 생각했다.

const url = new URL("./script.ts", import.meta.url);
// 여러 코드 맥락...
const worker = new Worker(url);

하지만 이 코드는 잘못된 URL을 생성한다. script.ts를 assets/media에서 가져오려고 하고 비디오 형식의 파일로 생각한다. 여러 번의 실험 끝에 Worker 생성과 URL 생성을 모두 인라인으로 작성해야 한다는 것을 깨달았다. 찾아보니 나처럼 삽질한 사람도 있었고, Webpack의 공식 설명도 있었다. Webpack이 Worker + URL의 조합을 코드에서 찾고, 그걸 실제 스크립트의 주소로 대치하는 걸로 보인다.

후기

Webpack의 번들링을 공기처럼 당연하게 느껴서 Webpack이 코드를 바꾼다는 생각을 안 하게 되었다. 내가 관리하는 원본 소스코드의 형상이 Webpack을 통과하면 달라진다는 것을 쉽게 잊게 된다. 만약 Vite처럼 native ECMAScript 모듈을 사용한다면 원본의 형상을 그대로 사용하니, url을 따로 선언해도 문제가 없을 걸로 보인다. 하지만, 한 번 해보니 모듈을 잘 가져오긴 하는데 import, export 구문을 사용하면 오류(SyntaxError: import declarations may only appear at top level of a module)가 발생한다. 공부해야 할 건 정말 끝이 없다.

0개의 댓글