diff --git a/README.md b/README.md index cc0b1128f043..9b16b22453f2 100644 --- a/README.md +++ b/README.md @@ -37,13 +37,14 @@ The generated project has dependencies that require **Node 4 or greater**. * [Generating a Route](#generating-a-route) * [Creating a Build](#creating-a-build) * [Build Targets and Environment Files](#build-targets-and-environment-files) -* [Bundling](#bundling) +* [Adding extra files to the build](#adding-extra-files-to-the-build) * [Running Unit Tests](#running-unit-tests) * [Running End-to-End Tests](#running-end-to-end-tests) * [Deploying the App via GitHub Pages](#deploying-the-app-via-github-pages) * [Linting and formatting code](#linting-and-formatting-code) * [Support for offline applications](#support-for-offline-applications) * [Commands autocompletion](#commands-autocompletion) +* [Global styles](#global-styles) * [CSS preprocessor integration](#css-preprocessor-integration) * [3rd Party Library Installation](#3rd-party-library-installation) * [Updating angular-cli](#updating-angular-cli) @@ -126,8 +127,8 @@ 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. -At build time, `src/app/environments/environment.ts` will be replaced by -`src/app/environments/environment.{NAME}.ts` where `NAME` is the argument +At build time, `src/environments/environment.ts` will be replaced by +`src/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`, @@ -145,14 +146,15 @@ ng build --dev ng build ``` -You can also add your own env files other than `dev` and `prod` by creating a -`src/app/environments/environment.{NAME}.ts` and use them by using the `--env=NAME` -flag on the build/serve commands. +You can also add your own env files other than `dev` and `prod` by doing the following: +- create a `src/environments/environment.NAME.ts` +- add `{ NAME: 'src/environments/environment.NAME.ts' }` to the the `apps[0].environments` object in `angular-cli.json` +- use them by using the `--env=NAME` flag on the build/serve commands. ### Bundling -Builds created with the `-prod` flag via `ng build -prod` or `ng serve -prod` bundle -all dependencies into a single file, and make use of tree-shaking techniques. +All builds make use of bundling, and using the `--prod` flag in `ng build --prod` +or `ng serve --prod` will also make use of uglifying and tree-shaking functionality. ### Running unit tests @@ -240,6 +242,13 @@ ng completion >> ~/.bash_profile source ~/.bash_profile ``` +### Global styles + +The `styles.css` file allows users to add global styles and supports +[CSS imports](https://developer.mozilla.org/en/docs/Web/CSS/@import). + +If the project is created with the `--style=sass` option, this will be a `.sass` +file instead, and the same applies to `scss/less/styl`. ### CSS Preprocessor integration diff --git a/addon/ng2/blueprints/component/index.js b/addon/ng2/blueprints/component/index.js index 235552fcc8c4..2f9f4cea9e9f 100644 --- a/addon/ng2/blueprints/component/index.js +++ b/addon/ng2/blueprints/component/index.js @@ -25,9 +25,9 @@ module.exports = { var defaultPrefix = ''; if (this.project.ngConfig && - this.project.ngConfig.defaults && - this.project.ngConfig.defaults.prefix) { - defaultPrefix = this.project.ngConfig.defaults.prefix + '-'; + this.project.ngConfig.apps[0] && + this.project.ngConfig.apps[0].prefix) { + defaultPrefix = this.project.ngConfig.apps[0].prefix + '-'; } var prefix = this.options.prefix ? defaultPrefix : ''; this.selector = stringUtils.dasherize(prefix + parsedPath.name); @@ -97,7 +97,7 @@ module.exports = { dir = dirParts.join(path.sep); } } - var srcDir = this.project.ngConfig.defaults.sourceDir; + var srcDir = this.project.ngConfig.apps[0].root; this.appDir = dir.substr(dir.indexOf(srcDir) + srcDir.length); this.generatePath = dir; return dir; diff --git a/addon/ng2/blueprints/ng2/files/__path__/app/index.ts b/addon/ng2/blueprints/ng2/files/__path__/app/index.ts index 87743c9d56ee..875bdb2f254f 100644 --- a/addon/ng2/blueprints/ng2/files/__path__/app/index.ts +++ b/addon/ng2/blueprints/ng2/files/__path__/app/index.ts @@ -1,3 +1,2 @@ -export * from './environments/environment'; export * from './app.component'; export * from './app.module'; diff --git a/addon/ng2/blueprints/ng2/files/public/.gitignore b/addon/ng2/blueprints/ng2/files/__path__/assets/.gitignore similarity index 100% rename from addon/ng2/blueprints/ng2/files/public/.gitignore rename to addon/ng2/blueprints/ng2/files/__path__/assets/.gitignore diff --git a/addon/ng2/blueprints/ng2/files/__path__/app/environments/environment.dev.ts b/addon/ng2/blueprints/ng2/files/__path__/environments/environment.dev.ts similarity index 100% rename from addon/ng2/blueprints/ng2/files/__path__/app/environments/environment.dev.ts rename to addon/ng2/blueprints/ng2/files/__path__/environments/environment.dev.ts diff --git a/addon/ng2/blueprints/ng2/files/__path__/app/environments/environment.prod.ts b/addon/ng2/blueprints/ng2/files/__path__/environments/environment.prod.ts similarity index 100% rename from addon/ng2/blueprints/ng2/files/__path__/app/environments/environment.prod.ts rename to addon/ng2/blueprints/ng2/files/__path__/environments/environment.prod.ts diff --git a/addon/ng2/blueprints/ng2/files/__path__/app/environments/environment.ts b/addon/ng2/blueprints/ng2/files/__path__/environments/environment.ts similarity index 100% rename from addon/ng2/blueprints/ng2/files/__path__/app/environments/environment.ts rename to addon/ng2/blueprints/ng2/files/__path__/environments/environment.ts diff --git a/addon/ng2/blueprints/ng2/files/__path__/main.ts b/addon/ng2/blueprints/ng2/files/__path__/main.ts index 4bdf15c04a38..80160e71251e 100644 --- a/addon/ng2/blueprints/ng2/files/__path__/main.ts +++ b/addon/ng2/blueprints/ng2/files/__path__/main.ts @@ -1,6 +1,9 @@ +import "./polyfills.ts"; + import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { enableProdMode } from '@angular/core'; -import { AppModule, environment } from './app/'; +import { environment } from './environments/environment'; +import { AppModule } from './app/'; if (environment.production) { enableProdMode(); diff --git a/addon/ng2/blueprints/ng2/files/__path__/polyfills.ts b/addon/ng2/blueprints/ng2/files/__path__/polyfills.ts index 8e73613fac7a..3b4c55b09656 100644 --- a/addon/ng2/blueprints/ng2/files/__path__/polyfills.ts +++ b/addon/ng2/blueprints/ng2/files/__path__/polyfills.ts @@ -1,4 +1,5 @@ -// Prefer CoreJS over the polyfills above +// This file includes polyfills needed by Angular 2 and is loaded before +// the app. You can add your own extra polyfills to this file. import 'core-js/es6/symbol'; import 'core-js/es6/object'; import 'core-js/es6/function'; diff --git a/addon/ng2/blueprints/ng2/files/__path__/styles.__styleext__ b/addon/ng2/blueprints/ng2/files/__path__/styles.__styleext__ new file mode 100644 index 000000000000..e50a47e75cc8 --- /dev/null +++ b/addon/ng2/blueprints/ng2/files/__path__/styles.__styleext__ @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ \ No newline at end of file diff --git a/addon/ng2/blueprints/ng2/files/__path__/test.ts b/addon/ng2/blueprints/ng2/files/__path__/test.ts index 622eb90bcb37..cf6d45b7f4e8 100644 --- a/addon/ng2/blueprints/ng2/files/__path__/test.ts +++ b/addon/ng2/blueprints/ng2/files/__path__/test.ts @@ -1,10 +1,5 @@ -import 'core-js/es6'; -import 'core-js/es7/reflect'; +import "./polyfills.ts"; -// Typescript emit helpers polyfill -import 'ts-helpers'; - -import 'zone.js/dist/zone'; import 'zone.js/dist/long-stack-trace-zone'; import 'zone.js/dist/jasmine-patch'; import 'zone.js/dist/async-test'; diff --git a/addon/ng2/blueprints/ng2/files/angular-cli.json b/addon/ng2/blueprints/ng2/files/angular-cli.json index 5b96ba0ba88c..4454880e6968 100644 --- a/addon/ng2/blueprints/ng2/files/angular-cli.json +++ b/addon/ng2/blueprints/ng2/files/angular-cli.json @@ -5,9 +5,21 @@ }, "apps": [ { - "main": "<%= sourceDir %>/main.ts", - "tsconfig": "<%= sourceDir %>/tsconfig.json", - "mobile": <%= isMobile %> + "root": "<%= sourceDir %>", + "outDir": "dist", + "assets": "assets", + "index": "index.html", + "main": "main.ts", + "test": "test.ts", + "tsconfig": "tsconfig.json", + "prefix": "<%= prefix %>", + "mobile": <%= isMobile %>, + "styles": "styles.<%= styleExt %>", + "environments": { + "source": "environments/environment.ts", + "prod": "environments/environment.prod.ts", + "dev": "environments/environment.dev.ts" + } } ], "addons": [], @@ -23,8 +35,6 @@ } }, "defaults": { - "prefix": "<%= prefix %>", - "sourceDir": "<%= sourceDir %>", "styleExt": "<%= styleExt %>", "prefixInterfaces": false, "lazyRoutePrefix": "+" diff --git a/addon/ng2/blueprints/ng2/files/public/.npmignore b/addon/ng2/blueprints/ng2/files/public/.npmignore deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/addon/ng2/commands/github-pages-deploy.ts b/addon/ng2/commands/github-pages-deploy.ts index 5dae59a40af6..6da895d0244e 100644 --- a/addon/ng2/commands/github-pages-deploy.ts +++ b/addon/ng2/commands/github-pages-deploy.ts @@ -6,8 +6,9 @@ import * as chalk from 'chalk'; import * as fs from 'fs'; import * as fse from 'fs-extra'; import * as path from 'path'; -import * as BuildTask from 'ember-cli/lib/tasks/build'; +import * as WebpackBuild from '../tasks/build-webpack'; import * as CreateGithubRepo from '../tasks/create-github-repo'; +import { CliConfig } from '../models/config'; const fsReadFile = Promise.denodeify(fs.readFile); const fsWriteFile = Promise.denodeify(fs.writeFile); @@ -26,11 +27,16 @@ module.exports = Command.extend({ type: String, default: 'new gh-pages version', description: 'The commit message to include with the build, must be wrapped in quotes.' - }, { + }, { + name: 'target', + type: String, + default: 'production', + aliases: ['t', { 'dev': 'development' }, { 'prod': 'production' }] + }, { name: 'environment', type: String, - default: 'production', - description: 'The Angular environment to create a build for' + default: '', + aliases: ['e'] }, { name: 'user-page', type: Boolean, @@ -59,8 +65,20 @@ module.exports = Command.extend({ var execOptions = { cwd: root }; + + if (options.environment === ''){ + if (options.target === 'development') { + options.environment = 'dev'; + } + if (options.target === 'production') { + options.environment = 'prod'; + } + } + var projectName = this.project.pkg.name; + const outDir = CliConfig.fromProject().apps[0].outDir; + let ghPagesBranch = 'gh-pages'; let destinationBranch = options.userPage ? 'master' : ghPagesBranch; let initialBranch; @@ -68,15 +86,19 @@ module.exports = Command.extend({ // declared here so that tests can stub exec const execPromise = Promise.denodeify(exec); - var buildTask = new BuildTask({ + var buildTask = new WebpackBuild({ ui: this.ui, analytics: this.analytics, - project: this.project + cliProject: this.project, + target: options.target, + environment: options.environment, + outputPath: outDir }); var buildOptions = { + target: options.target, environment: options.environment, - outputPath: 'dist/' + outputPath: outDir }; var createGithubRepoTask = new CreateGithubRepo({ @@ -155,13 +177,13 @@ module.exports = Command.extend({ } function copyFiles() { - return fsReadDir('dist') + return fsReadDir(outDir) .then((files) => Promise.all(files.map((file) => { if (file === '.gitignore'){ // don't overwrite the .gitignore file return Promise.resolve(); } - return fsCopy(path.join('dist', file), path.join('.', file)) + return fsCopy(path.join(outDir, file), path.join('.', file)) }))); } diff --git a/addon/ng2/commands/serve.ts b/addon/ng2/commands/serve.ts index c23ae850ad85..2ae43e4631dd 100644 --- a/addon/ng2/commands/serve.ts +++ b/addon/ng2/commands/serve.ts @@ -46,7 +46,6 @@ module.exports = Command.extend({ { name: 'live-reload-live-css', type: Boolean, default: true, description: 'Whether to live reload CSS (default true)' }, { 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' } diff --git a/addon/ng2/models/config.ts b/addon/ng2/models/config.ts index 8b41a2e2dc49..24e44b2bafe7 100644 --- a/addon/ng2/models/config.ts +++ b/addon/ng2/models/config.ts @@ -1,5 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; +import * as chalk from 'chalk'; const schemaPath = path.resolve(process.env.CLI_ROOT, 'lib/config/schema.json'); const schema = require(schemaPath); @@ -166,6 +167,24 @@ export class CliConfig { public static fromProject(): any { const configPath = CliConfig._configFilePath(); - return configPath ? require(configPath) : {}; + + if (!configPath) { + return {}; + } + + let config = require(configPath); + + if (config.defaults.sourceDir || config.defaults.prefix) { + config.apps[0].root = config.apps[0].root || config.defaults.sourceDir; + config.apps[0].prefix = config.apps[0].prefix || config.defaults.prefix; + + console.error(chalk.yellow( + 'The "defaults.prefix" and "defaults.sourceDir" properties of angular-cli.json ' + + 'are deprecated in favor of "apps[0].root" and "apps[0].prefix".\n' + + 'Please update in order to avoid errors in future versions of angular-cli.' + )); + } + + return config; } } diff --git a/addon/ng2/models/webpack-build-common.ts b/addon/ng2/models/webpack-build-common.ts index e71749a1a2eb..35cc21f82c79 100644 --- a/addon/ng2/models/webpack-build-common.ts +++ b/addon/ng2/models/webpack-build-common.ts @@ -6,25 +6,25 @@ import * as atl from 'awesome-typescript-loader'; import {findLazyModules} from './find-lazy-modules'; +export function getWebpackCommonConfig(projectRoot: string, environment: string, appConfig: any) { -export function getWebpackCommonConfig(projectRoot: string, sourceDir: string, outputDir: string) { - const sourceRoot = path.resolve(projectRoot, sourceDir); - const outputPath = path.resolve(projectRoot, outputDir); - const lazyModules = findLazyModules(path.resolve(projectRoot, sourceDir)); + const appRoot = path.resolve(projectRoot, appConfig.root); + const appMain = path.resolve(appRoot, appConfig.main); + const styles = path.resolve(appRoot, appConfig.styles); + const lazyModules = findLazyModules(appRoot); return { devtool: 'source-map', resolve: { extensions: ['', '.ts', '.js'], - root: sourceRoot + root: appRoot }, context: path.resolve(__dirname, './'), - entry: { - main: [path.join(sourceRoot, 'main.ts')], - polyfills: path.join(sourceRoot, 'polyfills.ts') + entry: { + main: [appMain, styles] }, output: { - path: outputPath, + path: path.resolve(projectRoot, appConfig.outDir), filename: '[name].bundle.js' }, module: { @@ -45,7 +45,7 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string, o loader: 'awesome-typescript-loader', query: { useForkChecker: true, - tsconfig: path.resolve(sourceRoot, 'tsconfig.json') + tsconfig: path.resolve(appRoot, appConfig.tsconfig) } }, { loader: 'angular2-template-loader' @@ -53,25 +53,43 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string, o ], exclude: [/\.(spec|e2e)\.ts$/] }, - { test: /\.json$/, loader: 'json-loader'}, - { test: /\.css$/, loaders: ['raw-loader', 'postcss-loader'] }, - { test: /\.styl$/, loaders: ['raw-loader', 'postcss-loader', 'stylus-loader'] }, - { test: /\.less$/, loaders: ['raw-loader', 'postcss-loader', 'less-loader'] }, - { test: /\.scss$|\.sass$/, loaders: ['raw-loader', 'postcss-loader', 'sass-loader'] }, - { test: /\.(jpg|png)$/, loader: 'url-loader?limit=128000'}, - { test: /\.html$/, loader: 'raw-loader' } + + // in main, load css as raw text +        { exclude: styles, test: /\.css$/, loaders: ['raw-loader', 'postcss-loader'] }, +        { exclude: styles, test: /\.styl$/, loaders: ['raw-loader', 'postcss-loader', 'stylus-loader'] }, +        { exclude: styles, test: /\.less$/, loaders: ['raw-loader', 'postcss-loader', 'less-loader'] }, +        { exclude: styles, test: /\.scss$|\.sass$/, loaders: ['raw-loader', 'postcss-loader', 'sass-loader'] }, + + // outside of main, load it via style-loader +        { include: styles, test: /\.css$/, loaders: ['style-loader', 'css-loader', 'postcss-loader'] }, +        { include: styles, test: /\.styl$/, loaders: ['style-loader', 'css-loader', 'postcss-loader', 'stylus-loader'] }, +        { include: styles, test: /\.less$/, loaders: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader'] }, +        { include: styles, test: /\.scss$|\.sass$/, loaders: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'] }, + +        { test: /\.json$/, loader: 'json-loader' }, +        { test: /\.(jpg|png)$/, loader: 'url-loader?limit=10000' }, +        { test: /\.html$/, loader: 'raw-loader' }, + + { test: /\.(woff|ttf|svg)$/, loader: 'url?limit=10000' }, + { test: /\.woff2$/, loader: 'url?limit=10000&mimetype=font/woff2' }, + { test: /\.eot$/, loader: 'file' } ] }, plugins: [ - new webpack.ContextReplacementPlugin(/.*/, sourceRoot, lazyModules), + new webpack.ContextReplacementPlugin(/.*/, appRoot, lazyModules), new atl.ForkCheckerPlugin(), new HtmlWebpackPlugin({ - template: path.resolve(sourceRoot, 'index.html'), + template: path.resolve(appRoot, appConfig.index), chunksSortMode: 'dependency' }), - new webpack.optimize.CommonsChunkPlugin({ - name: ['polyfills'] - }), + new webpack.NormalModuleReplacementPlugin( + // This plugin is responsible for swapping the environment files. + // Since it takes a RegExp as first parameter, we need to escape the path. + // See https://webpack.github.io/docs/list-of-plugins.html#normalmodulereplacementplugin + new RegExp(path.resolve(appRoot, appConfig.environments.source) + .replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&")), + path.resolve(appRoot, appConfig.environments[environment]) + ), new webpack.optimize.CommonsChunkPlugin({ minChunks: Infinity, name: 'inline', @@ -79,10 +97,10 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string, o sourceMapFilename: 'inline.map' }), new CopyWebpackPlugin([{ - context: path.resolve(projectRoot, './public'), + context: path.resolve(appRoot, appConfig.assets), from: '**/*', - to: outputPath - }]), + to: path.resolve(projectRoot, appConfig.outDir, appConfig.assets) + }]) ], node: { fs: 'empty', diff --git a/addon/ng2/models/webpack-build-development.ts b/addon/ng2/models/webpack-build-development.ts index e738c89a69e4..be24e1bb1cdf 100644 --- a/addon/ng2/models/webpack-build-development.ts +++ b/addon/ng2/models/webpack-build-development.ts @@ -1,12 +1,10 @@ -import { CliConfig } from './config'; const path = require('path') -export const getWebpackDevConfigPartial = function(projectRoot: string, sourceDir: string, outputDir: string) { - +export const getWebpackDevConfigPartial = function(projectRoot: string, appConfig: any) { return { devtool: 'source-map', output: { - path: path.resolve(projectRoot, outputDir), + path: path.resolve(projectRoot, appConfig.outDir), filename: '[name].bundle.js', sourceMapFilename: '[name].map', chunkFilename: '[id].chunk.js' @@ -14,7 +12,7 @@ export const getWebpackDevConfigPartial = function(projectRoot: string, sourceDi tslint: { emitErrors: false, failOnHint: false, - resourcePath: path.resolve(projectRoot, `./${sourceDir}`) + resourcePath: path.resolve(projectRoot, appConfig.root) }, node: { fs: 'empty', diff --git a/addon/ng2/models/webpack-build-mobile.ts b/addon/ng2/models/webpack-build-mobile.ts index 8930f52f391f..3f30234f883b 100644 --- a/addon/ng2/models/webpack-build-mobile.ts +++ b/addon/ng2/models/webpack-build-mobile.ts @@ -3,26 +3,26 @@ import * as path from 'path'; import * as OfflinePlugin from 'offline-plugin'; import * as CopyWebpackPlugin from 'copy-webpack-plugin'; import { PrerenderWebpackPlugin } from '../utilities/prerender-webpack-plugin.ts'; -import { CliConfig } from './config'; -export const getWebpackMobileConfigPartial = function (projectRoot: string, sourceDir: string, outputDir: string) { - let outputPath: string = path.resolve(projectRoot, outputDir); +export const getWebpackMobileConfigPartial = function (projectRoot: string, appConfig: any) { + // Hardcoded files and paths here should be part of appConfig when + // reworking the mobile app functionality return { plugins: [ new CopyWebpackPlugin([ - {from: path.resolve(projectRoot, `./${sourceDir}/icons`), to: path.resolve(outputPath, './icons')}, - {from: path.resolve(projectRoot, `./${sourceDir}/manifest.webapp`), to: outputPath} + {from: path.resolve(projectRoot, appConfig.root, 'icons'), to: path.resolve(projectRoot, appConfig.outDir, 'icons')}, + {from: path.resolve(projectRoot, appConfig.root, 'manifest.webapp'), to: path.resolve(projectRoot, appConfig.outDir)} ]), new PrerenderWebpackPlugin({ templatePath: 'index.html', - configPath: path.resolve(projectRoot, `./${sourceDir}/main-app-shell.ts`), - appPath: path.resolve(projectRoot, `./${sourceDir}`) + configPath: path.resolve(projectRoot, appConfig.root, 'main-app-shell.ts'), + appPath: path.resolve(projectRoot, appConfig.root) }) ] } }; -export const getWebpackMobileProdConfigPartial = function (projectRoot: string, sourceDir: string) { +export const getWebpackMobileProdConfigPartial = function (projectRoot: string, appConfig: any) { return { entry: { 'sw-install': path.resolve(__dirname, '../utilities/sw-install.js') diff --git a/addon/ng2/models/webpack-build-production.ts b/addon/ng2/models/webpack-build-production.ts index cde91bbd8bb8..d18e84442abb 100644 --- a/addon/ng2/models/webpack-build-production.ts +++ b/addon/ng2/models/webpack-build-production.ts @@ -3,15 +3,13 @@ import * as webpackMerge from 'webpack-merge'; // used to merge webpack configs import * as WebpackMd5Hash from 'webpack-md5-hash'; import * as CompressionPlugin from 'compression-webpack-plugin'; import * as webpack from 'webpack'; -import { CliConfig } from './config'; - -export const getWebpackProdConfigPartial = function(projectRoot: string, sourceDir: string, outputDir: string) { +export const getWebpackProdConfigPartial = function(projectRoot: string, appConfig: any) { return { debug: false, devtool: 'source-map', output: { - path: path.resolve(projectRoot, outputDir), + path: path.resolve(projectRoot, appConfig.outDir), filename: '[name].[chunkhash].bundle.js', sourceMapFilename: '[name].[chunkhash].bundle.map', chunkFilename: '[id].[chunkhash].chunk.js' @@ -37,7 +35,7 @@ export const getWebpackProdConfigPartial = function(projectRoot: string, sourceD tslint: { emitErrors: true, failOnHint: true, - resourcePath: path.resolve(projectRoot, `./${sourceDir}`) + resourcePath: path.resolve(projectRoot, appConfig.root) }, htmlLoader: { minimize: true, diff --git a/addon/ng2/models/webpack-build-test.js b/addon/ng2/models/webpack-build-test.js index d4fdbe37d854..38e04f1fd43e 100644 --- a/addon/ng2/models/webpack-build-test.js +++ b/addon/ng2/models/webpack-build-test.js @@ -3,16 +3,19 @@ const path = require('path'); const webpack = require('webpack'); -const getWebpackTestConfig = function(projectRoot, sourceDir) { +const getWebpackTestConfig = function(projectRoot, appConfig) { + + const appRoot = path.resolve(projectRoot, appConfig.root); + return { devtool: 'inline-source-map', context: path.resolve(__dirname, './'), resolve: { extensions: ['', '.ts', '.js'], - root: path.resolve(projectRoot, `./${sourceDir}`) + root: appRoot }, entry: { - test: path.resolve(projectRoot, `./${sourceDir}/test.ts`) + test: path.resolve(appRoot, appConfig.test) }, output: { path: './dist.test', @@ -43,7 +46,7 @@ const getWebpackTestConfig = function(projectRoot, sourceDir) { { loader: 'awesome-typescript-loader', query: { - tsconfig: path.resolve(projectRoot, `./${sourceDir}/tsconfig.json`), + tsconfig: path.resolve(appRoot, appConfig.tsconfig), module: 'commonjs', target: 'es5', useForkChecker: true @@ -61,7 +64,7 @@ const getWebpackTestConfig = function(projectRoot, sourceDir) { { test: /\.less$/, loaders: ['raw-loader', 'postcss-loader', 'less-loader'] }, { test: /\.scss$|\.sass$/, loaders: ['raw-loader', 'postcss-loader', 'sass-loader'] }, { test: /\.(jpg|png)$/, loader: 'url-loader?limit=128000' }, - { test: /\.html$/, loader: 'raw-loader', exclude: [path.resolve(projectRoot, `./${sourceDir}/index.html`)] } + { test: /\.html$/, loader: 'raw-loader', exclude: [path.resolve(appRoot, appConfig.index)] } ], postLoaders: [ { @@ -83,7 +86,7 @@ const getWebpackTestConfig = function(projectRoot, sourceDir) { tslint: { emitErrors: false, failOnHint: false, - resourcePath: `./${sourceDir}` + resourcePath: `./${appConfig.root}` }, node: { fs: 'empty', diff --git a/addon/ng2/models/webpack-config.ts b/addon/ng2/models/webpack-config.ts index 7dcd586d898f..3139dfdee9e4 100644 --- a/addon/ng2/models/webpack-config.ts +++ b/addon/ng2/models/webpack-config.ts @@ -2,7 +2,6 @@ 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'; import { getWebpackCommonConfig, getWebpackDevConfigPartial, @@ -18,33 +17,26 @@ export class NgCliWebpackConfig { private webpackDevConfigPartial: any; private webpackProdConfigPartial: any; private webpackBaseConfig: any; - private webpackMaterialConfig: any; - private webpackMaterialE2EConfig: any; private webpackMobileConfigPartial: any; private webpackMobileProdConfigPartial: any; - constructor(public ngCliProject: any, public target: string, public environment: string, outputDir: string) { - const sourceDir = CliConfig.fromProject().defaults.sourceDir; + constructor(public ngCliProject: any, public target: string, public environment: string, outputDir?: string) { + const appConfig = CliConfig.fromProject().apps[0]; - const environmentPath = `./${sourceDir}/app/environments/environment.${environment}.ts`; + appConfig.outDir = outputDir || appConfig.outDir; - this.webpackBaseConfig = getWebpackCommonConfig(this.ngCliProject.root, sourceDir, outputDir); - this.webpackDevConfigPartial = getWebpackDevConfigPartial(this.ngCliProject.root, sourceDir, outputDir); - this.webpackProdConfigPartial = getWebpackProdConfigPartial(this.ngCliProject.root, sourceDir, outputDir); + this.webpackBaseConfig = getWebpackCommonConfig(this.ngCliProject.root, environment, appConfig); + this.webpackDevConfigPartial = getWebpackDevConfigPartial(this.ngCliProject.root, appConfig); + this.webpackProdConfigPartial = getWebpackProdConfigPartial(this.ngCliProject.root, appConfig); if (CliConfig.fromProject().apps[0].mobile){ - this.webpackMobileConfigPartial = getWebpackMobileConfigPartial(this.ngCliProject.root, sourceDir, outputDir); - this.webpackMobileProdConfigPartial = getWebpackMobileProdConfigPartial(this.ngCliProject.root, sourceDir); + this.webpackMobileConfigPartial = getWebpackMobileConfigPartial(this.ngCliProject.root, appConfig); + this.webpackMobileProdConfigPartial = getWebpackMobileProdConfigPartial(this.ngCliProject.root, appConfig); this.webpackBaseConfig = webpackMerge(this.webpackBaseConfig, this.webpackMobileConfigPartial); this.webpackProdConfigPartial = webpackMerge(this.webpackProdConfigPartial, this.webpackMobileProdConfigPartial); } this.generateConfig(); - 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 { diff --git a/addon/ng2/tasks/serve-webpack.ts b/addon/ng2/tasks/serve-webpack.ts index 1eb7de193478..2bcfbee1f433 100644 --- a/addon/ng2/tasks/serve-webpack.ts +++ b/addon/ng2/tasks/serve-webpack.ts @@ -17,7 +17,7 @@ module.exports = Task.extend({ let lastHash = null; let webpackCompiler: any; - var config: NgCliWebpackConfig = new NgCliWebpackConfig(this.project, commandOptions.target, commandOptions.environment, commandOptions.outputPath).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 @@ -41,7 +41,7 @@ module.exports = Task.extend({ } const webpackDevServerConfiguration: IWebpackDevServerConfigurationOptions = { - contentBase: config.output.path, + contentBase: path.resolve(this.project.root, `./${CliConfig.fromProject().apps[0].root}`), historyApiFallback: true, stats: webpackDevServerOutputOptions, inline: true, diff --git a/addon/ng2/utilities/dynamic-path-parser.js b/addon/ng2/utilities/dynamic-path-parser.js index a74002125693..e362da26f864 100644 --- a/addon/ng2/utilities/dynamic-path-parser.js +++ b/addon/ng2/utilities/dynamic-path-parser.js @@ -4,7 +4,7 @@ var fs = require('fs'); module.exports = function dynamicPathParser(project, entityName) { var projectRoot = project.root; - var sourceDir = project.ngConfig.defaults.sourceDir; + var sourceDir = project.ngConfig.apps[0].root; var appRoot = path.join(sourceDir, 'app'); var cwd = process.env.PWD; diff --git a/addon/ng2/utilities/environment-plugin.ts b/addon/ng2/utilities/environment-plugin.ts deleted file mode 100644 index dd489ca0625a..000000000000 --- a/addon/ng2/utilities/environment-plugin.ts +++ /dev/null @@ -1,60 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; - -interface WebpackPlugin { - apply(compiler: any): void; -} - -interface EnvOptions { - path: string; - src: string; - dest: string; -} - -export class NgCliEnvironmentPlugin implements WebpackPlugin { - config: EnvOptions; - - constructor(public config: EnvOptions) {} - - isEnvFile(file: string): boolean { - return file === path.resolve(this.config.path, this.config.src); - } - - replaceFile(file: string): any { - return path.resolve(this.config.path, this.config.dest); - } - - updateResult(result: any): any { - ['request', 'userRequest', 'resource'] - .filter((key) => { return result[key]; }) - .forEach((key) => { - result[key] = this.replaceFile(result[key]); - }); - - return result; - } - - apply(compiler: any): void { - compiler.plugin('normal-module-factory', (normalModuleFactory: any) => { - normalModuleFactory.plugin('after-resolve', (result, callback) => { - var _resource = result['resource']; - if (!this.isEnvFile(_resource)) { - return callback(null, result); - } - var envFile = this.replaceFile(_resource); - - fs.stat(envFile, (err, stats) => { - if (err || !stats.isFile()) { - 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); - return callback(null, newResult); - }); - - }); - }); - } -} diff --git a/lib/config/schema.json b/lib/config/schema.json index d8598052dd14..168ad6a14e07 100644 --- a/lib/config/schema.json +++ b/lib/config/schema.json @@ -22,9 +22,62 @@ "items": { "type": "object", "properties": { - "main": "string", - "tsconfig": "string", - "mobile": "boolean" + "root": { + "type": "string" + }, + "outDir": { + "type": "string" + }, + "assets": { + "type": "string" + }, + "index": { + "type": "string" + }, + "main": { + "type": "string" + }, + "test": { + "type": "string" + }, + "tsconfig": { + "type": "string" + }, + "prefix": { + "type": "string" + }, + "mobile": { + "type": "boolean" + }, + "additionalEntries": { + "description": "Additional files to be included in the build.", + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "input": { + "type": "string" + }, + "output": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "additionalProperties": false + }, + "environments": { + "description": "Name and corresponding file for environment config.", + "type": "object", + "additionalProperties": true + } }, "additionalProperties": false }, @@ -81,12 +134,6 @@ "defaults": { "type": "object", "properties": { - "prefix": { - "type": "string" - }, - "sourceDir": { - "type": "string" - }, "styleExt": { "type": "string" }, @@ -101,4 +148,4 @@ } }, "additionalProperties": false -} +} \ No newline at end of file diff --git a/package.json b/package.json index 7368d6560011..d9013f6252ff 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,6 @@ "stylus": "^0.54.5", "stylus-loader": "^2.1.0", "symlink-or-copy": "^1.0.3", - "ts-helpers": "^1.1.1", "ts-loader": "^0.8.2", "tslint": "^3.11.0", "tslint-loader": "^2.1.4", diff --git a/plugins/karma.js b/plugins/karma.js index 1282650d6659..70758bc56e60 100644 --- a/plugins/karma.js +++ b/plugins/karma.js @@ -5,10 +5,11 @@ const init = (config) => { // load Angular CLI config if (!config.angularCliConfig) throw new Error('Missing \'angularCliConfig\' entry in Karma config'); - const angularCliConfig = require(path.join(config.basePath, config.angularCliConfig)) + const angularCliConfig = require(path.join(config.basePath, config.angularCliConfig)); + const appConfig = angularCliConfig.apps[0]; // add webpack config - config.webpack = getWebpackTestConfig(config.basePath, angularCliConfig.defaults.sourceDir); + config.webpack = getWebpackTestConfig(config.basePath, appConfig); config.webpackMiddleware = { noInfo: true, // Hide webpack output because its noisy. stats: { // Also prevent chunk and module display output, cleaner look. Only emit errors. diff --git a/tests/acceptance/dynamic-path-parser.spec.js b/tests/acceptance/dynamic-path-parser.spec.js index 9beab6306144..dcaec37331f6 100644 --- a/tests/acceptance/dynamic-path-parser.spec.js +++ b/tests/acceptance/dynamic-path-parser.spec.js @@ -11,14 +11,14 @@ describe('dynamic path parser', () => { var project; var entityName = 'temp-name'; var rootName = path.parse(process.cwd()).root + 'project'; - var sourceDir = 'src'; + var root = 'src'; beforeEach(() => { project = { root: rootName, ngConfig: { - defaults: { - sourceDir: sourceDir - } + apps: [{ + root: root + }] } }; var mockFolder = {}; diff --git a/tests/e2e/e2e_workflow.spec.js b/tests/e2e/e2e_workflow.spec.js index cf00746044d1..e72d63cb456e 100644 --- a/tests/e2e/e2e_workflow.spec.js +++ b/tests/e2e/e2e_workflow.spec.js @@ -109,7 +109,7 @@ describe('Basic end-to-end Workflow', function () { var mainBundlePath = path.join(process.cwd(), 'dist', 'main.bundle.js'); var mainBundleContent = fs.readFileSync(mainBundlePath, { encoding: 'utf8' }); - expect(mainBundleContent).to.include('production: true'); + expect(mainBundleContent.includes('production: true')).to.be.equal(true); }); it('Build fails on invalid build target', function (done) { @@ -310,11 +310,11 @@ describe('Basic end-to-end Workflow', function () { expect(existsSync(lcovReport)).to.be.equal(true); }); - it('moves all files that live inside `public` into `dist`', function () { + it('moves all files that live inside `assets` into `dist`', function () { this.timeout(420000); - const tmpFile = path.join(process.cwd(), 'public', 'test.abc'); - const tmpFileLocation = path.join(process.cwd(), 'dist', 'test.abc'); + const tmpFile = path.join(process.cwd(), 'src', 'assets', 'test.abc'); + const tmpFileLocation = path.join(process.cwd(), 'dist', 'assets', 'test.abc'); fs.writeFileSync(tmpFile, 'hello world'); sh.exec(`${ngBin} build`); @@ -486,6 +486,40 @@ describe('Basic end-to-end Workflow', function () { expect(indexHtml).to.include('main.bundle.js'); }); + it('styles.css is added to main bundle', function() { + this.timeout(420000); + + let stylesPath = path.join(process.cwd(), 'src', 'styles.css'); + let testStyle = 'body { background-color: blue; }'; + fs.writeFileSync(stylesPath, testStyle, 'utf8'); + + sh.exec(`${ngBin} build`); + + var mainBundlePath = path.join(process.cwd(), 'dist', 'main.bundle.js'); + var mainBundleContent = fs.readFileSync(mainBundlePath, { encoding: 'utf8' }); + + expect(mainBundleContent.includes(testStyle)).to.be.equal(true); + }); + + it('styles.css supports css imports', function() { + this.timeout(420000); + + let importedStylePath = path.join(process.cwd(), 'src', 'imported-styles.css'); + let testStyle = 'body { background-color: blue; }'; + fs.writeFileSync(importedStylePath, testStyle, 'utf8'); + + let stylesPath = path.join(process.cwd(), 'src', 'style.css'); + let importStyle = '@import \'./imported-style.css\';'; + fs.writeFileSync(stylesPath, importStyle, 'utf8'); + + sh.exec(`${ngBin} build`); + + var mainBundlePath = path.join(process.cwd(), 'dist', 'main.bundle.js'); + var mainBundleContent = fs.readFileSync(mainBundlePath, { encoding: 'utf8' }); + + expect(mainBundleContent.includes(testStyle)).to.be.equal(true); + }); + it('Serve and run e2e tests on dev environment', function () { this.timeout(240000);