本章将详细介绍Angular应用的构建过程、生产环境配置、以及各种部署策略,帮助你顺利将应用发布到生产环境。
// angular.json 关键配置
{
"projects": {
"my-app": {
"architect": {
"build": {
"configurations": {
"production": {
"optimization": true, // 启用优化
"outputHashing": "all", // 文件哈希命名
"sourceMap": false, // 关闭源映射
"namedChunks": false, // 关闭命名块
"extractLicenses": true, // 提取许可证
"vendorChunk": false, // 禁用供应商块
"buildOptimizer": true, // 构建优化器
"budgets": [ // 资源预算
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
],
"serviceWorker": true, // PWA支持
"ngswConfigPath": "ngsw-config.json"
},
"development": {
"optimization": false,
"sourceMap": true,
"namedChunks": true,
"vendorChunk": true
}
},
"defaultConfiguration": "production"
}
}
}
}
}
// src/environments/environment.ts (开发环境)
export const environment = {
production: false,
apiUrl: 'http://localhost:3000/api',
appVersion: '1.0.0-dev',
enableDebug: true,
sentryDsn: '', // 开发环境不启用Sentry
googleAnalyticsId: ''
};
// src/environments/environment.prod.ts (生产环境)
export const environment = {
production: true,
apiUrl: 'https://api.yourdomain.com/api',
appVersion: '1.0.0',
enableDebug: false,
sentryDsn: 'YOUR_SENTRY_DSN',
googleAnalyticsId: 'UA-XXXXXXXXX-X'
};
// 在组件中使用环境变量
import { environment } from '../environments/environment';
@Injectable()
export class ApiService {
private apiUrl = environment.apiUrl;
getData() {
return this.http.get(`${this.apiUrl}/data`);
}
}
预编译模板,提高运行时性能
移除未使用的代码
// Webpack会移除未使用的导出
export class UsedComponent {} // 保留
export class UnusedComponent {} // 移除
按需加载模块
// 路由懒加载
const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./admin/admin.module')
.then(m => m.AdminModule)
}
];
高性能Web服务器
传统Web服务器
JavaScript运行时
静态文件托管
# /etc/nginx/sites-available/angular-app
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
root /var/www/angular-app/dist;
index index.html;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript;
# 安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
location / {
try_files $uri $uri/ /index.html;
}
# 静态资源缓存
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# API代理
location /api/ {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
# 错误页面
error_page 404 /index.html;
error_page 500 502 503 504 /50x.html;
}
<VirtualHost *:80>
ServerName yourdomain.com
DocumentRoot /var/www/angular-app/dist
<Directory /var/www/angular-app/dist>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</Directory>
# 启用压缩
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript
AddOutputFilterByType DEFLATE application/javascript application/json
# 设置缓存头
<FilesMatch "\.(html|htm)$">
Header set Cache-Control "no-cache"
</FilesMatch>
<FilesMatch "\.(css|js|jpg|jpeg|png|gif|ico|woff|woff2|ttf|svg|eot)$">
Header set Cache-Control "max-age=31536000, public"
</FilesMatch>
</VirtualHost>
# 多阶段构建:构建阶段
FROM node:18-alpine AS builder
# 设置工作目录
WORKDIR /app
# 复制package文件
COPY package*.json ./
# 安装依赖(包括开发依赖)
RUN npm ci --only=production
# 复制源代码
COPY . .
# 构建应用
RUN npm run build -- --prod
# 运行阶段
FROM nginx:alpine
# 复制构建产物到nginx目录
COPY --from=builder /app/dist/angular-app /usr/share/nginx/html
# 复制自定义nginx配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 暴露端口
EXPOSE 80
# 启动nginx
CMD ["nginx", "-g", "daemon off;"]
# docker-compose.yml
version: '3.8'
services:
angular-app:
build: .
ports:
- "8080:80"
environment:
- NODE_ENV=production
- API_URL=https://api.example.com
networks:
- app-network
restart: unless-stopped
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./dist:/usr/share/nginx/html
ports:
- "80:80"
depends_on:
- angular-app
networks:
- app-network
networks:
app-network:
driver: bridge
# .github/workflows/deploy.yml
name: Deploy Angular App
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
build-and-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 test -- --watch=false --browsers=ChromeHeadless
- name: Build application
run: npm run build -- --prod
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: dist
path: dist/
deploy-production:
needs: build-and-test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: dist
- name: Deploy to AWS S3
uses: jakejarvis/s3-sync-action@v0.5.1
with:
args: --acl public-read --follow-symlinks --delete
env:
AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: 'us-east-1'
SOURCE_DIR: 'dist'
- name: Invalidate CloudFront cache
uses: chetan/invalidate-cloudfront-action@v2
env:
DISTRIBUTION: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }}
PATHS: '/*'
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: 'us-east-1'
# .gitlab-ci.yml
image: node:18-alpine
stages:
- test
- build
- deploy
cache:
paths:
- node_modules/
before_script:
- npm ci
test:
stage: test
script:
- npm run lint
- npm test -- --watch=false --browsers=ChromeHeadless
build:
stage: build
script:
- npm run build -- --prod
artifacts:
paths:
- dist/
expire_in: 1 week
only:
- main
- tags
deploy:
stage: deploy
script:
- apt-get update -qy
- apt-get install -y rsync
- rsync -avz --delete dist/ user@server:/var/www/angular-app/
- ssh user@server "systemctl reload nginx"
only:
- main
// firebase.json 配置
{
"hosting": {
"public": "dist/angular-app",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
],
"headers": [
{
"source": "**/*.@(js|css)",
"headers": [
{
"key": "Cache-Control",
"value": "max-age=31536000"
}
]
},
{
"source": "**/*.@(jpg|jpeg|png|gif|ico)",
"headers": [
{
"key": "Cache-Control",
"value": "max-age=2592000"
}
]
}
]
}
}
// 安全HTTP头设置
// nginx配置中添加
add_header Content-Security-Policy "default-src 'self';";
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "strict-origin-when-cross-origin";
同时运行两个生产环境,切换流量实现零停机部署
# 示例:使用Nginx实现蓝绿部署
# 绿色环境已运行,部署蓝色环境
cp -r dist/ /var/www/angular-app-blue/
# 切换Nginx配置
ln -sf /etc/nginx/sites-available/angular-app-blue \
/etc/nginx/sites-enabled/angular-app
# 重载Nginx
nginx -s reload
# 验证新版本,然后下线绿色环境
逐步向小部分用户发布新版本
# Nginx按IP分流配置
geo $canary {
default 0;
10.0.0.0/8 1; # 内部用户使用新版本
}
server {
location / {
if ($canary) {
proxy_pass http://canary-backend;
}
proxy_pass http://production-backend;
}
}