Skip to content

Commit

Permalink
feat(stark-build): add support for environment variables at runtime (…
Browse files Browse the repository at this point in the history
…importing environment.ts file) and at compilation time (using webpack Define plugin)

ISSUES CLOSED: #50
  • Loading branch information
christophercr committed Jun 26, 2018
1 parent e909416 commit c29e36b
Show file tree
Hide file tree
Showing 62 changed files with 558 additions and 345 deletions.
252 changes: 203 additions & 49 deletions docs/ENVIRONMENTS.md
Original file line number Diff line number Diff line change
@@ -1,87 +1,241 @@
# Environments support

## Environment.ts
Stark provides 2 different ways to get environment information depending on your needs:

In the `src/environments/model.ts` is provided the Environment interface as follows
- at runtime by importing the environment.ts file
- at compilation time by checking the global/ambient variables set by Webpack

```
...
export interface Environment {
## Environment information at runtime (environment.ts)

Stark provides the `StarkEnvironment` interface that describes which information you can get from the current environment.
It follows the guidelines described in the Angular CLI Wiki regarding [application environments](https://github.com/angular/angular-cli/wiki/stories-application-environments) and [HMR configuration](https://github.com/angular/angular-cli/wiki/stories-configure-hmr).

Such environment interface is defined as follows:

```typescript
import { NgModuleRef } from "@angular/core";

