MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Angular生产版本构建的环境变量配置

2021-08-273.5k 阅读

1. 环境变量在 Angular 生产版本构建中的重要性

在 Angular 应用开发过程中,环境变量起着关键作用,尤其是在生产版本构建阶段。不同的环境(如开发环境、测试环境、生产环境)往往需要不同的配置,例如 API 服务器地址、第三方服务密钥等。如果在所有环境中都使用相同的硬编码配置,不仅安全性存在风险,而且维护和部署也会变得异常困难。通过合理配置环境变量,我们可以轻松地切换应用在不同环境下的行为,实现开发、测试到生产的无缝过渡。

在生产环境中,应用需要与正式的后端服务交互,可能需要访问特定的数据库、使用生产级别的第三方服务,这些都依赖于准确的环境变量配置。同时,从安全性角度考虑,生产环境可能需要特殊的加密密钥、身份验证令牌等,这些信息不能在开发环境随意暴露,环境变量配置就成为了一种安全且灵活的解决方案。

2. Angular 环境文件基础

Angular 提供了一种内置的机制来管理不同环境的配置,即通过环境文件。在一个新创建的 Angular 项目中,我们可以在 src/environments 目录下找到两个主要的环境文件:environment.tsenvironment.prod.ts

environment.ts 用于开发环境的配置,而 environment.prod.ts 则专门用于生产环境的配置。这些文件本质上是普通的 TypeScript 文件,导出一个包含各种配置属性的对象。例如,在 environment.ts 中,我们可能有如下简单配置:

export const environment = {
  production: false,
  apiUrl: 'http://localhost:3000/api'
};

这里定义了 production 标志,表明当前是开发环境,同时指定了开发环境下的 API 服务器地址。而在 environment.prod.ts 中,配置可能如下:

export const environment = {
  production: true,
  apiUrl: 'https://api.example.com'
};

生产环境下,production 标志为 true,并且 API 地址切换到了正式的生产服务器地址。

当我们构建 Angular 应用时,Angular CLI 会根据构建目标自动选择对应的环境文件。例如,默认的 ng build 命令会使用 environment.ts,而 ng build --prod 命令会使用 environment.prod.ts

3. 自定义环境变量

除了 production 标志和一些基本配置,我们通常还需要添加自定义的环境变量来满足特定项目的需求。假设我们的应用使用了一个第三方地图服务,需要一个 API 密钥。我们可以在环境文件中添加这个变量。

首先在 environment.ts 中添加:

export const environment = {
  production: false,
  apiUrl: 'http://localhost:3000/api',
  mapApiKey: 'dev_map_api_key'
};

然后在 environment.prod.ts 中:

export const environment = {
  production: true,
  apiUrl: 'https://api.example.com',
  mapApiKey: 'prod_map_api_key'
};

在组件中使用这个变量也很简单。假设我们有一个地图组件 MapComponent,可以通过注入 environment 对象来获取配置:

import { Component } from '@angular/core';
import { environment } from '../../environments/environment';

@Component({
  selector: 'app-map',
  templateUrl: './map.component.html',
  styleUrls: ['./map.component.css']
})
export class MapComponent {
  mapApiKey = environment.mapApiKey;
  constructor() {}
}

在模板 map.component.html 中就可以使用 mapApiKey 变量来初始化地图服务:

<div>
  <app-map [apiKey]="mapApiKey"></app-map>
</div>

4. 构建过程中的环境变量替换

虽然 Angular 内置的环境文件机制很方便,但在某些复杂场景下,我们可能需要在构建过程中动态替换环境变量。例如,我们希望在 CI/CD 流程中根据部署目标动态设置一些环境变量,而不是在代码仓库中硬编码生产环境的敏感信息。

一种常见的方法是使用 @angular - cli - environment - replace 这样的插件。首先安装该插件:

npm install @angular - cli - environment - replace --save - dev

然后在 angular.json 文件中配置插件:

{
  "architect": {
    "build": {
      "builder": "@angular - cli - environment - replace:browser",
      "options": {
        "envFilePath": "./src/environments/environment.ts",
        "replaceValues": {
          "apiUrl": {
            "prod": "https://api.example.com",
            "dev": "http://localhost:3000/api"
          },
          "mapApiKey": {
            "prod": "prod_map_api_key",
            "dev": "dev_map_api_key"
          }
        },
        // 其他原有的构建选项
        "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.css"
        ],
        "scripts": []
      },
      "configurations": {
        "production": {
          "fileReplacements": [
            {
              "replace": "./src/environments/environment.ts",
              "with": "./src/environments/environment.prod.ts"
            }
          ],
          "optimization": true,
          "outputHashing": "all",
          "sourceMap": false,
          "extractCss": true,
          "namedChunks": false,
          "aot": true,
          "extractLicenses": true,
          "vendorChunk": false,
          "buildOptimizer": true,
          "budgets": [
            {
              "type": "initial",
              "maximumWarning": "2mb",
              "maximumError": "5mb"
            },
            {
              "type": "anyComponentStyle",
              "maximumWarning": "6kb",
              "maximumError": "10kb"
            }
          ]
        }
      }
    }
  }
}

