Skip to content

feat(env): decouple build from env file #1404

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 22, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 24 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ The generated project has dependencies that require **Node 4 or greater**.
* [Generating Components, Directives, Pipes and Services](#generating-components-directives-pipes-and-services)
* [Generating a Route](#generating-a-route)
* [Creating a Build](#creating-a-build)
* [Environments](#environments)
* [Build Targets and Environment Files](#build-targets-and-environment-files)
* [Bundling](#bundling)
* [Running Unit Tests](#running-unit-tests)
* [Running End-to-End Tests](#running-end-to-end-tests)
Expand Down Expand Up @@ -128,17 +128,33 @@ ng build

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

### Environments
### Build Targets and Environment Files

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

Environment defaults to `dev`, but you can generate a production build via
the `-prod` flag in either `ng build -prod` or `ng serve -prod`.
At build time, `src/app/environments/environment.ts` will be replaced by
`src/app/environments/environment.{NAME}.ts` where `NAME` is the argument
provided to the `--environment` flag.

These options also apply to the serve command. If you do not pass a value for `environment`,
it will default to `dev` for `development` and `prod` for `production`.

```bash
# these are equivalent
ng build --target=production --environment=prod
ng build --prod --env=prod
ng build --prod
# and so are these
ng build --target=development --environment=dev
ng build --dev --e=dev
ng build --dev
ng build
```

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

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

export const environment = {
production: false
Expand Down
17 changes: 15 additions & 2 deletions addon/ng2/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as WebpackBuild from '../tasks/build-webpack';
import * as WebpackBuildWatch from '../tasks/build-webpack-watch';

interface BuildOptions {
target?: string;
environment?: string;
outputPath?: string;
watch?: boolean;
Expand All @@ -16,28 +17,40 @@ module.exports = Command.extend({
aliases: ['b'],

availableOptions: [
{ name: 'environment', type: String, default: 'development', aliases: ['e', { 'dev': 'development' }, { 'prod': 'production' }] },
{ name: 'target', type: String, default: 'development', aliases: ['t', { 'dev': 'development' }, { 'prod': 'production' }] },
{ name: 'environment', type: String, default: '', aliases: ['e'] },
{ name: 'output-path', type: 'Path', default: 'dist/', aliases: ['o'] },
{ name: 'watch', type: Boolean, default: false, aliases: ['w'] },
{ name: 'watcher', type: String },
{ name: 'suppress-sizes', type: Boolean, default: false }
],

run: function (commandOptions: BuildOptions) {
if (commandOptions.environment === ''){
if (commandOptions.target === 'development') {
commandOptions.environment = 'dev';
}
if (commandOptions.target === 'production') {
commandOptions.environment = 'prod';
}
}

var project = this.project;
var ui = this.ui;
var buildTask = commandOptions.watch ?
new WebpackBuildWatch({
cliProject: project,
ui: ui,
outputPath: commandOptions.outputPath,
target: commandOptions.target,
environment: commandOptions.environment
}) :
new WebpackBuild({
cliProject: project,
ui: ui,
outputPath: commandOptions.outputPath,
environment: commandOptions.environment
target: commandOptions.target,
environment: commandOptions.environment,
});

return buildTask.run(commandOptions);
Expand Down
14 changes: 12 additions & 2 deletions addon/ng2/commands/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface ServeTaskOptions {
liveReloadPort?: number;
liveReloadBaseUrl?: string;
liveReloadLiveCss?: boolean;
target?: string;
environment?: string;
outputPath?: string;
ssl?: boolean;
Expand All @@ -45,22 +46,31 @@ module.exports = Command.extend({
{ name: 'live-reload-base-url', type: String, aliases: ['lrbu'], description: 'Defaults to baseURL' },
{ name: 'live-reload-port', type: Number, aliases: ['lrp'], description: '(Defaults to port number within [49152...65535])' },
{ name: 'live-reload-live-css', type: Boolean, default: true, description: 'Whether to live reload CSS (default true)' },
{ name: 'environment', type: String, default: 'development', aliases: ['e', { 'dev': 'development' }, { 'mat': 'material'}, { 'prod': 'production' }] },
{ name: 'target', type: String, default: 'development', aliases: ['t', { 'dev': 'development' }, { 'prod': 'production' }] },
{ name: 'environment', type: String, default: '', aliases: ['e'] },
{ name: 'output-path', type: 'Path', default: 'dist/', aliases: ['op', 'out'] },
{ name: 'ssl', type: Boolean, default: false },
{ name: 'ssl-key', type: String, default: 'ssl/server.key' },
{ name: 'ssl-cert', type: String, default: 'ssl/server.crt' }
],

run: function(commandOptions: ServeTaskOptions) {
if (commandOptions.environment === ''){
if (commandOptions.target === 'development') {
commandOptions.environment = 'dev';
}
if (commandOptions.target === 'production') {
commandOptions.environment = 'prod';
}
}

commandOptions.liveReloadHost = commandOptions.liveReloadHost || commandOptions.host;

return this._checkExpressPort(commandOptions)
.then(this._autoFindLiveReloadPort.bind(this))
.then((commandOptions: ServeTaskOptions) => {
commandOptions = assign({}, commandOptions, {
baseURL: this.project.config(commandOptions.environment).baseURL || '/'
baseURL: this.project.config(commandOptions.target).baseURL || '/'
});

if (commandOptions.proxy) {
Expand Down
23 changes: 11 additions & 12 deletions addon/ng2/models/webpack-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as path from 'path';
import * as fs from 'fs';
import * as webpackMerge from 'webpack-merge';
import { CliConfig } from './config';
import { NgCliEnvironmentPlugin } from '../utilities/environment-plugin';
Expand All @@ -22,9 +23,11 @@ export class NgCliWebpackConfig {
private webpackMobileConfigPartial: any;
private webpackMobileProdConfigPartial: any;

constructor(public ngCliProject: any, public environment: string) {
constructor(public ngCliProject: any, public target: string, public environment: string) {
const sourceDir = CliConfig.fromProject().defaults.sourceDir;

const environmentPath = `./${sourceDir}/app/environments/environment.${environment}.ts`;

this.webpackBaseConfig = getWebpackCommonConfig(this.ngCliProject.root, sourceDir);
this.webpackDevConfigPartial = getWebpackDevConfigPartial(this.ngCliProject.root, sourceDir);
this.webpackProdConfigPartial = getWebpackProdConfigPartial(this.ngCliProject.root, sourceDir);
Expand All @@ -37,27 +40,23 @@ export class NgCliWebpackConfig {
}

this.generateConfig();
this.config.plugins.unshift(new NgCliEnvironmentPlugin({env: this.environment}));
this.config.plugins.unshift(new NgCliEnvironmentPlugin({
path: path.resolve(this.ngCliProject.root, `./${sourceDir}/app/environments/`),
src: 'environment.ts',
dest: `environment.${this.environment}.ts`
}));
}

generateConfig(): void {
switch (this.environment) {
case "d":
case "dev":
switch (this.target) {
case "development":
case "develop":
this.config = webpackMerge(this.webpackBaseConfig, this.webpackDevConfigPartial);
break;

case "p":
case "prod":
case "production":
this.config = webpackMerge(this.webpackBaseConfig, this.webpackProdConfigPartial);
break;

default:
//TODO: Not sure what to put here. We have a default env passed anyways.
this.ngCliProject.ui.writeLine("Environment could not be determined while configuring your build system.", 3)
throw new Error("Invalid build target. Only 'development' and 'production' are available.");
break;
}
}
Expand Down
16 changes: 7 additions & 9 deletions addon/ng2/tasks/build-webpack-watch.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import {NgCliWebpackConfig} from '../models/webpack-config'
import {webpackOutputOptions} from '../models/';
import {ServeTaskOptions} from '../commands/serve';
import * as rimraf from 'rimraf';
import * as path from 'path';

const Task = require('ember-cli/lib/models/task');
const webpack = require('webpack');
const ProgressPlugin = require('webpack/lib/ProgressPlugin');

import * as Task from 'ember-cli/lib/models/task';
import * as webpack from 'webpack';
import * as ProgressPlugin from 'webpack/lib/ProgressPlugin';
import { NgCliWebpackConfig } from '../models/webpack-config';
import { webpackOutputOptions } from '../models/';
import { ServeTaskOptions } from '../commands/serve';

let lastHash: any = null;

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

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

const config = new NgCliWebpackConfig(project, runTaskOptions.environment).config;
const config = new NgCliWebpackConfig(project, runTaskOptions.target, runTaskOptions.environment).config;
const webpackCompiler = webpack(config);

webpackCompiler.apply(new ProgressPlugin({
Expand Down
13 changes: 6 additions & 7 deletions addon/ng2/tasks/build-webpack.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import {ServeTaskOptions} from '../commands/serve';
import {NgCliWebpackConfig} from '../models/webpack-config'
import {webpackOutputOptions} from '../models/'
import * as rimraf from 'rimraf';
import * as path from 'path';
import * as Task from 'ember-cli/lib/models/task';
import * as webpack from 'webpack';
import { ServeTaskOptions } from '../commands/serve';
import { NgCliWebpackConfig } from '../models/webpack-config';
import { webpackOutputOptions } from '../models/';

// Configure build and output;
var Task = require('ember-cli/lib/models/task');
const webpack = require('webpack');

let lastHash: any = null;

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

rimraf.sync(path.resolve(project.root, runTaskOptions.outputPath));
var config = new NgCliWebpackConfig(project, runTaskOptions.environment).config;
var config = new NgCliWebpackConfig(project, runTaskOptions.target, runTaskOptions.environment).config;
const webpackCompiler = webpack(config);

const ProgressPlugin = require('webpack/lib/ProgressPlugin');
Expand Down
2 changes: 1 addition & 1 deletion addon/ng2/tasks/serve-webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module.exports = Task.extend({
let lastHash = null;
let webpackCompiler: any;

var config: NgCliWebpackConfig = new NgCliWebpackConfig(this.project, commandOptions.environment).config;
var config: NgCliWebpackConfig = new NgCliWebpackConfig(this.project, commandOptions.target, commandOptions.environment).config;
// This allows for live reload of page when changes are made to repo.
// https://webpack.github.io/docs/webpack-dev-server.html#inline-mode
config.entry.main.unshift(`webpack-dev-server/client?http://localhost:${commandOptions.port}/`);
Expand Down
47 changes: 14 additions & 33 deletions addon/ng2/utilities/environment-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,27 @@
import * as fs from 'fs';
import * as path from 'path';

interface WebpackPlugin {
apply(compiler: any): void;
}

export class NgCliEnvironmentPlugin implements WebpackPlugin {
_file: string;
_alias: string;
_env: string;
interface EnvOptions {
path: string;
src: string;
dest: string;
}

constructor(config: any) {
if (typeof config === 'string') {
config = {env: config};
}
if (typeof config.env !== 'string') {
throw new Error('must provide env')
}
const ALIAS = {
'"dev"': 'dev',
'development': 'dev',
'"development"': 'dev',
'"prod"': 'prod',
'production': 'prod',
'"production"': 'prod',
'"test"': 'test',
'testing': 'test',
'"testing"': 'test',
};
const ENV = config.env.toLowerCase();
export class NgCliEnvironmentPlugin implements WebpackPlugin {
config: EnvOptions;

this._file = config.file || 'environment';
this._alias = config.alias || ALIAS;
this._env = this._alias[ENV] || ENV;
}
constructor(public config: EnvOptions) {}

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

replaceFile(file: string): any {
return file
.replace(this._file, this._file + '.' + this._env);
return path.resolve(this.config.path, this.config.dest);
}

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

fs.stat(envFile, (err, stats) => {
if (err || !stats.isFile()) {
var errorText = (!err && stats.isFile()) ? 'Is not a file.' : 'Does not exist.';
console.log('\nWARNING:\n' + envFile + '\n' + errorText + ' ' + 'Using file\n' + _resource + '\n');
return callback(null, result);
const destPath = path.resolve(this.config.path, this.config.dest);
const errorText = (!err && stats.isFile()) ? 'is not a file.' : 'does not exist.';
throw new Error(`${destPath} ${errorText}`);
}
// mutate result
var newResult = this.updateResult(result);
Expand Down
20 changes: 18 additions & 2 deletions tests/e2e/e2e_workflow.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,27 @@ describe('Basic end-to-end Workflow', function () {
it('Supports build config file replacement', function() {
this.timeout(420000);

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

expect(mainBundleContent).to.include('production: false');
expect(mainBundleContent).to.include('production: true');
});

it('Build fails on invalid build target', function (done) {
this.timeout(420000);
sh.exec(`${ngBin} build --target=potato`, (code) => {
expect(code).to.not.equal(0);
done();
});
});

it('Build fails on invalid environment file', function (done) {
this.timeout(420000);
sh.exec(`${ngBin} build --environment=potato`, (code) => {
expect(code).to.not.equal(0);
done();
});
});

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