16 - 部署与构建
📖 学习目标
通过本章节学习,您将掌握:
- 生产环境构建
- 环境配置管理
- 静态资源优化
- 服务器部署
- Docker容器化
- CI/CD流水线
- 性能监控
🎯 核心概念
1. 部署流程
开发环境 → 构建优化 → 测试验证 → 生产部署 → 监控维护
2. 构建优化
- 代码分割:按需加载模块
- Tree Shaking:移除未使用代码
- 压缩优化:减少文件大小
- 缓存策略:提高加载速度
3. 部署方式
- 静态托管:GitHub Pages、Netlify、Vercel
- 服务器部署:Nginx、Apache
- 容器化部署:Docker、Kubernetes
- 云平台部署:AWS、Azure、阿里云
🔧 生产环境构建
1. 基本构建配置
typescript
// angular.json
{
"projects": {
"my-app": {
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/my-app",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": [],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
}
}
}
}
}
}
}
2. 构建命令
bash
# 开发构建
ng build
# 生产构建
ng build --prod
# 指定环境
ng build --configuration=production
# 分析包大小
ng build --stats-json
npx webpack-bundle-analyzer dist/my-app/stats.json
# 构建并分析
ng build --prod --stats-json
3. 环境配置
typescript
// environments/environment.ts
export const environment = {
production: false,
apiUrl: 'http://localhost:3000/api',
appName: 'My App',
version: '1.0.0',
debug: true
};
// environments/environment.prod.ts
export const environment = {
production: true,
apiUrl: 'https://api.myapp.com',
appName: 'My App',
version: '1.0.0',
debug: false
};
// environments/environment.staging.ts
export const environment = {
production: false,
apiUrl: 'https://staging-api.myapp.com',
appName: 'My App (Staging)',
version: '1.0.0',
debug: true
};
🚀 静态资源优化
1. 图片优化
typescript
// 使用WebP格式
// 在angular.json中配置
{
"assets": [
{
"glob": "**/*",
"input": "src/assets/images",
"output": "assets/images"
}
]
}
// 组件中使用
@Component({
template: `
<picture>
<source srcset="assets/images/hero.webp" type="image/webp">
<img src="assets/images/hero.jpg" alt="Hero image">
</picture>
`
})
export class HeroComponent {}
2. 字体优化
scss
// styles.scss
@font-face {
font-family: 'CustomFont';
src: url('assets/fonts/custom-font.woff2') format('woff2'),
url('assets/fonts/custom-font.woff') format('woff');
font-display: swap; // 优化字体加载
font-weight: 400;
font-style: normal;
}
// 预加载关键字体
// index.html
<link rel="preload" href="assets/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
3. 缓存策略
typescript
// service-worker配置
// ngsw-config.json
{
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/*.css",
"/*.js"
]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
]
}
}
],
"dataGroups": [
{
"name": "api-freshness",
"urls": [
"/api/**"
],
"cacheConfig": {
"strategy": "freshness",
"maxSize": 100,
"maxAge": "3d",
"timeout": "10s"
}
}
]
}
🌐 服务器部署
1. Nginx配置
nginx
# nginx.conf
server {
listen 80;
server_name myapp.com;
root /var/www/myapp/dist;
index index.html;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Angular路由支持
location / {
try_files $uri $uri/ /index.html;
}
# API代理
location /api/ {
proxy_pass http://backend:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}
2. Apache配置
apache
# .htaccess
RewriteEngine On
# 启用Gzip压缩
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
</IfModule>
# 缓存设置
<IfModule mod_expires.c>
ExpiresActive on
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
</IfModule>
# Angular路由支持
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
# 安全头
<IfModule mod_headers.c>
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set X-XSS-Protection "1; mode=block"
</IfModule>
🐳 Docker容器化
1. 多阶段构建Dockerfile
dockerfile
# Dockerfile
# 构建阶段
FROM node:18-alpine AS build
WORKDIR /app
# 复制package文件
COPY package*.json ./
# 安装依赖
RUN npm ci --only=production
# 复制源代码
COPY . .
# 构建应用
RUN npm run build --prod
# 生产阶段
FROM nginx:alpine
# 复制构建结果
COPY --from=build /app/dist/my-app /usr/share/nginx/html
# 复制nginx配置
COPY nginx.conf /etc/nginx/nginx.conf
# 暴露端口
EXPOSE 80
# 启动nginx
CMD ["nginx", "-g", "daemon off;"]
2. Docker Compose配置
yaml
# docker-compose.yml
version: '3.8'
services:
frontend:
build: .
ports:
- "80:80"
environment:
- NODE_ENV=production
depends_on:
- backend
networks:
- app-network
backend:
image: myapp/backend:latest
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://user:password@db:5432/myapp
depends_on:
- db
networks:
- app-network
db:
image: postgres:13
environment:
- POSTGRES_DB=myapp
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- app-network
networks:
app-network:
driver: bridge
volumes:
postgres_data:
3. 构建和部署脚本
bash
#!/bin/bash
# deploy.sh
# 构建Docker镜像
docker build -t myapp/frontend:latest .
# 停止旧容器
docker-compose down
# 启动新容器
docker-compose up -d
# 清理未使用的镜像
docker image prune -f
echo "部署完成!"
🔄 CI/CD流水线
1. GitHub Actions
yaml
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm run test -- --watch=false --browsers=ChromeHeadless
- name: Run linting
run: npm run lint
- name: Build application
run: npm run build --prod
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to server
uses: appleboy/ssh-action@v0.1.5
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
script: |
cd /var/www/myapp
git pull origin main
npm ci
npm run build --prod
sudo systemctl reload nginx
2. GitLab CI/CD
yaml
# .gitlab-ci.yml
stages:
- test
- build
- deploy
variables:
NODE_VERSION: "18"
test:
stage: test
image: node:${NODE_VERSION}-alpine
script:
- npm ci
- npm run test -- --watch=false --browsers=ChromeHeadless
- npm run lint
artifacts:
reports:
junit: coverage/junit.xml
build:
stage: build
image: node:${NODE_VERSION}-alpine
script:
- npm ci
- npm run build --prod
artifacts:
paths:
- dist/
expire_in: 1 hour
deploy:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache openssh-client
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
script:
- scp -r dist/* $SERVER_USER@$SERVER_HOST:/var/www/myapp/
- ssh $SERVER_USER@$SERVER_HOST "sudo systemctl reload nginx"
only:
- main
📊 性能监控
1. Web Vitals监控
typescript
// performance.service.ts
import { Injectable } from '@angular/core';
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
@Injectable({
providedIn: 'root'
})
export class PerformanceService {
constructor() {
this.initWebVitals();
}
private initWebVitals() {
// 累积布局偏移
getCLS((metric) => {
this.sendMetric('CLS', metric.value);
});
// 首次输入延迟
getFID((metric) => {
this.sendMetric('FID', metric.value);
});
// 首次内容绘制
getFCP((metric) => {
this.sendMetric('FCP', metric.value);
});
// 最大内容绘制
getLCP((metric) => {
this.sendMetric('LCP', metric.value);
});
// 首次字节时间
getTTFB((metric) => {
this.sendMetric('TTFB', metric.value);
});
}
private sendMetric(name: string, value: number) {
// 发送到监控服务
if (navigator.sendBeacon) {
const data = JSON.stringify({
name,
value,
timestamp: Date.now(),
url: window.location.href
});
navigator.sendBeacon('/api/metrics', data);
}
}
}
2. 错误监控
typescript
// error-monitoring.service.ts
import { Injectable, ErrorHandler } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class ErrorMonitoringService implements ErrorHandler {
handleError(error: any): void {
console.error('Application error:', error);
// 发送错误到监控服务
this.sendError(error);
}
private sendError(error: any) {
const errorData = {
message: error.message,
stack: error.stack,
url: window.location.href,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent
};
// 发送到错误监控服务
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/errors', JSON.stringify(errorData));
}
}
}
// 在app.module.ts中注册
@NgModule({
providers: [
{ provide: ErrorHandler, useClass: ErrorMonitoringService }
]
})
export class AppModule {}
🎮 实践练习
练习1:配置生产环境构建
配置一个完整的生产环境构建流程:
- 环境变量配置
- 构建优化设置
- 静态资源优化
- 缓存策略配置
练习2:实现Docker部署
创建一个完整的Docker部署方案:
- 多阶段构建Dockerfile
- Docker Compose配置
- 自动化部署脚本
- 容器监控
📚 详细示例
完整的部署配置
typescript
// deploy.config.ts
export const deployConfig = {
environments: {
development: {
apiUrl: 'http://localhost:3000/api',
debug: true,
logLevel: 'debug'
},
staging: {
apiUrl: 'https://staging-api.myapp.com/api',
debug: true,
logLevel: 'info'
},
production: {
apiUrl: 'https://api.myapp.com/api',
debug: false,
logLevel: 'error'
}
},
build: {
optimization: true,
sourceMap: false,
namedChunks: false,
aot: true,
extractLicenses: true,
vendorChunk: false,
buildOptimizer: true
},
budgets: [
{
type: 'initial',
maximumWarning: '2mb',
maximumError: '5mb'
},
{
type: 'anyComponentStyle',
maximumWarning: '6kb',
maximumError: '10kb'
}
]
};
自动化部署脚本
bash
#!/bin/bash
# deploy-production.sh
set -e
echo "开始生产环境部署..."
# 检查环境
if [ "$NODE_ENV" != "production" ]; then
echo "错误:必须在生产环境中运行"
exit 1
fi
# 安装依赖
echo "安装依赖..."
npm ci --only=production
# 运行测试
echo "运行测试..."
npm run test -- --watch=false --browsers=ChromeHeadless
# 代码检查
echo "代码检查..."
npm run lint
# 构建应用
echo "构建应用..."
npm run build --prod
# 备份当前版本
echo "备份当前版本..."
if [ -d "/var/www/myapp-backup" ]; then
rm -rf /var/www/myapp-backup
fi
if [ -d "/var/www/myapp" ]; then
mv /var/www/myapp /var/www/myapp-backup
fi
# 部署新版本
echo "部署新版本..."
mv dist/my-app /var/www/myapp
# 重启服务
echo "重启服务..."
sudo systemctl reload nginx
# 健康检查
echo "健康检查..."
sleep 5
if curl -f http://localhost/health > /dev/null 2>&1; then
echo "部署成功!"
# 清理备份
rm -rf /var/www/myapp-backup
else
echo "部署失败,回滚..."
mv /var/www/myapp-backup /var/www/myapp
sudo systemctl reload nginx
exit 1
fi
echo "生产环境部署完成!"
✅ 学习检查
完成本章节后,请确认您能够:
- [ ] 配置生产环境构建
- [ ] 管理环境变量
- [ ] 优化静态资源
- [ ] 配置服务器部署
- [ ] 使用Docker容器化
- [ ] 设置CI/CD流水线
- [ ] 实现性能监控
🚀 下一步
完成本章节学习后,请继续学习17-响应式表单高级功能。