这样,在构建时,插件会根据指定的 replaceValues 替换环境变量。例如,执行 ng build --prod 时,apiUrl 会被替换为 https://api.example.commapApiKey 会被替换为 prod_map_api_key

5. 使用环境变量配置 API 调用

在实际项目中,API 调用的配置是环境变量的一个重要应用场景。我们可以通过在服务中使用环境变量来动态设置 API 基础地址。

创建一个 API 服务 ApiService

import { Injectable } from '@angular/core';
import { environment } from '../../environments/environment';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class ApiService {
  private apiBaseUrl = environment.apiUrl;

  constructor(private http: HttpClient) {}

  get<T>(endpoint: string) {
    return this.http.get<T>(`${this.apiBaseUrl}/${endpoint}`);
  }

  post<T>(endpoint: string, data: any) {
    return this.http.post<T>(`${this.apiBaseUrl}/${endpoint}`, data);
  }
}

在组件中使用这个服务:

import { Component } from '@angular/core';
import { ApiService } from '../services/api.service';

@Component({
  selector: 'app - data - display',
  templateUrl: './data - display.component.html',
  styleUrls: ['./data - display.component.css']
})
export class DataDisplayComponent {
  data: any;

  constructor(private apiService: ApiService) {}

  ngOnInit() {
    this.apiService.get('data').subscribe(result => {
      this.data = result;
    });
  }
}

这样,无论是在开发环境还是生产环境,API 调用都会根据环境变量中的 apiUrl 来进行正确的请求。

6. 环境变量与第三方服务集成

许多 Angular 应用会集成各种第三方服务,如支付网关、分析工具等。这些服务通常需要特定的密钥或配置参数,通过环境变量可以方便地管理这些信息。

以 Google Analytics 为例,我们需要在应用中添加跟踪代码。首先在环境文件中添加 Google Analytics 的跟踪 ID: 在 environment.ts 中:

export const environment = {
  production: false,
  apiUrl: 'http://localhost:3000/api',
  gaTrackingId: 'dev_ga_tracking_id'
};

environment.prod.ts 中:

export const environment = {
  production: true,
  apiUrl: 'https://api.example.com',
  gaTrackingId: 'prod_ga_tracking_id'
};

然后创建一个服务来初始化 Google Analytics:

import { Injectable } from '@angular/core';
import { environment } from '../../environments/environment';

declare const gtag: Function;

@Injectable({
  providedIn: 'root'
})
export class GaService {
  constructor() {
    if (environment.production) {
      this.initGa();
    }
  }

  private initGa() {
    (window as any).dataLayer = (window as any).dataLayer || [];
    function gtag() {
      (window as any).dataLayer.push(arguments);
    }
    gtag('js', new Date());
    gtag('config', environment.gaTrackingId);
  }
}

app.module.ts 中提供这个服务:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { AppComponent } from './app.component';
import { GaService } from './services/ga.service';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  providers: [GaService],
  bootstrap: [AppComponent]
})
export class AppModule {}

这样,在生产环境下,Google Analytics 会根据 environment.prod.ts 中的跟踪 ID 进行正确的初始化,而在开发环境下不会真正启用生产环境的跟踪。

7. 环境变量安全性考虑

在生产环境中,环境变量的安全性至关重要。如果敏感信息(如 API 密钥、数据库密码等)泄露,可能会导致严重的安全问题,如数据泄露、服务被恶意调用等。

首先,绝对不要将生产环境的敏感环境变量提交到版本控制系统中。可以通过 CI/CD 工具在部署时注入这些变量。例如,在使用 Jenkins 进行部署时,可以在构建任务的环境变量设置中添加生产环境的敏感信息。

其次,对于前端应用,尽量避免在客户端直接暴露敏感的环境变量。如果必须使用某些密钥来调用第三方服务,可以考虑在后端进行代理转发,由后端持有密钥并与第三方服务交互,前端只与后端 API 通信。这样即使前端代码被反编译,敏感信息也不会直接暴露。

另外,对环境变量进行加密传输和存储也是一种有效的安全措施。一些云服务提供商提供了安全的环境变量管理功能,如 AWS Secrets Manager、Google Cloud Secret Manager 等,可以在这些平台上存储和管理敏感的环境变量,并在部署时安全地注入到应用中。

8. 多阶段构建与环境变量

