From 704d817a5a411157b34c22fe2456e32be197d63f Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Thu, 21 Jul 2016 05:28:33 +0100 Subject: [PATCH] feat(env): decouple build from env file --- README.md | 32 +++++++++---- .../__path__/app/environments/environment.ts | 7 +-- addon/ng2/commands/build.ts | 17 ++++++- addon/ng2/commands/serve.ts | 14 +++++- addon/ng2/models/webpack-config.ts | 23 +++++---- addon/ng2/tasks/build-webpack-watch.ts | 16 +++---- addon/ng2/tasks/build-webpack.ts | 13 +++-- addon/ng2/tasks/serve-webpack.ts | 2 +- addon/ng2/utilities/environment-plugin.ts | 47 ++++++------------- tests/e2e/e2e_workflow.spec.js | 20 +++++++- 10 files changed, 112 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index fe4b2148d8bf..6224ddf97030 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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 diff --git a/addon/ng2/blueprints/ng2/files/__path__/app/environments/environment.ts b/addon/ng2/blueprints/ng2/files/__path__/app/environments/environment.ts index e63359828bb1..5c54e150e57b 100644 --- a/addon/ng2/blueprints/ng2/files/__path__/app/environments/environment.ts +++ b/addon/ng2/blueprints/ng2/files/__path__/app/environments/environment.ts @@ -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 diff --git a/addon/ng2/commands/build.ts b/addon/ng2/commands/build.ts index d564de57362b..935102fa8484 100644 --- a/addon/ng2/commands/build.ts +++ b/addon/ng2/commands/build.ts @@ -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; @@ -16,7 +17,8 @@ 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 }, @@ -24,6 +26,15 @@ module.exports = Command.extend({ ], 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 ? @@ -31,13 +42,15 @@ module.exports = Command.extend({ 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); diff --git a/addon/ng2/commands/serve.ts b/addon/ng2/commands/serve.ts index b800af766f3f..4880f1fd2c52 100644 --- a/addon/ng2/commands/serve.ts +++ b/addon/ng2/commands/serve.ts @@ -22,6 +22,7 @@ export interface ServeTaskOptions { liveReloadPort?: number; liveReloadBaseUrl?: string; liveReloadLiveCss?: boolean; + target?: string; environment?: string; outputPath?: string; ssl?: boolean; @@ -45,7 +46,8 @@ 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' }, @@ -53,6 +55,14 @@ module.exports = Command.extend({ ], 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; @@ -60,7 +70,7 @@ module.exports = Command.extend({ .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) { diff --git a/addon/ng2/models/webpack-config.ts b/addon/ng2/models/webpack-config.ts index f4d9beeedf6a..a215217c0afa 100644 --- a/addon/ng2/models/webpack-config.ts +++ b/addon/ng2/models/webpack-config.ts @@ -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'; @@ -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); @@ -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; } } diff --git a/addon/ng2/tasks/build-webpack-watch.ts b/addon/ng2/tasks/build-webpack-watch.ts index e54275b49af4..7e6514c6513f 100644 --- a/addon/ng2/tasks/build-webpack-watch.ts +++ b/addon/ng2/tasks/build-webpack-watch.ts @@ -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; @@ -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({ diff --git a/addon/ng2/tasks/build-webpack.ts b/addon/ng2/tasks/build-webpack.ts index c7ebed1998bd..8d98d76a6812 100644 --- a/addon/ng2/tasks/build-webpack.ts +++ b/addon/ng2/tasks/build-webpack.ts @@ -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({ @@ -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'); diff --git a/addon/ng2/tasks/serve-webpack.ts b/addon/ng2/tasks/serve-webpack.ts index f76e8acda18f..ff797cd6d050 100644 --- a/addon/ng2/tasks/serve-webpack.ts +++ b/addon/ng2/tasks/serve-webpack.ts @@ -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}/`); diff --git a/addon/ng2/utilities/environment-plugin.ts b/addon/ng2/utilities/environment-plugin.ts index 6825769d8f95..dd489ca0625a 100644 --- a/addon/ng2/utilities/environment-plugin.ts +++ b/addon/ng2/utilities/environment-plugin.ts @@ -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 { @@ -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); diff --git a/tests/e2e/e2e_workflow.spec.js b/tests/e2e/e2e_workflow.spec.js index fec4b87a6ffa..052efd377a21 100644 --- a/tests/e2e/e2e_workflow.spec.js +++ b/tests/e2e/e2e_workflow.spec.js @@ -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 () {