diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5c9661b --- /dev/null +++ b/.gitignore @@ -0,0 +1,48 @@ +# Created by .ignore support plugin (hsz.mobi) +### Node template +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history + +# Build result +dist + +# Documentation +docs + +# TypeScript definitions +typings diff --git a/.sass-lint.yml b/.sass-lint.yml new file mode 100644 index 0000000..c17c835 --- /dev/null +++ b/.sass-lint.yml @@ -0,0 +1,12 @@ +# Linter Options +options: + # Don't merge default rules + merge-default-rules: false +# File Options +files: + include: 'src/**/*.s+(a|c)ss' +# Rule Configuration +rules: + extends-before-mixins: 2 + extends-before-declarations: 2 + placeholder-in-extend: 2 diff --git a/README.md b/README.md new file mode 100644 index 0000000..bac0e42 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Angular 2 + Webpack boilerplate + +Work in progress. + +For now: run `npm install` and `npm start`, go to `http://localhost:9045/`. diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000..bb3b3b8 --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,95 @@ +/** + * Please see Karma config file reference for better understanding: + * http://karma-runner.github.io/latest/config/configuration-file.html + */ +module.exports = function(config) { + config.set({ + /** + * This path will be used for resolving. + */ + basePath: '', + + /** + * List of test frameworks we will use. Most of them are provided by separate packages (adapters). + * You can find them on npmjs.org: https://npmjs.org/browse/keyword/karma-adapter + */ + frameworks: ['jasmine', 'source-map-support'], + + /** + * Entry point / test environment builder is also written in TypeScript. + */ + files: ['./tests/main.ts'], + + /** + * Transform files before loading them. + */ + preprocessors: { + './src/**/*.ts': [ + 'webpack', + 'sourcemap', + 'coverage' + ], + './tests/**/*.ts': [ + 'webpack' + ] + }, + + webpack: require('./webpack.config.test'), + + /** + * Make dev server silent. + */ + webpackServer: { noInfo: true }, + + /** + * A lot of plugins are available for test results reporting. + * You can find them here: https://npmjs.org/browse/keyword/karma-reporter + */ + reporters: ['coverage', 'karma-remap-istanbul'], + + /** + * Simple summary (printed to the console) and JSON file which we will remap back to TypeScript. + */ + coverageReporter: { + dir: 'coverage', + reporters: [ + { type: 'text-summary' }, + { + type: 'json', + subdir: '.', + file: 'coverage-final.json' + } + ] + }, + + /** + * Map code coverage result back to TypeScript using `karma-remap-istanbul`. + */ + remapIstanbulReporter: { + src: 'coverage/coverage-final.json', + reports: { + lcovonly: 'coverage/lcov.info', + html: 'coverage/report' + }, + timeoutNotCreated: 5000, + timeoutNoMoreFiles: 1000 + }, + + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + + /** + * Only Phantom is used in this example. + * You can find more browser launchers here: https://npmjs.org/browse/keyword/karma-launcher + */ + browsers: ['PhantomJS'], + + /** + * This is CI mode: run once and exit. + * TODO: Non-CI mode + */ + autoWatch: true, + singleRun: true + }) +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..a3dfad5 --- /dev/null +++ b/package.json @@ -0,0 +1,82 @@ +{ + "name": "angular2-webpack-boilerplate", + "version": "1.0.0", + "description": "A boilerplate for Angular 2 and Webpack", + "keywords": [ + "angular2", + "webpack", + "typescript" + ], + "author": "Valentin Nazarov ", + "license": "MIT", + "directories": { + "test": "tests" + }, + "scripts": { + "clean": "rimraf ./node_modules ./docs ./typings ./coverage ./dist", + "clean:dist": "rimraf ./dist", + "lint": "npm run lint:ts && npm run lint:sass", + "lint:ts": "tslint src/**/*.ts tests/**/*.ts", + "lint:sass": "sass-lint", + "pretest": "npm run lint", + "test": "karma start", + "build": "webpack", + "postinstall": "typings install", + "docs": "npm run docs:typedoc && npm run docs:kss", + "docs:typedoc": "typedoc --options ./typedoc.json ./src/app", + "docs:kss": "kss --source ./src --destination ./docs/styleguide", + "start": "webpack-dev-server --inline --progress --profile --colors --watch --display-error-details --display-cached --content-base ./dist" + }, + "devDependencies": { + "@angular/common": "2.0.0-rc.2", + "@angular/compiler": "2.0.0-rc.2", + "@angular/core": "2.0.0-rc.2", + "@angular/http": "2.0.0-rc.2", + "@angular/platform-browser": "2.0.0-rc.2", + "@angular/platform-browser-dynamic": "2.0.0-rc.2", + "@angular/platform-server": "2.0.0-rc.2", + "@angular/router": "2.0.0-rc.2", + "awesome-typescript-loader": "^1.1.1", + "codelyzer": "0.0.23", + "core-js": "^2.4.0", + "css-loader": "^0.23.1", + "es6-shim": "^0.35.1", + "file-loader": "^0.8.5", + "html-loader": "^0.4.3", + "html-webpack-plugin": "^2.21.0", + "istanbul-instrumenter-loader": "^0.2.0", + "jasmine-core": "^2.4.1", + "json-loader": "^0.5.4", + "karma": "^0.13.22", + "karma-coverage": "^1.0.0", + "karma-jasmine": "^1.0.2", + "karma-phantomjs-launcher": "^1.0.0", + "karma-remap-istanbul": "^0.1.0", + "karma-source-map-support": "^1.1.0", + "karma-sourcemap-loader": "^0.3.7", + "karma-webpack": "^1.7.0", + "kss": "^3.0.0-beta.14", + "node-sass": "^3.7.0", + "phantomjs-prebuilt": "^2.1.7", + "raw-loader": "^0.5.1", + "reflect-metadata": "^0.1.3", + "remap-istanbul": "^0.6.4", + "rimraf": "^2.5.2", + "rxjs": "5.0.0-beta.6", + "sass-lint": "^1.7.0", + "sass-loader": "^3.2.0", + "source-map-loader": "^0.1.5", + "style-loader": "^0.13.1", + "to-string-loader": "^1.1.4", + "ts-helpers": "^1.1.1", + "tslint": "^3.11.0", + "tslint-loader": "^2.1.4", + "typedoc": "^0.4.3", + "typescript": "^1.8.10", + "typings": "^1.3.0", + "webpack": "^1.13.1", + "webpack-dev-server": "^1.14.1", + "webpack-md5-hash": "0.0.5", + "zone.js": "~0.6.12" + } +} diff --git a/src/app/app.component.ts b/src/app/app.component.ts new file mode 100644 index 0000000..3a11545 --- /dev/null +++ b/src/app/app.component.ts @@ -0,0 +1,19 @@ +import { Component, ViewEncapsulation } from '@angular/core'; +import { AppService } from './app.service'; + +@Component({ + selector: 'da-app', + template: require('./app.html'), + styles: [ + require('./app.scss') + ], + encapsulation: ViewEncapsulation.Native, + providers: [ + AppService + ] +}) +export class AppComponent { + constructor(public appService: AppService) { + + } +} diff --git a/src/app/app.html b/src/app/app.html new file mode 100644 index 0000000..4658077 --- /dev/null +++ b/src/app/app.html @@ -0,0 +1 @@ +