在容器化部署中,多阶段构建是一种常见的优化方式,同时也与环境变量配置紧密相关。假设我们使用 Docker 进行 Angular 应用的部署。

首先,我们有一个基础的 Dockerfile 用于构建 Angular 应用:

# 第一阶段:构建阶段
FROM node:14 - alpine as build - stage
WORKDIR /app
COPY package.json package - lock.json ./
RUN npm install
COPY. .
RUN npm run build -- --prod

# 第二阶段:运行阶段
FROM nginx:1.19.2 - alpine as production - stage
COPY --from = build - stage /app/dist/my - app /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

在这个过程中,我们可以通过 Docker 构建参数来传递环境变量。例如,我们希望在构建生产镜像时动态设置 API 地址。可以修改 Dockerfile 如下:

# 第一阶段:构建阶段
FROM node:14 - alpine as build - stage
WORKDIR /app
ARG API_URL
ENV API_URL=$API_URL
COPY package.json package - lock.json ./
RUN npm install
COPY. .
RUN sed -i "s|http://localhost:3000/api|$API_URL|g" src/environments/environment.prod.ts
RUN npm run build -- --prod

# 第二阶段:运行阶段
FROM nginx:1.19.2 - alpine as production - stage
COPY --from = build - stage /app/dist/my - app /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

然后在构建 Docker 镜像时传递 API_URL 参数:

docker build --build - arg API_URL = https://api.example.com -t my - app:prod.

这样,在构建生产镜像时,environment.prod.ts 中的 API 地址会被替换为实际的生产 API 地址。

9. 测试环境中的环境变量配置

测试环境对于确保应用在不同配置下的正确性至关重要。在测试过程中,我们同样需要合理配置环境变量。

在单元测试中,我们可以通过模拟环境变量来测试组件或服务在不同配置下的行为。例如,对于前面的 ApiService,我们可以在测试中模拟不同的 apiUrl 来测试 API 调用:

import { TestBed } from '@angular/core/testing';
import { ApiService } from './api.service';
import { HttpClientTestingModule } from '@angular/common/http/testing';

describe('ApiService', () => {
  let service: ApiService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        {
          provide: 'environment',
          useValue: {
            apiUrl: 'http://test - api.com'
          }
        }
      ]
    });
    service = TestBed.inject(ApiService);
  });

  it('should have correct apiBaseUrl', () => {
    expect(service.apiBaseUrl).toBe('http://test - api.com');
  });
});

在端到端(E2E)测试中,我们也可以根据测试环境的需要配置环境变量。例如,使用 Cypress 进行 E2E 测试时,可以在 cypress.json 中设置环境变量:

{
  "env": {
    "apiUrl": "http://test - api.com"
  }
}

然后在测试脚本中使用这个变量:

describe('Data Display', () => {
  it('should display data from API', () => {
    cy.request(Cypress.env('apiUrl') + '/data').then(response => {
      // 断言测试逻辑
    });
  });
});

这样可以确保在测试环境中,应用能够正确地与模拟或真实的测试后端服务进行交互,同时验证环境变量配置对应用功能的影响。

10. 环境变量配置的常见问题与解决方法

  • 构建时环境变量未正确替换:如果在构建过程中使用插件或自定义脚本进行环境变量替换,但替换没有生效,首先检查配置文件(如 angular.json)中插件的配置是否正确,确保 replaceValues 中的键值对与环境文件中的变量名一致。同时,检查构建命令是否正确触发了替换逻辑,例如是否在生产构建时使用了正确的配置选项。
  • 环境变量在运行时未生效:在组件或服务中使用环境变量时,如果发现运行时变量值不正确,检查导入的环境文件是否正确。确保在生产环境中导入的是 environment.prod.ts,在开发环境中导入的是 environment.ts。另外,检查是否存在缓存问题,特别是在开发过程中,如果更改了环境变量但没有看到预期效果,可以尝试清除浏览器缓存或重新启动开发服务器。
  • 敏感环境变量泄露风险:为了避免敏感环境变量泄露,严格遵守不在版本控制系统中提交敏感信息的原则。如果在 CI/CD 过程中使用环境变量,确保 CI/CD 工具的安全性,如设置强密码、定期更新工具版本等。同时,对存储敏感环境变量的配置文件(如 .env 文件)设置合适的文件权限,限制访问。

通过深入理解和合理配置 Angular 生产版本构建中的环境变量,我们可以提高应用的灵活性、安全性和可维护性,确保应用在不同环境下稳定、高效地运行。无论是简单的 API 地址切换,还是复杂的第三方服务集成和安全配置,环境变量都扮演着不可或缺的角色。在实际项目中,根据项目需求和架构特点,灵活运用各种环境变量配置方法,是成功开发和部署 Angular 应用的关键之一。