"한 file 위한 vanilla JS 가 fine. Npm dependency 와 TypeScript 가진 multi-file 가 bundler 손 뻗는 곳. Lesson 5 가 src/ 를 1 초 안에 dist/ 로 바꾸는, sourcemap / watch mode / Web Store-clean 출력 가진 minimum esbuild setup."
왜 bundler
File 당 약 200 줄까지, content.js / background.js / popup.js / panel.js 등 흩뿌려진 vanilla JS 가 fine. Bundler 가 자기 몫 하는 시점:
- Type 위해 TypeScript 원할 때.
- Internal ES module import 가진 third-party npm package import (flat single file 아닌 npm 의 어떤 것이든).
- Codebase 가 여러 entry point 가 쓰는 shared helper 가짐 (popup + panel + content 모두 같은 clip-row renderer 필요).
- src/ 의 한 source layout, dist/ 의 한 production layout 원함, build 동안 manifest 와 HTML 복사/transform.
Default 로서 esbuild
esbuild 가 JS/TS 를 밀리초 안에 compile. 몇 entry point 가진 주로 TypeScript 의 extension 에, entire build 가:
esbuild \
src/background.ts \
src/content.ts \
src/popup.ts \
src/panel.ts \
--bundle \
--outdir=dist \
--target=chrome120 \
--sourcemap=inline \
--format=iife
네 entry point, 한 command, 다 bundle 된 네 output file (background.js, content.js, popup.js, panel.js). Output 이 Chrome 이 content script 와 classic background page 에 원하는 IIFE format. --target=chrome120 가 esbuild 한테 transpile 안 하고 두는 JS feature 알려 줘.
Layout
clipdeck/
src/
background.ts
content.ts
popup.ts
panel.ts
shared/
clip-row.ts
escape-html.ts
types.ts
static/
manifest.json
popup.html
panel.html
icons/
16.png 32.png 48.png 128.png
vendor/
Readability.js
scripts/
build.mjs
dist/ # gitignore, 생성됨
package.json
tsconfig.json
Build script 가 static/ 를 dist/ 로 복사하고 src/ 를 dist/ 로 bundle. npm run build 후 chrome-loadable dist/ 가짐.
Build script
esbuild 의 Node API 가 node scripts/build.mjs 로 돌릴 수 있는 single file 줘:
import { build } from 'esbuild';
import { copyFile, mkdir, cp } from 'fs/promises';
await mkdir('dist', { recursive: true });
await cp('static', 'dist', { recursive: true });
await build({
entryPoints: ['src/background.ts', 'src/content.ts', 'src/popup.ts', 'src/panel.ts'],
bundle: true,
outdir: 'dist',
target: 'chrome120',
format: 'iife',
sourcemap: 'inline',
logLevel: 'info',
});
development 중 rebuild-on-save 위해 --watch flag 와 esbuild 의 context API 추가. Chrome extension reload library (extension-reloader, 또는 dev SW 통한 작은 chrome.runtime.reload trigger) 와 pair 해서 file 저장이 Chrome 의 extension reload trigger.
TypeScript setup
Extension 용 tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"types": ["chrome"],
"noEmit": true,
"skipLibCheck": true
},
"include": ["src"]
}
chrome.* API type 위해 @types/chrome install. noEmit: true 인 이유는 esbuild 가 실제 compilation 하고; tsc 는 type-check 위해서만 돌림. 별도 check 로 tsc --noEmit 돌리는 npm run typecheck script 추가.
package.json script
{
"scripts": {
"build": "node scripts/build.mjs",
"watch": "node scripts/build.mjs --watch",
"typecheck": "tsc --noEmit",
"package": "npm run typecheck && npm run build && cd dist && zip -r ../clipdeck.zip ."
},
"devDependencies": {
"esbuild": "^0.25.0",
"typescript": "^5.5.0",
"@types/chrome": "^0.0.300"
}
}
npm run package 가 typecheck, build, zip 을 한 shot 으로. 그 zip 이 Web Store 에 upload 하는 것. npm run watch 가 dev loop; chrome://extensions auto-reload (browser extension 이나 작은 chrome.runtime.reload SW listener) 와 pair.
Vendor 에서 migration
Track 7 의 Readability.js 가 flat file 로 vendor 됨. Bundler 와 함께, npm install @mozilla/readability 하고 normal 하게 import:
// src/content.ts
import { Readability } from '@mozilla/readability';
// ...이전과 정확히 같이 사용
Bundled content.js 가 Readability 의 코드 inline. 더 깔끔, version-tracked, 가능한 곳에서 tree-shaken. Static/vendor/ 디렉토리가 unused 됨 — build output 에서 삭제.