Skip to content

Commit 23e5eb9

Browse files
authored
feat(env): decouple build from env file (#1404)
1 parent 698ba0c commit 23e5eb9

File tree

10 files changed

+112
-79
lines changed

10 files changed

+112
-79
lines changed

README.md

+24-8
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ The generated project has dependencies that require **Node 4 or greater**.
2828
* [Generating Components, Directives, Pipes and Services](#generating-components-directives-pipes-and-services)
2929
* [Generating a Route](#generating-a-route)
3030
* [Creating a Build](#creating-a-build)
31-
* [Environments](#environments)
31+
* [Build Targets and Environment Files](#build-targets-and-environment-files)
3232
* [Bundling](#bundling)
3333
* [Running Unit Tests](#running-unit-tests)
3434
* [Running End-to-End Tests](#running-end-to-end-tests)
@@ -128,17 +128,33 @@ ng build
128128

129129
The build artifacts will be stored in the `dist/` directory.
130130

131-
### Environments
131+
### Build Targets and Environment Files
132132

133-
At build time, the `src/app/environment.ts` will be replaced by either
134-
`config/environment.dev.ts` or `config/environment.prod.ts`, depending on the
135-
current cli environment. The resulting file will be `dist/app/environment.ts`.
133+
A build can specify both a build target (`development` or `production`) and an
134+
environment file to be used with that build. By default, the development build
135+
target is used.
136136

137-
Environment defaults to `dev`, but you can generate a production build via
138-
the `-prod` flag in either `ng build -prod` or `ng serve -prod`.
137+
At build time, `src/app/environments/environment.ts` will be replaced by
138+
`src/app/environments/environment.{NAME}.ts` where `NAME` is the argument
139+
provided to the `--environment` flag.
140+
141+
These options also apply to the serve command. If you do not pass a value for `environment`,
142+
it will default to `dev` for `development` and `prod` for `production`.
143+
144+
```bash
145+
# these are equivalent
146+
ng build --target=production --environment=prod
147+
ng build --prod --env=prod
148+
ng build --prod
149+
# and so are these
150+
ng build --target=development --environment=dev
151+
ng build --dev --e=dev
152+
ng build --dev
153+
ng build
154+
```
139155

140156
You can also add your own env files other than `dev` and `prod` by creating a
141-
`config/environment.{NAME}.ts` and use them by using the `--env=NAME`
157+
`src/app/environments/environment.{NAME}.ts` and use them by using the `--env=NAME`
142158
flag on the build/serve commands.
143159

144160
### Bundling

addon/ng2/blueprints/ng2/files/__path__/app/environments/environment.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
// The file for the current environment will overwrite this one during build
2-
// Different environments can be found in ./environment.{dev|prod}.ts
3-
// The build system defaults to the dev environment
1+
// The file for the current environment will overwrite this one during build.
2+
// Different environments can be found in ./environment.{dev|prod}.ts, and
3+
// you can create your own and use it with the --env flag.
4+
// The build system defaults to the dev environment.
45

56
export const environment = {
67
production: false

addon/ng2/commands/build.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as WebpackBuild from '../tasks/build-webpack';
33
import * as WebpackBuildWatch from '../tasks/build-webpack-watch';
44

55
interface BuildOptions {
6+
target?: string;
67
environment?: string;
78
outputPath?: string;
89
watch?: boolean;
@@ -16,28 +17,40 @@ module.exports = Command.extend({
1617
aliases: ['b'],
1718

1819
availableOptions: [
19-
{ name: 'environment', type: String, default: 'development', aliases: ['e', { 'dev': 'development' }, { 'prod': 'production' }] },
20+
{ name: 'target', type: String, default: 'development', aliases: ['t', { 'dev': 'development' }, { 'prod': 'production' }] },
21+
{ name: 'environment', type: String, default: '', aliases: ['e'] },
2022
{ name: 'output-path', type: 'Path', default: 'dist/', aliases: ['o'] },
2123
{ name: 'watch', type: Boolean, default: false, aliases: ['w'] },
2224
{ name: 'watcher', type: String },
2325
{ name: 'suppress-sizes', type: Boolean, default: false }
2426
],
2527

2628
run: function (commandOptions: BuildOptions) {
29+
if (commandOptions.environment === ''){
30+
if (commandOptions.target === 'development') {
31+
commandOptions.environment = 'dev';
32+
}
33+
if (commandOptions.target === 'production') {
34+
commandOptions.environment = 'prod';
35+
}
36+
}
37+
2738
var project = this.project;
2839
var ui = this.ui;
2940
var buildTask = commandOptions.watch ?
3041
new WebpackBuildWatch({
3142
cliProject: project,
3243
ui: ui,
3344
outputPath: commandOptions.outputPath,
45+
target: commandOptions.target,
3446
environment: commandOptions.environment
3547
}) :
3648
new WebpackBuild({
3749
cliProject: project,
3850
ui: ui,
3951
outputPath: commandOptions.outputPath,
40-
environment: commandOptions.environment
52+
target: commandOptions.target,
53+
environment: commandOptions.environment,
4154
});
4255

4356
return buildTask.run(commandOptions);

addon/ng2/commands/serve.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export interface ServeTaskOptions {
2222
liveReloadPort?: number;
2323
liveReloadBaseUrl?: string;
2424
liveReloadLiveCss?: boolean;
25+
target?: string;
2526
environment?: string;
2627
outputPath?: string;
2728
ssl?: boolean;
@@ -45,22 +46,31 @@ module.exports = Command.extend({
4546
{ name: 'live-reload-base-url', type: String, aliases: ['lrbu'], description: 'Defaults to baseURL' },
4647
{ name: 'live-reload-port', type: Number, aliases: ['lrp'], description: '(Defaults to port number within [49152...65535])' },
4748
{ name: 'live-reload-live-css', type: Boolean, default: true, description: 'Whether to live reload CSS (default true)' },
48-
{ name: 'environment', type: String, default: 'development', aliases: ['e', { 'dev': 'development' }, { 'mat': 'material'}, { 'prod': 'production' }] },
49+
{ name: 'target', type: String, default: 'development', aliases: ['t', { 'dev': 'development' }, { 'prod': 'production' }] },
50+
{ name: 'environment', type: String, default: '', aliases: ['e'] },
4951
{ name: 'output-path', type: 'Path', default: 'dist/', aliases: ['op', 'out'] },
5052
{ name: 'ssl', type: Boolean, default: false },
5153
{ name: 'ssl-key', type: String, default: 'ssl/server.key' },
5254
{ name: 'ssl-cert', type: String, default: 'ssl/server.crt' }
5355
],
5456

5557
run: function(commandOptions: ServeTaskOptions) {
58+
if (commandOptions.environment === ''){
59+
if (commandOptions.target === 'development') {
60+
commandOptions.environment = 'dev';
61+
}
62+
if (commandOptions.target === 'production') {
63+
commandOptions.environment = 'prod';
64+
}
65+
}
5666

5767
commandOptions.liveReloadHost = commandOptions.liveReloadHost || commandOptions.host;
5868

5969
return this._checkExpressPort(commandOptions)
6070
.then(this._autoFindLiveReloadPort.bind(this))
6171
.then((commandOptions: ServeTaskOptions) => {
6272
commandOptions = assign({}, commandOptions, {
63-
baseURL: this.project.config(commandOptions.environment).baseURL || '/'
73+
baseURL: this.project.config(commandOptions.target).baseURL || '/'
6474
});
6575

6676
if (commandOptions.proxy) {

addon/ng2/models/webpack-config.ts

+11-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as path from 'path';
2+
import * as fs from 'fs';
23
import * as webpackMerge from 'webpack-merge';
34
import { CliConfig } from './config';
45
import { NgCliEnvironmentPlugin } from '../utilities/environment-plugin';
@@ -22,9 +23,11 @@ export class NgCliWebpackConfig {
2223
private webpackMobileConfigPartial: any;
2324
private webpackMobileProdConfigPartial: any;
2425

25-
constructor(public ngCliProject: any, public environment: string) {
26+
constructor(public ngCliProject: any, public target: string, public environment: string) {
2627
const sourceDir = CliConfig.fromProject().defaults.sourceDir;
2728

29+
const environmentPath = `./${sourceDir}/app/environments/environment.${environment}.ts`;
30+
2831
this.webpackBaseConfig = getWebpackCommonConfig(this.ngCliProject.root, sourceDir);
2932
this.webpackDevConfigPartial = getWebpackDevConfigPartial(this.ngCliProject.root, sourceDir);
3033
this.webpackProdConfigPartial = getWebpackProdConfigPartial(this.ngCliProject.root, sourceDir);
@@ -37,27 +40,23 @@ export class NgCliWebpackConfig {
3740
}
3841

3942
this.generateConfig();
40-
this.config.plugins.unshift(new NgCliEnvironmentPlugin({env: this.environment}));
43+
this.config.plugins.unshift(new NgCliEnvironmentPlugin({
44+
path: path.resolve(this.ngCliProject.root, `./${sourceDir}/app/environments/`),
45+
src: 'environment.ts',
46+
dest: `environment.${this.environment}.ts`
47+
}));
4148
}
4249

4350
generateConfig(): void {
44-
switch (this.environment) {
45-
case "d":
46-
case "dev":
51+
switch (this.target) {
4752
case "development":
48-
case "develop":
4953
this.config = webpackMerge(this.webpackBaseConfig, this.webpackDevConfigPartial);
5054
break;
51-
52-
case "p":
53-
case "prod":
5455
case "production":
5556
this.config = webpackMerge(this.webpackBaseConfig, this.webpackProdConfigPartial);
5657
break;
57-
5858
default:
59-
//TODO: Not sure what to put here. We have a default env passed anyways.
60-
this.ngCliProject.ui.writeLine("Environment could not be determined while configuring your build system.", 3)
59+
throw new Error("Invalid build target. Only 'development' and 'production' are available.");
6160
break;
6261
}
6362
}

addon/ng2/tasks/build-webpack-watch.ts

+7-9
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
import {NgCliWebpackConfig} from '../models/webpack-config'
2-
import {webpackOutputOptions} from '../models/';
3-
import {ServeTaskOptions} from '../commands/serve';
41
import * as rimraf from 'rimraf';
52
import * as path from 'path';
6-
7-
const Task = require('ember-cli/lib/models/task');
8-
const webpack = require('webpack');
9-
const ProgressPlugin = require('webpack/lib/ProgressPlugin');
10-
3+
import * as Task from 'ember-cli/lib/models/task';
4+
import * as webpack from 'webpack';
5+
import * as ProgressPlugin from 'webpack/lib/ProgressPlugin';
6+
import { NgCliWebpackConfig } from '../models/webpack-config';
7+
import { webpackOutputOptions } from '../models/';
8+
import { ServeTaskOptions } from '../commands/serve';
119

1210
let lastHash: any = null;
1311

@@ -18,7 +16,7 @@ module.exports = Task.extend({
1816

1917
rimraf.sync(path.resolve(project.root, runTaskOptions.outputPath));
2018

21-
const config = new NgCliWebpackConfig(project, runTaskOptions.environment).config;
19+
const config = new NgCliWebpackConfig(project, runTaskOptions.target, runTaskOptions.environment).config;
2220
const webpackCompiler = webpack(config);
2321

2422
webpackCompiler.apply(new ProgressPlugin({

addon/ng2/tasks/build-webpack.ts

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import {ServeTaskOptions} from '../commands/serve';
2-
import {NgCliWebpackConfig} from '../models/webpack-config'
3-
import {webpackOutputOptions} from '../models/'
41
import * as rimraf from 'rimraf';
52
import * as path from 'path';
3+
import * as Task from 'ember-cli/lib/models/task';
4+
import * as webpack from 'webpack';
5+
import { ServeTaskOptions } from '../commands/serve';
6+
import { NgCliWebpackConfig } from '../models/webpack-config';
7+
import { webpackOutputOptions } from '../models/';
68

79
// Configure build and output;
8-
var Task = require('ember-cli/lib/models/task');
9-
const webpack = require('webpack');
10-
1110
let lastHash: any = null;
1211

1312
module.exports = Task.extend({
@@ -17,7 +16,7 @@ module.exports = Task.extend({
1716
var project = this.cliProject;
1817

1918
rimraf.sync(path.resolve(project.root, runTaskOptions.outputPath));
20-
var config = new NgCliWebpackConfig(project, runTaskOptions.environment).config;
19+
var config = new NgCliWebpackConfig(project, runTaskOptions.target, runTaskOptions.environment).config;
2120
const webpackCompiler = webpack(config);
2221

2322
const ProgressPlugin = require('webpack/lib/ProgressPlugin');

addon/ng2/tasks/serve-webpack.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ module.exports = Task.extend({
1515
let lastHash = null;
1616
let webpackCompiler: any;
1717

18-
var config: NgCliWebpackConfig = new NgCliWebpackConfig(this.project, commandOptions.environment).config;
18+
var config: NgCliWebpackConfig = new NgCliWebpackConfig(this.project, commandOptions.target, commandOptions.environment).config;
1919
// This allows for live reload of page when changes are made to repo.
2020
// https://webpack.github.io/docs/webpack-dev-server.html#inline-mode
2121
config.entry.main.unshift(`webpack-dev-server/client?http://localhost:${commandOptions.port}/`);

addon/ng2/utilities/environment-plugin.ts

+14-33
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,27 @@
11
import * as fs from 'fs';
2+
import * as path from 'path';
23

34
interface WebpackPlugin {
45
apply(compiler: any): void;
56
}
67

7-
export class NgCliEnvironmentPlugin implements WebpackPlugin {
8-
_file: string;
9-
_alias: string;
10-
_env: string;
8+
interface EnvOptions {
9+
path: string;
10+
src: string;
11+
dest: string;
12+
}
1113

12-
constructor(config: any) {
13-
if (typeof config === 'string') {
14-
config = {env: config};
15-
}
16-
if (typeof config.env !== 'string') {
17-
throw new Error('must provide env')
18-
}
19-
const ALIAS = {
20-
'"dev"': 'dev',
21-
'development': 'dev',
22-
'"development"': 'dev',
23-
'"prod"': 'prod',
24-
'production': 'prod',
25-
'"production"': 'prod',
26-
'"test"': 'test',
27-
'testing': 'test',
28-
'"testing"': 'test',
29-
};
30-
const ENV = config.env.toLowerCase();
14+
export class NgCliEnvironmentPlugin implements WebpackPlugin {
15+
config: EnvOptions;
3116

32-
this._file = config.file || 'environment';
33-
this._alias = config.alias || ALIAS;
34-
this._env = this._alias[ENV] || ENV;
35-
}
17+
constructor(public config: EnvOptions) {}
3618

3719
isEnvFile(file: string): boolean {
38-
return file.indexOf(this._file + '.') !== -1;
20+
return file === path.resolve(this.config.path, this.config.src);
3921
}
4022

4123
replaceFile(file: string): any {
42-
return file
43-
.replace(this._file, this._file + '.' + this._env);
24+
return path.resolve(this.config.path, this.config.dest);
4425
}
4526

4627
updateResult(result: any): any {
@@ -64,9 +45,9 @@ export class NgCliEnvironmentPlugin implements WebpackPlugin {
6445

6546
fs.stat(envFile, (err, stats) => {
6647
if (err || !stats.isFile()) {
67-
var errorText = (!err && stats.isFile()) ? 'Is not a file.' : 'Does not exist.';
68-
console.log('\nWARNING:\n' + envFile + '\n' + errorText + ' ' + 'Using file\n' + _resource + '\n');
69-
return callback(null, result);
48+
const destPath = path.resolve(this.config.path, this.config.dest);
49+
const errorText = (!err && stats.isFile()) ? 'is not a file.' : 'does not exist.';
50+
throw new Error(`${destPath} ${errorText}`);
7051
}
7152
// mutate result
7253
var newResult = this.updateResult(result);

tests/e2e/e2e_workflow.spec.js

+18-2
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,27 @@ describe('Basic end-to-end Workflow', function () {
102102
it('Supports build config file replacement', function() {
103103
this.timeout(420000);
104104

105-
sh.exec(`${ngBin} build --dev`);
105+
sh.exec(`${ngBin} build --env=prod`);
106106
var mainBundlePath = path.join(process.cwd(), 'dist', 'main.bundle.js');
107107
var mainBundleContent = fs.readFileSync(mainBundlePath, { encoding: 'utf8' });
108108

109-
expect(mainBundleContent).to.include('production: false');
109+
expect(mainBundleContent).to.include('production: true');
110+
});
111+
112+
it('Build fails on invalid build target', function (done) {
113+
this.timeout(420000);
114+
sh.exec(`${ngBin} build --target=potato`, (code) => {
115+
expect(code).to.not.equal(0);
116+
done();
117+
});
118+
});
119+
120+
it('Build fails on invalid environment file', function (done) {
121+
this.timeout(420000);
122+
sh.exec(`${ngBin} build --environment=potato`, (code) => {
123+
expect(code).to.not.equal(0);
124+
done();
125+
});
110126
});
111127

112128
it('Can run `ng build` in created project', function () {

0 commit comments

Comments
 (0)