"앞의 여섯 track은 순순히 협조하는 페이지를 상대로 뼈대를 세운 거야. Track 7은 반격하는 페이지를 상대해 — 무거운 framework, 시도 때도 없이 다시 그려지는 화면, handler를 붙이기도 전에 내용을 갈아치우는 SPA. 이 lesson에서 연장통 지도를 그리고, 다음 여섯 lesson에서 하나씩 갈아."
DOM 도구가 뭐냐면
Track 3에서 배운 기본을 넘어서, content script와 페이지의 진짜 구조를 이어 주는 것들이야. 크게 세 갈래로 갈려:
- 뽑아내기 — 엉망인 markup에서 깔끔하게 정리된 데이터를 건져 내는 일. Readability, schema.org 파싱, OpenGraph, 직접 만든 CSS-selector 모음 같은 것들.
- 담아두기 — user가 지금 보고 있는 걸 그대로 기록하는 일. 선택한 텍스트, screenshot, 스크롤 위치, viewport 크기.
- 고치기 — user를 위해 페이지를 손보는 일. 입력창 채우기, 가짜 event 쏘기, overlay 끼워 넣기, 원하는 데로 스크롤하기.
ClipDeck v1은 뽑아내기 (긴 clip을 위한 본문 추출) 랑 담아두기 (선택 영역 + screenshot)를 써. 고치기는 v2에서 붙일 거야 — 저장해 둔 조각을 페이지에 다시 넣어 주는 기능이지. Track 7에서 셋을 다 다루는 건 결국 쓰는 근육이 같아서야.
'현실 페이지' 라서 붙는 조정
이 도구들이 교과서 DOM 예제랑 다른 건, 현실의 페이지가 테스트 코드는 절대 안 하는 방식으로 extension 한테 적대적이기 때문이야:
- Framework가 앞을 가려. React, Vue, Svelte가 DOM의 주인이야. state가 바뀌면 component를 다시 그리면서, 아빠가 끼워 넣은 overlay를 그대로 밀어 버려.
- 가짜 event는 안 통해. Framework의 입력창은 자기 setter로 state를 따라가.
input.value에 직접 값을 넣어도 framework는 입력이 바뀐 줄 몰라. - 늦게 불러오는 것들. 기사 이미지, 댓글, 관련 글은 user가 스크롤해야 그때 붙어.
DOMContentLoaded시점에 읽는 코드는 그 뒤에 오는 걸 전부 놓쳐. - CSS collision. Manifest로 선언한 extension CSS가 page CSP 때문에 거절되는 건 아니지만, light DOM에 둔 UI는 page selector와 부딪힐 수 있어. 격리가 필요하면 Shadow DOM이나 충분히 좁은 class 이름을 써.
- Shadow DOM. 열려 있는 shadow root는 뒤질 수 있는데 닫힌 건 못 해. 요즘 site 들이 component 마다 이걸 점점 더 쓰고, 그럼 querySelector가 아무것도 못 찾아.
'user 신뢰' 라서 붙는 조정
페이지는 user의 공간이야. 뭘 바꾸든 그걸 존중해야 해:
- 확정 전에 보여 주기. 뭘 할 건지 먼저 보여 주고, user가 확인하거나 물러날 수 있게 해. Track 7 Lesson 6에서 제대로 다뤄.
- 잠깐은 되돌릴 수 있게. 되돌리기 힘든 일 (clip 삭제, 입력창 채우기) 에는 5 초짜리 undo를 붙여. 그 뒤엔 확정하고.
- 흔적을 남기기. 입력창을 건드렸으면 작은 표시 (테두리 색, 옆에 라벨)를 남겨서 ClipDeck이 한 일이라는 걸 user가 기억하게 해.
- 놀래키지 않기. 자동으로 채우지 말고, 자동으로 저장하지 말고, user가 시작하지 않은 일은 아무것도 하지 마.
Bundle을 어떻게 할까
도구 중엔 남의 library에서 오는 것도 있어 (Readability 같은 거). 가져오는 방법이 셋이야:
- 그냥 넣기 — source 파일을 extension에 떨궈 놓고 같이 commit 해. 단순하고, 눈으로 확인하기 쉽고, 평범한 JS library 라면 build 단계도 필요 없어.
- npm + bundler — 요즘 방식이야. esbuild, Rollup, Vite가 content.js 하나로 묶어 줘. ES module import를 쓰는 library 라면 이 길밖에 없고.
- Classic file order — 정적
content_scripts에는type: 'module'field가 없어. Classic file을 manifest 순서대로 load하거나 import를 bundle해. Runtime의import(chrome.runtime.getURL(...))경로는 web-accessible-resource/CORS 조건이 더 붙으니 manifest module 대용품처럼 쓰면 안 돼.
ClipDeck v1은 Readability.js 파일 하나만 넣고 bundler는 안 써. 한 파일로 감당이 안 될 만큼 자랐을 때 어떻게 bundler로 넘어가는지는 Track 8 (packaging)에서 다룰 거야.
이 track에서 붙는 것
lesson 여섯 개에 도구 여섯 개야:
- Lesson 2 — 기사 본문을 뽑는 Readability.js.
- Lesson 3 — Selection과 Range API를 깊게 (toString 너머로).
- Lesson 4 — 선택 영역 screenshot을 위한 chrome.tabs.captureVisibleTab + canvas 자르기.
- Lesson 5 — React를 속이지 않고 입력창 채우는 요령.
- Lesson 6 — 보여 주고 확인받는 패턴.
- Lesson 7 — inline edit과 undo를 갖춘 ClipDeck의 Update와 Delete.
다 끝나면 ClipDeck이 이런 현실적인 흐름을 감당해. user가 긴 기사를 읽다가 세 문단을 잡고, Ctrl+Shift+K를 누르고, 뭐가 저장될지 미리 보고, 확인하고, 그다음 side panel에서 clip 제목을 고치고 아까 중복 저장한 걸 지워. 그동안 읽던 자리를 한 번도 안 잃고.