Chrome 扩展开发完全指南 / 第 17 章:容器化开发(Docker)
第 17 章:容器化开发(Docker)
容器化是现代软件开发的标准实践。将 Chrome 扩展的开发环境和构建流水线容器化,可以确保团队成员使用一致的环境,简化 CI/CD 集成,并实现自动化测试和发布。
17.1 为什么使用 Docker
| 问题 | 无 Docker | 有 Docker |
|---|
| 环境一致性 | “在我电脑上能跑” | 统一容器环境 |
| 依赖管理 | 手动安装 Node.js 版本 | Dockerfile 定义 |
| CI/CD | 复杂的环境配置 | 标准镜像 |
| 新成员入门 | 安装各种工具 | docker compose up |
| 多版本测试 | 需要安装多个 Node | 多个容器 |
17.2 开发环境容器化
17.2.1 Dockerfile
# Dockerfile
FROM node:20-alpine AS base
WORKDIR /app
# 安装依赖
COPY package.json package-lock.json ./
RUN npm ci
# 复制源代码
COPY . .
# 开发阶段
FROM base AS dev
# 安装开发依赖
RUN npm ci --include=dev
# 暴露开发服务器端口
EXPOSE 5173
CMD ["npm", "run", "dev"]
# 构建阶段
FROM base AS build
RUN npm run build
# 输出阶段 — 仅包含构建产物
FROM scratch AS output
COPY --from=build /app/dist /dist
17.2.2 Docker Compose 配置
# docker-compose.yml
version: '3.8'
services:
# 开发环境
dev:
build:
context: .
target: dev
volumes:
- .:/app
- /app/node_modules
ports:
- "5173:5173"
environment:
- NODE_ENV=development
stdin_open: true
tty: true
# 构建扩展
build:
build:
context: .
target: build
volumes:
- ./dist:/app/dist
# 代码检查
lint:
build:
context: .
target: dev
command: npm run lint
volumes:
- .:/app
- /app/node_modules
# 单元测试
test:
build:
context: .
target: dev
command: npm run test
volumes:
- .:/app
- /app/node_modules
# 打包
package:
build:
context: .
target: build
command: sh -c "cd dist && zip -r /output/extension.zip ."
volumes:
- ./release:/output
17.2.3 使用方式
# 启动开发环境
docker compose up dev
# 构建扩展
docker compose run build
# 运行代码检查
docker compose run lint
# 运行测试
docker compose run test
# 打包发布版本
docker compose run package
# 输出: ./release/extension.zip
17.3 .dockerignore
# .dockerignore
node_modules
npm-debug.log*
dist
release
.git
.gitignore
*.md
.env*
.DS_Store
.vscode
.idea
coverage
tests
17.4 CI/CD 流水线
17.4.1 GitHub Actions
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
# 代码检查
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint code
run: npm run lint
# 单元测试
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm run test
# 构建
build:
needs: [lint, test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Package
run: cd dist && zip -r ../extension.zip .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: extension
path: extension.zip
# 发布到 Chrome Web Store (仅 main 分支)
publish:
needs: [build]
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: extension
- name: Publish to Chrome Web Store
uses: Klemensas/chrome-extension-upload-action@v1
with:
refresh-token: ${{ secrets.CWS_REFRESH_TOKEN }}
client-id: ${{ secrets.CWS_CLIENT_ID }}
client-secret: ${{ secrets.CWS_CLIENT_SECRET }}
extension-id: ${{ secrets.CWS_EXTENSION_ID }}
file-name: extension.zip
publish: true
17.4.2 使用 Docker 的 CI/CD
# .github/workflows/ci-docker.yml
name: CI/CD (Docker)
on:
push:
branches: [main]
pull_request:
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Docker image
uses: docker/build-push-action@v5
with:
context: .
target: build
push: false
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Run tests
run: |
docker compose run --rm test
- name: Package extension
run: |
docker compose run --rm package
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: extension
path: release/extension.zip
17.5 测试容器化
17.5.1 Puppeteer 测试
// tests/e2e/extension.test.js
const puppeteer = require('puppeteer');
const path = require('path');
describe('Extension E2E Tests', () => {
let browser;
let page;
beforeAll(async () => {
const extensionPath = path.resolve(__dirname, '../../dist');
browser = await puppeteer.launch({
headless: 'new',
args: [
`--disable-extensions-except=${extensionPath}`,
`--load-extension=${extensionPath}`,
'--no-sandbox'
]
});
// 获取扩展的 service worker
const targets = await browser.targets();
const serviceWorkerTarget = targets.find(
t => t.type() === 'service_worker'
);
page = await browser.newPage();
});
afterAll(async () => {
await browser?.close();
});
test('extension loads correctly', async () => {
await page.goto('https://example.com');
// 等待 Content Script 注入
await page.waitForSelector('[data-ext-loaded]', { timeout: 5000 });
});
test('popup displays correctly', async () => {
// 通过 URL 打开 popup
const popupPage = await browser.newPage();
await popupPage.goto(
`chrome-extension://${extensionId}/popup/popup.html`
);
const title = await popupPage.textContent('h1');
expect(title).toBeTruthy();
});
});
17.5.2 测试 Dockerfile
# Dockerfile.test
FROM node:20-slim
# 安装 Chromium 依赖
RUN apt-get update && apt-get install -y \
chromium \
fonts-ipafont-gothic \
fonts-wqy-zenhei \
fonts-thai-tlwg \
fonts-freefont-ttf \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/*
# Puppeteer 配置
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD ["npm", "run", "test:e2e"]
17.6 多阶段构建优化
17.6.1 缓存优化
# Dockerfile.optimized
FROM node:20-alpine AS deps
WORKDIR /app
# 仅复制依赖文件(利用 Docker 缓存层)
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci
# 构建阶段
FROM deps AS build
COPY . .
RUN npm run build
# 发布阶段
FROM node:20-alpine AS release
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY package.json ./
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
CMD node -e "console.log('ok')"
17.6.2 多架构构建
# 构建支持多架构的镜像
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t my-extension-builder:latest \
--push .
17.7 发布自动化
17.7.1 版本管理脚本
// package.json
{
"scripts": {
"version:patch": "npm version patch",
"version:minor": "npm version minor",
"version:major": "npm version major",
"build": "vite build",
"package": "npm run build && cd dist && zip -r ../release/extension-v$npm_package_version.zip .",
"release": "npm run package && echo 'Release ready: release/extension-v$npm_package_version.zip'"
}
}
# 发布流程
npm run version:minor # 版本号 +0.1.0
npm run release # 构建并打包
git push --follow-tags # 推送版本标签
17.7.2 Makefile
# Makefile
.PHONY: dev build test lint package clean
VERSION := $(shell node -p "require('./manifest.json').version")
dev:
docker compose up dev
build:
docker compose run --rm build
test:
docker compose run --rm test
lint:
docker compose run --rm lint
package:
docker compose run --rm package
@echo "✅ Extension packaged: release/extension.zip"
clean:
docker compose down -v
rm -rf dist release node_modules
release: lint test build package
@echo "✅ Release v$(VERSION) ready"
@echo "📦 File: release/extension.zip"
help:
@echo "Available commands:"
@echo " make dev - Start development environment"
@echo " make build - Build extension"
@echo " make test - Run tests"
@echo " make lint - Run linter"
@echo " make package - Package extension for release"
@echo " make release - Full release pipeline"
@echo " make clean - Clean all artifacts"
17.8 注意事项
| 问题 | 说明 | 解决方案 |
|---|
| Docker 镜像过大 | 包含不必要的文件 | 使用多阶段构建和 .dockerignore |
| npm ci 很慢 | 网络问题 | 使用 npm 镜像或 Docker 缓存 |
| 测试中 Chrome 崩溃 | 资源限制 | 增加 Docker 内存限制 --shm-size |
| 热更新不生效 | 卷挂载问题 | 确认 volume 配置正确 |
| 文件权限问题 | 容器内外 UID 不一致 | 在 Dockerfile 中设置 USER |
17.9 扩展阅读