{{ appService.getTitle() }}

diff --git a/src/app/app.scss b/src/app/app.scss new file mode 100644 index 0000000..c0d5796 --- /dev/null +++ b/src/app/app.scss @@ -0,0 +1,16 @@ +$heading-color: #333; +$heading-highlight-color: #fa2800; + +// App title +// +// :hover - Highlights when hovering +// +// Styleguide base +.app__title { + font-size: 4rem; + color: $heading-color; + + &:hover { + color: $heading-highlight-color; + } +} diff --git a/src/app/app.service.ts b/src/app/app.service.ts new file mode 100644 index 0000000..f4c9acb --- /dev/null +++ b/src/app/app.service.ts @@ -0,0 +1,7 @@ +export class AppService { + title: string = "Hello, world!"; + + public getTitle(): string { + return this.title; + } +} diff --git a/src/index.ejs b/src/index.ejs new file mode 100644 index 0000000..22ab039 --- /dev/null +++ b/src/index.ejs @@ -0,0 +1,16 @@ + + + + <%= webpackConfig.metadata.title %> + + + + + + + + +Loading… +<% for (var chunk in htmlWebpackPlugin.files.chunks) { %><% } %> + + \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..37efc72 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,13 @@ +import { bootstrap } from '@angular/platform-browser-dynamic'; +import { enableProdMode } from '@angular/core'; + +import { AppComponent } from './app/app.component'; + +if (process.env.ENV === 'prod') { + enableProdMode(); +} + +/** + * TODO: Example service (HTTP interaction), routing, nested directives, pipe + related tests + */ +bootstrap(AppComponent); diff --git a/src/polyfills.ts b/src/polyfills.ts new file mode 100644 index 0000000..75b921d --- /dev/null +++ b/src/polyfills.ts @@ -0,0 +1,29 @@ +/** + * Use CoreJS polyfills. + */ +import 'core-js/es6'; +import 'core-js/es7/reflect'; + +/** + * Quote from docs: "A Zone is an execution context that persists across async tasks. You can think of it as + * thread-local storage for JavaScript VMs." Angular 2 uses this library. + * See details here: https://github.com/angular/zone.js/ + */ +// TODO: Move to `vendor.ts`? +import 'zone.js/dist/zone'; + +/** + * This package helps to avoid generation of helper code in each file (e.g. for decorators). + * See details here: https://github.com/ngParty/ts-helpers + */ +import 'ts-helpers'; + +if (process.env.ENV === 'production') { + // TODO: Either do something here or negate condition and remove `else` +} else { + /** + * You can use `import` only in a namespace or module in TypeScript, so use `require` here. + */ + Error['stackTraceLimit'] = Infinity; + require('zone.js/dist/long-stack-trace-zone'); +} diff --git a/src/vendor.ts b/src/vendor.ts new file mode 100644 index 0000000..a8a9dfa --- /dev/null +++ b/src/vendor.ts @@ -0,0 +1,10 @@ +/** + * Angular 2 and RxJS. + */ +import '@angular/platform-browser'; +import '@angular/platform-browser-dynamic'; +import '@angular/core'; +import '@angular/common'; +import '@angular/http'; +import '@angular/router'; +import 'rxjs'; diff --git a/tests/app/app.component.spec.ts b/tests/app/app.component.spec.ts new file mode 100644 index 0000000..460fd3b --- /dev/null +++ b/tests/app/app.component.spec.ts @@ -0,0 +1,20 @@ +import { + beforeEachProviders, + it, + inject, + expect +} from '@angular/core/testing'; + +import { AppService } from '../../src/app/app.service'; + +describe('App Service', () => { + beforeEachProviders(() => { + return [ + AppService + ]; + }); + + it('should return title', inject([AppService], (appService) => { + expect(appService.getTitle()).toBe('Hello, world!'); + })); +}); diff --git a/tests/main.ts b/tests/main.ts new file mode 100644 index 0000000..1124111 --- /dev/null +++ b/tests/main.ts @@ -0,0 +1,37 @@ +/** + * TODO: Comments + */ +import 'core-js/es6'; +import 'core-js/es7/reflect'; +import 'reflect-metadata'; +import 'zone.js/dist/zone'; +import 'zone.js/dist/sync-test'; +import 'zone.js/dist/async-test'; +import 'zone.js/dist/fake-async-test'; +import 'zone.js/dist/jasmine-patch'; + +import 'ts-helpers'; + +import 'zone.js/dist/sync-test'; +import 'zone.js/dist/async-test'; +import 'zone.js/dist/fake-async-test'; + +import { setBaseTestProviders } from '@angular/core/testing'; + +import { + TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, + TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS +} from '@angular/platform-browser-dynamic/testing'; + +setBaseTestProviders( + TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, + TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS +); + +let testContext = (<{ context?: Function }>require).context( + './', + true, + /\.spec\.ts/ +); + +testContext.keys().forEach(testContext); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..cd93acf --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "module": "commonjs", + "moduleResolution": "node", + "target": "es5", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "noImplicitAny": false, + "removeComments": false, + "noEmitHelpers": true, + "sourceMap": true + }, + "exclude": [ + "node_modules" + ], + "filesGlob": [ + "./src/app/**/*.ts", + "./typings/index.d.ts", + "!./node_modules/**" + ], + "awesomeTypescriptLoaderOptions": { + "resolveGlobs": true, + "forkChecker": true + }, + "compileOnSave": false, + "buildOnSave": false +} \ No newline at end of file diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 0000000..f7e54bc --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "module": "commonjs", + "moduleResolution": "node", + "target": "es5", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "noImplicitAny": false, + "removeComments": false, + "noEmitHelpers": true, + "sourceMap": false, + "inlineSourceMap": true + }, + "exclude": [ + "node_modules" + ], + "filesGlob": [ + "./src/app/**/*.ts", + "./typings/index.d.ts", + "!./node_modules/**" + ], + "awesomeTypescriptLoaderOptions": { + "resolveGlobs": true, + "forkChecker": true + }, + "compileOnSave": false, + "buildOnSave": false +} \ No newline at end of file diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..e24415d --- /dev/null +++ b/tslint.json @@ -0,0 +1,26 @@ +{ + "rulesDirectory": [ + "node_modules/codelyzer" + ], + "rules": { + "directive-selector-name": [true, "camelCase"], + "component-selector-name": [true, "kebab-case"], + "directive-selector-type": [true, "attribute"], + "component-selector-type": [true, "element"], + "directive-selector-prefix": [true, "da"], + "component-selector-prefix": [true, "da"], + "use-input-property-decorator": true, + "use-output-property-decorator": true, + "use-host-property-decorator": true, + "no-attribute-parameter-decorator": true, + "no-input-rename": true, + "no-output-rename": true, + "no-forward-ref" :true, + "use-life-cycle-interface": true, + "use-pipe-transform-interface": true, + "pipe-naming": [true, "camelCase", "da"], + "component-class-suffix": true, + "directive-class-suffix": true, + "import-destructuring-spacing": true + } +} \ No newline at end of file diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 0000000..76be11a --- /dev/null +++ b/typedoc.json @@ -0,0 +1,15 @@ +{ + "mode": "modules", + "out": "docs/app", + "theme": "default", + "ignoreCompilerErrors": "true", + "experimentalDecorators": "true", + "emitDecoratorMetadata": "true", + "target": "ES5", + "moduleResolution": "node", + "preserveConstEnums": "true", + "stripInternal": "true", + "suppressExcessPropertyErrors": "true", + "suppressImplicitAnyIndexErrors": "true", + "module": "commonjs" +} \ No newline at end of file diff --git a/typings.json b/typings.json new file mode 100644 index 0000000..bb2a18b --- /dev/null +++ b/typings.json @@ -0,0 +1,8 @@ +{ + "name": "angular2-webpack-boilerplate", + "globalDependencies": { + "core-js": "registry:dt/core-js#0.0.0+20160602141332", + "jasmine": "registry:dt/jasmine#2.2.0+20160505161446", + "node": "registry:dt/node#6.0.0+20160613154055" + } +} \ No newline at end of file diff --git a/webpack.config.common.js b/webpack.config.common.js new file mode 100644 index 0000000..3f72ee1 --- /dev/null +++ b/webpack.config.common.js @@ -0,0 +1,98 @@ +'use strict'; + +/** + * Please see webpack config reference for better understanding: + * https://webpack.github.io/docs/configuration.html + */ +const webpack = require('webpack'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlugin; + +module.exports = { + /** + * These parameters will be used for rendering `index.html`. + */ + metadata: { + title: 'Demo Application', + baseUrl: '/' + }, + + entry: { + 'polyfills': './src/polyfills.ts', + 'vendor': './src/vendor.ts', + 'app': './src/main.ts' + }, + + resolve: { + extensions: ['', '.ts', '.js', '.scss', '.html'] + }, + + module: { + loaders: [ + /** + * Loader for TypeScript. + * No need to exclude tests by `(spec|e2e)` mask here, as they are in separate directory. + * + * See project repository for details / configuration reference: + * https://github.com/s-panferov/awesome-typescript-loader + */ + {test: /\.ts$/, loader: 'awesome-typescript-loader'}, + + /** + * Loaders for HTML templates, JSON files, SASS/SCSS stylesheets. See details at projects' repositories: + * + * https://github.com/webpack/json-loader + * https://github.com/webpack/html-loader + * https://github.com/gajus/to-string-loader + */ + {test: /\.json$/, loader: 'json-loader'}, + {test: /\.html$/, loader: 'html-loader'}, + {test: /\.scss$/, loader: 'to-string-loader!css-loader!sass-loader'} + ] + }, + + plugins: [ + /** + * This plugin is a part of `awesome-typescript-loader`, it allows to run type checking in a separate process, + * so webpack won't wait for it. + */ + new ForkCheckerPlugin(), + + /** + * Quote from webpack docs: "Assign the module and chunk ids by occurrence count. Ids that are used often get + * lower (shorter) ids. This make ids predictable, reduces total file size and is recommended." + * + * See https://webpack.github.io/docs/list-of-plugins.html#occurrenceorderplugin + */ + new webpack.optimize.OccurenceOrderPlugin(true), + + /** + * This plugin simplifies generation of `index.html` file. Especially useful for production environment, + * where your files have hashes in their names. + * + * We have auto-injection disabled here, otherwise scripts will be automatically inserted at the end + * of `body` element. + * + * See https://www.npmjs.com/package/html-webpack-plugin for details. + * + * TODO: Google Analytics and other stuff like that + */ + new HtmlWebpackPlugin({ + title: 'Demo Application', + template: 'src/index.ejs', + chunksSortMode: 'dependency', + inject: false + }), + + /** + * This plugin helps to share common code between pages. + * + * For more info see: + * https://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin + * https://github.com/webpack/docs/wiki/optimization#multi-page-app + */ + new webpack.optimize.CommonsChunkPlugin({ + name: ['vendor', 'polyfills'] + }) + ] +}; diff --git a/webpack.config.dev.js b/webpack.config.dev.js new file mode 100644 index 0000000..38aad39 --- /dev/null +++ b/webpack.config.dev.js @@ -0,0 +1,47 @@ +'use strict'; + +/** + * TODO: Comments + */ +const webpack = require('webpack'); +const ENV = process.env.ENV = process.env.NODE_ENV = 'dev'; +let config = require('./webpack.config.common'); + +config.devtool = 'inline-source-map'; + +config.output = { + path: './dist', + publicPath: 'http://localhost:9045/', + filename: '[name].js', + chunkFilename: '[id].chunk.js', + sourceMapFilename: '[name].map' +}; + +config.debug = true; + +config.devServer = { + historyApiFallback: true, + stats: 'minimal', + outputPath: 'dist', + host: 'localhost', + port: 9045, + watchOptions: { + aggregateTimeout: 300, + poll: 1000 + } +}; + +/** + * Quote from webpack docs: "Define free variables. Useful for having development builds with debug logging + * or adding global constants." + * + * Note, that values are _evaluated_, not just assigned (this is why we use `JSON.stringify` here). + */ +config.plugins.push(new webpack.DefinePlugin({ + 'ENV': JSON.stringify(ENV), + 'process.env': { + 'ENV': JSON.stringify(ENV) + } +})); + +module.exports = config; diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..8bde0a5 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,22 @@ +'use strict'; + +/** + * TODO: Comments? + * TODO: HMR + */ + +switch (process.env.NODE_ENV) { + case 'prod': + case 'production': + module.exports = require('./webpack.config.prod'); + break; + case 'test': + case 'testing': + module.exports = require('./webpack.config.test'); + break; + case 'dev': + case 'development': + default: + module.exports = require('./webpack.config.dev'); + break; +} diff --git a/webpack.config.prod.js b/webpack.config.prod.js new file mode 100644 index 0000000..b675828 --- /dev/null +++ b/webpack.config.prod.js @@ -0,0 +1,39 @@ +'use strict'; + +/** + * TODO: Comments + */ +const webpack = require('webpack'); +const ENV = process.env.ENV = process.env.NODE_ENV = 'prod'; +let config = require('./webpack.config.common'); + +config.devtool = 'source-map'; + +config.output = { + path: './dist', + publicPath: '/', + filename: '[name].[hash].js', + chunkFilename: '[id].[hash].chunk.js', + sourceMapFilename: '[name].map' +}; + +/** + * TODO: Something better? + */ +config.htmlLoader = { minimize: false }; + +config.plugins.push( + new webpack.NoErrorsPlugin(), + new webpack.optimize.DedupePlugin(), + new webpack.optimize.UglifyJsPlugin() +); + +/** + * Same purpose as in dev config. + */ +config.plugins.push(new webpack.DefinePlugin({ + 'ENV': JSON.stringify(ENV), + 'process.env': { + 'ENV': JSON.stringify(ENV) + } +})); diff --git a/webpack.config.test.js b/webpack.config.test.js new file mode 100644 index 0000000..eeb9138 --- /dev/null +++ b/webpack.config.test.js @@ -0,0 +1,72 @@ +/** + * Config for test environment is pretty different from dev and prod, so we don't use common config as base. + */ +module.exports = { + /** + * Inline source maps, generated by TypeScript compiler, will be used. + */ + devtool: 'inline-source-map', + + /** + * Entry point / test environment builder is also written in TypeScript. + */ + entry: './tests/main.ts', + + verbose: true, + + resolve: { + extensions: ['', '.ts', '.js'] + }, + + module: { + preLoaders: [ + /** + * Lint TypeScript before loading it. + * See https://github.com/wbuchwalter/tslint-loader for details. + */ + { + test: /\.ts$/, + loader: 'tslint-loader', + exclude: ['node_modules'] + } + ], + loaders: [ + /** + * Unlike ts-loader, awesome-typescript-loader doesn't allow to override TS compiler options + * in query params. We use separate `tsconfig.test.json` file, which only differs in one thing: + * inline source maps. They are used for code coverage report. + * + * See project repository for details / configuration reference: + * https://github.com/s-panferov/awesome-typescript-loader + */ + { + test: /\.ts$/, + loader: 'awesome-typescript-loader', + query: { + tsconfig: 'tsconfig.test.json' + } + }, + /** + * These loaders are used in other environments as well. + */ + { test: /\.json$/, loader: 'json-loader' }, + { test: /\.html$/, loader: 'html-loader' }, + { test: /\.scss$/, loader: 'to-string-loader!css-loader!sass-loader' } + ], + postLoaders: [ + /** + * Instruments TS source files for subsequent code coverage. + * See https://github.com/deepsweet/istanbul-instrumenter-loader + */ + { + test: /\.ts$/, + loader: 'istanbul-instrumenter-loader', + exclude: [ + /node_modules/, + /tests/, + /\.(e2e|spec)\.ts$/ + ] + } + ] + } +};