Background
chrome extension의 content script에서, 다른 모듈을 동적으로 import하고자 했습니다.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import
// boot.ts
console.log('loaded')
// content-script.ts
import('boot.ts') // content script가 실행될 때, loaded 콘솔이 뜨면 됩니다!
content-scripts에서 같은 경로 내 boot.ts를 동적 import하면 되는 상황이었고,
번들러는 Webpack을 사용하고 있었습니다.
content scripts
https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts?hl=ko
Chrome Extension은 브라우저의 기능을 확장하고 웹 페이지와 상호작용할 수 있는 기능을 제공합니다.
이 중 content script는 특정 웹 페이지에서 실행되는 JavaScript 파일로, 웹 페이지의 DOM을 읽고 수정할 수 있습니다.
→ 웹 페이지와 동일한 환경에서 실행되기 때문에, 웹 페이지의 자바스크립트와 상호작용하거나 DOM 요소를 조작할 수 있는 것이죠!
content scripts에서 외부 파일 사용하기
content script에서 외부 파일을 불러오기 위해서는 몇 가지 설정이 필요합니다.
1. web_accessible_resources
conent script에서는 manifest.json의 web accessible resources에 정의된 asset들에만 접근할 수 있습니다.
// manifest.json
{
"name": "My Extension",
"version": "1.0",
"manifest_version": 3,
"permissions": ["activeTab"],
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
],
"web_accessible_resources": [
{
"resources": ["boot.js"], // 추가!
"matches": ["<all_urls>"]
}
]
}
2. chrome.runtime.getURL
content script에서 extension의 asset을 포함하는 경우에도 (일반적인 경우와 마찬가지로) 자원의 전체 URL을 지정해야합니다.
→ chrome.runtime.getURL API를 사용해, 현재 chrome extension의 경로에서 파일을 탐색할 수 있습니다.
const scriptUrl = chrome.runtime.getURL("boot.js");
이를 통해 content script에서 동적으로 boot.js 파일을 불러올 수 있겠죠.
Problems
위 설정을 적용하면, content script를 아래와 같이 작성할 수 있습니다.
// boot.ts
console.log('loaded')
// content-script.ts
const dynamicModuleUrl = chrome.runtime.getURL("boot.js");
import(dynamicModuleUrl)
그런데, 막상 익스텐션을 실행해보면...
익스텐션 경로에서 파일을 찾을 수 없다는 에러가 발생합니다!
하지만 막상 위 링크로 접속하면
번들링된 boot.js 파일이 정상적으로 존재하는 것을 확인할 수 있습니다. 이게 어떻게 된 일일까요?
Caused
원인을 파악하기 위해 원격 모듈(boot.js)를 import하는, content-script.js 번들링 파일을 살펴봤습니다.
(() => {
var r = {
6910: (r) => {
function e(r) {
return Promise.resolve().then(() => {
var e = new Error("Cannot find module '" + r + "'");
throw ((e.code = "MODULE_NOT_FOUND"), e);
});
}
(e.keys = () => []), (e.resolve = e), (e.id = 6910), (r.exports = e);
},
},
e = {};
function o(t) {
var n = e[t];
if (void 0 !== n) return n.exports;
var s = (e[t] = { exports: {} });
return r[t](s, s.exports, o), s.exports;
}
(o.o = (r, e) => Object.prototype.hasOwnProperty.call(r, e)),
(() => {
"use strict";
var r = chrome.runtime.getURL("boot.js");
o(6910)(r);
})();
})();
이 코드에서 Webpack이 동적 import를 처리하려고 시도하는 부분은 다음과 같습니다:
var r = chrome.runtime.getURL("boot.js");
o(6910)(r);
하지만 막상 o(6910) 함수는
var r = {
6910: (r) => {
function e(r) {
return Promise.resolve().then(() => {
var e = new Error("Cannot find module '" + r + "'");
throw ((e.code = "MODULE_NOT_FOUND"), e);
});
}
(e.keys = () => []), (e.resolve = e), (e.id = 6910), (r.exports = e);
},
};
Cannot find module Error만을 반환하고 있습니다!
아하...
즉 번들링 결과물에서부터 chrome.runtime.getURL("boot.js") 경로의 모듈은 존재하지 않기 때문에
런타임에서도 당연히 Cannot find module 에러가 발생했던 것입니다!
Webpack과 동적 파일 번들링
Webpack은 정적 분석 도구로서, 빌드 시점에 모든 모듈을 분석하고 번들링합니다.
→ 그리고 이는 동적 import를 사용할 때도 예외는 아닙니다.
동적 import를 사용할 때, Webpack은 경로를 정적으로 분석하여 해당 모듈을 번들링 과정에 포함시키려고 시도합니다.
그러나 chrome.runtime.getURL을 통해 생성된 경로는 런타임에만 알 수 있기 때문에,
Webpack은 이 경로를 인식하지 못하고 위와 같은 에러를 발생시키고 마는 것이죠.
Solution
이를 해결하기 위해서는?
Webpack이 해당 import를 무시하고, 런타임 시점에 해당 경로로 모듈을 동적으로 로드할 수 있게 되겠죠!
→ webpackIgnore: true 플래그를 사용해 이를 구현할 수 있습니다.
// boot.ts
console.log('loaded')
// content-script.ts
const dynamicModuleUrl = chrome.runtime.getURL("boot.js");
import(
/* webpackIgnore: true */
dynamicModuleUrl
);
해당 플래그를 통해 Webpack에게 해당 import 구문을 무시하도록 지시할 수 있습니다.
(() => {
"use strict";
var t = chrome.runtime.getURL("boot.js");
import(t);
})();
번들링 된 content-script.js 파일입니다.
이전과는 달리, Webpack이 해당 경로를 분석하고 번들링하지 않았음을 확인할 수 있습니다.
이를 통해 런타임 시점에 해당 경로로 모듈을 동적으로 로드할 수 있겠죠.
콘솔을 통해 동적 import가 정상적으로 이뤄졌음을 확인한 모습입니다.
'source-code > FrontEnd' 카테고리의 다른 글
[chrome extension] 이미지 파일 안전하게 불러오기 (0) | 2024.08.09 |
---|---|
Feature-Sliced Design(FSD) 도입기 (0) | 2024.08.03 |
WebComponent에서 styled-components 사용하기 (0) | 2024.07.10 |
[jest] waitFor을 통한 비동기 함수 테스트 (0) | 2024.06.23 |
[jest] test.each를 통해 여러 입력값 테스트 간결하게 구현하기 (0) | 2024.06.13 |