export interface StarkEnvironment {
/**
* Whether the current environment is production (as described in Angular CLI Wiki)
* @link https://github.com/angular/angular-cli/wiki/stories-application-environments
*/
production: boolean;
/**
* Whether the current environment has Hot Module Replacement enabled (as described in Angular CLI Wiki)
* @link https://github.com/angular/angular-cli/wiki/stories-configure-hmr
*/
hmr: boolean;
...
/**
* Array of providers to be included only in this environment.
* For example: you might want to add a detailed logging provider only in development.
*/
ENV_PROVIDERS: any[];
/**
* Function to modify/decorate the NgModule Instance created by Angular for a given platform.
* Useful to enable/disable some Angular specifics such as the debug tools.
*/
decorateModuleRef(modRef: NgModuleRef<any>): NgModuleRef<any>;
}
```

`production`, which is a boolean, will specify is your application runs under a production environment,
while `hmr` will indicate if it runs under a Hot Module Replacement environment.
In your project, the files to define the different environments will be located in `src/environments`:

```txt
|
+---src
| |
| +---environments # configuration variables for each environment
| | | #
| | | environment.e2e.prod.ts # e2e tests configuration
| | | environment.hmr.ts # development with HMR (Hot Module Replacement) configuration
| | | environment.prod.ts # production configuration
| | \ environment.ts # development configuration
| |
| \ ...
|
\ ...
```

This interface will then be defined by default in `src/environments/environment.ts`:
Then in each file, an `environment` constant of type `StarkEnvironment` should be exported providing the values needed for each environment:

```
...
export const environment: Environment = {
production: false,
```typescript
// environment.prod.ts

import { NgModuleRef } from "@angular/core";
import { disableDebugTools } from "@angular/platform-browser";
import { StarkEnvironment } from "@nationalbankbelgium/stark-core";

export const environment: StarkEnvironment = {
production: true,
hmr: false,
showDevModule: true,
...
ENV_PROVIDERS: [ProductionOnlyProvider],

decorateModuleRef(modRef: NgModuleRef<any>): NgModuleRef<any> {
disableDebugTools(); // disable debug tools in production
return modRef;
}
};
```

## How to find out which environment is your application is currently using?
### How to get environment variables in your application?

All you have to do is to import the environment.ts constant anywhere you want in your application:
All you have to do is to import the `environment.ts` constant anywhere you want in your application.

```
**You should always import from `environments/environment` because Webpack will internally replace such file with the right environment file as defined in the `fileReplacements` option in the `angular.json` file.**

This way, you will be able to programmatically read the different environment variables you need.
For example, you can determine which providers you will include for a specific environment in your AppModule:

```typescript
import { NgModule } from "@angular/core";
// the environment file should always be imported from this path
import { environment } from "environments/environment";

@NgModule({
bootstrap: [AppComponent],
declarations: [AppComponent],
imports: [...],
providers: [
environment.ENV_PROVIDERS,
...
]
})
export class AppModule { ... }
```

This way, you will be able to programmatically determine which environment is used in your app
with simple checks, as follows:
You can also determine on which environment your app is currently running with this simple check:

```
environment.production
// if the output of this line is true, we are in production environment.
// Otherwise, we are in hmr environment.
```typescript
// the environment file should always be imported from this path
import { environment } from "environments/environment";

// if true, your app is running in production environment
if (environment.production) {
/* the code in this block will be executed only in production */
}
```

## How to add a new environment ?
### How to add a new environment?

First, create you new Ts file in the `src/environments` folder.
First, create your new environment.ts file in the `src/environments` folder.

Then, make sure your new environment extends the `src/environments/model.ts` interface.
Then, make sure your new environment implements the `StarkEnvironment` interface. For example, the `environment.dummy-env.ts` file:

Finally, define the options and file replacement of your new environment in the `angular.json` file.
```typescript
import { NgModuleRef } from "@angular/core";
import { StarkEnvironment } from "@nationalbankbelgium/stark-core";

For exemple, the environment.dummyEnv.ts file:
export const environment: StarkEnvironment = {
production: false / true,
hmr: false,
ENV_PROVIDERS: [],

decorateModuleRef(modRef: NgModuleRef<any>): NgModuleRef<any> {
/* do something here */
}
};
```
import { enableProdMode, NgModuleRef } from "@angular/core";
import { disableDebugTools } from "@angular/platform-browser";
import { Environment } from "./model";

enableProdMode();
Finally, define the file replacement of your new environment in the `angular.json` file so that Webpack can replace the default file `environments/environment` with your new file:

```text
{
...
"dummyEnv": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.dummy-env.ts"
}
]
}
}
```

### How to add more properties to the environment file?

In case you want to add more properties, you should first create your own interface which should extend the `StarkEnvironment` interface.
For example:

```typescript
import { StarkEnvironment } from "@nationalbankbelgium/stark-core";

export interface YourOwnEnvironment extends StarkEnvironment {
someProperty: any;
}
```

export const environment: Environment = {
Then adapt the different environment files to add the new properties:

production: false,
```typescript
import { NgModuleRef } from "@angular/core";
import { StarkEnvironment } from "@nationalbankbelgium/stark-core";

export const environment: StarkEnvironment = {
production: false / true,
hmr: false,
ENV_PROVIDERS: [],
someProperty: "some value", // your new property

decorateModuleRef(modRef: NgModuleRef<any>): NgModuleRef<any> {
disableDebugTools();
return modRef;
},
ENV_PROVIDERS: []
}
/* do something here */
}
};
```

And in the angular.json file:
## Environment information at compilation time (Webpack global variables)

Thanks to the Webpack [DefinePlugin](https://webpack.js.org/plugins/define-plugin), Stark provides some global variables that are available at compilation time, which means that you can
implement some checks in your code and this will be analyzed when your application bundle is being built by Webpack.

The global variables available at compilation time are the following:

- `ENV` which indicates the current environment: `"development"` or `"production"`
- `HMR` which indicates whether the Hot Module Replacement support is enabled (true/false).

### How to get target environment at compilation time?

Since Webpack defines the environment variables as global, you can use them everywhere in your code so you can, for example, determine on which environment your app is currently running
and execute some logic only on that specific environment:

```typescript
// if true, your app is running in development environment
if (ENV === "development") {
/* the code inside this block will be executed only in development */
}
```

To avoid Typescript compilation issues regarding these global variables, make sure that you include the typings from the stark-build package in your app `tsconfig.json`:

```text
{
"extends": "./node_modules/@nationalbankbelgium/stark-build/tsconfig.json",
"compilerOptions": {
...
"typeRoots": [
"./node_modules/@types",
"./node_modules/@nationalbankbelgium/stark-build/typings" // typings from stark-build
],
...
},
...
}
```
"dummyEnv" : {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.dummyEnv.ts"
}
]
}

### Why do you need the target environment at compilation time?

Sometimes you might need to add some logic or import some files only when your application is running in development or production.

**In this case, when Webpack builds your application, the final bundle will contain also that code and/or imports that will only be used on a specific environment.
For example, the specific code related to development will never be executed in production and yet it will be included in your production build which will increase the size of your bundle.**

This is why knowing the target environment at compilation time is useful. You can put the logic inside an if block and then such code will be tree-shaken by Webpack as it will recognize it as dead code:

```typescript
// this check is translated to "if (false)" when ENV is "production"
// allowing Webpack to identify it as dead code and so remove it
if (ENV === "development") {
/* the code inside this block will only be included in development */
}
```
23 changes: 21 additions & 2 deletions docs/WEBPACK.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Webpack Customizations

Stark-Build uses several Webpack plugins as well as some utils in order to leverage lots of functionality and customizations to your builds (DEV and PROD).
The stark-build package uses several Webpack plugins as well as some utils in order to leverage lots of functionality and customizations to your builds (DEV and PROD).

This is the list of utils and plugins used by the stark-build package:

Expand Down Expand Up @@ -41,7 +41,7 @@ The only thing you need to do is to configure the **baseHref** and **deployUrl**

Allows to customize the base url in the _index.html_ via the webpack config.

In Stark-Build, the custom base url provided to this plugin is the one you define in the **baseHref** option of your project's _angular.json_ file:
In stark-build, the custom base url provided to this plugin is the one you define in the **baseHref** option of your project's _angular.json_ file:

```json
{
Expand All @@ -64,3 +64,22 @@ In Stark-Build, the custom base url provided to this plugin is the one you defin
```

This plugin will automatically add the base tag `<base href="<custom-base-url>">` to the _index.html_ so you don't need to add it manually yourself.

#### [DefinePlugin](https://webpack.js.org/plugins/define-plugin)

Allows to define global variables that will be available during the compilation and building of the application bundles.

In this case, stark-build provides some global variables that are available at compilation time, which means that you can implement some checks in your code and this will be analyzed when your application bundle is being built by Webpack.

The global variables available at compilation time are the following:

- `ENV` which indicates the current environment: `"development"` or `"production"`
- `HMR` which indicates whether the Hot Module Replacement support is enabled (true/false).

Since the Define plugin defines those variables as global, you can use them everywhere in your code like this:

```typescript
if (ENV === "development") {
/* the code inside this block will be executed only in development */
}
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@
"update:starter": "npm run clean:starter && npm run install:starter"
},
"lint-staged": {
"*.{css,js,json,pcss,scss,ts}": [
"*.{css,js,json,md,pcss,scss,ts}": [
"prettier --write",
"git add"
]
Expand Down
Loading

0 comments on commit c29e36b

Please sign in to comment.