强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 扩展阅读